refactor: centralize domain values and enforce numeric lint

This commit is contained in:
2026-09-13 14:37:39 +03:00
parent 8ba005346d
commit 8637ad2f34
44 changed files with 543 additions and 174 deletions
@@ -0,0 +1,3 @@
export const MAX_CARD_NAME_LENGTH = 100;
export const MAX_CATEGORY_NAME_LENGTH = 80;
export const MAX_OPERATION_DESCRIPTION_LENGTH = 500;
@@ -1,3 +1,6 @@
import { BankOperationKind } from './bank-operation-kind';
const ISO_MONTH_LENGTH = 7;
export function moscowToday(now = new Date()): string {
return new Intl.DateTimeFormat('en-CA', {
timeZone: 'Europe/Moscow',
@@ -30,12 +33,21 @@ export function bankBalance(
let balance = opening;
for (const operation of operations) {
if (operation.cardId === cardId) {
if (operation.kind === 'expense' || operation.kind === 'transfer')
if (
operation.kind === BankOperationKind.Expense ||
operation.kind === BankOperationKind.Transfer
)
balance -= operation.amountKopecks;
else if (operation.kind === 'topup' || operation.kind === 'refund')
else if (
operation.kind === BankOperationKind.Topup ||
operation.kind === BankOperationKind.Refund
)
balance += operation.amountKopecks;
}
if (operation.kind === 'transfer' && operation.targetCardId === cardId)
if (
operation.kind === BankOperationKind.Transfer &&
operation.targetCardId === cardId
)
balance += operation.amountKopecks;
}
return balance;
@@ -60,13 +72,23 @@ export function bankSummary(
for (const operation of operations) {
if (
typeof period === 'string'
? operation.date.slice(0, 7) !== period
? operation.date.slice(0, ISO_MONTH_LENGTH) !== period
: operation.date < period.from || operation.date > period.to
)
continue;
if (operation.kind !== 'expense' && operation.kind !== 'refund') continue;
const expense = operation.kind === 'expense' ? operation.amountKopecks : 0n;
const refund = operation.kind === 'refund' ? operation.amountKopecks : 0n;
if (
operation.kind !== BankOperationKind.Expense &&
operation.kind !== BankOperationKind.Refund
)
continue;
const expense =
operation.kind === BankOperationKind.Expense
? operation.amountKopecks
: 0n;
const refund =
operation.kind === BankOperationKind.Refund
? operation.amountKopecks
: 0n;
expenses += expense;
refunds += refund;
if (operation.categoryId !== null) {
@@ -0,0 +1,8 @@
export const BankOperationKind = {
Expense: 'expense',
Topup: 'topup',
Transfer: 'transfer',
Refund: 'refund',
} as const;
export type BankOperationKind =
(typeof BankOperationKind)[keyof typeof BankOperationKind];
@@ -1,6 +1,9 @@
import { decimalFromString, formatDecimal } from '../../../../common/decimal';
import type { Money } from '../../../../common/money.types';
const MIN_KOPECKS = -9_223_372_036_854_775_808n;
const MAX_KOPECKS = 9_223_372_036_854_775_807n;
export function parseRubMoney(money: Money, positive = false): bigint {
if (
money?.currency !== 'RUB' ||
@@ -12,7 +15,7 @@ export function parseRubMoney(money: Money, positive = false): bigint {
if (amount.scale > 2)
throw new RangeError('RUB money supports at most two decimal places');
const kopecks = amount.value * 10n ** BigInt(2 - amount.scale);
if (kopecks < -(2n ** 63n) || kopecks > 2n ** 63n - 1n)
if (kopecks < MIN_KOPECKS || kopecks > MAX_KOPECKS)
throw new RangeError('RUB money is outside the signed 64-bit range');
if (positive && kopecks <= 0n)
throw new RangeError('Operation amount must be positive');