feat(server): add cash

This commit is contained in:
Sergey Krylov 2025-09-12 07:27:00 +03:00
parent 4b2e05fa65
commit e7c0a48ed1
15 changed files with 402 additions and 1 deletions

View File

@ -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")
}

View File

@ -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 {}

View File

@ -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);
}
}

View File

@ -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 {}

View File

@ -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<CashDto> {
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<CashDto> {
const cash = await this.prismaService.cash.findUnique({
where: {
id: cashId,
userId,
},
});
if (!cash) {
throw new NotFoundException('Кэш не найден');
}
return cash;
}
async getAllCashByUserId(userId: User['id']): Promise<CashDto[]> {
return this.prismaService.cash.findMany({
where: { userId },
orderBy: { createdAt: 'asc' },
});
}
async editCash(userId: User['id'], cashId: Cash['id'], dto: EditCashDto): Promise<CashDto> {
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<CashDto> {
const cash = await this.getCashById(userId, cashId);
return this.prismaService.cash.delete({
where: { id: cash.id },
});
}
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
export { CreateCashDto } from './create-cash.dto';
export { CashDto } from './cash.dto';
export { EditCashDto } from './edit-cash.dto';