Compare commits

..

7 Commits
main ... 19.0

12 changed files with 183 additions and 563 deletions

15
.gitignore vendored Normal file
View File

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

View File

@ -4,71 +4,50 @@ This module enhances the display of decimal numbers in Odoo by styling the decim
## Features ## Features
- Wraps decimal parts in a CSS class for custom styling - **Visual Enhancement**: Decimal parts are styled to be smaller (80% font size) and slightly transparent (0.7 opacity), making large numbers easier to read.
- Works across all numeric field types (float, monetary, percentage, etc.) - **Universal Application**: Works across **all** views (List, Form, Kanban, Pivot, etc.) and all fields (Float, Monetary, Quantity).
- **Fixed**: Proper decimal formatting in inventory detailed operations - **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.
- Enhanced support for stock moves and inventory operations - **Localization Aware**: Correctly handles decimal separators (dot or comma) based on the user's language settings.
- Handles escaped HTML markup properly in list views
## Recent Fixes ## Technical Details
### Inventory Operations Fix ### Architecture
- **Issue**: Decimal formatting was not working in inventory detailed operations The module has been refactored to use a **MutationObserver** approach:
- **Root Cause**: Previous implementation stripped decimal formatting from stock moves to avoid HTML escaping issues 1. **Global Observer**: A `MutationObserver` watches the document body for changes.
- **Solution**: 2. **Safe Text Scanning**: It scans text nodes using a `TreeWalker` to find numbers.
- Improved markup handling to preserve decimal formatting while avoiding escaping issues 3. **Regex Matching**: It uses a strict Regular Expression to identify decimal parts.
- Added specific patches for stock move line renderers - It dynamically builds the regex using Odoo's `localization.decimalPoint` setting.
- Enhanced the `wrapDecimal` function to handle inventory-specific cases - Example (English): Matches `123.45` (.45 is styled).
- Added proper view inheritance for stock move line views - Example (Indonesian): Matches `123,45` (,45 is styled).
- **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.
### Technical Changes ### Advantages
1. **Enhanced List View Patch**: Instead of stripping formatting from stock moves, now properly handles escaped HTML - **No Conflicts**: Does not override `formatFloat` or `formatMonetary` functions, avoiding conflicts with other modules that modify formatting.
2. **New Stock Move Patch**: Added specific handling for stock move line renderers and enhanced formatter registry - **Robust**: Works even on custom widgets or non-standard views, as long as the number is rendered as text.
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. The decimal part will appear smaller and slightly transparent compared to the integer part. The module automatically applies decimal styling to all numeric fields. No configuration is required.
## Debugging ## Debugging
### Console Debugging ### Console
1. Open browser developer tools (F12) You can verify the module is running by checking the browser console:
2. Go to Console tab - Look for: `"Web Decimal Style: Loading DOM Decorator observer..."`
3. Look for messages starting with:
- "Web Decimal Style module loaded successfully"
- "Universal Decimal Patch: Formatting"
- "Stock Operations: Applying decimal formatting"
### Manual Testing ### Troubleshooting
In the browser console, you can test the decimal formatting function: If styling is not applied:
```javascript 1. **Check Localization**: Ensure your user's language setting has the correct "Decimal Separator" configured.
// Test the wrapDecimal function 2. **Browser Cache**: Clear your browser cache or force refresh (Ctrl+Shift+R) to ensure the new JS is loaded.
testDecimalStyle('123.45') 3. **Inspect Element**: Right-click a number. You should see the decimal part wrapped in:
``` ```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. Update the module list 3. Upgrade the module `web_decimal_style`.
4. Install "Web Decimal Style" 4. Refresh your browser.
The formatting will be applied automatically to all numeric fields across the system, including inventory operations.

View File

@ -14,11 +14,12 @@
'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/fields/formatters_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/decimal_observer.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/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,56 +1,4 @@
import { localization } from "@web/core/l10n/localization"; // Web Decimal Style: wrapDecimal deprecated, now identity function.
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,66 +1,8 @@
/* Web Decimal Style - Improved readability for better accessibility */ /* Web Decimal Style - CSS Override Approach */
/* Base decimal styling - Better contrast for 40+ users */ /* Base decimal styling */
.o_decimal { .o_decimal {
font-size: 0.8em !important; font-size: 0.80em !important;
opacity: 0.85 !important; opacity: 0.7 !important;
display: inline-block !important; display: inline-block;
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,174 +1,117 @@
/** @odoo-module **/ /** @odoo-module **/
console.log('Loading ultra-safe decimal observer...'); import { localization } from "@web/core/l10n/localization";
// Function to safely apply decimal styling to any element console.log('Web Decimal Style: Loading DOM Decorator observer...');
function safelyApplyDecimalStyling(element) {
if (!element || !element.textContent || !document.contains(element)) return false;
// Skip if already processed or if Owl is updating this element let timeout;
if (element.querySelector('.o_decimal') || const processedNodes = new WeakSet();
element.classList.contains('o_decimal') ||
element.hasAttribute('data-decimal-processed') ||
element.hasAttribute('data-owl-updating')) return false;
// CRITICAL: Skip any editable elements or elements that might become editable function processNode(textNode) {
if (element.isContentEditable || if (processedNodes.has(textNode)) return;
element.closest('.o_field_widget:not(.o_readonly_modifier)') ||
element.closest('[contenteditable="true"]') ||
element.closest('input') ||
element.closest('textarea') ||
element.closest('.o_input') ||
element.closest('.o_field_float:not(.o_readonly_modifier)') ||
element.closest('.o_field_monetary:not(.o_readonly_modifier)') ||
element.hasAttribute('contenteditable')) {
return false;
}
try { // Safety check: is it still in doc?
const text = element.textContent.trim(); if (!document.contains(textNode)) return;
if (!text) return false;
// Mark as processed to avoid conflicts // Check parent
element.setAttribute('data-decimal-processed', 'true'); const parent = textNode.parentNode;
if (!parent) return;
// Improved patterns that properly distinguish thousands separators from decimal separators // Skip unsafe parents
const patterns = [ const tagName = parent.tagName;
// Currency with decimal: Rp 6,210,000.00 (comma for thousands, dot for decimal) if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'OPTION', 'TITLE'].includes(tagName)) return;
/^(.*Rp\s*)(\d{1,3}(?:,\d{3})*)\.(\d{2})(.*)$/, if (parent.isContentEditable) return;
// Standard decimal with thousands: 1,234.56 (comma for thousands, dot for decimal) if (parent.closest && parent.closest('[contenteditable="true"]')) return;
/^(.*)(\d{1,3}(?:,\d{3})*)\.(\d+)(.*)$/, if (parent.classList.contains('o_decimal')) return; // Already wrapped
// Simple decimal without thousands: 240.000, 123.45
/^(.*)(\d+)\.(\d+)(.*)$/
];
for (const pattern of patterns) { const text = textNode.nodeValue;
const match = text.match(pattern); 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) { if (match) {
const [, prefix, integerPart, decimalPart, suffix] = match; // match[0] = "0.00"
// match[1] = "0" (last int digit)
// match[2] = "." (decimal point)
// match[3] = "00" (decimals)
console.log('Ultra-Safe Decimal Observer: Styling decimal number', text); const lastIntDigit = match[1];
const decimals = match[3];
// Triple-check the element is safe to modify // Split at match start + length of lastIntDigit
if (element.textContent.trim() === text && const splitIndex = match.index + lastIntDigit.length;
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) const middleNode = textNode.splitText(splitIndex); // Starts with decimal point
// Only add decimals to numbers that are clearly quantities in readonly contexts const endNode = middleNode.splitText(1 + decimals.length); // Rest
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);
// Triple-check before updating // middleNode contains ".00"
if (element.textContent.trim() === text &&
document.contains(element) &&
element.closest('.o_readonly_modifier')) {
element.innerHTML = `${text}<span class="o_decimal">.000</span>`;
return true;
}
}
// Do NOT process numbers with commas that don't have decimal points const span = document.createElement('span');
// These are likely thousands separators (e.g., 8,500 should stay as 8,500) span.className = 'o_decimal';
if (text.match(/^\d{1,3}(?:,\d{3})+$/) && !text.includes('.')) { span.textContent = middleNode.nodeValue;
console.log('Ultra-Safe Decimal Observer: Skipping thousands separator number', text);
return false;
}
} catch (error) {
console.warn('Ultra-safe decimal styling error (non-critical):', error);
// Remove the processing flag if there was an error
element.removeAttribute('data-decimal-processed');
}
return false;
}
// Ultra-safe processing of containers
function ultraSafelyProcessContainer(container) {
if (!container || !document.contains(container)) return;
try { try {
// Only process elements in readonly contexts to avoid editable field conflicts parent.replaceChild(span, middleNode);
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])'); processedNodes.add(textNode);
processedNodes.add(endNode);
elements.forEach(element => { processedNodes.add(span.firstChild);
// Only process leaf elements in readonly contexts } catch (e) {
if ((element.children.length === 0 || console.warn('Web Decimal Style: Failed to replace node', e);
(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);
} }
} }
// Ultra-conservative observer with longer debounce
const ultraSafeObserver = new MutationObserver((mutations) => { function scanDocument() {
// Longer debounce to avoid conflicts with user interactions const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
clearTimeout(ultraSafeObserver.timeout); const nodesToProcess = [];
ultraSafeObserver.timeout = setTimeout(() => { while (walker.nextNode()) {
try { nodesToProcess.push(walker.currentNode);
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
} }
});
for (const node of nodesToProcess) {
processNode(node);
} }
}); }
} catch (error) {
console.warn('Ultra-safe observer error (non-critical):', error); function scheduleUpdate() {
if (timeout) cancelAnimationFrame(timeout);
timeout = requestAnimationFrame(scanDocument);
}
const observer = new MutationObserver((mutations) => {
let shouldUpdate = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
// 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;
}
}
if (shouldUpdate) {
scheduleUpdate();
} }
}, 500); // Longer debounce for 500ms
}); });
// Start ultra-safe observing if (document.body) {
function startUltraSafeObserver() { observer.observe(document.body, { childList: true, subtree: true, characterData: true });
console.log('Ultra-Safe Decimal Observer: Starting ultra-conservative observation'); scheduleUpdate();
// 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', () => {
setTimeout(startUltraSafeObserver, 2000); // Extra delay
});
} else { } else {
setTimeout(startUltraSafeObserver, 2000); // Extra delay document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
scheduleUpdate();
});
} }
console.log('Ultra-safe decimal observer loaded');

View File

@ -1,135 +1,6 @@
import { registry } from "@web/core/registry"; /** @odoo-module **/
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";
console.log('Loading Web Decimal Style formatters patch...'); // Web Decimal Style: Formatters patching disabled in favor of CSS Custom Highlight API.
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,78 +1,4 @@
/** @odoo-module **/ /** @odoo-module **/
import { patch } from "@web/core/utils/patch"; // Web Decimal Style: Inventory decimal patch disabled in favor of CSS Custom Highlight API.
import { ListRenderer } from "@web/views/list/list_renderer"; console.log('Web Decimal Style: Inventory decimal patch disabled.');
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//span[@t-esc='aggregate.value']" position="replace"> <xpath expr="//tfoot//t[@t-esc='aggregates[column.name].value']" position="replace">
<span t-out="aggregate.value" t-att-data-tooltip="aggregate.help"/> <t t-out="aggregates[column.name].value"/>
</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='formatAggregateValue(group, column)']" position="replace"> <xpath expr="//t[@t-esc='groupAggregate.value']" position="replace">
<t t-if="column.type === 'field'" t-out="formatAggregateValue(group, column)"/> <t t-out="groupAggregate.value"/>
</xpath> </xpath>
</t> </t>
</templates> </templates>

View File

@ -2,11 +2,7 @@
<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_variation')]" position="attributes"> <xpath expr="//div[hasclass('o_value')]" 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,11 +1,4 @@
/** @odoo-module **/ /** @odoo-module **/
import { PurchaseDashBoard } from "@purchase/views/purchase_dashboard"; // Web Decimal Style: Purchase dashboard patch disabled in favor of CSS Custom Highlight API.
import { patch } from "@web/core/utils/patch"; console.log('Web Decimal Style: Purchase dashboard patch disabled.');
import { wrapDecimal } from "@web_decimal_style/core/utils/numbers_patch";
patch(PurchaseDashBoard.prototype, {
formatDecimal(value) {
return wrapDecimal(value);
}
});

View File

@ -0,0 +1,6 @@
/** @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.');