- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/ directory (2925 files, 43MB) — no more runtime reads from Python Odoo - Replace OdooAddonsPath config with FrontendDir pointing to local frontend/ - Rewire bundle.go, static.go, templates.go, webclient.go to read from frontend/ instead of external Python Odoo addons directory - Auto-detect frontend/ and build/ dirs relative to binary in main.go - Delete obsolete Python helper scripts (tools/*.py) The Go server is now fully self-contained: single binary + frontend/ folder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.5 KiB
JavaScript
99 lines
3.5 KiB
JavaScript
import { reactive } from "@odoo/owl";
|
|
import { rpc } from "@web/core/network/rpc";
|
|
import { user } from "@web/core/user";
|
|
import { formatFloat, humanNumber } from "@web/core/utils/numbers";
|
|
import { nbsp } from "@web/core/utils/strings";
|
|
import { session } from "@web/session";
|
|
|
|
export const currencies = session.currencies || {};
|
|
// to make sure code is reading currencies from here
|
|
delete session.currencies;
|
|
|
|
export function getCurrency(id) {
|
|
return currencies[id];
|
|
}
|
|
|
|
export async function getCurrencyRates() {
|
|
const rates = reactive({});
|
|
|
|
function recordsToRates(records) {
|
|
return Object.fromEntries(records.map((r) => [r.id, r.inverse_rate]));
|
|
}
|
|
|
|
const model = "res.currency";
|
|
const method = "read";
|
|
const url = `/web/dataset/call_kw/${model}/${method}`;
|
|
const context = {
|
|
...user.context,
|
|
to_currency: user.activeCompany.currency_id,
|
|
};
|
|
const params = {
|
|
model,
|
|
method,
|
|
args: [Object.keys(currencies).map(Number), ["inverse_rate"]],
|
|
kwargs: { context },
|
|
};
|
|
const records = await rpc(url, params, {
|
|
cache: {
|
|
type: "disk",
|
|
update: "once",
|
|
callback: (records, hasChanged) => {
|
|
if (hasChanged) {
|
|
Object.assign(rates, recordsToRates(records));
|
|
}
|
|
},
|
|
},
|
|
});
|
|
Object.assign(rates, recordsToRates(records));
|
|
return rates;
|
|
}
|
|
|
|
/**
|
|
* Returns a string representing a monetary value. The result takes into account
|
|
* the user settings (to display the correct decimal separator, currency, ...).
|
|
*
|
|
* @param {number} value the value that should be formatted
|
|
* @param {number} [currencyId] the id of the 'res.currency' to use
|
|
* @param {Object} [options]
|
|
* additional options to override the values in the python description of the
|
|
* field.
|
|
* @param {Object} [options.data] a mapping of field names to field values,
|
|
* required with options.currencyField
|
|
* @param {boolean} [options.noSymbol] this currency has not a sympbol
|
|
* @param {boolean} [options.humanReadable] if true, large numbers are formatted
|
|
* to a human readable format.
|
|
* @param {number} [options.minDigits] see @humanNumber
|
|
* @param {boolean} [options.trailingZeros] if false, numbers will have zeros
|
|
* to the right of the last non-zero digit hidden
|
|
* @param {[number, number]} [options.digits] the number of digits that should
|
|
* be used, instead of the default digits precision in the field. The first
|
|
* number is always ignored (legacy constraint)
|
|
* @param {number} [options.minDigits] the minimum number of decimal digits to display.
|
|
* Displays maximum 6 decimal places if no precision is provided.
|
|
* @returns {string}
|
|
*/
|
|
export function formatCurrency(amount, currencyId, options = {}) {
|
|
const currency = getCurrency(currencyId);
|
|
|
|
const digits = (options.digits !== undefined)? options.digits : (currency && currency.digits)
|
|
|
|
let formattedAmount;
|
|
if (options.humanReadable) {
|
|
formattedAmount = humanNumber(amount, {
|
|
decimals: digits ? digits[1] : 2,
|
|
minDigits: options.minDigits,
|
|
});
|
|
} else {
|
|
formattedAmount = formatFloat(amount, { digits, minDigits: options.minDigits, trailingZeros: options.trailingZeros });
|
|
}
|
|
|
|
if (!currency || options.noSymbol) {
|
|
return formattedAmount;
|
|
}
|
|
const formatted = [currency.symbol, formattedAmount];
|
|
if (currency.position === "after") {
|
|
formatted.reverse();
|
|
}
|
|
return formatted.join(nbsp);
|
|
}
|