From 8c074d85de3bc55d0ba750b86bc7b026649fa6bf Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 12 Sep 2026 19:59:10 +0300 Subject: [PATCH] chore: save pending changes --- .../migration.sql | 1 + prisma/schema.prisma | 1 + src/bank-cards/bank-card.dto.ts | 7 ++++++ src/bank-cards/bank-cards.service.ts | 13 ++++++++++ test/bank-cards.e2e-spec.ts | 24 +++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 prisma/migrations/20260912120000_add_card_payment_system/migration.sql diff --git a/prisma/migrations/20260912120000_add_card_payment_system/migration.sql b/prisma/migrations/20260912120000_add_card_payment_system/migration.sql new file mode 100644 index 0000000..8f99340 --- /dev/null +++ b/prisma/migrations/20260912120000_add_card_payment_system/migration.sql @@ -0,0 +1 @@ +ALTER TABLE "BankCard" ADD COLUMN "paymentSystem" TEXT CHECK ("paymentSystem" IN ('mir', 'visa', 'mastercard')); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9ffd9bd..f86c636 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,6 +47,7 @@ model BankCard { name String bankName String? lastFour String? + paymentSystem String? openingBalanceKopecks BigInt openingDate String version Int @default(1) diff --git a/src/bank-cards/bank-card.dto.ts b/src/bank-cards/bank-card.dto.ts index 265482e..f74cdde 100644 --- a/src/bank-cards/bank-card.dto.ts +++ b/src/bank-cards/bank-card.dto.ts @@ -25,6 +25,7 @@ import { MoneyDto } from '../common/money.dto'; const trim = ({ value }: { value: unknown }) => typeof value === 'string' ? value.trim() : value; +export const bankPaymentSystems = ['mir', 'visa', 'mastercard'] as const; export const bankOperationKinds = [ 'expense', 'topup', @@ -57,6 +58,10 @@ export class CreateBankCardDto { @IsOptional() @Matches(/^[0-9]{4}$/) lastFour?: string | null; + @ApiPropertyOptional({ enum: bankPaymentSystems, nullable: true }) + @IsOptional() + @IsIn(bankPaymentSystems) + paymentSystem?: string | null; @ApiProperty({ type: RubMoneyInputDto }) @IsDefined() @ValidateNested() @@ -180,6 +185,8 @@ export class BankCardDto { @ApiProperty({ type: String }) name: string; @ApiProperty({ type: String, nullable: true }) bankName: string | null; @ApiProperty({ type: String, nullable: true }) lastFour: string | null; + @ApiProperty({ enum: bankPaymentSystems, nullable: true }) paymentSystem: + string | null; @ApiProperty({ type: MoneyDto }) openingBalance: MoneyDto; @ApiProperty({ type: String }) openingDate: string; @ApiProperty({ type: MoneyDto }) balance: MoneyDto; diff --git a/src/bank-cards/bank-cards.service.ts b/src/bank-cards/bank-cards.service.ts index 9a092c7..c588aec 100644 --- a/src/bank-cards/bank-cards.service.ts +++ b/src/bank-cards/bank-cards.service.ts @@ -20,6 +20,7 @@ import { moscowToday, } from './bank-card.calculations'; import { + bankPaymentSystems, BankCardDto, BankOperationDto, BankOperationsQueryDto, @@ -84,7 +85,13 @@ function cardInput(dto: CreateBankCardDto) { const bankName = dto.bankName?.trim() || null; if (bankName && bankName.length > 100) fail(); if (dto.lastFour != null && !/^\d{4}$/.test(dto.lastFour)) fail(); + if ( + dto.paymentSystem != null && + !bankPaymentSystems.some((system) => system === dto.paymentSystem) + ) + fail(); return { + ...(dto.paymentSystem == null ? {} : { paymentSystem: dto.paymentSystem }), name: name(dto.name, 100), bankName, lastFour: dto.lastFour || null, @@ -180,6 +187,7 @@ export class BankCardsService { name: card.name, bankName: card.bankName, lastFour: card.lastFour, + paymentSystem: card.paymentSystem, openingDate: card.openingDate, openingBalance: rubMoney(card.openingBalanceKopecks), balance: rubMoney(bankBalance(card.id, card.openingBalanceKopecks, rows)), @@ -292,6 +300,10 @@ export class BankCardsService { dto.lastFour === undefined ? (previous.lastFour ?? undefined) : (dto.lastFour ?? undefined), + paymentSystem: + dto.paymentSystem === undefined + ? previous.paymentSystem + : dto.paymentSystem, openingBalance: dto.openingBalance ?? rubMoney(previous.openingBalanceKopecks), openingDate: dto.openingDate ?? previous.openingDate, @@ -316,6 +328,7 @@ export class BankCardsService { where: { id, userId, version: dto.version }, data: { ...input, + paymentSystem: input.paymentSystem ?? null, version: { increment: 1 }, ...(dto.archived === undefined ? {} diff --git a/test/bank-cards.e2e-spec.ts b/test/bank-cards.e2e-spec.ts index 0aa7729..3653b7f 100644 --- a/test/bank-cards.e2e-spec.ts +++ b/test/bank-cards.e2e-spec.ts @@ -709,4 +709,28 @@ describe('Manual bank cards configured HTTP and real SQLite', () => { }), ).rejects.toMatchObject({ code: 'P2002' }); }); + it('persists payment systems, preserves omitted metadata and allows clearing', async () => { + const card = await createCard('1000', owner, { paymentSystem: 'mir' }); + expect((await getCard(card.id)).paymentSystem).toBe('mir'); + await auth('patch', '/' + card.id) + .send({ version: 1, name: 'Renamed' }) + .expect(200); + expect((await getCard(card.id)).paymentSystem).toBe('mir'); + await auth('patch', '/' + card.id) + .send({ version: 2, paymentSystem: 'visa' }) + .expect(200); + expect((await getCard(card.id)).paymentSystem).toBe('visa'); + await auth('patch', '/' + card.id) + .send({ version: 3, paymentSystem: 'unknown' }) + .expect(400); + await auth('patch', '/' + card.id) + .send({ version: 3, paymentSystem: null }) + .expect(200); + expect((await getCard(card.id)).paymentSystem).toBeNull(); + expect((await createCard()).paymentSystem).toBeNull(); + expect( + (await createCard('1000', owner, { paymentSystem: 'mastercard' })) + .paymentSystem, + ).toBe('mastercard'); + }); });