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