/** * Framework-agnostic money DISPLAY formatting. * * The platform stores amounts as a plain number plus a currency (an ISO 4217 code, * optionally a symbol and a decimal-places override from the host `Currency` row). * This module turns that into a localized string via `Intl.NumberFormat`, with a * deterministic fallback so output is machine-stable regardless of the host OS * locale - the same principle as the date org-format seam. * * It is pure - no React, no host access - so it lives in core-utils behind the * `@ethisyscore/core-utils/money` sub-path. Resolving WHICH currency to render in * (the amount's own currency, or the org's reporting currency) and CONVERTING * between currencies are host-aware concerns handled by the plugin-ui `useCurrency` * hook, which formats through this module. * * When no currency is given, `formatMoney` falls back to the organisation's * reporting currency via the {@link ./orgCurrency} seam (the same module-ref + * fail-open-to-`GBP` mechanism as the date org-format seam), so a plugin can render * reporting-currency amounts without threading a code or hardcoding one. */ /** * Deterministic fallback locale. Matches the date seam's UK fallback so money and * dates render consistently when no explicit locale is supplied, and so output does * not drift with the OS locale of whatever host renders it. */ declare const MONEY_FALLBACK_LOCALE = "en-GB"; /** Options for {@link formatMoney}. */ interface FormatMoneyOptions { /** * ISO 4217 code (e.g. `"GBP"`, `"EUR"`) - drives `Intl` currency formatting. * Optional: when omitted, `formatMoney` resolves the organisation's reporting * currency (see {@link ./orgCurrency}), falling back to `GBP` when none is stamped. */ currencyCode?: string; /** * Fraction digits to show. When null/undefined, `Intl`'s per-currency default is * used (GBP -> 2, JPY -> 0). Pass a number to force a currency's decimal-places * override from the host `Currency` row. */ decimalPlaces?: number | null; /** BCP-47 locale; defaults to {@link MONEY_FALLBACK_LOCALE} for stable output. */ locale?: string; /** * Symbol to use only in the fallback path when `Intl` cannot format the currency * code (e.g. a non-ISO custom code). Ignored on the happy path, where `Intl` * supplies the symbol. */ symbol?: string | null; } /** * Formats a monetary amount. The currency is resolved in this order: * * - `formatMoney(amount, "EUR")` — a positional ISO code, with the currency's * natural decimals. * - `formatMoney(amount, { currencyCode, decimalPlaces, locale, symbol })` — the * options form; any of `currencyCode`/`decimalPlaces` may be omitted. * - `formatMoney(amount)` (or an options object without `currencyCode`) — the * organisation's reporting currency stamped via the {@link ./orgCurrency} seam, * including that currency's decimal-places override when the caller gave none. * When no org currency is stamped it falls back to {@link MONEY_FALLBACK_CURRENCY} * (`GBP`), the same deterministic fail-open as the date org-format seam. * * On an unknown/invalid currency code (which makes `Intl.NumberFormat` throw) it * falls back to a symbol/code prefix plus the fixed-decimal amount, so a bad code * degrades to a readable string rather than throwing on a render path. */ declare function formatMoney(amount: number, currency?: string | FormatMoneyOptions): string; /** Options for {@link formatCompactMoney}. */ interface FormatCompactMoneyOptions { /** ISO 4217 code (e.g. `"GBP"`, `"EUR"`) - resolves the leading symbol. */ currencyCode: string; /** * Fallback symbol used ONLY when `Intl` cannot resolve `currencyCode` (a non-ISO * custom code). For a valid ISO code `Intl`'s own symbol always wins - this option * does not override it. Mirrors {@link FormatMoneyOptions.symbol} and the * {@link getCurrencySymbol} fallback contract. */ symbol?: string | null; /** BCP-47 locale used only to resolve the symbol; defaults to {@link MONEY_FALLBACK_LOCALE}. */ locale?: string; } /** * Formats an amount as an abbreviated currency string for compact display, such * as chart axis ticks where space is tight - e.g. `"£1.3m"`, `"£500k"`, `"£99"`. * * Thresholds on the absolute value: >= 1,000,000 renders in millions with a * lowercase `m` and one decimal place; >= 1,000 renders in thousands with a * lowercase `k` and no decimals; otherwise the whole amount with no decimals. * A leading minus is kept before the symbol (e.g. `"-£1.3m"`). * * Unlike {@link formatMoney} this is NOT org-locale driven beyond resolving the * leading symbol via {@link getCurrencySymbol}; the number itself is formatted * with fixed abbreviations so axis labels stay short and machine-stable. */ declare function formatCompactMoney(amount: number, options: FormatCompactMoneyOptions): string; /** * Resolves the currency symbol for a code in a locale (e.g. `"GBP"` -> `"£"`), * for use in input adornments and labels. Falls back to the supplied `symbol`, then * the code itself, when `Intl` cannot resolve it. */ declare function getCurrencySymbol(currencyCode: string, locale?: string, symbol?: string | null): string; /** * Organisation-driven reporting-currency resolution. * * Plugin (and host) surfaces render reporting-currency amounts in whatever the * organisation has configured as its reporting currency, without threading a * currency code through every call site. This module holds the module-level * reporting-currency ref, its setter, and the readers with a deterministic * fallback constant. It is pure — no React, no host access — so it lives in * core-utils behind the `@ethisyscore/core-utils/money` sub-path, mirroring the * date org-format seam (`@ethisyscore/core-utils/date/org-format`). * * The ref is stamped by the surface sync hook — the SDK * `useReportingCurrencySync` reads the host * `settings:get-organisation-reporting-currency` tool and calls * `setOrgReportingCurrency`. When no reporting currency is stamped (a * standalone/mock run, an old host, or a failed fetch) resolution falls back to * the deterministic UK default `GBP`, NOT the OS locale, so output is * machine-stable regardless of the host. This is the exact fail-open contract of * the date seam's UK `dd/MM/yyyy` fallback. * * Deciding WHICH currency an amount is in (its own currency vs the org's * reporting currency) and CONVERTING between currencies stay host-aware concerns * of the plugin-ui `useCurrency` hook. This seam only answers "what is the org's * reporting currency" for the pure {@link formatMoney} default path. */ /** * Deterministic fallback reporting currency when no org currency is stamped. * Matches the date seam's UK fallback (`ORG_DATE_FALLBACK_FORMAT`) so money and * dates degrade consistently, and so output does not drift with the OS locale of * whatever host renders it. */ declare const MONEY_FALLBACK_CURRENCY = "GBP"; /** The organisation's reporting currency as stamped by the surface sync hook. */ interface OrgReportingCurrency { /** ISO 4217 code (e.g. `"GBP"`, `"EUR"`) drives `Intl` currency formatting. */ code: string; /** * Host decimal-places override for the currency, or null to use the currency's * ISO default (GBP -> 2, JPY -> 0). Applied by {@link formatMoney} only when the * caller supplies no explicit decimalPlaces. */ decimalPlaces: number | null; } /** * Stamps the module-level reporting-currency ref. Called by the surface sync hook * each time the organisation's reporting currency resolves. Pass `null` to clear * it and fall back to the deterministic {@link MONEY_FALLBACK_CURRENCY}. */ declare function setOrgReportingCurrency(currency: OrgReportingCurrency | null): void; /** The org's stamped reporting currency, or null to fall back to the UK default. */ declare function getOrgReportingCurrency(): OrgReportingCurrency | null; /** * The org's effective reporting-currency code: the stamped code when present, else * the deterministic {@link MONEY_FALLBACK_CURRENCY} (`GBP`). Never null, so callers * always have a well-formed code to format with. */ declare function resolveOrgReportingCurrencyCode(): string; /** * Framework-agnostic money PARSING — the inverse of {@link formatMoney} for form * inputs and editable amount fields. * * A user (or a round-tripped display value) types an amount in the en-GB / * dot-decimal display shape — a currency symbol or ISO code prefix, comma grouping * separators, stray whitespace, and a dot decimal point ("£1,234.56", "GBP 1,234.56"). * This turns that back into a plain number, or null when there is no meaningful value * to parse. * * It is deliberately lenient about the symbol, currency-code prefix and grouping so * it tolerates the display forms {@link formatMoney} produces in the en-GB fallback * locale, but it is NOT a locale-universal inverse of {@link formatMoney}. Only the * dot-decimal shape is understood: a comma is always treated as a grouping separator * and dropped, so comma-decimal locales (de-DE `1.234,56`) are OUT OF SCOPE and would * mis-parse. The platform stores and edits amounts in the dot-decimal numeric form. * * Ported from the per-plugin `parseCurrency` helper so consuming plugins drop the * local copy. */ /** * Parses a formatted currency string back to a number. Returns null for * null/undefined, an empty/whitespace-only string, or the literal `"N/A"` * placeholder; normalises the Unicode minus, strips every non-numeric character * (currency symbols, alpha currency-code prefixes, grouping commas and whitespace), * then `parseFloat`s the remainder, returning null when the result is not a number. * * Parses only the en-GB / dot-decimal display shape (comma thousands, dot decimal); * see the module docs — it is NOT a locale-universal inverse of {@link formatMoney}. * A leading minus is preserved, so `"-£10.00"` parses to `-10`. Garbage that contains * no leading number (`"abc"`) yields null. * * @example * parseMoney("£1,234.56") // 1234.56 * parseMoney("GBP 1,234.56") // 1234.56 * parseMoney("−£10.00") // -10 (Unicode minus U+2212) * parseMoney("ZZZZ10.00") // 10 (invalid-code fallback prefix) * parseMoney("N/A") // null * parseMoney("") // null * parseMoney(null) // null */ declare function parseMoney(value: string | null | undefined): number | null; export { type FormatCompactMoneyOptions, type FormatMoneyOptions, MONEY_FALLBACK_CURRENCY, MONEY_FALLBACK_LOCALE, type OrgReportingCurrency, formatCompactMoney, formatMoney, getCurrencySymbol, getOrgReportingCurrency, parseMoney, resolveOrgReportingCurrencyCode, setOrgReportingCurrency };