56 lines
2.1 KiB
TypeScript
56 lines
2.1 KiB
TypeScript
import { formatMoney } from "@vmall/shared";
|
|
import type { Currency } from "@vmall/shared";
|
|
|
|
/** Currency-aware money formatter: exponents come from the API, cached app-wide. */
|
|
export function useMoney() {
|
|
const { $api } = useNuxtApp();
|
|
const { locale } = useI18n();
|
|
const currencies = useState<Currency[]>("vmall.currencies", () => []);
|
|
|
|
async function load(): Promise<void> {
|
|
if (currencies.value.length > 0) return;
|
|
try {
|
|
currencies.value = await $api.listCurrencies();
|
|
} catch {
|
|
/* keep empty; fmt falls back to exponent 2 */
|
|
}
|
|
}
|
|
|
|
function exponentOf(currency: string): number {
|
|
return currencies.value.find((c) => c.code === currency)?.exponent ?? 2;
|
|
}
|
|
|
|
function fmt(amountMinor: number, currency: string): string {
|
|
return formatMoney(amountMinor, currency, exponentOf(currency), locale.value);
|
|
}
|
|
|
|
/** Currency for money fields without an explicit currency: the table's base. */
|
|
const shopCurrency = computed(
|
|
() => currencies.value.find((c) => c.is_base)?.code ?? currencies.value[0]?.code ?? "USD",
|
|
);
|
|
|
|
/** Parse a major-unit decimal string into integer minor units; null when invalid. */
|
|
function majorToMinor(value: string, currency: string): number | null {
|
|
const clean = value.trim();
|
|
if (!/^\d+(?:\.\d+)?$/.test(clean)) return null;
|
|
const exponent = exponentOf(currency);
|
|
const [whole, fraction = ""] = clean.split(".");
|
|
if (fraction.length > exponent) return null;
|
|
const minor = Number(whole) * 10 ** exponent + Number(fraction.padEnd(exponent, "0") || 0);
|
|
return Number.isSafeInteger(minor) ? minor : null;
|
|
}
|
|
|
|
/** Render integer minor units as a major-unit string for form inputs. */
|
|
function minorToMajor(minor: number, currency: string): string {
|
|
const exponent = exponentOf(currency);
|
|
const sign = minor < 0 ? "-" : "";
|
|
const abs = Math.abs(minor);
|
|
const whole = Math.trunc(abs / 10 ** exponent);
|
|
if (exponent === 0) return `${sign}${whole}`;
|
|
const fraction = (abs % 10 ** exponent).toString().padStart(exponent, "0");
|
|
return `${sign}${whole}.${fraction}`;
|
|
}
|
|
|
|
return { currencies, load, fmt, shopCurrency, majorToMinor, minorToMajor };
|
|
}
|