first commit
This commit is contained in:
commit
56bfed79ac
31
__manifest__.py
Normal file
31
__manifest__.py
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
'name': 'Web Decimal Style',
|
||||
'version': '1.0',
|
||||
'category': 'Web',
|
||||
'summary': 'Style decimals differently in number fields',
|
||||
'description': """
|
||||
This module overrides the default number and monetary formatters
|
||||
to wrap the decimal part in a CSS class for custom styling.
|
||||
""",
|
||||
'depends': ['web', 'account'],
|
||||
'data': [],
|
||||
'assets': {
|
||||
'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/fields/formatters_patch.js',
|
||||
'web_decimal_style/static/src/views/fields/float_field.xml',
|
||||
'web_decimal_style/static/src/views/fields/monetary_field.xml',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'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/fields/formatters_patch.js',
|
||||
'web_decimal_style/static/src/views/fields/float_field.xml',
|
||||
'web_decimal_style/static/src/views/fields/monetary_field.xml',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
37
static/src/core/utils/numbers_patch.js
Normal file
37
static/src/core/utils/numbers_patch.js
Normal file
@ -0,0 +1,37 @@
|
||||
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.
|
||||
* @param {string} formattedValue
|
||||
* @returns {import("@odoo/owl").Markup|string}
|
||||
*/
|
||||
export function wrapDecimal(formattedValue) {
|
||||
if (typeof formattedValue !== "string" || !formattedValue) {
|
||||
return formattedValue;
|
||||
}
|
||||
// If it's already a markup object or contains our span, don't double wrap
|
||||
if (typeof formattedValue === "object" || formattedValue.includes('class="o_decimal"')) {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
const decimalPoint = localization.decimalPoint;
|
||||
|
||||
if (formattedValue.includes(decimalPoint)) {
|
||||
const parts = formattedValue.split(decimalPoint);
|
||||
const integerPart = parts.slice(0, -1).join(decimalPoint);
|
||||
const decimalPartWithSymbol = parts[parts.length - 1];
|
||||
|
||||
// Try to separate digits from trailing symbols/spaces
|
||||
const match = decimalPartWithSymbol.match(/^(\d+)(.*)$/);
|
||||
if (match) {
|
||||
const digits = match[1];
|
||||
const rest = match[2];
|
||||
return markup(`${integerPart}<span class="o_decimal">${decimalPoint}${digits}</span>${rest}`);
|
||||
} else {
|
||||
// Fallback: wrap the whole decimal part if it doesn't start with digits
|
||||
return markup(`${integerPart}<span class="o_decimal">${decimalPoint}${decimalPartWithSymbol}</span>`);
|
||||
}
|
||||
}
|
||||
return formattedValue;
|
||||
}
|
||||
5
static/src/css/decimal_style.css
Normal file
5
static/src/css/decimal_style.css
Normal file
@ -0,0 +1,5 @@
|
||||
.o_decimal {
|
||||
font-size: 0.85em !important;
|
||||
opacity: 0.8 !important;
|
||||
display: inline-block;
|
||||
}
|
||||
8
static/src/views/fields/float_field.xml
Normal file
8
static/src/views/fields/float_field.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="web_decimal_style.FloatField" t-inherit="web.FloatField" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//span[@t-if='props.readonly']" position="replace">
|
||||
<span t-if="props.readonly" t-out="formattedValue" />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
89
static/src/views/fields/formatters_patch.js
Normal file
89
static/src/views/fields/formatters_patch.js
Normal file
@ -0,0 +1,89 @@
|
||||
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";
|
||||
|
||||
// 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,
|
||||
});
|
||||
return this.props.readonly ? wrapDecimal(res) : 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 });
|
||||
}
|
||||
return this.props.readonly ? wrapDecimal(res) : res;
|
||||
}
|
||||
});
|
||||
|
||||
// Patch Tax Totals (Account module)
|
||||
try {
|
||||
const { TaxTotalsComponent, TaxGroupComponent } = require("@account/components/tax_totals/tax_totals");
|
||||
if (TaxTotalsComponent) {
|
||||
patch(TaxTotalsComponent.prototype, {
|
||||
formatMonetary(value) {
|
||||
const res = formatMonetary(value, { currencyId: this.totals.currency_id });
|
||||
return wrapDecimal(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (TaxGroupComponent) {
|
||||
patch(TaxGroupComponent.prototype, {
|
||||
formatMonetary(value) {
|
||||
const res = formatMonetary(value, { currencyId: this.props.totals.currency_id });
|
||||
return wrapDecimal(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Account module not loaded or could not be patched, skipping TaxTotals patching");
|
||||
}
|
||||
|
||||
// Also patch Registry for List Views
|
||||
const formattersRegistry = registry.category("formatters");
|
||||
|
||||
function patchRegistryFormatter(name) {
|
||||
if (formattersRegistry.contains(name)) {
|
||||
const original = formattersRegistry.get(name);
|
||||
formattersRegistry.add(name, (...args) => {
|
||||
const res = original(...args);
|
||||
return wrapDecimal(res);
|
||||
}, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
patchRegistryFormatter("float");
|
||||
patchRegistryFormatter("monetary");
|
||||
patchRegistryFormatter("percentage");
|
||||
patchRegistryFormatter("float_factor");
|
||||
patchRegistryFormatter("float_time");
|
||||
8
static/src/views/fields/monetary_field.xml
Normal file
8
static/src/views/fields/monetary_field.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="web_decimal_style.MonetaryField" t-inherit="web.MonetaryField" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//span[@t-if='props.readonly']" position="replace">
|
||||
<span t-if="props.readonly" t-out="formattedValue" />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
Loading…
Reference in New Issue
Block a user