feat(finance): add settlement currency and cycle fx rates

This commit is contained in:
2026-03-10 16:46:59 +04:00
parent 4c0508f618
commit fb85219409
38 changed files with 3546 additions and 114 deletions

View File

@@ -1,7 +1,7 @@
export { BillingPeriod } from './billing-period'
export { DOMAIN_ERROR_CODE, DomainError } from './errors'
export { BillingCycleId, HouseholdId, MemberId, PurchaseEntryId } from './ids'
export { CURRENCIES, Money } from './money'
export { CURRENCIES, FX_RATE_SCALE_MICROS, Money, convertMoney } from './money'
export { normalizeSupportedLocale, SUPPORTED_LOCALES } from './locale'
export {
Temporal,

View File

@@ -1,6 +1,7 @@
import { DOMAIN_ERROR_CODE, DomainError } from './errors'
export const CURRENCIES = ['GEL', 'USD'] as const
export const FX_RATE_SCALE_MICROS = 1_000_000n
export type CurrencyCode = (typeof CURRENCIES)[number]
@@ -73,6 +74,23 @@ function formatMajorUnits(minor: bigint): string {
return `${sign}${whole.toString()}.${fractionString}`
}
function divideRoundedHalfUp(dividend: bigint, divisor: bigint): bigint {
if (divisor === 0n) {
throw new DomainError(DOMAIN_ERROR_CODE.INVALID_MONEY_AMOUNT, 'Division by zero')
}
const sign = dividend < 0n ? -1n : 1n
const absoluteDividend = dividend < 0n ? -dividend : dividend
const quotient = absoluteDividend / divisor
const remainder = absoluteDividend % divisor
if (remainder * 2n >= divisor) {
return (quotient + 1n) * sign
}
return quotient * sign
}
export class Money {
readonly amountMinor: bigint
readonly currency: CurrencyCode
@@ -257,3 +275,23 @@ export class Money {
}
}
}
export function convertMoney(
amount: Money,
targetCurrency: CurrencyCode,
rateMicros: bigint
): Money {
if (rateMicros <= 0n) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_MONEY_AMOUNT,
`Exchange rate must be positive: ${rateMicros.toString()}`
)
}
if (amount.currency === targetCurrency) {
return amount
}
const convertedMinor = divideRoundedHalfUp(amount.amountMinor * rateMicros, FX_RATE_SCALE_MICROS)
return Money.fromMinor(convertedMinor, targetCurrency)
}