refactor: rewrite decimal observer to use Odoo localization for robust and accurate styling of decimal parts.
This commit is contained in:
parent
19e19de89a
commit
f2ac13c446
89
README.md
89
README.md
@ -4,71 +4,50 @@ This module enhances the display of decimal numbers in Odoo by styling the decim
|
||||
|
||||
## Features
|
||||
|
||||
- Wraps decimal parts in a CSS class for custom styling
|
||||
- Works across all numeric field types (float, monetary, percentage, etc.)
|
||||
- **Fixed**: Proper decimal formatting in inventory detailed operations
|
||||
- Enhanced support for stock moves and inventory operations
|
||||
- Handles escaped HTML markup properly in list views
|
||||
- **Visual Enhancement**: Decimal parts are styled to be smaller (80% font size) and slightly transparent (0.7 opacity), making large numbers easier to read.
|
||||
- **Universal Application**: Works across **all** views (List, Form, Kanban, Pivot, etc.) and all fields (Float, Monetary, Quantity).
|
||||
- **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.
|
||||
- **Localization Aware**: Correctly handles decimal separators (dot or comma) based on the user's language settings.
|
||||
|
||||
## Recent Fixes
|
||||
## Technical Details
|
||||
|
||||
### Inventory Operations Fix
|
||||
- **Issue**: Decimal formatting was not working in inventory detailed operations
|
||||
- **Root Cause**: Previous implementation stripped decimal formatting from stock moves to avoid HTML escaping issues
|
||||
- **Solution**:
|
||||
- Improved markup handling to preserve decimal formatting while avoiding escaping issues
|
||||
- Added specific patches for stock move line renderers
|
||||
- Enhanced the `wrapDecimal` function to handle inventory-specific cases
|
||||
- Added proper view inheritance for stock move line views
|
||||
### Architecture
|
||||
The module has been refactored to use a **MutationObserver** approach:
|
||||
1. **Global Observer**: A `MutationObserver` watches the document body for changes.
|
||||
2. **Safe Text Scanning**: It scans text nodes using a `TreeWalker` to find numbers.
|
||||
3. **Regex Matching**: It uses a strict Regular Expression to identify decimal parts.
|
||||
- It dynamically builds the regex using Odoo's `localization.decimalPoint` setting.
|
||||
- Example (English): Matches `123.45` (.45 is styled).
|
||||
- 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
|
||||
1. **Enhanced List View Patch**: Instead of stripping formatting from stock moves, now properly handles escaped HTML
|
||||
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
|
||||
### Advantages
|
||||
- **No Conflicts**: Does not override `formatFloat` or `formatMonetary` functions, avoiding conflicts with other modules that modify formatting.
|
||||
- **Robust**: Works even on custom widgets or non-standard views, as long as the number is rendered as text.
|
||||
|
||||
## 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
|
||||
|
||||
### Console Debugging
|
||||
1. Open browser developer tools (F12)
|
||||
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"
|
||||
### Console
|
||||
You can verify the module is running by checking the browser console:
|
||||
- Look for: `"Web Decimal Style: Loading DOM Decorator observer..."`
|
||||
|
||||
### Manual Testing
|
||||
In the browser console, you can test the decimal formatting function:
|
||||
```javascript
|
||||
// Test the wrapDecimal function
|
||||
testDecimalStyle('123.45')
|
||||
```
|
||||
|
||||
### 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
|
||||
### Troubleshooting
|
||||
If styling is not applied:
|
||||
1. **Check Localization**: Ensure your user's language setting has the correct "Decimal Separator" configured.
|
||||
2. **Browser Cache**: Clear your browser cache or force refresh (Ctrl+Shift+R) to ensure the new JS is loaded.
|
||||
3. **Inspect Element**: Right-click a number. You should see the decimal part wrapped in:
|
||||
```html
|
||||
<span class="o_decimal">.00</span>
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the module
|
||||
2. Restart Odoo
|
||||
3. Update the module list
|
||||
4. Install "Web Decimal Style"
|
||||
|
||||
The formatting will be applied automatically to all numeric fields across the system, including inventory operations.
|
||||
1. Install the module.
|
||||
2. Restart Odoo.
|
||||
3. Upgrade the module `web_decimal_style`.
|
||||
4. Refresh your browser.
|
||||
@ -14,6 +14,7 @@
|
||||
'web.assets_backend': [
|
||||
'web_decimal_style/static/src/css/decimal_style.css',
|
||||
'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/tax_totals_patch.js',
|
||||
'web_decimal_style/static/src/views/fields/float_field.xml',
|
||||
|
||||
@ -1,56 +1,4 @@
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { markup } from "@odoo/owl";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
// Web Decimal Style: wrapDecimal deprecated, now identity function.
|
||||
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 || '.';
|
||||
|
||||
|
||||
|
||||
// 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}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle whole numbers - force decimal formatting for inventory
|
||||
if (formattedValue.match(/^\d+$/)) {
|
||||
const result = markup(`${formattedValue}<span class="o_decimal">.000</span>`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
font-size: 0.8em !important;
|
||||
opacity: 0.85 !important;
|
||||
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;
|
||||
font-size: 0.80em !important;
|
||||
opacity: 0.7 !important;
|
||||
display: inline-block;
|
||||
}
|
||||
@ -1,184 +1,117 @@
|
||||
/** @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
|
||||
function safelyApplyDecimalStyling(element) {
|
||||
if (!element || !element.textContent || !document.contains(element)) return false;
|
||||
console.log('Web Decimal Style: Loading DOM Decorator observer...');
|
||||
|
||||
// Skip if already processed or if Owl is updating this element
|
||||
if (element.querySelector('.o_decimal') ||
|
||||
element.classList.contains('o_decimal') ||
|
||||
element.hasAttribute('data-decimal-processed') ||
|
||||
element.hasAttribute('data-owl-updating')) return false;
|
||||
let timeout;
|
||||
const processedNodes = new WeakSet();
|
||||
|
||||
// CRITICAL: Skip any editable elements or elements that might become editable
|
||||
if (element.isContentEditable ||
|
||||
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;
|
||||
}
|
||||
function processNode(textNode) {
|
||||
if (processedNodes.has(textNode)) return;
|
||||
|
||||
try {
|
||||
const text = element.textContent.trim();
|
||||
if (!text) return false;
|
||||
// Safety check: is it still in doc?
|
||||
if (!document.contains(textNode)) return;
|
||||
|
||||
// Mark as processed to avoid conflicts
|
||||
element.setAttribute('data-decimal-processed', 'true');
|
||||
// Check parent
|
||||
const parent = textNode.parentNode;
|
||||
if (!parent) return;
|
||||
|
||||
// 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\u00A0]*)(\d[\d,\s\u00A0]*)\.(\d{2})(.*)$/,
|
||||
// Generic decimal with thousand separators (dot as decimal)
|
||||
/^(.*?)([\d,\s\u00A0]*\d)\.(\d+)(.*)$/,
|
||||
// Simple decimal
|
||||
/^(.*?)(\d+)\.(\d+)(.*)$/
|
||||
];
|
||||
// Skip unsafe parents
|
||||
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
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern);
|
||||
if (match) {
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
const text = textNode.nodeValue;
|
||||
if (!text || text.length < 3) return;
|
||||
|
||||
// console.log('Ultra-Safe Decimal Observer: MATCH FOUND', {text, prefix, integerPart, decimalPart, suffix});
|
||||
// Get the correct decimal point from Odoo localization
|
||||
const decimalPoint = localization.decimalPoint || '.';
|
||||
// Escape special regex characters (like dot)
|
||||
const escapedPoint = decimalPoint.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
// Triple-check the element is safe to modify
|
||||
// For tfoot, we are more permissive because they are always readonly
|
||||
if (element.textContent.trim() === text &&
|
||||
document.contains(element) &&
|
||||
(!element.closest('.o_field_widget:not(.o_readonly_modifier)') || element.closest('tfoot'))) {
|
||||
// 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).
|
||||
|
||||
console.log('Ultra-Safe Decimal Observer: STYLING', text, element.tagName, element.className);
|
||||
const newHTML = `${prefix || ''}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix || ''}`;
|
||||
element.innerHTML = newHTML;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We match 'g' to find it anywhere, but typically we take the first one.
|
||||
const regex = new RegExp(`(\\d)(${escapedPoint})(\\d+)`, 'g');
|
||||
|
||||
// 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);
|
||||
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)
|
||||
|
||||
// Triple-check before updating
|
||||
if (element.textContent.trim() === text &&
|
||||
document.contains(element) &&
|
||||
element.closest('.o_readonly_modifier')) {
|
||||
element.innerHTML = `${text}<span class="o_decimal">.000</span>`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const lastIntDigit = match[1];
|
||||
const decimals = match[3];
|
||||
|
||||
// Do NOT process numbers with commas that don't have decimal points
|
||||
// These are likely thousands separators (e.g., 8,500 should stay as 8,500)
|
||||
if (text.match(/^\d{1,3}(?:,\d{3})+$/) && !text.includes('.')) {
|
||||
console.log('Ultra-Safe Decimal Observer: Skipping thousands separator number', text);
|
||||
return false;
|
||||
}
|
||||
// Split at match start + length of lastIntDigit
|
||||
const splitIndex = match.index + lastIntDigit.length;
|
||||
|
||||
} 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');
|
||||
}
|
||||
const middleNode = textNode.splitText(splitIndex); // Starts with decimal point
|
||||
const endNode = middleNode.splitText(1 + decimals.length); // Rest
|
||||
|
||||
return false;
|
||||
}
|
||||
// middleNode contains ".00"
|
||||
|
||||
// Ultra-safe processing of containers
|
||||
function ultraSafelyProcessContainer(container) {
|
||||
if (!container || !document.contains(container)) return;
|
||||
const span = document.createElement('span');
|
||||
span.className = 'o_decimal';
|
||||
span.textContent = middleNode.nodeValue;
|
||||
|
||||
try {
|
||||
// Select elements likely to contain formatted decimals in readonly contexts or footers
|
||||
const elements = container.querySelectorAll('.o_readonly_modifier td:not([data-decimal-processed]), .o_readonly_modifier span:not([data-decimal-processed]), tfoot td:not([data-decimal-processed]), tfoot span:not([data-decimal-processed]), .o_list_number:not([data-decimal-processed])');
|
||||
|
||||
elements.forEach(element => {
|
||||
// Optimization: If a TD has a SPAN, we prefer to style the SPAN to preserve tooltips
|
||||
if (element.tagName === 'TD' && element.querySelector('span')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process leaf elements or elements with very simple structures
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Ultra-conservative observer with longer debounce
|
||||
const ultraSafeObserver = new MutationObserver((mutations) => {
|
||||
// Longer debounce to avoid conflicts with user interactions
|
||||
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);
|
||||
parent.replaceChild(span, middleNode);
|
||||
processedNodes.add(textNode);
|
||||
processedNodes.add(endNode);
|
||||
processedNodes.add(span.firstChild);
|
||||
} catch (e) {
|
||||
console.warn('Web Decimal Style: Failed to replace node', e);
|
||||
}
|
||||
}, 500); // Longer debounce for 500ms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function scanDocument() {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
||||
const nodesToProcess = [];
|
||||
while (walker.nextNode()) {
|
||||
nodesToProcess.push(walker.currentNode);
|
||||
}
|
||||
|
||||
for (const node of nodesToProcess) {
|
||||
processNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
// 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', () => {
|
||||
setTimeout(startUltraSafeObserver, 2000); // Extra delay
|
||||
});
|
||||
if (document.body) {
|
||||
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
||||
scheduleUpdate();
|
||||
} 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 disabled');
|
||||
@ -1,108 +1,6 @@
|
||||
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";
|
||||
|
||||
|
||||
|
||||
// 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,
|
||||
});
|
||||
|
||||
// Start of Owl Safe Decimal Styling
|
||||
if (this.props.readonly) {
|
||||
return wrapDecimal(res);
|
||||
}
|
||||
// End of Owl Safe Decimal Styling
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
// Start of Owl Safe Decimal Styling
|
||||
if (this.props.readonly) {
|
||||
return wrapDecimal(res);
|
||||
}
|
||||
// End of Owl Safe Decimal Styling
|
||||
|
||||
return res;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Field patching error (non-critical):', error);
|
||||
}
|
||||
|
||||
// Safe Registry Patching for List Views
|
||||
// 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);
|
||||
// Safely wrap with Owl Markup
|
||||
return wrapDecimal(res);
|
||||
} catch (error) {
|
||||
console.warn(`Formatter ${name} error (non-critical):`, error);
|
||||
return original(...args); // Fallback to original
|
||||
}
|
||||
}, { force: true });
|
||||
|
||||
|
||||
} 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);
|
||||
}
|
||||
/** @odoo-module **/
|
||||
|
||||
// Web Decimal Style: Formatters patching disabled in favor of CSS Custom Highlight API.
|
||||
console.log('Web Decimal Style: Formatters patch disabled.');
|
||||
|
||||
|
||||
|
||||
@ -1,81 +1,4 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
|
||||
console.log('Loading inventory decimal patch...');
|
||||
|
||||
// Safe DOM manipulation approach to avoid Owl lifecycle conflicts
|
||||
// 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');
|
||||
// Web Decimal Style: Inventory decimal patch disabled in favor of CSS Custom Highlight API.
|
||||
console.log('Web Decimal Style: Inventory decimal patch disabled.');
|
||||
@ -1,11 +1,4 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { PurchaseDashBoard } from "@purchase/views/purchase_dashboard";
|
||||
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);
|
||||
}
|
||||
});
|
||||
// Web Decimal Style: Purchase dashboard patch disabled in favor of CSS Custom Highlight API.
|
||||
console.log('Web Decimal Style: Purchase dashboard patch disabled.');
|
||||
|
||||
@ -1,32 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { wrapDecimal } from "@web_decimal_style/core/utils/numbers_patch";
|
||||
import { TaxTotalsComponent } from "@account/components/tax_totals/tax_totals";
|
||||
|
||||
|
||||
|
||||
// Patch TaxTotalsComponent (the main widget)
|
||||
patch(TaxTotalsComponent.prototype, {
|
||||
formatMonetary(value) {
|
||||
const result = super.formatMonetary(value);
|
||||
return wrapDecimal(result);
|
||||
}
|
||||
});
|
||||
|
||||
// We also need to patch TaxGroupComponent which is a sub-component defined in the same file
|
||||
// However, it's not exported directly in Odoo 16/17/18/19 usually, but accessible via TaxTotalsComponent.components
|
||||
if (TaxTotalsComponent.components && TaxTotalsComponent.components.TaxGroupComponent) {
|
||||
const TaxGroupComponent = TaxTotalsComponent.components.TaxGroupComponent;
|
||||
|
||||
patch(TaxGroupComponent.prototype, {
|
||||
formatMonetary(value) {
|
||||
const result = super.formatMonetary(value);
|
||||
return wrapDecimal(result);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('Web Decimal Style: Could not find TaxGroupComponent to patch');
|
||||
}
|
||||
// Web Decimal Style: Tax totals patch disabled in favor of CSS Custom Highlight API.
|
||||
console.log('Web Decimal Style: Tax totals patch disabled.');
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user