refactor: organize backend by business responsibility
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
export function moscowToday(now = new Date()): string {
|
||||
return new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Europe/Moscow',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).format(now);
|
||||
}
|
||||
|
||||
export function isCalendarDate(value: string): boolean {
|
||||
if (value.length !== 10 || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
||||
if (value.startsWith('0000')) return false;
|
||||
const date = new Date(`${value}T00:00:00.000Z`);
|
||||
return (
|
||||
!Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value
|
||||
);
|
||||
}
|
||||
|
||||
export function bankBalance(
|
||||
cardId: string,
|
||||
opening: bigint,
|
||||
operations: Array<{
|
||||
kind: string;
|
||||
cardId: string;
|
||||
targetCardId: string | null;
|
||||
amountKopecks: bigint;
|
||||
}>,
|
||||
): bigint {
|
||||
// ponytail: O(N) history fold; optimize only after measuring growing histories.
|
||||
let balance = opening;
|
||||
for (const operation of operations) {
|
||||
if (operation.cardId === cardId) {
|
||||
if (operation.kind === 'expense' || operation.kind === 'transfer')
|
||||
balance -= operation.amountKopecks;
|
||||
else if (operation.kind === 'topup' || operation.kind === 'refund')
|
||||
balance += operation.amountKopecks;
|
||||
}
|
||||
if (operation.kind === 'transfer' && operation.targetCardId === cardId)
|
||||
balance += operation.amountKopecks;
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
export function bankSummary(
|
||||
period: string | { from: string; to: string },
|
||||
operations: Array<{
|
||||
kind: string;
|
||||
date: string;
|
||||
amountKopecks: bigint;
|
||||
categoryId: string | null;
|
||||
}>,
|
||||
) {
|
||||
let expenses = 0n;
|
||||
let refunds = 0n;
|
||||
const categories = new Map<
|
||||
string,
|
||||
{ categoryId: string; expenses: bigint; refunds: bigint; net: bigint }
|
||||
>();
|
||||
// ponytail: O(N) history fold; optimize only after measuring growing histories.
|
||||
for (const operation of operations) {
|
||||
if (
|
||||
typeof period === 'string'
|
||||
? operation.date.slice(0, 7) !== 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;
|
||||
expenses += expense;
|
||||
refunds += refund;
|
||||
if (operation.categoryId !== null) {
|
||||
const category = categories.get(operation.categoryId) ?? {
|
||||
categoryId: operation.categoryId,
|
||||
expenses: 0n,
|
||||
refunds: 0n,
|
||||
net: 0n,
|
||||
};
|
||||
category.expenses += expense;
|
||||
category.refunds += refund;
|
||||
category.net = category.expenses - category.refunds;
|
||||
categories.set(category.categoryId, category);
|
||||
}
|
||||
}
|
||||
return {
|
||||
expenses,
|
||||
refunds,
|
||||
net: expenses - refunds,
|
||||
categories: [...categories.values()],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user