Files
goodie/frontend/web/static/src/core/currency.js
Marc 8741282322 Eliminate Python dependency: embed frontend assets in odoo-go
- 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>
2026-03-31 23:09:12 +02:00

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);
}