Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3b744c2b2 | |||
| f2ac13c446 | |||
| 19e19de89a | |||
| d28cfcaa89 | |||
| 61c3f010a9 | |||
| 80930a9535 | |||
| 6d0c445279 |
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Python
|
||||||
|
*.py[cod]
|
||||||
|
__pycache__/
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Odoo
|
||||||
|
*.po~
|
||||||
|
*.pot~
|
||||||
|
|
||||||
|
# Editor / System
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
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
|
## 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.
|
|
||||||
@ -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',
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
@ -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');
|
|
||||||
@ -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');
|
|
||||||
|
|||||||
@ -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');
|
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
6
static/src/views/tax_totals_patch.js
Normal file
6
static/src/views/tax_totals_patch.js
Normal 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.');
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user