Files
backend/src/modules/banking/bank-cards/presentation/bank-card.dto.ts
T

313 lines
9.6 KiB
TypeScript

import {
ApiProperty,
ApiPropertyOptional,
IntersectionType,
OmitType,
PartialType,
} from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import {
IsBoolean,
IsDefined,
IsIn,
IsInt,
IsOptional,
IsString,
IsUUID,
Matches,
Max,
MaxLength,
Min,
MinLength,
ValidateIf,
ValidateNested,
} from 'class-validator';
import { MoneyDto } from '../../../../common/money.dto';
import {
FIRST_PAGE,
MAX_CARD_NAME_LENGTH,
MAX_CATEGORY_NAME_LENGTH,
MAX_OPERATION_DESCRIPTION_LENGTH,
MIN_CARD_NAME_LENGTH,
MIN_CATEGORY_NAME_LENGTH,
MIN_ENTITY_VERSION,
} from '../domain/bank-card-limits';
import { BankOperationKind } from '../domain/bank-operation-kind';
const MIN_PAGE_SIZE = 1;
export const DEFAULT_BANK_PAGE_SIZE = 50;
const MAX_BANK_PAGE_SIZE = 100;
const trim = ({ value }: { value: unknown }) =>
typeof value === 'string' ? value.trim() : value;
export const bankPaymentSystems = ['mir', 'visa', 'mastercard'] as const;
const bankOperationKinds = Object.values(BankOperationKind);
export class RubMoneyInputDto {
@ApiProperty({ enum: ['RUB'] }) @IsIn(['RUB']) declare currency: string;
@ApiProperty({ pattern: '^-?[0-9]+(\\.[0-9]{1,2})?$' })
@IsString()
// eslint-disable-next-line security/detect-unsafe-regex -- Anchored decimal grammar: the literal decimal point separates disjoint digit runs; backtracking is linear.
@Matches(/^-?\d+(\.\d{1,2})?$/)
declare value: string;
}
export class CreateBankCardDto {
@ApiProperty({ format: 'uuid' }) @IsUUID() declare id: string;
@ApiProperty({ minLength: MIN_CARD_NAME_LENGTH, maxLength: MAX_CARD_NAME_LENGTH })
@Transform(trim)
@IsString()
@MinLength(MIN_CARD_NAME_LENGTH)
@MaxLength(MAX_CARD_NAME_LENGTH)
declare name: string;
@ApiPropertyOptional({
type: String,
nullable: true,
maxLength: MAX_CARD_NAME_LENGTH,
})
@IsOptional()
@Transform(trim)
@IsString()
@MaxLength(MAX_CARD_NAME_LENGTH)
bankName?: null | string;
@ApiPropertyOptional({ type: String, nullable: true, pattern: '^[0-9]{4}$' })
@IsOptional()
@Matches(/^\d{4}$/)
lastFour?: null | string;
@ApiPropertyOptional({ enum: bankPaymentSystems, nullable: true })
@IsOptional()
@IsIn(bankPaymentSystems)
paymentSystem?: null | string;
@ApiProperty({ type: RubMoneyInputDto })
@IsDefined()
@ValidateNested()
@Type(() => RubMoneyInputDto)
declare openingBalance: MoneyDto;
@ApiProperty({ format: 'date' })
@IsString()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
declare openingDate: string;
}
export class UpdateBankCardDto extends PartialType(
OmitType(CreateBankCardDto, ['id'] as const),
{ skipNullProperties: false },
) {
@ApiProperty({ minimum: MIN_ENTITY_VERSION }) @IsInt() @Min(MIN_ENTITY_VERSION) declare version: number;
@ApiPropertyOptional()
@ValidateIf((_object, value: unknown) => value !== undefined)
@IsBoolean()
archived?: boolean;
}
export class CategoryNameDto {
@ApiProperty({ minLength: MIN_CATEGORY_NAME_LENGTH, maxLength: MAX_CATEGORY_NAME_LENGTH })
@Transform(trim)
@IsString()
@MinLength(MIN_CATEGORY_NAME_LENGTH)
@MaxLength(MAX_CATEGORY_NAME_LENGTH)
declare name: string;
}
export class UpdateExpenseCategoryDto extends CategoryNameDto {
@ApiProperty({ minimum: MIN_ENTITY_VERSION }) @IsInt() @Min(MIN_ENTITY_VERSION) declare version: number;
}
export class CreateBankOperationDto {
@ApiProperty({ format: 'uuid' }) @IsUUID() declare id: string;
@ApiProperty({ enum: bankOperationKinds })
@IsIn(bankOperationKinds)
declare kind: BankOperationKind;
@ApiProperty({ format: 'uuid' }) @IsUUID() declare cardId: string;
@ApiPropertyOptional({ format: 'uuid' })
@ValidateIf((_object, value: unknown) => value !== undefined)
@IsUUID()
targetCardId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@ValidateIf((_object, value: unknown) => value !== undefined)
@IsUUID()
categoryId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@ValidateIf((_object, value: unknown) => value !== undefined)
@IsUUID()
originalExpenseId?: string;
@ApiProperty({ type: RubMoneyInputDto })
@IsDefined()
@ValidateNested()
@Type(() => RubMoneyInputDto)
declare amount: MoneyDto;
@ApiProperty({ format: 'date' })
@IsString()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
declare date: string;
@ApiPropertyOptional({ maxLength: MAX_OPERATION_DESCRIPTION_LENGTH })
@ValidateIf((_object, value: unknown) => value !== undefined)
@IsString()
@MaxLength(MAX_OPERATION_DESCRIPTION_LENGTH)
description?: string;
}
export class UpdateBankOperationDto extends PartialType(
OmitType(CreateBankOperationDto, ['id'] as const),
{ skipNullProperties: false },
) {
@ApiProperty({ minimum: MIN_ENTITY_VERSION }) @IsInt() @Min(MIN_ENTITY_VERSION) declare version: number;
}
export class BankCardsQueryDto {
@ApiPropertyOptional({ enum: ['true', 'false'] })
@IsOptional()
@IsIn(['true', 'false'])
includeArchived?: string;
}
export class BankPageQueryDto {
@ApiPropertyOptional({ minimum: FIRST_PAGE, default: FIRST_PAGE })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(FIRST_PAGE)
page?: number;
@ApiPropertyOptional({
minimum: MIN_PAGE_SIZE,
maximum: MAX_BANK_PAGE_SIZE,
default: DEFAULT_BANK_PAGE_SIZE,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(MIN_PAGE_SIZE)
@Max(MAX_BANK_PAGE_SIZE)
limit?: number;
}
export class BankPeriodQueryDto {
@ApiPropertyOptional({
pattern: '^[0-9]{4}-(0[1-9]|1[0-2])$',
description: 'Use month or both from and to, exclusively.',
})
@IsOptional()
@Matches(/^\d{4}-(0[1-9]|1[0-2])$/)
month?: string;
@ApiPropertyOptional({
format: 'date',
description: 'Inclusive start date; requires to.',
})
@IsOptional()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
from?: string;
@ApiPropertyOptional({
format: 'date',
description: 'Inclusive end date; requires from.',
})
@IsOptional()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
to?: string;
}
export class BankOperationsQueryDto extends IntersectionType(
BankPageQueryDto,
BankPeriodQueryDto,
) {}
export class BankSummaryQueryDto extends BankPeriodQueryDto {
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
cardId?: string;
}
export class BankVersionQueryDto {
@ApiProperty({ minimum: MIN_ENTITY_VERSION })
@Type(() => Number)
@IsInt()
@Min(MIN_ENTITY_VERSION)
declare version: number;
}
export class ExpenseCategoryDto {
@ApiProperty({ type: String }) declare id: string;
@ApiProperty({ type: String }) declare name: string;
@ApiProperty({ type: Boolean }) declare isDefault: boolean;
@ApiProperty({ type: Number }) declare version: number;
}
export class BankCardDto {
@ApiProperty({ type: String }) declare id: string;
@ApiProperty({ type: String }) declare name: string;
@ApiProperty({ type: String, nullable: true }) declare bankName:
null | string;
@ApiProperty({ type: String, nullable: true }) declare lastFour:
null | string;
@ApiProperty({ enum: bankPaymentSystems, nullable: true })
declare paymentSystem: null | string;
@ApiProperty({ type: MoneyDto }) declare openingBalance: MoneyDto;
@ApiProperty({ type: String }) declare openingDate: string;
@ApiProperty({ type: MoneyDto }) declare balance: MoneyDto;
@ApiProperty({ type: String, nullable: true }) declare archivedAt:
null | string;
@ApiProperty({ type: Number }) declare version: number;
}
export class BankOperationDto {
@ApiProperty({ type: String }) declare id: string;
@ApiProperty({ enum: bankOperationKinds }) declare kind: BankOperationKind;
@ApiProperty({ type: String }) declare cardId: string;
@ApiProperty({ type: String }) declare cardName: string;
@ApiProperty({ type: String, nullable: true }) declare targetCardId:
null | string;
@ApiProperty({ type: String, nullable: true }) declare targetCardName:
null | string;
@ApiProperty({ type: String, nullable: true }) declare categoryId:
null | string;
@ApiProperty({ type: String, nullable: true }) declare categoryName:
null | string;
@ApiProperty({ type: String, nullable: true }) declare originalExpenseId:
null | string;
@ApiProperty({ type: MoneyDto }) declare amount: MoneyDto;
@ApiProperty({ type: MoneyDto, nullable: true })
declare availableRefund: MoneyDto | null;
@ApiProperty({ type: String }) declare date: string;
@ApiProperty({ type: String, nullable: true }) declare description:
null | string;
@ApiProperty({ type: Number }) declare version: number;
@ApiProperty({ type: String }) declare createdAt: string;
}
export class BankOperationsPageDto {
@ApiProperty({ type: BankOperationDto, isArray: true })
declare items: BankOperationDto[];
@ApiProperty({ type: Number }) declare page: number;
@ApiProperty({ type: Number }) declare total: number;
@ApiProperty({ type: Boolean }) declare hasNext: boolean;
}
export class BankSummaryCategoryDto {
@ApiProperty({ type: String }) declare categoryId: string;
@ApiProperty({ type: String }) declare categoryName: string;
@ApiProperty({ type: MoneyDto }) declare expenses: MoneyDto;
@ApiProperty({ type: MoneyDto }) declare refunds: MoneyDto;
@ApiProperty({ type: MoneyDto }) declare net: MoneyDto;
}
export class BankSummaryDto {
@ApiPropertyOptional({ type: String }) month?: string;
@ApiPropertyOptional({ format: 'date' }) from?: string;
@ApiPropertyOptional({ format: 'date' }) to?: string;
@ApiProperty({ type: String, nullable: true }) declare cardId: null | string;
@ApiProperty({ type: MoneyDto }) declare expenses: MoneyDto;
@ApiProperty({ type: MoneyDto }) declare refunds: MoneyDto;
@ApiProperty({ type: MoneyDto }) declare net: MoneyDto;
@ApiProperty({ type: BankSummaryCategoryDto, isArray: true })
declare categories: BankSummaryCategoryDto[];
}