mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
// Generic finance utilities for formatting and arithmetic
|
|
export type Currency = string; // ISO 4217, e.g. 'EUR', 'USD'
|
|
|
|
export interface FormatOptions {
|
|
locale?: string; // defaults to 'en-US'
|
|
currency?: Currency; // defaults to 'EUR'
|
|
minimumFractionDigits?: number;
|
|
maximumFractionDigits?: number;
|
|
}
|
|
|
|
const DEFAULT_LOCALE = 'en-US';
|
|
const DEFAULT_CURRENCY: Currency = 'EUR';
|
|
|
|
export const formatNumber = (value: number, locale = DEFAULT_LOCALE) => {
|
|
return new Intl.NumberFormat(locale).format(value);
|
|
};
|
|
|
|
export const formatCurrency = (value: number | bigint, opts?: Partial<FormatOptions>) => {
|
|
const locale = opts?.locale ?? DEFAULT_LOCALE;
|
|
const currency = opts?.currency ?? DEFAULT_CURRENCY;
|
|
const minimumFractionDigits = opts?.minimumFractionDigits ?? 0;
|
|
const maximumFractionDigits = opts?.maximumFractionDigits ?? 0;
|
|
|
|
// If passed a bigint, treat it as cents and convert to major units if maximumFractionDigits>0
|
|
let numericValue: number;
|
|
if (typeof value === 'bigint') {
|
|
numericValue = Number(value) / Math.pow(10, maximumFractionDigits);
|
|
} else {
|
|
numericValue = value;
|
|
}
|
|
|
|
return new Intl.NumberFormat(locale, {
|
|
style: 'currency',
|
|
currency,
|
|
minimumFractionDigits,
|
|
maximumFractionDigits,
|
|
}).format(numericValue);
|
|
};
|
|
|
|
// A small helper to avoid floating point rounding issues by using integer cents
|
|
export class Money {
|
|
// internal representation: cents for a 2-decimal currency by default
|
|
private cents: bigint;
|
|
private readonly scale: number; // number of fraction digits (e.g. 2 for cents)
|
|
|
|
constructor(amount: number | bigint, scale = 2) {
|
|
this.scale = scale;
|
|
if (typeof amount === 'bigint') {
|
|
this.cents = amount;
|
|
} else {
|
|
// convert float to scaled integer
|
|
const multiplier = Math.pow(10, this.scale);
|
|
this.cents = BigInt(Math.round(amount * multiplier));
|
|
}
|
|
}
|
|
|
|
static fromCents(cents: bigint, scale = 2) {
|
|
const m = new Money(0, scale);
|
|
m.cents = cents;
|
|
return m;
|
|
}
|
|
|
|
add(other: Money) {
|
|
this._assertSameScale(other);
|
|
return Money.fromCents(this.cents + other.cents, this.scale);
|
|
}
|
|
|
|
sub(other: Money) {
|
|
this._assertSameScale(other);
|
|
return Money.fromCents(this.cents - other.cents, this.scale);
|
|
}
|
|
|
|
mul(factor: number) {
|
|
const result = Number(this.cents) * factor;
|
|
return Money.fromCents(BigInt(Math.round(result)), this.scale);
|
|
}
|
|
|
|
div(divisor: number) {
|
|
const result = Number(this.cents) / divisor;
|
|
return Money.fromCents(BigInt(Math.round(result)), this.scale);
|
|
}
|
|
|
|
toNumber() {
|
|
return Number(this.cents) / Math.pow(10, this.scale);
|
|
}
|
|
|
|
toCents(): bigint {
|
|
return this.cents;
|
|
}
|
|
|
|
format(opts?: Partial<FormatOptions>) {
|
|
const maximumFractionDigits = opts?.maximumFractionDigits ?? this.scale;
|
|
return formatCurrency(this.cents, {
|
|
locale: opts?.locale,
|
|
currency: opts?.currency,
|
|
minimumFractionDigits: opts?.minimumFractionDigits ?? this.scale,
|
|
maximumFractionDigits,
|
|
});
|
|
}
|
|
|
|
private _assertSameScale(other: Money) {
|
|
if (other.scale !== this.scale) {
|
|
throw new Error('Money scale mismatch');
|
|
}
|
|
}
|
|
}
|
|
|
|
export default {
|
|
formatCurrency,
|
|
formatNumber,
|
|
Money,
|
|
};
|