feat(server): add income target
This commit is contained in:
parent
eda67581ab
commit
f8d766beb0
@ -76,17 +76,19 @@ model IncomeList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model IncomeItem {
|
model IncomeItem {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
incomeList IncomeList @relation(fields: [incomeListId], references: [id], onDelete: Cascade)
|
incomeList IncomeList @relation(fields: [incomeListId], references: [id], onDelete: Cascade)
|
||||||
incomeListId String
|
incomeListId String
|
||||||
title String
|
title String
|
||||||
description String?
|
description String?
|
||||||
date DateTime
|
date DateTime
|
||||||
amount Float
|
amount Float
|
||||||
currency String @default("RUB")
|
currency String @default("RUB")
|
||||||
count Float @default(1)
|
count Float @default(1)
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
targetType IncomeItemToType? @map("target_type")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
targetSourceId String? @map("target_source_id")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@map("incomes_items")
|
@@map("incomes_items")
|
||||||
}
|
}
|
||||||
@ -132,3 +134,10 @@ enum ExpenseItemFromType {
|
|||||||
|
|
||||||
@@map("expense_item_from_types")
|
@@map("expense_item_from_types")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum IncomeItemToType {
|
||||||
|
BANK_CARD
|
||||||
|
CASH
|
||||||
|
|
||||||
|
@@map("income_item_to_types")
|
||||||
|
}
|
||||||
|
|||||||
@ -88,6 +88,11 @@ export class BankCardService {
|
|||||||
await this.bankAccountService.changeBalance(card.bankAccount.id, card.bankAccount.balance - amount);
|
await this.bankAccountService.changeBalance(card.bankAccount.id, card.bankAccount.balance - amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async addIncome(userId: User['id'], bankCardId: BankCard['id'], amount: number) {
|
||||||
|
const card = await this.findCardById(userId, bankCardId);
|
||||||
|
await this.bankAccountService.changeBalance(card.bankAccount.id, card.bankAccount.balance + amount);
|
||||||
|
}
|
||||||
|
|
||||||
private async findCardById(userId: User['id'], bankCardId: BankCard['id']) {
|
private async findCardById(userId: User['id'], bankCardId: BankCard['id']) {
|
||||||
const card = await this.prismaService.bankCard.findUnique({
|
const card = await this.prismaService.bankCard.findUnique({
|
||||||
where: { id: bankCardId },
|
where: { id: bankCardId },
|
||||||
|
|||||||
@ -71,4 +71,9 @@ export class CashService {
|
|||||||
const cash = await this.getCashById(userId, cashId);
|
const cash = await this.getCashById(userId, cashId);
|
||||||
await this.editCash(userId, cash.id, { balance: cash.balance - amount });
|
await this.editCash(userId, cash.id, { balance: cash.balance - amount });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async addIncome(userId: User['id'], cashId: BankCard['id'], amount: number) {
|
||||||
|
const cash = await this.getCashById(userId, cashId);
|
||||||
|
await this.editCash(userId, cash.id, { balance: cash.balance + amount });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,38 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IncomeItemToType } from '@prisma/client';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
|
IsEnum,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString, ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
import { IsISODateTimeString } from '../../../../shared/validators';
|
import { IsISODateTimeString } from '../../../../shared/validators';
|
||||||
|
|
||||||
|
class IncomeSourceDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Источник дохода',
|
||||||
|
example: 'CASH',
|
||||||
|
enum: IncomeItemToType,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Источник дохода обязателен к заполнению' })
|
||||||
|
@IsEnum(IncomeItemToType)
|
||||||
|
type: IncomeItemToType;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ID карты или наличных ',
|
||||||
|
example: '123',
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'ID источника дохода обязателен к заполнению' })
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class CreateIncomeListItemDto {
|
export class CreateIncomeListItemDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Заголовок дохода',
|
description: 'Заголовок дохода',
|
||||||
@ -56,4 +81,14 @@ export class CreateIncomeListItemDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString({ message: 'Валюта дохода должна быть строкой.' })
|
@IsString({ message: 'Валюта дохода должна быть строкой.' })
|
||||||
currency?: string;
|
currency?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Источник',
|
||||||
|
type: IncomeSourceDto,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => IncomeSourceDto)
|
||||||
|
target?: IncomeSourceDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,37 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IncomeItem, IncomeList } from '@prisma/client';
|
import {
|
||||||
|
IncomeItem,
|
||||||
|
IncomeItemToType,
|
||||||
|
IncomeList,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import {
|
||||||
|
IsEnum,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsString,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
class IncomeSourceDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Источник дохода',
|
||||||
|
example: 'CASH',
|
||||||
|
enum: IncomeItemToType,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Источник дохода обязателен к заполнению' })
|
||||||
|
@IsEnum(IncomeItemToType)
|
||||||
|
type: IncomeItemToType;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ID карты или наличных ',
|
||||||
|
example: '123',
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'ID источника дохода обязателен к заполнению' })
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class IncomeListItemDto {
|
export class IncomeListItemDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -52,6 +84,14 @@ export class IncomeListItemDto {
|
|||||||
})
|
})
|
||||||
currency: IncomeItem['currency'];
|
currency: IncomeItem['currency'];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Источник',
|
||||||
|
type: IncomeSourceDto,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
@Type(() => IncomeSourceDto)
|
||||||
|
target?: IncomeSourceDto | null;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Дата создания',
|
description: 'Дата создания',
|
||||||
type: Date,
|
type: Date,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IncomeItem } from '@prisma/client';
|
|
||||||
|
|
||||||
import { IncomeListItemDto } from '../income-list-item/income-list-item.dto';
|
import { IncomeListItemDto } from '../income-list-item/income-list-item.dto';
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ export class IncomeListDto {
|
|||||||
type: [IncomeListItemDto],
|
type: [IncomeListItemDto],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
items?: IncomeItem[];
|
items?: IncomeListItemDto[];
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Дата создания',
|
description: 'Дата создания',
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { BankCardModule } from '@/modules/bank-card/bank-card.module';
|
||||||
|
import { CashModule } from '@/modules/cash/cash.module';
|
||||||
|
|
||||||
import { IncomesController } from './incomes.controller';
|
import { IncomesController } from './incomes.controller';
|
||||||
import { IncomesService } from './incomes.service';
|
import { IncomesService } from './incomes.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [
|
||||||
|
BankCardModule,
|
||||||
|
CashModule,
|
||||||
|
],
|
||||||
controllers: [IncomesController],
|
controllers: [IncomesController],
|
||||||
providers: [IncomesService],
|
providers: [IncomesService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,19 +1,28 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { IncomeItem, IncomeList, User } from '@prisma/client';
|
import {
|
||||||
|
IncomeItem, IncomeItemToType, IncomeList, User,
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
import { BankCardService } from '@/modules/bank-card/bank-card.service';
|
||||||
|
import { CashService } from '@/modules/cash/cash.service';
|
||||||
import { PrismaService } from '@/modules/prisma/prisma.service';
|
import { PrismaService } from '@/modules/prisma/prisma.service';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateIncomeListDto, CreateIncomeListItemDto,
|
CreateIncomeListDto,
|
||||||
|
CreateIncomeListItemDto,
|
||||||
DeletedIncomeListDto,
|
DeletedIncomeListDto,
|
||||||
EditIncomeListDto, EditIncomeListItemDto,
|
EditIncomeListDto,
|
||||||
IncomeListDto, IncomeListItemDto,
|
EditIncomeListItemDto,
|
||||||
|
IncomeListDto,
|
||||||
|
IncomeListItemDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IncomesService {
|
export class IncomesService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly bankCardService: BankCardService,
|
||||||
|
private readonly cashService: CashService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createList(dto: CreateIncomeListDto, userId: User['id']): Promise<IncomeListDto> {
|
async createList(dto: CreateIncomeListDto, userId: User['id']): Promise<IncomeListDto> {
|
||||||
@ -84,9 +93,25 @@ export class IncomesService {
|
|||||||
date,
|
date,
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
|
target,
|
||||||
} = dto;
|
} = dto;
|
||||||
const list = await this.findList(listId, userId);
|
const list = await this.findList(listId, userId);
|
||||||
|
|
||||||
|
switch (target?.type) {
|
||||||
|
case IncomeItemToType.BANK_CARD:
|
||||||
|
await this.bankCardService.addIncome(userId, target.id, amount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IncomeItemToType.CASH:
|
||||||
|
await this.cashService.addIncome(userId, target.id, amount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undefined
|
||||||
|
case undefined:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return this.prismaService.incomeItem.create({
|
return this.prismaService.incomeItem.create({
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
@ -94,6 +119,8 @@ export class IncomesService {
|
|||||||
date,
|
date,
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
|
targetType: target?.type,
|
||||||
|
targetSourceId: target?.id,
|
||||||
incomeList: {
|
incomeList: {
|
||||||
connect: { id: list.id },
|
connect: { id: list.id },
|
||||||
},
|
},
|
||||||
@ -156,7 +183,29 @@ export class IncomesService {
|
|||||||
throw new NotFoundException('Лист доходов не найден');
|
throw new NotFoundException('Лист доходов не найден');
|
||||||
}
|
}
|
||||||
|
|
||||||
return existingList;
|
const { items, ...rest } = existingList;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
items: items.map((item) => {
|
||||||
|
const { targetSourceId, targetType, ...restItem } = item;
|
||||||
|
|
||||||
|
if (targetSourceId && targetType) {
|
||||||
|
return {
|
||||||
|
...restItem,
|
||||||
|
target: {
|
||||||
|
type: targetType,
|
||||||
|
id: targetSourceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...restItem,
|
||||||
|
target: null,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findListItem(id: IncomeItem['id'], listId: IncomeList['id'], userId: User['id']) {
|
private async findListItem(id: IncomeItem['id'], listId: IncomeList['id'], userId: User['id']) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user