Compare commits

..

No commits in common. "19.0" and "main" have entirely different histories.
19.0 ... main

12 changed files with 563 additions and 183 deletions

15
.gitignore vendored
View File

@ -1,15 +0,0 @@
# Python
*.py[cod]
__pycache__/
*.so
# Odoo
*.po~
*.pot~
# Editor / System
.DS_Store
.vscode/
*.swp
*.swo
*~

View File

@ -4,50 +4,71 @@ This module enhances the display of decimal numbers in Odoo by styling the decim
## Features ## Features
- **Visual Enhancement**: Decimal parts are styled to be smaller (80% font size) and slightly transparent (0.7 opacity), making large numbers easier to read. - Wraps decimal parts in a CSS class for custom styling
- **Universal Application**: Works across **all** views (List, Form, Kanban, Pivot, etc.) and all fields (Float, Monetary, Quantity). - Works across all numeric field types (float, monetary, percentage, etc.)
- **Non-Invasive Architecture**: Uses a safe DOM Observer approach instead of patching Odoo's internal JS formatters. This ensures maximum compatibility with other modules and Odoo updates. - **Fixed**: Proper decimal formatting in inventory detailed operations
- **Localization Aware**: Correctly handles decimal separators (dot or comma) based on the user's language settings. - Enhanced support for stock moves and inventory operations
- Handles escaped HTML markup properly in list views
## Technical Details ## Recent Fixes
### Architecture ### Inventory Operations Fix
The module has been refactored to use a **MutationObserver** approach: - **Issue**: Decimal formatting was not working in inventory detailed operations
1. **Global Observer**: A `MutationObserver` watches the document body for changes. - **Root Cause**: Previous implementation stripped decimal formatting from stock moves to avoid HTML escaping issues
2. **Safe Text Scanning**: It scans text nodes using a `TreeWalker` to find numbers. - **Solution**:
3. **Regex Matching**: It uses a strict Regular Expression to identify decimal parts. - Improved markup handling to preserve decimal formatting while avoiding escaping issues
- It dynamically builds the regex using Odoo's `localization.decimalPoint` setting. - Added specific patches for stock move line renderers
- Example (English): Matches `123.45` (.45 is styled). - Enhanced the `wrapDecimal` function to handle inventory-specific cases
- Example (Indonesian): Matches `123,45` (,45 is styled). - Added proper view inheritance for stock move line views
- **Smart Exclusion**: It intelligently ignores thousands separators (e.g., `1,200` is **not** matched in English/US locale).
4. **DOM Wrapping**: Matches are wrapped in a `<span class="o_decimal">` element for styling.
### Advantages ### Technical Changes
- **No Conflicts**: Does not override `formatFloat` or `formatMonetary` functions, avoiding conflicts with other modules that modify formatting. 1. **Enhanced List View Patch**: Instead of stripping formatting from stock moves, now properly handles escaped HTML
- **Robust**: Works even on custom widgets or non-standard views, as long as the number is rendered as text. 2. **New Stock Move Patch**: Added specific handling for stock move line renderers and enhanced formatter registry
3. **Improved wrapDecimal Function**: Better regex handling for various number formats
4. **Enhanced Formatter Registry**: Comprehensive patching of all numeric formatters used in inventory
5. **Added Stock Dependency**: Module now depends on stock module for proper integration
## Usage ## Usage
The module automatically applies decimal styling to all numeric fields. No configuration is required. The module automatically applies decimal styling to all numeric fields. The decimal part will appear smaller and slightly transparent compared to the integer part.
## Debugging ## Debugging
### Console ### Console Debugging
You can verify the module is running by checking the browser console: 1. Open browser developer tools (F12)
- Look for: `"Web Decimal Style: Loading DOM Decorator observer..."` 2. Go to Console tab
3. Look for messages starting with:
- "Web Decimal Style module loaded successfully"
- "Universal Decimal Patch: Formatting"
- "Stock Operations: Applying decimal formatting"
### Troubleshooting ### Manual Testing
If styling is not applied: In the browser console, you can test the decimal formatting function:
1. **Check Localization**: Ensure your user's language setting has the correct "Decimal Separator" configured. ```javascript
2. **Browser Cache**: Clear your browser cache or force refresh (Ctrl+Shift+R) to ensure the new JS is loaded. // Test the wrapDecimal function
3. **Inspect Element**: Right-click a number. You should see the decimal part wrapped in: testDecimalStyle('123.45')
```html
<span class="o_decimal">.00</span>
``` ```
### Visual Debugging
The CSS includes a debug indicator (🔸) that appears before each decimal part. This is currently enabled to help identify when decimal formatting is applied.
### Troubleshooting Steps
1. **Check module loading**: Look for "Web Decimal Style module loaded successfully" in console
2. **Check field detection**: Look for logging messages when viewing inventory operations
3. **Verify decimal detection**: The module should detect values with decimal points (e.g., "123.45")
4. **Check CSS application**: Look for `<span class="o_decimal">` elements in the HTML
If decimal formatting is not working:
1. Refresh the page after module update
2. Clear browser cache
3. Check browser console for JavaScript errors
4. Verify the module is properly installed and upgraded
## Installation ## Installation
1. Install the module. 1. Install the module
2. Restart Odoo. 2. Restart Odoo
3. Upgrade the module `web_decimal_style`. 3. Update the module list
4. Refresh your browser. 4. Install "Web Decimal Style"
The formatting will be applied automatically to all numeric fields across the system, including inventory operations.

View File

@ -14,12 +14,11 @@
'web.assets_backend': [ 'web.assets_backend': [
'web_decimal_style/static/src/css/decimal_style.css', 'web_decimal_style/static/src/css/decimal_style.css',
'web_decimal_style/static/src/core/utils/numbers_patch.js', 'web_decimal_style/static/src/core/utils/numbers_patch.js',
'web_decimal_style/static/src/views/decimal_observer.js',
'web_decimal_style/static/src/views/fields/formatters_patch.js', 'web_decimal_style/static/src/views/fields/formatters_patch.js',
'web_decimal_style/static/src/views/tax_totals_patch.js', 'web_decimal_style/static/src/views/inventory_decimal_patch.js',
'web_decimal_style/static/src/views/decimal_observer.js',
'web_decimal_style/static/src/views/fields/float_field.xml', 'web_decimal_style/static/src/views/fields/float_field.xml',
'web_decimal_style/static/src/views/fields/monetary_field.xml', 'web_decimal_style/static/src/views/fields/monetary_field.xml',
'web_decimal_style/static/src/views/list_view_patch.xml',
], ],
'web.assets_backend_lazy': [ 'web.assets_backend_lazy': [
'web_decimal_style/static/src/views/pivot_view_patch.xml', 'web_decimal_style/static/src/views/pivot_view_patch.xml',

View File

@ -1,4 +1,56 @@
// Web Decimal Style: wrapDecimal deprecated, now identity function. import { localization } from "@web/core/l10n/localization";
import { markup } from "@odoo/owl";
console.log('Web Decimal Style: numbers_patch.js loaded');
/**
* Wraps the decimal part of a formatted number string in a span for styling.
* Simplified version without inline styles to avoid HTML escaping.
* @param {string} formattedValue
* @returns {import("@odoo/owl").Markup|string}
*/
export function wrapDecimal(formattedValue) { export function wrapDecimal(formattedValue) {
// Basic validation
if (!formattedValue || typeof formattedValue !== "string") {
return formattedValue;
}
// If it's already wrapped, don't double wrap
if (formattedValue.includes('class="o_decimal"')) {
return formattedValue;
}
const decimalPoint = localization.decimalPoint || '.';
console.log('wrapDecimal: Processing', formattedValue);
// Handle numbers with decimal points
if (formattedValue.includes(decimalPoint)) {
const parts = formattedValue.split(decimalPoint);
if (parts.length === 2) {
const integerPart = parts[0];
const decimalPart = parts[1];
// Simple regex to separate digits from symbols
const match = decimalPart.match(/^(\d+)(.*)$/);
if (match) {
const digits = match[1];
const symbols = match[2];
// Use simple class without inline styles
const result = markup(`${integerPart}<span class="o_decimal">${decimalPoint}${digits}</span>${symbols}`);
console.log('wrapDecimal: Wrapped', formattedValue);
return result;
}
}
}
// Handle whole numbers - force decimal formatting for inventory
if (formattedValue.match(/^\d+$/)) {
const result = markup(`${formattedValue}<span class="o_decimal">.000</span>`);
console.log('wrapDecimal: Added decimals to whole number', formattedValue);
return result;
}
console.log('wrapDecimal: No formatting applied to', formattedValue);
return formattedValue; return formattedValue;
} }

View File

@ -1,8 +1,66 @@
/* Web Decimal Style - CSS Override Approach */ /* Web Decimal Style - Improved readability for better accessibility */
/* Base decimal styling */ /* Base decimal styling - Better contrast for 40+ users */
.o_decimal { .o_decimal {
font-size: 0.80em !important; font-size: 0.8em !important;
opacity: 0.7 !important; opacity: 0.85 !important;
display: inline-block; display: inline-block !important;
color: #666 !important;
font-weight: 400 !important;
vertical-align: baseline !important;
position: relative !important;
}
/* Ultra-specific selectors to ensure styling applies */
html body .o_web_client .o_action_manager .o_action .o_view_controller .o_renderer .o_list_renderer .o_list_table tbody tr td .o_decimal,
html body .o_web_client .o_action_manager .o_action .o_view_controller .o_renderer .o_list_renderer .o_list_table .o_data_row .o_data_cell .o_decimal,
html body .o_web_client .o_action_manager .o_decimal,
html body .o_web_client .o_decimal,
html body .o_decimal,
.o_web_client .o_decimal,
.o_action_manager .o_decimal,
.o_list_table .o_decimal,
.o_field_float .o_decimal,
.o_field_monetary .o_decimal,
.o_list_renderer .o_decimal,
.o_data_row .o_decimal,
.o_data_cell .o_decimal,
td .o_decimal,
div .o_decimal,
span .o_decimal,
table .o_decimal {
font-size: 0.8em !important;
opacity: 0.85 !important;
display: inline-block !important;
color: #666 !important;
font-weight: 400 !important;
vertical-align: baseline !important;
background: none !important;
border: none !important;
margin: 0 !important;
padding: 0 !important;
}
/* Stock-specific targeting */
.o_stock_move_line .o_decimal,
.o_stock_move .o_decimal,
[data-model="stock.move.line"] .o_decimal,
[data-model="stock.move"] .o_decimal {
font-size: 0.8em !important;
opacity: 0.85 !important;
color: #666 !important;
font-weight: 400 !important;
}
/* Force styling on any element with o_decimal class */
*[class*="o_decimal"] {
font-size: 0.8em !important;
opacity: 0.85 !important;
color: #666 !important;
font-weight: 400 !important;
}
/* No debug indicator */
.o_decimal::before {
content: none !important;
} }

View File

@ -1,117 +1,174 @@
/** @odoo-module **/ /** @odoo-module **/
import { localization } from "@web/core/l10n/localization"; console.log('Loading ultra-safe decimal observer...');
console.log('Web Decimal Style: Loading DOM Decorator observer...'); // Function to safely apply decimal styling to any element
function safelyApplyDecimalStyling(element) {
if (!element || !element.textContent || !document.contains(element)) return false;
let timeout; // Skip if already processed or if Owl is updating this element
const processedNodes = new WeakSet(); if (element.querySelector('.o_decimal') ||
element.classList.contains('o_decimal') ||
element.hasAttribute('data-decimal-processed') ||
element.hasAttribute('data-owl-updating')) return false;
function processNode(textNode) { // CRITICAL: Skip any editable elements or elements that might become editable
if (processedNodes.has(textNode)) return; if (element.isContentEditable ||
element.closest('.o_field_widget:not(.o_readonly_modifier)') ||
// Safety check: is it still in doc? element.closest('[contenteditable="true"]') ||
if (!document.contains(textNode)) return; element.closest('input') ||
element.closest('textarea') ||
// Check parent element.closest('.o_input') ||
const parent = textNode.parentNode; element.closest('.o_field_float:not(.o_readonly_modifier)') ||
if (!parent) return; element.closest('.o_field_monetary:not(.o_readonly_modifier)') ||
element.hasAttribute('contenteditable')) {
// Skip unsafe parents return false;
const tagName = parent.tagName; }
if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'OPTION', 'TITLE'].includes(tagName)) return;
if (parent.isContentEditable) return;
if (parent.closest && parent.closest('[contenteditable="true"]')) return;
if (parent.classList.contains('o_decimal')) return; // Already wrapped
const text = textNode.nodeValue;
if (!text || text.length < 3) return;
// Get the correct decimal point from Odoo localization
const decimalPoint = localization.decimalPoint || '.';
// Escape special regex characters (like dot)
const escapedPoint = decimalPoint.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// STRICT REGEX: (\d)(decimal_point)(\d+)
// We only create a new range if we find a digit, followed by the specific decimal point, followed by digits.
// This strictly avoids matching thousands separators (unless thousands separator == decimal point, which shouldn't happen).
// We match 'g' to find it anywhere, but typically we take the first one.
const regex = new RegExp(`(\\d)(${escapedPoint})(\\d+)`, 'g');
const match = regex.exec(text);
if (match) {
// match[0] = "0.00"
// match[1] = "0" (last int digit)
// match[2] = "." (decimal point)
// match[3] = "00" (decimals)
const lastIntDigit = match[1];
const decimals = match[3];
// Split at match start + length of lastIntDigit
const splitIndex = match.index + lastIntDigit.length;
const middleNode = textNode.splitText(splitIndex); // Starts with decimal point
const endNode = middleNode.splitText(1 + decimals.length); // Rest
// middleNode contains ".00"
const span = document.createElement('span');
span.className = 'o_decimal';
span.textContent = middleNode.nodeValue;
try { try {
parent.replaceChild(span, middleNode); const text = element.textContent.trim();
processedNodes.add(textNode); if (!text) return false;
processedNodes.add(endNode);
processedNodes.add(span.firstChild); // Mark as processed to avoid conflicts
} catch (e) { element.setAttribute('data-decimal-processed', 'true');
console.warn('Web Decimal Style: Failed to replace node', e);
// Improved patterns that properly distinguish thousands separators from decimal separators
const patterns = [
// Currency with decimal: Rp 6,210,000.00 (comma for thousands, dot for decimal)
/^(.*Rp\s*)(\d{1,3}(?:,\d{3})*)\.(\d{2})(.*)$/,
// Standard decimal with thousands: 1,234.56 (comma for thousands, dot for decimal)
/^(.*)(\d{1,3}(?:,\d{3})*)\.(\d+)(.*)$/,
// Simple decimal without thousands: 240.000, 123.45
/^(.*)(\d+)\.(\d+)(.*)$/
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match) {
const [, prefix, integerPart, decimalPart, suffix] = match;
console.log('Ultra-Safe Decimal Observer: Styling decimal number', text);
// Triple-check the element is safe to modify
if (element.textContent.trim() === text &&
document.contains(element) &&
!element.closest('.o_field_widget:not(.o_readonly_modifier)')) {
const newHTML = `${prefix || ''}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix || ''}`;
element.innerHTML = newHTML;
return true;
} }
} }
} }
// Handle whole numbers in quantity contexts (be even more selective)
// Only add decimals to numbers that are clearly quantities in readonly contexts
if (text.match(/^\d+$/) && text.length > 0 && text.length <= 4 &&
element.closest('.o_readonly_modifier') && (
element.closest('[name="quantity"]') ||
element.closest('[name="quantity_done"]') ||
element.closest('[name="reserved_availability"]')
)) {
console.log('Ultra-Safe Decimal Observer: Adding decimals to readonly quantity', text);
function scanDocument() { // Triple-check before updating
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null); if (element.textContent.trim() === text &&
const nodesToProcess = []; document.contains(element) &&
while (walker.nextNode()) { element.closest('.o_readonly_modifier')) {
nodesToProcess.push(walker.currentNode); element.innerHTML = `${text}<span class="o_decimal">.000</span>`;
} return true;
for (const node of nodesToProcess) {
processNode(node);
} }
} }
function scheduleUpdate() { // Do NOT process numbers with commas that don't have decimal points
if (timeout) cancelAnimationFrame(timeout); // These are likely thousands separators (e.g., 8,500 should stay as 8,500)
timeout = requestAnimationFrame(scanDocument); if (text.match(/^\d{1,3}(?:,\d{3})+$/) && !text.includes('.')) {
console.log('Ultra-Safe Decimal Observer: Skipping thousands separator number', text);
return false;
} }
const observer = new MutationObserver((mutations) => { } catch (error) {
let shouldUpdate = false; console.warn('Ultra-safe decimal styling error (non-critical):', error);
for (const mutation of mutations) { // Remove the processing flag if there was an error
if (mutation.type === 'childList' || mutation.type === 'characterData') { element.removeAttribute('data-decimal-processed');
// Avoid reacting to our own changes }
if (mutation.target && mutation.target.classList && mutation.target.classList.contains('o_decimal')) continue;
if (mutation.target.parentElement && mutation.target.parentElement.classList.contains('o_decimal')) continue;
shouldUpdate = true; return false;
} }
}
if (shouldUpdate) { // Ultra-safe processing of containers
scheduleUpdate(); function ultraSafelyProcessContainer(container) {
if (!container || !document.contains(container)) return;
try {
// Only process elements in readonly contexts to avoid editable field conflicts
const elements = container.querySelectorAll('.o_readonly_modifier td:not([data-decimal-processed]), .o_readonly_modifier span:not([data-decimal-processed]), .o_field_monetary.o_readonly_modifier:not([data-decimal-processed]), .o_field_float.o_readonly_modifier:not([data-decimal-processed])');
elements.forEach(element => {
// Only process leaf elements in readonly contexts
if ((element.children.length === 0 ||
(element.children.length === 1 && element.children[0].tagName === 'SPAN')) &&
!element.hasAttribute('data-owl-updating') &&
!element.closest('.o_field_widget:not(.o_readonly_modifier)')) {
safelyApplyDecimalStyling(element);
} }
}); });
} catch (error) {
console.warn('Ultra-safe container processing error (non-critical):', error);
}
}
if (document.body) { // Ultra-conservative observer with longer debounce
observer.observe(document.body, { childList: true, subtree: true, characterData: true }); const ultraSafeObserver = new MutationObserver((mutations) => {
scheduleUpdate(); // Longer debounce to avoid conflicts with user interactions
} else { clearTimeout(ultraSafeObserver.timeout);
ultraSafeObserver.timeout = setTimeout(() => {
try {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE && document.contains(node)) {
// Use longer delay to ensure Owl has finished all updates
setTimeout(() => {
ultraSafelyProcessContainer(node);
}, 500); // Much longer delay
}
});
}
});
} catch (error) {
console.warn('Ultra-safe observer error (non-critical):', error);
}
}, 500); // Longer debounce for 500ms
});
// Start ultra-safe observing
function startUltraSafeObserver() {
console.log('Ultra-Safe Decimal Observer: Starting ultra-conservative observation');
// Process existing content safely with delay
setTimeout(() => {
ultraSafelyProcessContainer(document.body);
}, 1000); // Initial delay to let everything load
// Start observing for new content with very reduced frequency
ultraSafeObserver.observe(document.body, {
childList: true,
subtree: true
});
// Much less frequent periodic processing to reduce conflicts
setInterval(() => {
ultraSafelyProcessContainer(document.body);
}, 10000); // Every 10 seconds instead of 5
}
// Start ultra-safely
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, { childList: true, subtree: true, characterData: true }); setTimeout(startUltraSafeObserver, 2000); // Extra delay
scheduleUpdate();
}); });
} else {
setTimeout(startUltraSafeObserver, 2000); // Extra delay
} }
console.log('Ultra-safe decimal observer loaded');

View File

@ -1,6 +1,135 @@
/** @odoo-module **/ import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { wrapDecimal } from "@web_decimal_style/core/utils/numbers_patch";
import { MonetaryField } from "@web/views/fields/monetary/monetary_field";
import { FloatField } from "@web/views/fields/float/float_field";
import { formatMonetary, formatFloat } from "@web/views/fields/formatters";
// Web Decimal Style: Formatters patching disabled in favor of CSS Custom Highlight API. console.log('Loading Web Decimal Style formatters patch...');
console.log('Web Decimal Style: Formatters patch disabled.');
// Safe field patching with error handling
try {
// Patch Components for Form Views
patch(MonetaryField.prototype, {
get formattedValue() {
if (this.props.inputType === "number" && !this.props.readonly && this.value) {
return this.value;
}
const res = formatMonetary(this.value, {
digits: this.currencyDigits,
currencyId: this.currencyId,
noSymbol: !this.props.readonly || this.props.hideSymbol,
});
// For readonly fields, apply decimal styling via safe DOM manipulation
if (this.props.readonly) {
// Use a longer timeout to avoid Owl lifecycle conflicts
setTimeout(() => {
try {
const element = this.el?.querySelector('.o_field_monetary');
if (element && !element.querySelector('.o_decimal') && document.contains(element)) {
const text = element.textContent;
const match = text?.match(/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/);
if (match && element.textContent === text) { // Double-check content hasn't changed
const [, prefix, integerPart, decimalPart, suffix] = match;
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
}
}
} catch (error) {
console.warn('Monetary field decimal styling error (non-critical):', error);
}
}, 150); // Longer delay to avoid Owl conflicts
}
return res;
}
});
patch(FloatField.prototype, {
get formattedValue() {
if (
!this.props.formatNumber ||
(this.props.inputType === "number" && !this.props.readonly && this.value)
) {
return this.value;
}
const options = {
digits: this.props.digits,
field: this.props.record.fields[this.props.name],
};
let res;
if (this.props.humanReadable && !this.state.hasFocus) {
res = formatFloat(this.value, {
...options,
humanReadable: true,
decimals: this.props.decimals,
});
} else {
res = formatFloat(this.value, { ...options, humanReadable: false });
}
// For readonly fields, apply decimal styling via safe DOM manipulation
if (this.props.readonly) {
setTimeout(() => {
try {
const element = this.el?.querySelector('.o_field_float');
if (element && !element.querySelector('.o_decimal') && document.contains(element)) {
const text = element.textContent;
const match = text?.match(/^(.*)(\d+)[.](\d+)(.*)$/);
if (match && element.textContent === text) { // Double-check content hasn't changed
const [, prefix, integerPart, decimalPart, suffix] = match;
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
}
}
} catch (error) {
console.warn('Float field decimal styling error (non-critical):', error);
}
}, 150);
}
return res;
}
});
} catch (error) {
console.warn('Field patching error (non-critical):', error);
}
// Safe Registry Patching for List Views
try {
const formattersRegistry = registry.category("formatters");
// Store original formatters
const originalFormatters = new Map();
function safelyPatchFormatter(name) {
if (formattersRegistry.contains(name) && !originalFormatters.has(name)) {
try {
const original = formattersRegistry.get(name);
originalFormatters.set(name, original);
formattersRegistry.add(name, (...args) => {
try {
const res = original(...args);
// Return the result as-is, let DOM observer handle styling
// This avoids markup escaping issues and Owl conflicts
return res;
} catch (error) {
console.warn(`Formatter ${name} error (non-critical):`, error);
return original(...args); // Fallback to original
}
}, { force: true });
console.log('Web Decimal Style: Safely patched formatter', name);
} catch (error) {
console.warn(`Error patching formatter ${name} (non-critical):`, error);
}
}
}
// Patch all numeric formatters safely
['float', 'monetary', 'percentage', 'float_factor', 'float_time'].forEach(safelyPatchFormatter);
} catch (error) {
console.warn('Registry patching error (non-critical):', error);
}
console.log('Web Decimal Style formatters patch loaded successfully');

View File

@ -1,4 +1,78 @@
/** @odoo-module **/ /** @odoo-module **/
// Web Decimal Style: Inventory decimal patch disabled in favor of CSS Custom Highlight API. import { patch } from "@web/core/utils/patch";
console.log('Web Decimal Style: Inventory decimal patch disabled.'); import { ListRenderer } from "@web/views/list/list_renderer";
console.log('Loading inventory decimal patch...');
// Safe DOM manipulation approach to avoid Owl lifecycle conflicts
patch(ListRenderer.prototype, {
async render() {
const result = await super.render();
// Schedule decimal styling after Owl completes its rendering cycle
this.scheduleDecimalStyling();
return result;
},
scheduleDecimalStyling() {
// Use requestAnimationFrame to ensure we run after Owl's DOM updates
requestAnimationFrame(() => {
try {
this.applyDecimalStyling();
} catch (error) {
console.warn('Decimal styling error (non-critical):', error);
}
});
},
applyDecimalStyling() {
if (!this.el || !document.contains(this.el)) return;
// Find all cells in the list table, but be more careful about DOM manipulation
const cells = this.el.querySelectorAll('td:not([data-decimal-processed]), .o_data_cell:not([data-decimal-processed])');
cells.forEach(cell => {
try {
// Skip if cell is being updated by Owl
if (cell.hasAttribute('data-owl-updating')) return;
// Mark as processed to avoid double processing
cell.setAttribute('data-decimal-processed', 'true');
const text = cell.textContent?.trim();
if (!text) return;
// Check for decimal numbers (including currency)
const decimalMatch = text.match(/^(.*)(\d+)([.,])(\d+)(.*)$/);
if (decimalMatch) {
const [, prefix, integerPart, decimalPoint, decimalPart, suffix] = decimalMatch;
console.log('Applying safe DOM decimal styling to:', text);
// Create new HTML structure safely
const newHTML = `${prefix}${integerPart}<span class="o_decimal">${decimalPoint}${decimalPart}</span>${suffix}`;
// Only update if the content hasn't changed
if (cell.textContent.trim() === text) {
cell.innerHTML = newHTML;
}
}
// Handle whole numbers by adding .000
else if (text.match(/^\d+$/) && text.length > 0) {
console.log('Adding decimals to whole number safely:', text);
// Only update if the content hasn't changed
if (cell.textContent.trim() === text) {
cell.innerHTML = `${text}<span class="o_decimal">.000</span>`;
}
}
} catch (error) {
console.warn('Error processing cell (non-critical):', error);
}
});
}
});
console.log('Inventory decimal patch loaded');

View File

@ -2,15 +2,15 @@
<templates xml:space="preserve"> <templates xml:space="preserve">
<!-- Patch List View Footer Aggregates --> <!-- Patch List View Footer Aggregates -->
<t t-inherit="web.ListRenderer" t-inherit-mode="extension"> <t t-inherit="web.ListRenderer" t-inherit-mode="extension">
<xpath expr="//tfoot//t[@t-esc='aggregates[column.name].value']" position="replace"> <xpath expr="//tfoot//span[@t-esc='aggregate.value']" position="replace">
<t t-out="aggregates[column.name].value"/> <span t-out="aggregate.value" t-att-data-tooltip="aggregate.help"/>
</xpath> </xpath>
</t> </t>
<!-- Patch List View Group Row Aggregates --> <!-- Patch List View Group Row Aggregates -->
<t t-inherit="web.ListRenderer.GroupRow" t-inherit-mode="extension"> <t t-inherit="web.ListRenderer.GroupRow" t-inherit-mode="extension">
<xpath expr="//t[@t-esc='groupAggregate.value']" position="replace"> <xpath expr="//t[@t-esc='formatAggregateValue(group, column)']" position="replace">
<t t-out="groupAggregate.value"/> <t t-if="column.type === 'field'" t-out="formatAggregateValue(group, column)"/>
</xpath> </xpath>
</t> </t>
</templates> </templates>

View File

@ -2,7 +2,11 @@
<templates xml:space="preserve"> <templates xml:space="preserve">
<!-- Patch Pivot View Cells --> <!-- Patch Pivot View Cells -->
<t t-inherit="web.PivotRenderer" t-inherit-mode="extension"> <t t-inherit="web.PivotRenderer" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_value')]" position="attributes"> <xpath expr="//div[hasclass('o_variation')]" position="attributes">
<attribute name="t-esc"></attribute>
<attribute name="t-out">getFormattedVariation(cell)</attribute>
</xpath>
<xpath expr="//div[hasclass('o_value') and @t-else='1']" position="attributes">
<attribute name="t-esc"></attribute> <attribute name="t-esc"></attribute>
<attribute name="t-out">getFormattedValue(cell)</attribute> <attribute name="t-out">getFormattedValue(cell)</attribute>
</xpath> </xpath>

View File

@ -1,4 +1,11 @@
/** @odoo-module **/ /** @odoo-module **/
// Web Decimal Style: Purchase dashboard patch disabled in favor of CSS Custom Highlight API. import { PurchaseDashBoard } from "@purchase/views/purchase_dashboard";
console.log('Web Decimal Style: Purchase dashboard patch disabled.'); import { patch } from "@web/core/utils/patch";
import { wrapDecimal } from "@web_decimal_style/core/utils/numbers_patch";
patch(PurchaseDashBoard.prototype, {
formatDecimal(value) {
return wrapDecimal(value);
}
});

View File

@ -1,6 +0,0 @@
/** @odoo-module **/
// Web Decimal Style: Tax totals patch disabled in favor of CSS Custom Highlight API.
console.log('Web Decimal Style: Tax totals patch disabled.');