From e7c0a48ed116f2b5d1cc48f5cb5d5f67174613e1 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Fri, 12 Sep 2025 07:27:00 +0300 Subject: [PATCH] feat(server): add cash --- server/prisma/schema.prisma | 15 +++- server/src/modules/app.module.ts | 2 + server/src/modules/cash/cash.controller.ts | 70 +++++++++++++++++++ server/src/modules/cash/cash.module.ts | 10 +++ server/src/modules/cash/cash.service.ts | 69 ++++++++++++++++++ .../cash/decorators/create-cash.decorator.ts | 24 +++++++ .../cash/decorators/delete-cash.decorator.ts | 32 +++++++++ .../cash/decorators/edit-cash.decorator.ts | 32 +++++++++ .../cash/decorators/get-all-cash.decorator.ts | 24 +++++++ .../cash/decorators/get-cash.decorator.ts | 32 +++++++++ server/src/modules/cash/decorators/index.ts | 5 ++ server/src/modules/cash/dto/cash.dto.ts | 35 ++++++++++ .../src/modules/cash/dto/create-cash.dto.ts | 24 +++++++ server/src/modules/cash/dto/edit-cash.dto.ts | 26 +++++++ server/src/modules/cash/dto/index.ts | 3 + 15 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 server/src/modules/cash/cash.controller.ts create mode 100644 server/src/modules/cash/cash.module.ts create mode 100644 server/src/modules/cash/cash.service.ts create mode 100644 server/src/modules/cash/decorators/create-cash.decorator.ts create mode 100644 server/src/modules/cash/decorators/delete-cash.decorator.ts create mode 100644 server/src/modules/cash/decorators/edit-cash.decorator.ts create mode 100644 server/src/modules/cash/decorators/get-all-cash.decorator.ts create mode 100644 server/src/modules/cash/decorators/get-cash.decorator.ts create mode 100644 server/src/modules/cash/decorators/index.ts create mode 100644 server/src/modules/cash/dto/cash.dto.ts create mode 100644 server/src/modules/cash/dto/create-cash.dto.ts create mode 100644 server/src/modules/cash/dto/edit-cash.dto.ts create mode 100644 server/src/modules/cash/dto/index.ts diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 150c937..32a4c2b 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -26,7 +26,8 @@ model User { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") expensesLists ExpenseList[] - BankCard BankCard[] + bankCard BankCard[] + cash Cash[] @@map("users") } @@ -81,3 +82,15 @@ model BankCard { @@map("bank_cards") } + +model Cash { + id String @id @default(uuid()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + name String + balance Float @default(0) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("cash") +} diff --git a/server/src/modules/app.module.ts b/server/src/modules/app.module.ts index 48626b1..c68997a 100644 --- a/server/src/modules/app.module.ts +++ b/server/src/modules/app.module.ts @@ -4,6 +4,7 @@ import { ConfigModule } from '@nestjs/config'; import { AuthModule } from './auth/auth.module'; import { BankAccountModule } from './bank-account/bank-account.module'; import { BankCardModule } from './bank-card/bank-card.module'; +import { CashModule } from './cash/cash.module'; import { ExpensesModule } from './expenses/expenses.module'; import { PrismaModule } from './prisma/prisma.module'; @@ -17,6 +18,7 @@ import { PrismaModule } from './prisma/prisma.module'; ExpensesModule, BankAccountModule, BankCardModule, + CashModule, ], }) export class AppModule {} diff --git a/server/src/modules/cash/cash.controller.ts b/server/src/modules/cash/cash.controller.ts new file mode 100644 index 0000000..aeb94be --- /dev/null +++ b/server/src/modules/cash/cash.controller.ts @@ -0,0 +1,70 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, +} from '@nestjs/common'; +import { Cash, User } from '@prisma/client'; + +import { Authorized } from '@/shared/decorators'; + +import { CashService } from './cash.service'; +import { + CreateCash, + DeleteCash, + EditCash, + GetAllCash, + GetCash, +} from './decorators'; +import { CreateCashDto, EditCashDto } from './dto'; + +@Controller('cash') +export class CashController { + constructor(private readonly cashService: CashService) {} + + @CreateCash() + @Post() + async createCash( + @Authorized('userId') userId: User['id'], + @Body() dto: CreateCashDto, + ) { + return this.cashService.createCash(userId, dto); + } + + @GetCash() + @Get(':id') + async getCashById( + @Authorized('userId') userId: User['id'], + @Param('id') id: Cash['id'], + ) { + return this.cashService.getCashById(userId, id); + } + + @GetAllCash() + @Get() + async getAllCashByUserId(@Authorized('userId') userId: User['id']) { + return this.cashService.getAllCashByUserId(userId); + } + + @EditCash() + @Patch(':id') + async editCash( + @Authorized('userId') userId: User['id'], + @Param('id') id: Cash['id'], + @Body() dto: EditCashDto, + ) { + return this.cashService.editCash(userId, id, dto); + } + + @DeleteCash() + @Delete(':id') + async deleteCash( + @Authorized('userId') userId: User['id'], + @Param('id') id: Cash['id'], + ) { + return this.cashService.deleteCash(userId, id); + } +} diff --git a/server/src/modules/cash/cash.module.ts b/server/src/modules/cash/cash.module.ts new file mode 100644 index 0000000..5b0ef0e --- /dev/null +++ b/server/src/modules/cash/cash.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { CashController } from './cash.controller'; +import { CashService } from './cash.service'; + +@Module({ + controllers: [CashController], + providers: [CashService], +}) +export class CashModule {} diff --git a/server/src/modules/cash/cash.service.ts b/server/src/modules/cash/cash.service.ts new file mode 100644 index 0000000..c7c3578 --- /dev/null +++ b/server/src/modules/cash/cash.service.ts @@ -0,0 +1,69 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Cash, User } from '@prisma/client'; + +import { PrismaService } from '@/modules/prisma/prisma.service'; + +import { CashDto, CreateCashDto, EditCashDto } from './dto'; + +@Injectable() +export class CashService { + constructor(private readonly prismaService: PrismaService) {} + + async createCash(userId: User['id'], dto: CreateCashDto): Promise { + const { balance, name } = dto; + + return this.prismaService.cash.create({ + data: { + name, + balance, + user: { + connect: { id: userId }, + }, + }, + }); + } + + async getCashById(userId: User['id'], cashId: Cash['id']): Promise { + const cash = await this.prismaService.cash.findUnique({ + where: { + id: cashId, + userId, + }, + }); + + if (!cash) { + throw new NotFoundException('Кэш не найден'); + } + + return cash; + } + + async getAllCashByUserId(userId: User['id']): Promise { + return this.prismaService.cash.findMany({ + where: { userId }, + orderBy: { createdAt: 'asc' }, + }); + } + + async editCash(userId: User['id'], cashId: Cash['id'], dto: EditCashDto): Promise { + const { balance, name } = dto; + + const cash = await this.getCashById(userId, cashId); + + return this.prismaService.cash.update({ + where: { id: cashId }, + data: { + name: name ?? cash.name, + balance: balance ?? cash.balance, + }, + }); + } + + async deleteCash(userId: User['id'], cashId: Cash['id']): Promise { + const cash = await this.getCashById(userId, cashId); + + return this.prismaService.cash.delete({ + where: { id: cash.id }, + }); + } +} diff --git a/server/src/modules/cash/decorators/create-cash.decorator.ts b/server/src/modules/cash/decorators/create-cash.decorator.ts new file mode 100644 index 0000000..f92c811 --- /dev/null +++ b/server/src/modules/cash/decorators/create-cash.decorator.ts @@ -0,0 +1,24 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBearerAuth, ApiOkResponse, ApiOperation } from '@nestjs/swagger'; + +import { Authorization } from '@/shared/decorators'; + +import { CashDto } from '../dto'; + +function Documentation() { + return applyDecorators( + ApiOperation({ + summary: 'Создать наличные', + description: 'Создать наличные', + }), + ApiBearerAuth(), + ApiOkResponse({ description: 'Успешно созданы наличные', type: CashDto }), + ); +} + +export function CreateCash() { + return applyDecorators( + Authorization(), + Documentation(), + ); +} diff --git a/server/src/modules/cash/decorators/delete-cash.decorator.ts b/server/src/modules/cash/decorators/delete-cash.decorator.ts new file mode 100644 index 0000000..50983a9 --- /dev/null +++ b/server/src/modules/cash/decorators/delete-cash.decorator.ts @@ -0,0 +1,32 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, +} from '@nestjs/swagger'; + +import { Authorization } from '@/shared/decorators'; + +import { CashDto } from '../dto'; + +function Documentation() { + return applyDecorators( + ApiOperation({ + summary: 'Удалить наличные', + description: 'Удалить наличные', + }), + ApiBearerAuth(), + ApiParam({ name: 'id', type: String, description: 'ID наличных' }), + ApiNotFoundResponse({ description: 'Кэш не найден' }), + ApiOkResponse({ description: 'Успешно удалены наличные', type: CashDto }), + ); +} + +export function DeleteCash() { + return applyDecorators( + Authorization(), + Documentation(), + ); +} diff --git a/server/src/modules/cash/decorators/edit-cash.decorator.ts b/server/src/modules/cash/decorators/edit-cash.decorator.ts new file mode 100644 index 0000000..165a218 --- /dev/null +++ b/server/src/modules/cash/decorators/edit-cash.decorator.ts @@ -0,0 +1,32 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, +} from '@nestjs/swagger'; + +import { Authorization } from '@/shared/decorators'; + +import { CashDto } from '../dto'; + +function Documentation() { + return applyDecorators( + ApiOperation({ + summary: 'Редактировать наличные', + description: 'Редактировать наличные', + }), + ApiBearerAuth(), + ApiParam({ name: 'id', type: String, description: 'ID наличных' }), + ApiNotFoundResponse({ description: 'Кэш не найден' }), + ApiOkResponse({ description: 'Успешно отредактированы наличные', type: CashDto }), + ); +} + +export function EditCash() { + return applyDecorators( + Authorization(), + Documentation(), + ); +} diff --git a/server/src/modules/cash/decorators/get-all-cash.decorator.ts b/server/src/modules/cash/decorators/get-all-cash.decorator.ts new file mode 100644 index 0000000..6bafe32 --- /dev/null +++ b/server/src/modules/cash/decorators/get-all-cash.decorator.ts @@ -0,0 +1,24 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBearerAuth, ApiOkResponse, ApiOperation } from '@nestjs/swagger'; + +import { Authorization } from '@/shared/decorators'; + +import { CashDto } from '../dto'; + +function Documentation() { + return applyDecorators( + ApiOperation({ + summary: 'Найти все наличные', + description: 'Найти все наличные', + }), + ApiBearerAuth(), + ApiOkResponse({ description: 'Успешно найдены наличные', type: [CashDto] }), + ); +} + +export function GetAllCash() { + return applyDecorators( + Authorization(), + Documentation(), + ); +} diff --git a/server/src/modules/cash/decorators/get-cash.decorator.ts b/server/src/modules/cash/decorators/get-cash.decorator.ts new file mode 100644 index 0000000..6d30e54 --- /dev/null +++ b/server/src/modules/cash/decorators/get-cash.decorator.ts @@ -0,0 +1,32 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, +} from '@nestjs/swagger'; + +import { Authorization } from '@/shared/decorators'; + +import { CashDto } from '../dto'; + +function Documentation() { + return applyDecorators( + ApiOperation({ + summary: 'Найти наличные', + description: 'Найти наличные', + }), + ApiBearerAuth(), + ApiParam({ name: 'id', type: String, description: 'ID наличных' }), + ApiNotFoundResponse({ description: 'Кэш не найден' }), + ApiOkResponse({ description: 'Успешно найдены наличные', type: CashDto }), + ); +} + +export function GetCash() { + return applyDecorators( + Authorization(), + Documentation(), + ); +} diff --git a/server/src/modules/cash/decorators/index.ts b/server/src/modules/cash/decorators/index.ts new file mode 100644 index 0000000..1ac208d --- /dev/null +++ b/server/src/modules/cash/decorators/index.ts @@ -0,0 +1,5 @@ +export { CreateCash } from './create-cash.decorator'; +export { DeleteCash } from './delete-cash.decorator'; +export { EditCash } from './edit-cash.decorator'; +export { GetAllCash } from './get-all-cash.decorator'; +export { GetCash } from './get-cash.decorator'; diff --git a/server/src/modules/cash/dto/cash.dto.ts b/server/src/modules/cash/dto/cash.dto.ts new file mode 100644 index 0000000..362330a --- /dev/null +++ b/server/src/modules/cash/dto/cash.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CashDto { + @ApiProperty({ + description: 'ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'Название', + example: 'Наличные', + type: String, + }) + name: string; + + @ApiProperty({ + description: 'Баланс', + example: 10_000.00, + type: Number, + }) + balance: number; + + @ApiProperty({ + description: 'Дата создания', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Дата обновления', + type: Date, + }) + updatedAt: Date; +} diff --git a/server/src/modules/cash/dto/create-cash.dto.ts b/server/src/modules/cash/dto/create-cash.dto.ts new file mode 100644 index 0000000..ca22dec --- /dev/null +++ b/server/src/modules/cash/dto/create-cash.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsNotEmpty, IsNumber, IsOptional, IsString, +} from 'class-validator'; + +export class CreateCashDto { + @ApiProperty({ + description: 'Название', + example: 'Наличные', + type: String, + }) + @IsString({ message: 'Название должно быть строкой' }) + @IsNotEmpty({ message: 'Название обязательно к заполнению' }) + name: string; + + @ApiProperty({ + description: 'Баланс', + example: 10_000.00, + type: Number, + }) + @IsNumber({ maxDecimalPlaces: 2 }, { message: 'Баланс должен быть числом' }) + @IsOptional() + balance?: number; +} diff --git a/server/src/modules/cash/dto/edit-cash.dto.ts b/server/src/modules/cash/dto/edit-cash.dto.ts new file mode 100644 index 0000000..6258e0e --- /dev/null +++ b/server/src/modules/cash/dto/edit-cash.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; + +export class EditCashDto { + @ApiProperty({ + description: 'Название', + example: 'Наличные', + type: String, + }) + @IsString({ message: 'Название должно быть строкой' }) + @IsOptional() + name?: string; + + @ApiProperty({ + description: 'Баланс', + example: 10_000.00, + type: Number, + }) + @IsNumber({ maxDecimalPlaces: 2 }, { message: 'Баланс должен быть числом' }) + @IsOptional() + balance?: number; +} diff --git a/server/src/modules/cash/dto/index.ts b/server/src/modules/cash/dto/index.ts new file mode 100644 index 0000000..0c90302 --- /dev/null +++ b/server/src/modules/cash/dto/index.ts @@ -0,0 +1,3 @@ +export { CreateCashDto } from './create-cash.dto'; +export { CashDto } from './cash.dto'; +export { EditCashDto } from './edit-cash.dto';