chore: strengthen backend eslint profile
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
import {
|
||||
MAX_CARD_NAME_LENGTH,
|
||||
MAX_OPERATION_DESCRIPTION_LENGTH,
|
||||
} from '../domain/bank-card-limits';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import {
|
||||
MAX_CARD_NAME_LENGTH,
|
||||
MAX_OPERATION_DESCRIPTION_LENGTH,
|
||||
} from '../domain/bank-card-limits';
|
||||
import { isCalendarDate, moscowToday } from '../domain/bank-card.calculations';
|
||||
import { parseRubMoney } from '../domain/rub-money';
|
||||
import {
|
||||
bankPaymentSystems,
|
||||
BankPeriodQueryDto,
|
||||
CreateBankCardDto,
|
||||
CreateBankOperationDto,
|
||||
} from '../presentation/bank-card.dto';
|
||||
import { isCalendarDate, moscowToday } from '../domain/bank-card.calculations';
|
||||
import { parseRubMoney } from '../domain/rub-money';
|
||||
|
||||
export const BankErrorCode = {
|
||||
InvalidInput: 'BANK_INVALID_INPUT',
|
||||
@@ -54,8 +55,9 @@ export function periodBounds(query: BankPeriodQueryDto) {
|
||||
!isCalendarDate(query.from) ||
|
||||
!isCalendarDate(query.to) ||
|
||||
query.from > query.to
|
||||
)
|
||||
) {
|
||||
return fail();
|
||||
}
|
||||
return { from: query.from, to: query.to };
|
||||
}
|
||||
export function conflict(
|
||||
@@ -93,8 +95,9 @@ export function currentDate(value: string) {
|
||||
if (value > moscowToday()) fail(BankErrorCode.InvalidDate);
|
||||
}
|
||||
export function name(value: string, max: number) {
|
||||
if (typeof value !== 'string' || !value.trim() || value.trim().length > max)
|
||||
if (typeof value !== 'string' || !value.trim() || value.trim().length > max) {
|
||||
fail();
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
export function cardInput(dto: CreateBankCardDto) {
|
||||
@@ -104,8 +107,9 @@ export function cardInput(dto: CreateBankCardDto) {
|
||||
if (
|
||||
dto.paymentSystem != null &&
|
||||
!bankPaymentSystems.some((system) => system === dto.paymentSystem)
|
||||
)
|
||||
) {
|
||||
fail();
|
||||
}
|
||||
return {
|
||||
...(dto.paymentSystem == null ? {} : { paymentSystem: dto.paymentSystem }),
|
||||
name: name(dto.name, MAX_CARD_NAME_LENGTH),
|
||||
@@ -120,8 +124,9 @@ export function operationInput(dto: CreateBankOperationDto) {
|
||||
dto.description != null &&
|
||||
(typeof dto.description !== 'string' ||
|
||||
dto.description.length > MAX_OPERATION_DESCRIPTION_LENGTH)
|
||||
)
|
||||
) {
|
||||
fail();
|
||||
}
|
||||
return {
|
||||
kind: dto.kind,
|
||||
cardId: dto.cardId,
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { BankCard, Prisma } from '../../../../../generated/prisma/client';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import { bankBalance } from '../domain/bank-card.calculations';
|
||||
import { rubMoney } from '../domain/rub-money';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
import {
|
||||
BankCardDto,
|
||||
CreateBankCardDto,
|
||||
UpdateBankCardDto,
|
||||
} from '../presentation/bank-card.dto';
|
||||
import { bankBalance } from '../domain/bank-card.calculations';
|
||||
import { rubMoney } from '../domain/rub-money';
|
||||
|
||||
import {
|
||||
cardInput,
|
||||
BankErrorCode,
|
||||
cardInput,
|
||||
conflict,
|
||||
currentDate,
|
||||
fail,
|
||||
fingerprint,
|
||||
missing,
|
||||
} from './bank-card-input';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
type Tx = Prisma.TransactionClient;
|
||||
@Injectable()
|
||||
export class BankCardsService {
|
||||
@@ -26,6 +26,7 @@ export class BankCardsService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly repository: BankCardsRepository,
|
||||
) {}
|
||||
|
||||
private async cardView(tx: Tx, card: BankCard): Promise<BankCardDto> {
|
||||
const rows = await tx.bankCardOperation.findMany({
|
||||
where: {
|
||||
@@ -47,6 +48,7 @@ export class BankCardsService {
|
||||
archivedAt: card.archivedAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
list(userId: string, includeArchived = false) {
|
||||
return this.prisma.$transaction(async (tx) =>
|
||||
Promise.all(
|
||||
@@ -59,11 +61,13 @@ export class BankCardsService {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
get(userId: string, id: string) {
|
||||
return this.prisma.$transaction(async (tx) =>
|
||||
this.cardView(tx, await this.repository.card(tx, userId, id)),
|
||||
);
|
||||
}
|
||||
|
||||
createCard(userId: string, dto: CreateBankCardDto) {
|
||||
const input = cardInput(dto),
|
||||
hash = fingerprint(input);
|
||||
@@ -71,8 +75,10 @@ export class BankCardsService {
|
||||
const existing = await tx.bankCard.findUnique({ where: { id: dto.id } });
|
||||
if (existing) {
|
||||
if (existing.userId !== userId) missing();
|
||||
if (existing.createFingerprint !== hash)
|
||||
// eslint-disable-next-line security/detect-possible-timing-attacks -- This compares an idempotency fingerprint after ownership validation, not a secret.
|
||||
if (existing.createFingerprint !== hash) {
|
||||
conflict(BankErrorCode.IntentConflict);
|
||||
}
|
||||
return this.cardView(tx, existing);
|
||||
}
|
||||
currentDate(input.openingDate);
|
||||
@@ -85,6 +91,7 @@ export class BankCardsService {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
updateCard(userId: string, id: string, dto: UpdateBankCardDto) {
|
||||
return this.repository.write(async (tx) => {
|
||||
const previous = await this.repository.card(tx, userId, id);
|
||||
@@ -93,17 +100,17 @@ export class BankCardsService {
|
||||
id,
|
||||
name: dto.name ?? previous.name,
|
||||
bankName:
|
||||
dto.bankName === undefined
|
||||
? (previous.bankName ?? undefined)
|
||||
: (dto.bankName ?? undefined),
|
||||
dto.bankName === undefined ?
|
||||
(previous.bankName ?? undefined) :
|
||||
(dto.bankName ?? undefined),
|
||||
lastFour:
|
||||
dto.lastFour === undefined
|
||||
? (previous.lastFour ?? undefined)
|
||||
: (dto.lastFour ?? undefined),
|
||||
dto.lastFour === undefined ?
|
||||
(previous.lastFour ?? undefined) :
|
||||
(dto.lastFour ?? undefined),
|
||||
paymentSystem:
|
||||
dto.paymentSystem === undefined
|
||||
? previous.paymentSystem
|
||||
: dto.paymentSystem,
|
||||
dto.paymentSystem === undefined ?
|
||||
previous.paymentSystem :
|
||||
dto.paymentSystem,
|
||||
openingBalance:
|
||||
dto.openingBalance ?? rubMoney(previous.openingBalanceKopecks),
|
||||
openingDate: dto.openingDate ?? previous.openingDate,
|
||||
@@ -111,8 +118,9 @@ export class BankCardsService {
|
||||
const financial =
|
||||
input.openingBalanceKopecks !== previous.openingBalanceKopecks ||
|
||||
input.openingDate !== previous.openingDate;
|
||||
if (financial && previous.archivedAt)
|
||||
if (financial && previous.archivedAt) {
|
||||
conflict(BankErrorCode.CardArchived);
|
||||
}
|
||||
currentDate(input.openingDate);
|
||||
if (
|
||||
await tx.bankCardOperation.findFirst({
|
||||
@@ -123,20 +131,21 @@ export class BankCardsService {
|
||||
OR: [{ cardId: id }, { targetCardId: id }],
|
||||
},
|
||||
})
|
||||
)
|
||||
) {
|
||||
fail(BankErrorCode.OpeningDateConflict);
|
||||
}
|
||||
const changed = await tx.bankCard.updateMany({
|
||||
where: { id, userId, version: dto.version },
|
||||
data: {
|
||||
...input,
|
||||
paymentSystem: input.paymentSystem ?? null,
|
||||
version: { increment: 1 },
|
||||
...(dto.archived === undefined
|
||||
? {}
|
||||
: {
|
||||
archivedAt: dto.archived
|
||||
? (previous.archivedAt ?? new Date())
|
||||
: null,
|
||||
...(dto.archived === undefined ?
|
||||
{} :
|
||||
{
|
||||
archivedAt: dto.archived ?
|
||||
(previous.archivedAt ?? new Date()) :
|
||||
null,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { BankOperationKind } from '../domain/bank-operation-kind';
|
||||
import { GoneException, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
BankCardOperation,
|
||||
Prisma,
|
||||
} from '../../../../../generated/prisma/client';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import { BankOperationKind } from '../domain/bank-operation-kind';
|
||||
import { rubMoney } from '../domain/rub-money';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
import {
|
||||
BankOperationDto,
|
||||
DEFAULT_BANK_PAGE_SIZE,
|
||||
BankOperationsQueryDto,
|
||||
BankPageQueryDto,
|
||||
CreateBankOperationDto,
|
||||
DEFAULT_BANK_PAGE_SIZE,
|
||||
UpdateBankOperationDto,
|
||||
} from '../presentation/bank-card.dto';
|
||||
import { rubMoney } from '../domain/rub-money';
|
||||
|
||||
import {
|
||||
BankErrorCode,
|
||||
conflict,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
operationInput,
|
||||
periodBounds,
|
||||
} from './bank-card-input';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
type Tx = Prisma.TransactionClient;
|
||||
@Injectable()
|
||||
export class BankOperationsService {
|
||||
@@ -33,34 +33,35 @@ export class BankOperationsService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly repository: BankCardsRepository,
|
||||
) {}
|
||||
|
||||
private async operationView(
|
||||
tx: Tx,
|
||||
row: BankCardOperation,
|
||||
): Promise<BankOperationDto> {
|
||||
const card = await this.repository.card(tx, row.userId, row.cardId);
|
||||
const target = row.targetCardId
|
||||
? await this.repository.card(tx, row.userId, row.targetCardId)
|
||||
: null;
|
||||
const source = row.originalExpenseId
|
||||
? await this.repository.operation(tx, row.userId, row.originalExpenseId)
|
||||
: null;
|
||||
const target = row.targetCardId ?
|
||||
await this.repository.card(tx, row.userId, row.targetCardId) :
|
||||
null;
|
||||
const source = row.originalExpenseId ?
|
||||
await this.repository.operation(tx, row.userId, row.originalExpenseId) :
|
||||
null;
|
||||
const categoryId = source?.categoryId ?? row.categoryId;
|
||||
const category = categoryId
|
||||
? await tx.expenseCategory.findFirst({
|
||||
const category = categoryId ?
|
||||
await tx.expenseCategory.findFirst({
|
||||
where: { id: categoryId, userId: row.userId },
|
||||
})
|
||||
: null;
|
||||
}) :
|
||||
null;
|
||||
const refunds =
|
||||
row.kind === BankOperationKind.Expense || source
|
||||
? await tx.bankCardOperation.findMany({
|
||||
row.kind === BankOperationKind.Expense || source ?
|
||||
await tx.bankCardOperation.findMany({
|
||||
where: {
|
||||
userId: row.userId,
|
||||
originalExpenseId: source?.id ?? row.id,
|
||||
deletedAt: null,
|
||||
...(source ? { id: { not: row.id } } : {}),
|
||||
},
|
||||
})
|
||||
: [];
|
||||
}) :
|
||||
[];
|
||||
return {
|
||||
id: row.id,
|
||||
kind: row.kind as BankOperationDto['kind'],
|
||||
@@ -73,18 +74,19 @@ export class BankOperationsService {
|
||||
originalExpenseId: row.originalExpenseId,
|
||||
amount: rubMoney(row.amountKopecks),
|
||||
availableRefund:
|
||||
row.kind === BankOperationKind.Expense || source
|
||||
? rubMoney(
|
||||
row.kind === BankOperationKind.Expense || source ?
|
||||
rubMoney(
|
||||
(source?.amountKopecks ?? row.amountKopecks) -
|
||||
refunds.reduce((sum, r) => sum + r.amountKopecks, 0n),
|
||||
)
|
||||
: null,
|
||||
refunds.reduce((sum, r) => sum + r.amountKopecks, 0n),
|
||||
) :
|
||||
null,
|
||||
date: row.date,
|
||||
description: row.description,
|
||||
version: row.version,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
private async validateOperation(
|
||||
tx: Tx,
|
||||
userId: string,
|
||||
@@ -102,23 +104,26 @@ export class BankOperationsService {
|
||||
if (
|
||||
(id === input.cardId || id === input.targetCardId) &&
|
||||
input.date < card.openingDate
|
||||
)
|
||||
) {
|
||||
fail(BankErrorCode.InvalidDate);
|
||||
}
|
||||
}
|
||||
if (
|
||||
old &&
|
||||
(old.kind !== input.kind ||
|
||||
old.originalExpenseId !== input.originalExpenseId)
|
||||
)
|
||||
) {
|
||||
fail(BankErrorCode.ImmutableOperation);
|
||||
}
|
||||
if (input.kind === BankOperationKind.Transfer) {
|
||||
if (
|
||||
!input.targetCardId ||
|
||||
input.targetCardId === input.cardId ||
|
||||
input.categoryId ||
|
||||
input.originalExpenseId
|
||||
)
|
||||
) {
|
||||
fail();
|
||||
}
|
||||
} else if (input.targetCardId) fail();
|
||||
if (input.kind === BankOperationKind.Expense) {
|
||||
if (input.originalExpenseId) fail();
|
||||
@@ -133,11 +138,13 @@ export class BankOperationsService {
|
||||
!(await tx.expenseCategory.findFirst({
|
||||
where: { userId, id: input.categoryId },
|
||||
}))
|
||||
)
|
||||
) {
|
||||
missing();
|
||||
}
|
||||
} else if (input.categoryId) fail();
|
||||
if (input.kind !== BankOperationKind.Refund && input.originalExpenseId)
|
||||
if (input.kind !== BankOperationKind.Refund && input.originalExpenseId) {
|
||||
fail();
|
||||
}
|
||||
if (input.kind === BankOperationKind.Refund) {
|
||||
if (!input.originalExpenseId) fail();
|
||||
const source = await this.repository.operation(
|
||||
@@ -149,8 +156,9 @@ export class BankOperationsService {
|
||||
source.deletedAt ||
|
||||
source.kind !== BankOperationKind.Expense ||
|
||||
source.cardId !== input.cardId
|
||||
)
|
||||
) {
|
||||
fail(BankErrorCode.InvalidRefund);
|
||||
}
|
||||
if (
|
||||
(
|
||||
await tx.bankCardOperation.updateMany({
|
||||
@@ -163,8 +171,9 @@ export class BankOperationsService {
|
||||
data: { version: { increment: 1 } },
|
||||
})
|
||||
).count !== 1
|
||||
)
|
||||
) {
|
||||
conflict();
|
||||
}
|
||||
const refunds = await tx.bankCardOperation.findMany({
|
||||
where: {
|
||||
userId,
|
||||
@@ -176,10 +185,11 @@ export class BankOperationsService {
|
||||
if (input.date < source.date) fail(BankErrorCode.InvalidDate);
|
||||
if (
|
||||
refunds.reduce((sum, r) => sum + r.amountKopecks, 0n) +
|
||||
input.amountKopecks >
|
||||
input.amountKopecks >
|
||||
source.amountKopecks
|
||||
)
|
||||
) {
|
||||
conflict(BankErrorCode.RefundLimit);
|
||||
}
|
||||
}
|
||||
if (old?.kind === BankOperationKind.Expense) {
|
||||
const refunds = await tx.bankCardOperation.findMany({
|
||||
@@ -190,11 +200,13 @@ export class BankOperationsService {
|
||||
(input.cardId !== old.cardId ||
|
||||
refunds.some((row) => row.date < input.date) ||
|
||||
refunds.reduce((sum, row) => sum + row.amountKopecks, 0n) >
|
||||
input.amountKopecks)
|
||||
)
|
||||
input.amountKopecks)
|
||||
) {
|
||||
conflict(BankErrorCode.DependentRefunds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createOperation(userId: string, dto: CreateBankOperationDto) {
|
||||
const normalized = operationInput(dto),
|
||||
hash = fingerprint(normalized);
|
||||
@@ -204,10 +216,13 @@ export class BankOperationsService {
|
||||
});
|
||||
if (existing) {
|
||||
if (existing.userId !== userId) missing();
|
||||
if (existing.createFingerprint !== hash)
|
||||
// eslint-disable-next-line security/detect-possible-timing-attacks -- This compares an idempotency fingerprint after ownership validation, not a secret.
|
||||
if (existing.createFingerprint !== hash) {
|
||||
conflict(BankErrorCode.IntentConflict);
|
||||
if (existing.deletedAt)
|
||||
}
|
||||
if (existing.deletedAt) {
|
||||
throw new GoneException({ code: BankErrorCode.OperationDeleted });
|
||||
}
|
||||
return this.operationView(tx, existing);
|
||||
}
|
||||
const input = { ...normalized };
|
||||
@@ -220,6 +235,7 @@ export class BankOperationsService {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getOperation(userId: string, id: string) {
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const row = await this.repository.operation(tx, userId, id);
|
||||
@@ -227,6 +243,7 @@ export class BankOperationsService {
|
||||
return this.operationView(tx, row);
|
||||
});
|
||||
}
|
||||
|
||||
updateOperation(userId: string, id: string, dto: UpdateBankOperationDto) {
|
||||
return this.repository.write(async (tx) => {
|
||||
const old = await this.repository.operation(tx, userId, id);
|
||||
@@ -252,29 +269,33 @@ export class BankOperationsService {
|
||||
data: { ...input, version: { increment: 1 } },
|
||||
})
|
||||
).count !== 1
|
||||
)
|
||||
) {
|
||||
conflict();
|
||||
}
|
||||
return this.operationView(
|
||||
tx,
|
||||
await this.repository.operation(tx, userId, id),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
deleteOperation(userId: string, id: string, version: number) {
|
||||
return this.repository.write(async (tx) => {
|
||||
const row = await this.repository.operation(tx, userId, id);
|
||||
if (row.deletedAt) return;
|
||||
if (row.version !== version) conflict();
|
||||
await this.repository.card(tx, userId, row.cardId, true);
|
||||
if (row.targetCardId)
|
||||
if (row.targetCardId) {
|
||||
await this.repository.card(tx, userId, row.targetCardId, true);
|
||||
}
|
||||
if (
|
||||
row.kind === BankOperationKind.Expense &&
|
||||
(await tx.bankCardOperation.findFirst({
|
||||
where: { userId, originalExpenseId: id, deletedAt: null },
|
||||
}))
|
||||
)
|
||||
) {
|
||||
conflict(BankErrorCode.DependentRefunds);
|
||||
}
|
||||
if (row.originalExpenseId) {
|
||||
const source = await this.repository.operation(
|
||||
tx,
|
||||
@@ -288,8 +309,9 @@ export class BankOperationsService {
|
||||
data: { version: { increment: 1 } },
|
||||
})
|
||||
).count !== 1
|
||||
)
|
||||
) {
|
||||
conflict();
|
||||
}
|
||||
}
|
||||
if (
|
||||
(
|
||||
@@ -298,30 +320,32 @@ export class BankOperationsService {
|
||||
data: { deletedAt: new Date(), version: { increment: 1 } },
|
||||
})
|
||||
).count !== 1
|
||||
)
|
||||
) {
|
||||
conflict();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
history(
|
||||
userId: string,
|
||||
id: string,
|
||||
query: BankOperationsQueryDto | BankPageQueryDto,
|
||||
expenses = false,
|
||||
) {
|
||||
const period = expenses
|
||||
? undefined
|
||||
: periodBounds(query as BankOperationsQueryDto);
|
||||
const period = expenses ?
|
||||
undefined :
|
||||
periodBounds(query as BankOperationsQueryDto);
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
await this.repository.card(tx, userId, id);
|
||||
const page = query.page ?? 1,
|
||||
limit = query.limit ?? DEFAULT_BANK_PAGE_SIZE;
|
||||
const limit = query.limit ?? DEFAULT_BANK_PAGE_SIZE,
|
||||
page = query.page ?? 1;
|
||||
const where: Prisma.BankCardOperationWhereInput = {
|
||||
userId,
|
||||
deletedAt: null,
|
||||
OR: [{ cardId: id }, { targetCardId: id }],
|
||||
...(period
|
||||
? { date: { gte: period.from, lte: period.to } }
|
||||
: { kind: BankOperationKind.Expense }),
|
||||
...(period ?
|
||||
{ date: { gte: period.from, lte: period.to } } :
|
||||
{ kind: BankOperationKind.Expense }),
|
||||
};
|
||||
const total = await tx.bankCardOperation.count({ where });
|
||||
const rows = await tx.bankCardOperation.findMany({
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { MAX_CATEGORY_NAME_LENGTH } from '../domain/bank-card-limits';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
|
||||
import { conflict, missing, name } from './bank-card-input';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import { MAX_CATEGORY_NAME_LENGTH } from '../domain/bank-card-limits';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
import { conflict, missing, name } from './bank-card-input';
|
||||
@Injectable()
|
||||
export class ExpenseCategoriesService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly repository: BankCardsRepository,
|
||||
) {}
|
||||
|
||||
async categories(userId: string) {
|
||||
return (
|
||||
await this.prisma.expenseCategory.findMany({
|
||||
@@ -24,6 +25,7 @@ export class ExpenseCategoriesService {
|
||||
version,
|
||||
}));
|
||||
}
|
||||
|
||||
createCategory(userId: string, value: string) {
|
||||
const label = name(value, MAX_CATEGORY_NAME_LENGTH);
|
||||
return this.repository.write(async (tx) => {
|
||||
@@ -44,6 +46,7 @@ export class ExpenseCategoriesService {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
updateCategory(userId: string, id: string, value: string, version: number) {
|
||||
const label = name(value, MAX_CATEGORY_NAME_LENGTH);
|
||||
return this.repository.write(async (tx) => {
|
||||
@@ -60,8 +63,9 @@ export class ExpenseCategoriesService {
|
||||
},
|
||||
})
|
||||
).count !== 1
|
||||
)
|
||||
) {
|
||||
conflict();
|
||||
}
|
||||
return {
|
||||
id,
|
||||
name: label,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BankSummaryQueryDto } from '../presentation/bank-card.dto';
|
||||
|
||||
import { bankSummary } from '../domain/bank-card.calculations';
|
||||
import { rubMoney } from '../domain/rub-money';
|
||||
|
||||
import { periodBounds } from './bank-card-input';
|
||||
import { BankCardsRepository } from '../infrastructure/bank-cards.repository';
|
||||
import { BankSummaryQueryDto } from '../presentation/bank-card.dto';
|
||||
import { periodBounds } from './bank-card-input';
|
||||
@Injectable()
|
||||
export class GetBankSummaryUseCase {
|
||||
constructor(private readonly repository: BankCardsRepository) {}
|
||||
|
||||
Reference in New Issue
Block a user