diff --git a/static/src/views/decimal_observer.js b/static/src/views/decimal_observer.js
index 6569a18..4087602 100644
--- a/static/src/views/decimal_observer.js
+++ b/static/src/views/decimal_observer.js
@@ -1,109 +1,146 @@
/** @odoo-module **/
-console.log('Loading comprehensive decimal observer...');
+console.log('Loading safe decimal observer...');
-// Function to apply decimal styling to any element
-function applyDecimalStyling(element) {
- if (!element || !element.textContent) return;
+// Function to safely apply decimal styling to any element
+function safelyApplyDecimalStyling(element) {
+ if (!element || !element.textContent || !document.contains(element)) return false;
- // Skip if already processed
- if (element.querySelector('.o_decimal') || element.classList.contains('o_decimal')) return;
+ // 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;
- const text = element.textContent.trim();
- if (!text) return;
-
- // Pattern for decimal numbers (including currency formats)
- const patterns = [
- // Standard decimal: 123.45, 1,234.56
- /^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
- // Currency with Rp: Rp 6,210,000.00
- /^(.*Rp\s*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
- // Simple decimal: 240.000
- /^(.*)(\d+)[.](\d+)(.*)$/
- ];
-
- for (const pattern of patterns) {
- const match = text.match(pattern);
- if (match) {
- const [, prefix, integerPart, decimalPart, suffix] = match;
-
- console.log('Decimal Observer: Styling', text);
-
- // Create new HTML with decimal styling
- const newHTML = `${prefix || ''}${integerPart}.${decimalPart}${suffix || ''}`;
- element.innerHTML = newHTML;
- return true;
+ try {
+ const text = element.textContent.trim();
+ if (!text) return false;
+
+ // Mark as processed to avoid conflicts
+ element.setAttribute('data-decimal-processed', 'true');
+
+ // Pattern for decimal numbers (including currency formats)
+ const patterns = [
+ // Currency with Rp: Rp 6,210,000.00
+ /^(.*Rp\s*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
+ // Standard decimal: 123.45, 1,234.56
+ /^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
+ // Simple decimal: 240.000
+ /^(.*)(\d+)[.](\d+)(.*)$/
+ ];
+
+ for (const pattern of patterns) {
+ const match = text.match(pattern);
+ if (match) {
+ const [, prefix, integerPart, decimalPart, suffix] = match;
+
+ console.log('Safe Decimal Observer: Styling', text);
+
+ // Double-check the element hasn't changed before updating
+ if (element.textContent.trim() === text && document.contains(element)) {
+ const newHTML = `${prefix || ''}${integerPart}.${decimalPart}${suffix || ''}`;
+ element.innerHTML = newHTML;
+ return true;
+ }
+ }
}
- }
-
- // Handle whole numbers in quantity contexts
- if (text.match(/^\d+$/) && (
- element.closest('[name="quantity"]') ||
- element.closest('.o_field_float') ||
- element.closest('td') && element.closest('table')
- )) {
- console.log('Decimal Observer: Adding decimals to whole number', text);
- element.innerHTML = `${text}.000`;
- return true;
+
+ // Handle whole numbers in quantity contexts (be more selective)
+ if (text.match(/^\d+$/) && text.length > 0 && (
+ element.closest('[name="quantity"]') ||
+ (element.closest('td') && element.closest('table') && text.length <= 6) // Only for reasonable quantities
+ )) {
+ console.log('Safe Decimal Observer: Adding decimals to whole number', text);
+
+ // Double-check before updating
+ if (element.textContent.trim() === text && document.contains(element)) {
+ element.innerHTML = `${text}.000`;
+ return true;
+ }
+ }
+
+ } catch (error) {
+ console.warn('Safe decimal styling error (non-critical):', error);
+ // Remove the processing flag if there was an error
+ element.removeAttribute('data-decimal-processed');
}
return false;
}
-// Process all elements in a container
-function processContainer(container) {
- if (!container) return;
+// Safe processing of containers
+function safelyProcessContainer(container) {
+ if (!container || !document.contains(container)) return;
- // Find all text-containing elements
- const elements = container.querySelectorAll('td, span, div, .o_field_monetary, .o_field_float');
-
- elements.forEach(element => {
- // Only process leaf elements (no child elements with text)
- if (element.children.length === 0 ||
- (element.children.length === 1 && element.children[0].tagName === 'SPAN')) {
- applyDecimalStyling(element);
- }
- });
+ try {
+ // Find all text-containing elements, but be more selective
+ const elements = container.querySelectorAll('td:not([data-decimal-processed]), span:not([data-decimal-processed]), .o_field_monetary:not([data-decimal-processed]), .o_field_float:not([data-decimal-processed])');
+
+ elements.forEach(element => {
+ // Only process leaf elements and skip if Owl is managing them
+ if ((element.children.length === 0 ||
+ (element.children.length === 1 && element.children[0].tagName === 'SPAN')) &&
+ !element.hasAttribute('data-owl-updating')) {
+ safelyApplyDecimalStyling(element);
+ }
+ });
+ } catch (error) {
+ console.warn('Safe container processing error (non-critical):', error);
+ }
}
-// Observer for dynamic content
-const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- if (mutation.type === 'childList') {
- mutation.addedNodes.forEach((node) => {
- if (node.nodeType === Node.ELEMENT_NODE) {
- // Process the new node and its children
- processContainer(node);
+// More conservative observer
+const safeObserver = new MutationObserver((mutations) => {
+ // Debounce the processing to avoid conflicts
+ clearTimeout(safeObserver.timeout);
+ safeObserver.timeout = setTimeout(() => {
+ try {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'childList') {
+ mutation.addedNodes.forEach((node) => {
+ if (node.nodeType === Node.ELEMENT_NODE && document.contains(node)) {
+ // Use requestAnimationFrame to ensure we run after Owl updates
+ requestAnimationFrame(() => {
+ safelyProcessContainer(node);
+ });
+ }
+ });
}
});
+ } catch (error) {
+ console.warn('Safe observer error (non-critical):', error);
}
- });
+ }, 200); // Debounce for 200ms
});
-// Start observing when DOM is ready
-function startObserver() {
- console.log('Decimal Observer: Starting comprehensive observation');
+// Start observing safely
+function startSafeObserver() {
+ console.log('Safe Decimal Observer: Starting conservative observation');
- // Process existing content
- processContainer(document.body);
+ // Process existing content safely
+ requestAnimationFrame(() => {
+ safelyProcessContainer(document.body);
+ });
- // Start observing for new content
- observer.observe(document.body, {
+ // Start observing for new content with reduced frequency
+ safeObserver.observe(document.body, {
childList: true,
subtree: true
});
- // Also process content periodically for dynamic updates
+ // Periodic processing with longer intervals to reduce conflicts
setInterval(() => {
- processContainer(document.body);
- }, 2000);
+ requestAnimationFrame(() => {
+ safelyProcessContainer(document.body);
+ });
+ }, 5000); // Every 5 seconds instead of 2
}
-// Start immediately or when DOM is ready
+// Start safely
if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', startObserver);
+ document.addEventListener('DOMContentLoaded', startSafeObserver);
} else {
- startObserver();
+ startSafeObserver();
}
-console.log('Comprehensive decimal observer loaded');
\ No newline at end of file
+console.log('Safe decimal observer loaded');
\ No newline at end of file
diff --git a/static/src/views/fields/formatters_patch.js b/static/src/views/fields/formatters_patch.js
index 621df94..6eb0172 100644
--- a/static/src/views/fields/formatters_patch.js
+++ b/static/src/views/fields/formatters_patch.js
@@ -7,103 +7,129 @@ import { formatMonetary, formatFloat } from "@web/views/fields/formatters";
console.log('Loading Web Decimal Style formatters patch...');
-// 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 DOM manipulation
- if (this.props.readonly) {
- setTimeout(() => {
- const element = this.el?.querySelector('.o_field_monetary');
- if (element && !element.querySelector('.o_decimal')) {
- const text = element.textContent;
- const match = text?.match(/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/);
- if (match) {
- const [, prefix, integerPart, decimalPart, suffix] = match;
- element.innerHTML = `${prefix}${integerPart}.${decimalPart}${suffix}`;
- }
- }
- }, 50);
- }
-
- 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,
+// 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,
});
- } else {
- res = formatFloat(this.value, { ...options, humanReadable: false });
- }
-
- // For readonly fields, apply decimal styling via DOM manipulation
- if (this.props.readonly) {
- setTimeout(() => {
- const element = this.el?.querySelector('.o_field_float');
- if (element && !element.querySelector('.o_decimal')) {
- const text = element.textContent;
- const match = text?.match(/^(.*)(\d+)[.](\d+)(.*)$/);
- if (match) {
- const [, prefix, integerPart, decimalPart, suffix] = match;
- element.innerHTML = `${prefix}${integerPart}.${decimalPart}${suffix}`;
- }
- }
- }, 50);
- }
-
- return res;
- }
-});
-
-// Enhanced Registry Patching for List Views
-const formattersRegistry = registry.category("formatters");
-
-// Store original formatters
-const originalFormatters = new Map();
-
-function patchFormatter(name) {
- if (formattersRegistry.contains(name) && !originalFormatters.has(name)) {
- const original = formattersRegistry.get(name);
- originalFormatters.set(name, original);
-
- formattersRegistry.add(name, (...args) => {
- const res = original(...args);
- // Return the result as-is, let DOM observer handle styling
- // This avoids markup escaping issues
+ // 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}.${decimalPart}${suffix}`;
+ }
+ }
+ } catch (error) {
+ console.warn('Monetary field decimal styling error (non-critical):', error);
+ }
+ }, 150); // Longer delay to avoid Owl conflicts
+ }
+
return res;
- }, { force: true });
-
- console.log('Web Decimal Style: Patched formatter', name);
- }
+ }
+ });
+
+ 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}.${decimalPart}${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);
}
-// Patch all numeric formatters
-['float', 'monetary', 'percentage', 'float_factor', 'float_time'].forEach(patchFormatter);
+// 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');
diff --git a/static/src/views/inventory_decimal_patch.js b/static/src/views/inventory_decimal_patch.js
index 2fc0c4f..3d7dbff 100644
--- a/static/src/views/inventory_decimal_patch.js
+++ b/static/src/views/inventory_decimal_patch.js
@@ -5,47 +5,71 @@ import { ListRenderer } from "@web/views/list/list_renderer";
console.log('Loading inventory decimal patch...');
-// Post-render DOM manipulation approach to avoid markup escaping issues
+// Safe DOM manipulation approach to avoid Owl lifecycle conflicts
patch(ListRenderer.prototype, {
async render() {
const result = await super.render();
- // After rendering, find and modify decimal numbers in the DOM
- setTimeout(() => {
- this.applyDecimalStyling();
- }, 100);
+ // 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) return;
+ if (!this.el || !document.contains(this.el)) return;
- // Find all cells in the list table
- const cells = this.el.querySelectorAll('td, .o_data_cell');
+ // 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 => {
- // Skip if already processed
- if (cell.querySelector('.o_decimal')) return;
-
- 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;
+ try {
+ // Skip if cell is being updated by Owl
+ if (cell.hasAttribute('data-owl-updating')) return;
- console.log('Applying DOM decimal styling to:', text);
+ // Mark as processed to avoid double processing
+ cell.setAttribute('data-decimal-processed', 'true');
- // Create new HTML structure
- const newHTML = `${prefix}${integerPart}${decimalPoint}${decimalPart}${suffix}`;
- cell.innerHTML = newHTML;
- }
- // Handle whole numbers by adding .000
- else if (text.match(/^\d+$/)) {
- console.log('Adding decimals to whole number:', text);
- cell.innerHTML = `${text}.000`;
+ 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}${decimalPoint}${decimalPart}${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}.000`;
+ }
+ }
+ } catch (error) {
+ console.warn('Error processing cell (non-critical):', error);
}
});
}