chore: save pending changes

This commit is contained in:
2026-09-12 19:59:10 +03:00
parent e6f58ef0ee
commit 8c074d85de
5 changed files with 46 additions and 0 deletions
@@ -0,0 +1 @@
ALTER TABLE "BankCard" ADD COLUMN "paymentSystem" TEXT CHECK ("paymentSystem" IN ('mir', 'visa', 'mastercard'));
+1
View File
@@ -47,6 +47,7 @@ model BankCard {
name String name String
bankName String? bankName String?
lastFour String? lastFour String?
paymentSystem String?
openingBalanceKopecks BigInt openingBalanceKopecks BigInt
openingDate String openingDate String
version Int @default(1) version Int @default(1)
+7
View File
@@ -25,6 +25,7 @@ import { MoneyDto } from '../common/money.dto';
const trim = ({ value }: { value: unknown }) => const trim = ({ value }: { value: unknown }) =>
typeof value === 'string' ? value.trim() : value; typeof value === 'string' ? value.trim() : value;
export const bankPaymentSystems = ['mir', 'visa', 'mastercard'] as const;
export const bankOperationKinds = [ export const bankOperationKinds = [
'expense', 'expense',
'topup', 'topup',
@@ -57,6 +58,10 @@ export class CreateBankCardDto {
@IsOptional() @IsOptional()
@Matches(/^[0-9]{4}$/) @Matches(/^[0-9]{4}$/)
lastFour?: string | null; lastFour?: string | null;
@ApiPropertyOptional({ enum: bankPaymentSystems, nullable: true })
@IsOptional()
@IsIn(bankPaymentSystems)
paymentSystem?: string | null;
@ApiProperty({ type: RubMoneyInputDto }) @ApiProperty({ type: RubMoneyInputDto })
@IsDefined() @IsDefined()
@ValidateNested() @ValidateNested()
@@ -180,6 +185,8 @@ export class BankCardDto {
@ApiProperty({ type: String }) name: string; @ApiProperty({ type: String }) name: string;
@ApiProperty({ type: String, nullable: true }) bankName: string | null; @ApiProperty({ type: String, nullable: true }) bankName: string | null;
@ApiProperty({ type: String, nullable: true }) lastFour: 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: MoneyDto }) openingBalance: MoneyDto;
@ApiProperty({ type: String }) openingDate: string; @ApiProperty({ type: String }) openingDate: string;
@ApiProperty({ type: MoneyDto }) balance: MoneyDto; @ApiProperty({ type: MoneyDto }) balance: MoneyDto;
+13
View File
@@ -20,6 +20,7 @@ import {
moscowToday, moscowToday,
} from './bank-card.calculations'; } from './bank-card.calculations';
import { import {
bankPaymentSystems,
BankCardDto, BankCardDto,
BankOperationDto, BankOperationDto,
BankOperationsQueryDto, BankOperationsQueryDto,
@@ -84,7 +85,13 @@ function cardInput(dto: CreateBankCardDto) {
const bankName = dto.bankName?.trim() || null; const bankName = dto.bankName?.trim() || null;
if (bankName && bankName.length > 100) fail(); if (bankName && bankName.length > 100) fail();
if (dto.lastFour != null && !/^\d{4}$/.test(dto.lastFour)) fail(); if (dto.lastFour != null && !/^\d{4}$/.test(dto.lastFour)) fail();
if (
dto.paymentSystem != null &&
!bankPaymentSystems.some((system) => system === dto.paymentSystem)
)
fail();
return { return {
...(dto.paymentSystem == null ? {} : { paymentSystem: dto.paymentSystem }),
name: name(dto.name, 100), name: name(dto.name, 100),
bankName, bankName,
lastFour: dto.lastFour || null, lastFour: dto.lastFour || null,
@@ -180,6 +187,7 @@ export class BankCardsService {
name: card.name, name: card.name,
bankName: card.bankName, bankName: card.bankName,
lastFour: card.lastFour, lastFour: card.lastFour,
paymentSystem: card.paymentSystem,
openingDate: card.openingDate, openingDate: card.openingDate,
openingBalance: rubMoney(card.openingBalanceKopecks), openingBalance: rubMoney(card.openingBalanceKopecks),
balance: rubMoney(bankBalance(card.id, card.openingBalanceKopecks, rows)), balance: rubMoney(bankBalance(card.id, card.openingBalanceKopecks, rows)),
@@ -292,6 +300,10 @@ export class BankCardsService {
dto.lastFour === undefined dto.lastFour === undefined
? (previous.lastFour ?? undefined) ? (previous.lastFour ?? undefined)
: (dto.lastFour ?? undefined), : (dto.lastFour ?? undefined),
paymentSystem:
dto.paymentSystem === undefined
? previous.paymentSystem
: dto.paymentSystem,
openingBalance: openingBalance:
dto.openingBalance ?? rubMoney(previous.openingBalanceKopecks), dto.openingBalance ?? rubMoney(previous.openingBalanceKopecks),
openingDate: dto.openingDate ?? previous.openingDate, openingDate: dto.openingDate ?? previous.openingDate,
@@ -316,6 +328,7 @@ export class BankCardsService {
where: { id, userId, version: dto.version }, where: { id, userId, version: dto.version },
data: { data: {
...input, ...input,
paymentSystem: input.paymentSystem ?? null,
version: { increment: 1 }, version: { increment: 1 },
...(dto.archived === undefined ...(dto.archived === undefined
? {} ? {}
+24
View File
@@ -709,4 +709,28 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
}), }),
).rejects.toMatchObject({ code: 'P2002' }); ).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');
});
}); });