import { Result } from "@common/helpers"; import DineroFactory, { Currency, Dinero } from "dinero.js"; import { Percentage } from "./percentage"; import { Quantity } from "./quantity"; import { ValueObject } from "./value-object"; const DEFAULT_SCALE = 2; type CurrencyData = Currency; export type RoundingMode = | "HALF_ODD" | "HALF_EVEN" | "HALF_UP" | "HALF_DOWN" | "HALF_TOWARDS_ZERO" | "HALF_AWAY_FROM_ZERO" | "DOWN"; interface IMoneyValueProps { amount: number; scale: number; currency_code: string; } interface IMoneyValue { amount: number; scale: number; currency: Dinero.Currency; getValue(): IMoneyValueProps; convertScale(newScale: number): MoneyValue; add(addend: MoneyValue): MoneyValue; subtract(subtrahend: MoneyValue): MoneyValue; multiply(multiplier: number | Quantity, roundingMode?: RoundingMode): MoneyValue; divide(divisor: number, roundingMode?: RoundingMode): MoneyValue; percentage(percentage: number, roundingMode?: RoundingMode): MoneyValue; equalsTo(comparator: MoneyValue): boolean; greaterThan(comparator: MoneyValue): boolean; lessThan(comparator: MoneyValue): boolean; isZero(): boolean; isPositive(): boolean; isNegative(): boolean; hasSameCurrency(comparator: MoneyValue): boolean; hasSameAmount(comparator: MoneyValue): boolean; format(locale: string): string; } export class MoneyValue extends ValueObject implements IMoneyValue { private readonly dinero: Dinero; static create(props: IMoneyValueProps) { return Result.ok(new MoneyValue(props)); } constructor(props: IMoneyValueProps) { super(props); const { amount, scale, currency_code } = props; this.dinero = Object.freeze( DineroFactory({ amount, precision: scale, currency: currency_code as Currency, }) ); // 🔒 Garantiza inmutabilidad } get amount(): number { return this.dinero.getAmount() / Math.pow(10, this.dinero.getPrecision()); } get currency(): CurrencyData { return this.dinero.getCurrency(); } get scale(): number { return this.dinero.getPrecision(); } getValue(): IMoneyValueProps { return this.props; } convertScale(newScale: number, roundingMode: RoundingMode = "HALF_UP"): MoneyValue { const _newDinero = this.dinero.convertPrecision(newScale, roundingMode); return new MoneyValue({ amount: _newDinero.getAmount(), scale: _newDinero.getPrecision(), currency_code: _newDinero.getCurrency(), }); } add(addend: MoneyValue): MoneyValue { return new MoneyValue({ amount: this.dinero.add(addend.dinero).getAmount(), scale: this.scale, currency_code: this.currency, }); } subtract(subtrahend: MoneyValue): MoneyValue { return new MoneyValue({ amount: this.dinero.subtract(subtrahend.dinero).getAmount(), scale: this.scale, currency_code: this.currency, }); } multiply(multiplier: number | Quantity, roundingMode?: RoundingMode): MoneyValue { const _multiplier = typeof multiplier === "number" ? multiplier : multiplier.toNumber(); const _newDinero = this.dinero.multiply(_multiplier, roundingMode); return new MoneyValue({ amount: _newDinero.getAmount(), scale: _newDinero.getPrecision(), currency_code: _newDinero.getCurrency(), }); } divide(divisor: number | Quantity, roundingMode?: RoundingMode): MoneyValue { const _divisor = typeof divisor === "number" ? divisor : divisor.toNumber(); const _newDinero = this.dinero.divide(_divisor, roundingMode); return new MoneyValue({ amount: _newDinero.getAmount(), scale: _newDinero.getPrecision(), currency_code: _newDinero.getCurrency(), }); } percentage(percentage: number | Percentage, roundingMode?: RoundingMode): MoneyValue { const _percentage = typeof percentage === "number" ? percentage : percentage.toNumber(); const _newDinero = this.dinero.percentage(_percentage, roundingMode); return new MoneyValue({ amount: _newDinero.getAmount(), scale: _newDinero.getPrecision(), currency_code: _newDinero.getCurrency(), }); } equalsTo(comparator: MoneyValue): boolean { return this.dinero.equalsTo(comparator.dinero); } greaterThan(comparator: MoneyValue): boolean { return this.dinero.greaterThan(comparator.dinero); } lessThan(comparator: MoneyValue): boolean { return this.dinero.lessThan(comparator.dinero); } isZero(): boolean { return this.dinero.isZero(); } isPositive(): boolean { return this.amount > 0; } isNegative(): boolean { return this.amount < 0; } hasSameCurrency(comparator: MoneyValue): boolean { return this.dinero.hasSameCurrency(comparator.dinero); } hasSameAmount(comparator: MoneyValue): boolean { return this.dinero.hasSameAmount(comparator.dinero); } format(locale: string): string { const amount = this.amount; const currency = this.currency; const scale = this.scale; return new Intl.NumberFormat(locale, { style: "currency", currency: currency, minimumFractionDigits: scale, maximumFractionDigits: scale, useGrouping: true, }).format(amount); } }