Files
backend/src/modules/banking/bank-cards/domain/bank-operation.rules.ts
T
codex-bot deda7ffc2c
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m10s
Backend verify / verify (push) Failing after 7m50s
refactor: single bank operation projection and domain rules (WealthLedger/backend#168) (#189)
2026-11-04 21:37:59 +03:00

80 lines
2.3 KiB
TypeScript

import { BankOperationKind } from './bank-operation-kind';
export type OperationFields = {
cardId: string;
categoryId?: null | string;
kind: BankOperationKind;
originalExpenseId?: null | string;
targetCardId?: null | string;
};
export enum OperationFieldRule {
InvalidCombination = 'invalidCombination',
MissingOriginalExpense = 'missingOriginalExpense',
MissingTargetCard = 'missingTargetCard',
SameTargetCard = 'sameTargetCard',
}
function transferConflict(fields: OperationFields): null | OperationFieldRule {
if (!fields.targetCardId) {
return OperationFieldRule.MissingTargetCard;
}
if (fields.targetCardId === fields.cardId) {
return OperationFieldRule.SameTargetCard;
}
if (fields.categoryId || fields.originalExpenseId) {
return OperationFieldRule.InvalidCombination;
}
return null;
}
function expenseConflict(fields: OperationFields): null | OperationFieldRule {
if (fields.originalExpenseId) {
return OperationFieldRule.InvalidCombination;
}
// Expense without a category uses the user's default category in the service.
return null;
}
function refundConflict(fields: OperationFields): null | OperationFieldRule {
return fields.originalExpenseId
? null
: OperationFieldRule.MissingOriginalExpense;
}
/**
* Pure kind/field compatibility rules for bank operations; no Prisma or Nest.
* Returns the first violated rule, or null when the combination is allowed.
*/
export function operationFieldConflict(
fields: OperationFields,
): null | OperationFieldRule {
if (fields.kind === BankOperationKind.Transfer) {
return transferConflict(fields);
}
if (fields.targetCardId) return OperationFieldRule.InvalidCombination;
if (fields.kind === BankOperationKind.Expense) {
return expenseConflict(fields);
}
if (fields.categoryId) return OperationFieldRule.InvalidCombination;
if (fields.kind === BankOperationKind.Refund) {
return refundConflict(fields);
}
// Topup: no target, no category, no purchase link.
return fields.originalExpenseId
? OperationFieldRule.InvalidCombination
: null;
}
export function operationKindChanged(
oldKind: string,
newKind: string,
oldOriginalExpenseId: null | string,
newOriginalExpenseId: null | string,
): boolean {
return (
oldKind !== newKind ||
oldOriginalExpenseId !== newOriginalExpenseId
);
}