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
@@ -1,3 +1,4 @@
import { BankOperationKind } from '../domain/bank-operation-kind';
import { GoneException, Injectable } from '@nestjs/common';
import {
BankCardOperation,
@@ -6,6 +7,7 @@ import {
import { PrismaService } from '../../../../prisma/prisma.service';
import {
BankOperationDto,
DEFAULT_BANK_PAGE_SIZE,
BankOperationsQueryDto,
BankPageQueryDto,
CreateBankOperationDto,
@@ -14,6 +16,7 @@ import {
import { rubMoney } from '../domain/rub-money';
import {
BankErrorCode,
conflict,
currentDate,
fail,
@@ -48,7 +51,7 @@ export class BankOperationsService {
})
: null;
const refunds =
row.kind === 'expense' || source
row.kind === BankOperationKind.Expense || source
? await tx.bankCardOperation.findMany({
where: {
userId: row.userId,
@@ -70,7 +73,7 @@ export class BankOperationsService {
originalExpenseId: row.originalExpenseId,
amount: rubMoney(row.amountKopecks),
availableRefund:
row.kind === 'expense' || source
row.kind === BankOperationKind.Expense || source
? rubMoney(
(source?.amountKopecks ?? row.amountKopecks) -
refunds.reduce((sum, r) => sum + r.amountKopecks, 0n),
@@ -100,15 +103,15 @@ export class BankOperationsService {
(id === input.cardId || id === input.targetCardId) &&
input.date < card.openingDate
)
fail('BANK_INVALID_DATE');
fail(BankErrorCode.InvalidDate);
}
if (
old &&
(old.kind !== input.kind ||
old.originalExpenseId !== input.originalExpenseId)
)
fail('BANK_IMMUTABLE_OPERATION');
if (input.kind === 'transfer') {
fail(BankErrorCode.ImmutableOperation);
if (input.kind === BankOperationKind.Transfer) {
if (
!input.targetCardId ||
input.targetCardId === input.cardId ||
@@ -117,7 +120,7 @@ export class BankOperationsService {
)
fail();
} else if (input.targetCardId) fail();
if (input.kind === 'expense') {
if (input.kind === BankOperationKind.Expense) {
if (input.originalExpenseId) fail();
if (!input.categoryId) {
const category = await tx.expenseCategory.findUnique({
@@ -133,8 +136,9 @@ export class BankOperationsService {
)
missing();
} else if (input.categoryId) fail();
if (input.kind !== 'refund' && input.originalExpenseId) fail();
if (input.kind === 'refund') {
if (input.kind !== BankOperationKind.Refund && input.originalExpenseId)
fail();
if (input.kind === BankOperationKind.Refund) {
if (!input.originalExpenseId) fail();
const source = await this.repository.operation(
tx,
@@ -143,10 +147,10 @@ export class BankOperationsService {
);
if (
source.deletedAt ||
source.kind !== 'expense' ||
source.kind !== BankOperationKind.Expense ||
source.cardId !== input.cardId
)
fail('BANK_INVALID_REFUND');
fail(BankErrorCode.InvalidRefund);
if (
(
await tx.bankCardOperation.updateMany({
@@ -169,15 +173,15 @@ export class BankOperationsService {
...(old ? { id: { not: old.id } } : {}),
},
});
if (input.date < source.date) fail('BANK_INVALID_DATE');
if (input.date < source.date) fail(BankErrorCode.InvalidDate);
if (
refunds.reduce((sum, r) => sum + r.amountKopecks, 0n) +
input.amountKopecks >
source.amountKopecks
)
conflict('BANK_REFUND_LIMIT');
conflict(BankErrorCode.RefundLimit);
}
if (old?.kind === 'expense') {
if (old?.kind === BankOperationKind.Expense) {
const refunds = await tx.bankCardOperation.findMany({
where: { userId, originalExpenseId: old.id, deletedAt: null },
});
@@ -188,7 +192,7 @@ export class BankOperationsService {
refunds.reduce((sum, row) => sum + row.amountKopecks, 0n) >
input.amountKopecks)
)
conflict('BANK_DEPENDENT_REFUNDS');
conflict(BankErrorCode.DependentRefunds);
}
}
createOperation(userId: string, dto: CreateBankOperationDto) {
@@ -201,9 +205,9 @@ export class BankOperationsService {
if (existing) {
if (existing.userId !== userId) missing();
if (existing.createFingerprint !== hash)
conflict('BANK_INTENT_CONFLICT');
conflict(BankErrorCode.IntentConflict);
if (existing.deletedAt)
throw new GoneException({ code: 'BANK_OPERATION_DELETED' });
throw new GoneException({ code: BankErrorCode.OperationDeleted });
return this.operationView(tx, existing);
}
const input = { ...normalized };
@@ -265,12 +269,12 @@ export class BankOperationsService {
if (row.targetCardId)
await this.repository.card(tx, userId, row.targetCardId, true);
if (
row.kind === 'expense' &&
row.kind === BankOperationKind.Expense &&
(await tx.bankCardOperation.findFirst({
where: { userId, originalExpenseId: id, deletedAt: null },
}))
)
conflict('BANK_DEPENDENT_REFUNDS');
conflict(BankErrorCode.DependentRefunds);
if (row.originalExpenseId) {
const source = await this.repository.operation(
tx,
@@ -310,14 +314,14 @@ export class BankOperationsService {
return this.prisma.$transaction(async (tx) => {
await this.repository.card(tx, userId, id);
const page = query.page ?? 1,
limit = query.limit ?? 50;
limit = query.limit ?? DEFAULT_BANK_PAGE_SIZE;
const where: Prisma.BankCardOperationWhereInput = {
userId,
deletedAt: null,
OR: [{ cardId: id }, { targetCardId: id }],
...(period
? { date: { gte: period.from, lte: period.to } }
: { kind: 'expense' }),
: { kind: BankOperationKind.Expense }),
};
const total = await tx.bankCardOperation.count({ where });
const rows = await tx.bankCardOperation.findMany({