203 lines
4.6 KiB
TypeScript
203 lines
4.6 KiB
TypeScript
/**
|
|
* Money utility functions using decimal.js for precise financial calculations
|
|
* Feature: financial-core-upgrade
|
|
* Validates: Requirements 4.1-4.4, 13.1-13.4
|
|
*/
|
|
|
|
import Decimal from 'decimal.js';
|
|
|
|
// Configure decimal.js for financial calculations
|
|
Decimal.set({
|
|
precision: 20,
|
|
rounding: Decimal.ROUND_HALF_UP,
|
|
});
|
|
|
|
// Currency symbols mapping
|
|
const CURRENCY_SYMBOLS: Record<string, string> = {
|
|
CNY: '¥',
|
|
USD: '$',
|
|
EUR: '€',
|
|
JPY: '¥',
|
|
GBP: '£',
|
|
HKD: 'HK$',
|
|
AUD: 'A$',
|
|
CAD: 'C$',
|
|
SGD: 'S$',
|
|
KRW: '₩',
|
|
THB: '฿',
|
|
TWD: 'NT$',
|
|
INR: '₹',
|
|
RUB: '₽',
|
|
BRL: 'R$',
|
|
};
|
|
|
|
/**
|
|
* Money utility object with all financial calculation functions
|
|
*/
|
|
export const Money = {
|
|
/**
|
|
* Add two monetary values
|
|
*/
|
|
add(a: number | string, b: number | string): number {
|
|
return new Decimal(a).plus(b).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Subtract b from a
|
|
*/
|
|
subtract(a: number | string, b: number | string): number {
|
|
return new Decimal(a).minus(b).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Multiply two values
|
|
*/
|
|
multiply(a: number | string, b: number | string): number {
|
|
return new Decimal(a).times(b).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Divide a by b
|
|
*/
|
|
divide(a: number | string, b: number | string): number {
|
|
const divisor = new Decimal(b);
|
|
if (divisor.isZero()) {
|
|
throw new Error('Division by zero');
|
|
}
|
|
return new Decimal(a).dividedBy(divisor).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Round to specified decimal places (default 2)
|
|
*/
|
|
round(value: number | string, decimals: number = 2): number {
|
|
return new Decimal(value).toDecimalPlaces(decimals).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Format money for display with currency symbol (Standard v7.0)
|
|
*/
|
|
format(value: number | string, currency: string = 'CNY'): string {
|
|
const num = new Decimal(value).toNumber();
|
|
try {
|
|
return new Intl.NumberFormat('zh-CN', {
|
|
style: 'currency',
|
|
currency: currency,
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
}).format(num);
|
|
} catch (e) {
|
|
// Fallback for invalid currency codes
|
|
const symbol = CURRENCY_SYMBOLS[currency] || currency;
|
|
const formatted = num.toLocaleString('zh-CN', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
return `${symbol}${formatted}`;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format money without currency symbol
|
|
*/
|
|
formatNumber(value: number | string): string {
|
|
const num = new Decimal(value).toDecimalPlaces(2).toNumber();
|
|
return num.toLocaleString('zh-CN', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Parse money string to number
|
|
*/
|
|
parse(value: string): number {
|
|
const cleaned = value.replace(/[¥$€£₩฿₹₽,\s]/g, '');
|
|
return new Decimal(cleaned).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Check if value is a valid monetary amount
|
|
*/
|
|
isValid(value: unknown): boolean {
|
|
try {
|
|
if (value === null || value === undefined || value === '') {
|
|
return false;
|
|
}
|
|
new Decimal(value as string | number);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calculate daily interest
|
|
*/
|
|
calculateDailyInterest(balance: number, annualRate: number): number {
|
|
return this.round(this.divide(this.multiply(balance, annualRate), 365), 2);
|
|
},
|
|
|
|
/**
|
|
* Compare two monetary values
|
|
*/
|
|
compare(a: number | string, b: number | string): number {
|
|
const result = new Decimal(a).comparedTo(b);
|
|
return typeof result === 'number' ? result : 0;
|
|
},
|
|
|
|
/**
|
|
* Check if value is zero
|
|
*/
|
|
isZero(value: number | string): boolean {
|
|
return new Decimal(value).isZero();
|
|
},
|
|
|
|
/**
|
|
* Check if value is positive
|
|
*/
|
|
isPositive(value: number | string): boolean {
|
|
return new Decimal(value).isPositive();
|
|
},
|
|
|
|
/**
|
|
* Check if value is negative
|
|
*/
|
|
isNegative(value: number | string): boolean {
|
|
return new Decimal(value).isNegative();
|
|
},
|
|
|
|
/**
|
|
* Get absolute value
|
|
*/
|
|
abs(value: number | string): number {
|
|
return new Decimal(value).abs().toNumber();
|
|
},
|
|
|
|
/**
|
|
* Get minimum of two values
|
|
*/
|
|
min(a: number | string, b: number | string): number {
|
|
return Decimal.min(a, b).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Get maximum of two values
|
|
*/
|
|
max(a: number | string, b: number | string): number {
|
|
return Decimal.max(a, b).toNumber();
|
|
},
|
|
|
|
/**
|
|
* Sum an array of values
|
|
*/
|
|
sum(values: (number | string)[]): number {
|
|
return values.reduce<number>((acc, val) => this.add(acc, val), 0);
|
|
},
|
|
};
|
|
|
|
// Export individual functions for convenience
|
|
export const { add, subtract, multiply, divide, round, format, parse, isValid, calculateDailyInterest } = Money;
|
|
|
|
export default Money;
|