# OpenSpec Change: enterprise-money - **Change ID**: enterprise-money - **Package**: `@suaveplan/money` - **Category**: finance - **Target path**: `finance/currency/money` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/money.md` --- ## Summary Deliver `@suaveplan/money` on the genesis branch from scratch at enterprise quality. The package provides precise currency / money arithmetic using BigInt minor-unit representation. It ships the immutable `Money` class, a `CurrencyRegistry` (constructor-injectable, with a default global instance), ISO 4217 metadata, virtual-currency registration, `Intl`-backed formatting, allocation helpers, aggregation helpers, `MoneyRange`, locale-aware parse, BigInt-safe JSON serialization, Zod schemas, property-based tests, and a typed error hierarchy. Zero runtime dependencies beyond `tslib` and `zod`. --- ## Current-state audit (genesis) Does not exist on genesis — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base for `MoneyError` hierarchy | | `@suaveplan/types` (`workspace:^`) | `LoggerLike` (not used at runtime; types only) | | `zod` (`catalog:forms`) | `MoneyLike` / `CurrencyCode` schemas | | `tslib` (`catalog:build`) | Runtime helpers | Dev dep: `fast-check catalog:testing`. --- ## Public API surface (from main branch, fully preserved + genesis additions) ```ts // Core class Money { static fromMajor(value: number | string, currency: string, registry?: CurrencyRegistry): Money static fromMinor(minorUnits: bigint, currency: string, registry?: CurrencyRegistry): Money static zero(currency: string, registry?: CurrencyRegistry): Money static withRegistry(registry: CurrencyRegistry): typeof Money get amount(): bigint // minor units get currency(): string add(other: Money): Money subtract(other: Money): Money multiply(factor: number): Money divide(divisor: number, mode?: RoundingMode): Money compare(other: Money): -1 | 0 | 1 equals(other: Money): boolean abs(): Money negate(): Money isZero(): boolean isPositive(): boolean isNegative(): boolean toMajor(): number toDecimalString(): string toJSON(): { amount: string; currency: string } } // Registry class CurrencyRegistry { static global(): CurrencyRegistry getCurrency(code: string): CurrencyInfo | undefined getCurrencyOrThrow(code: string): CurrencyInfo getAllCurrencyCodes(): string[] registerCurrency(info: CurrencyInfo): void getVirtualCurrencies(): CurrencyInfo[] isVirtualCurrency(code: string): boolean minorUnitMultiplier(code: string): bigint } function getCurrency(code: string): CurrencyInfo | undefined // delegates to global function getCurrencyOrThrow(code: string): CurrencyInfo function getAllCurrencyCodes(): string[] function registerCurrency(info: CurrencyInfo): void function getVirtualCurrencies(): CurrencyInfo[] function isVirtualCurrency(code: string): boolean function minorUnitMultiplier(code: string): bigint // Formatting function formatMoney(money: Money, options?: FormatOptions): string function formatMoneyCompact(money: Money, options?: FormatOptions): string function parseMoney(input: string, currency: string, locale?: string): Money // Allocation function allocateEvenly(money: Money, parts: number): Money[] function allocateByRatios(money: Money, ratios: number[]): Money[] function allocateByPercentages(money: Money, percentages: number[]): Money[] function sumMoney(moneys: Money[]): Money function minMoney(...moneys: Money[]): Money function maxMoney(...moneys: Money[]): Money function applyRounding(money: Money, mode: RoundingMode): Money // MoneyRange (genesis addition) class MoneyRange { constructor(min: Money, max: Money) contains(money: Money): boolean intersect(other: MoneyRange): MoneyRange | null union(other: MoneyRange): MoneyRange toArray(step: Money): Money[] } // Serialization function moneyToJSON(money: Money): { amount: string; currency: string } function moneyFromJSON(json: { amount: string; currency: string }): Money // Zod schemas const MoneyLikeSchema: z.ZodType const CurrencyCodeSchema: z.ZodType const FormatOptionsSchema: z.ZodType // Error types class MoneyError extends SuaveplanError { } class CurrencyMismatchError extends MoneyError { } class UnknownCurrencyError extends MoneyError { } class AllocationError extends MoneyError { } class ParseError extends MoneyError { } ``` --- ## File layout ```text finance/currency/money/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/ money.types.ts money.types.md core/ money/ money.ts / money.md / money.types.ts / money.test.ts / index.ts currency-registry/ currency-registry.ts / .md / .types.ts / .test.ts / index.ts currencies/ currencies.ts / currencies.md / currencies.test.ts / index.ts money-range/ money-range.ts / .md / .test.ts / index.ts formatting/ formatter/ formatter.ts / formatter.md / formatter.test.ts / index.ts allocation/ allocator/ allocator.ts / allocator.md / allocator.test.ts / index.ts schemas/ money-schemas/ money-schemas.ts / money-schemas.md / money-schemas.test.ts / index.ts serialization/ json/ json.ts / json.md / json.test.ts / index.ts tests/ property.test.ts ``` --- ## Impact Downstream: `@suaveplan/currency` (uses `MoneyLike`), `@suaveplan/currency-providers`, payment and billing packages. **Prerequisites on Verdaccio**: `@suaveplan/error`, `zod`.