diff --git a/src/app.module.ts b/src/app.module.ts index db1186a..3344b30 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -41,9 +41,9 @@ import { WalletsModule } from "./modules/wallets/wallets.module"; TypeOrmModule.forRootAsync(databaseConfigs()), HttpModule.register({ global: true, timeout: 5000, headers: { "Content-Type": "application/json" } }), FastifyMulterModule, + AuthModule, UsersModule, TicketsModule, - AuthModule, DanakServicesModule, AnnouncementsModule, UploaderModule, diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index cd65924..1b3b6c4 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -360,3 +360,14 @@ export const enum DiscountMessage { export const enum EmailMessage { EMAIL_VERIFICATION = "تایید ایمیل", } + +export const enum FinancialMessage { + FINANCIAL_TYPE_INVALID = "نوع اطلاعات باید یکی از مقادیر REAL یا LEGAL باشد", + ECONOMIC_CODE_INVALID = "کد اقتصادی باید یک رشته باشد", + REGISTRATION_ID_INVALID = "شناسه ثبت باید یک رشته باشد", + NATIONAL_ID_INVALID = "شناسه ملی باید یک رشته باشد", + FIXED_PHONE_INVALID = "شماره تماس ثابت باید یک رشته باشد", + USER_ID_REQUIRED = "شناسه کاربر مورد نیاز است", + USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID معتبر باشد", + FINANCIAL_INFO_ALREADY_EXISTS = "دیتا اطلاعات مالی وجود دارد", +} diff --git a/src/modules/danak-services/entities/danak-service.entity.ts b/src/modules/danak-services/entities/danak-service.entity.ts index f1cbe13..90f333b 100644 --- a/src/modules/danak-services/entities/danak-service.entity.ts +++ b/src/modules/danak-services/entities/danak-service.entity.ts @@ -1,10 +1,9 @@ -import { Column, Entity, ManyToMany, ManyToOne, OneToMany } from "typeorm"; +import { Column, Entity, ManyToOne, OneToMany } from "typeorm"; import { DanakServiceCategory } from "./danak-service-category.entity"; import { DanakServiceImage } from "./danak-service-image.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { Announcement } from "../../announcements/entities/announcement.entity"; -import { Discount } from "../../discounts/entities/discount.entity"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { Ticket } from "../../tickets/entities/ticket.entity"; import { ServicesLanguage } from "../enums/services-language.enum"; @@ -67,9 +66,6 @@ export class DanakService extends BaseEntity { @OneToMany(() => SubscriptionPlan, (plan) => plan.service, { cascade: true }) subscriptionPlans: SubscriptionPlan[]; - - @ManyToMany(() => Discount, (discount) => discount.services, { nullable: true }) - discounts?: Discount[]; } // @ManyToMany(() => User, (user) => user.danakServices) diff --git a/src/modules/discounts/entities/discount.entity.ts b/src/modules/discounts/entities/discount.entity.ts index 3d55bfa..78c6e65 100644 --- a/src/modules/discounts/entities/discount.entity.ts +++ b/src/modules/discounts/entities/discount.entity.ts @@ -1,9 +1,10 @@ +// eslint-disable-next-line import/no-named-as-default import Decimal from "decimal.js"; -import { Column, Entity, JoinTable, ManyToMany } from "typeorm"; +import { Column, Entity, ManyToOne } from "typeorm"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; -import { DanakService } from "../../danak-services/entities/danak-service.entity"; +import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { DiscountCalculationType, DiscountType } from "../enums/discount-type.enum"; @Entity() @@ -32,7 +33,6 @@ export class Discount extends BaseEntity { @Column({ type: "timestamptz" }) endDate: Date; - @ManyToMany(() => DanakService, (danakService) => danakService.discounts, { nullable: true }) - @JoinTable() - services?: DanakService[]; + @ManyToOne(() => SubscriptionPlan, { nullable: true, onDelete: "SET NULL" }) + subscriptionPlan: SubscriptionPlan; } diff --git a/src/modules/discounts/providers/discounts.service.ts b/src/modules/discounts/providers/discounts.service.ts index bf717b4..7d98e78 100644 --- a/src/modules/discounts/providers/discounts.service.ts +++ b/src/modules/discounts/providers/discounts.service.ts @@ -3,7 +3,7 @@ import { randomBytes } from "node:crypto"; import { BadRequestException, Injectable } from "@nestjs/common"; import { CommonMessage, DiscountMessage } from "../../../common/enums/message.enum"; -import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; +// import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; import { CreateDiscountDto } from "../DTO/create-discount.dto"; import { DiscountRepository } from "../repositories/discount.repository"; @@ -11,7 +11,7 @@ import { DiscountRepository } from "../repositories/discount.repository"; export class DiscountService { constructor( private readonly discountRepository: DiscountRepository, - private readonly danakServicesService: DanakServicesService, + // private readonly danakServicesService: DanakServicesService, ) {} /******************************************** */ @@ -27,8 +27,8 @@ export class DiscountService { }); if (services && services.length) { - const foundServices = await this.danakServicesService.findServicesByIds(services); - discount.services = foundServices; + // const foundServices = await this.danakServicesService.findServicesByIds(services); + // discount.services = foundServices; } await this.discountRepository.save(discount); diff --git a/src/modules/invoices/entities/invoice-item.entity.ts b/src/modules/invoices/entities/invoice-item.entity.ts index 9dc4774..87b381a 100644 --- a/src/modules/invoices/entities/invoice-item.entity.ts +++ b/src/modules/invoices/entities/invoice-item.entity.ts @@ -21,7 +21,6 @@ export class InvoiceItem extends BaseEntity { @Column({ type: "int", nullable: false }) discount: number; - // @Column({ type: "decimal", precision: 16, scale: 2, nullable: false, transformer: new DecimalTransformer() }) unitPrice: Decimal; diff --git a/src/modules/users/DTO/create-user-financial.dto.ts b/src/modules/users/DTO/create-user-financial.dto.ts new file mode 100644 index 0000000..a06de20 --- /dev/null +++ b/src/modules/users/DTO/create-user-financial.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID } from "class-validator"; + +import { FinancialMessage } from "../../../common/enums/message.enum"; +import { FinancialType } from "../enums/financial-type.enum"; + +export class CreateFinancialDto { + @ApiProperty({ enum: FinancialType, description: "Financial information type: REAL or LEGAL" }) + @IsEnum(FinancialType, { message: FinancialMessage.FINANCIAL_TYPE_INVALID }) + type: FinancialType; + + @ApiPropertyOptional({ description: "Economic code" }) + @IsOptional() + @IsString({ message: FinancialMessage.ECONOMIC_CODE_INVALID }) + economicCode?: string; + + @ApiPropertyOptional({ description: "Registration ID" }) + @IsOptional() + @IsString({ message: FinancialMessage.REGISTRATION_ID_INVALID }) + registrationId?: string; + + @ApiPropertyOptional({ description: "number" }) + @IsOptional() + @IsString({ message: FinancialMessage.FIXED_PHONE_INVALID }) + number?: string; + + @ApiPropertyOptional({ description: "number" }) + @IsOptional() + @IsString({ message: FinancialMessage.NATIONAL_ID_INVALID }) + nationalId?: string; + + @ApiPropertyOptional({ description: "User id", example: "123e4567-e89b-12d3-a456-426614174000" }) + @IsOptional() + @IsNotEmpty({ message: FinancialMessage.USER_ID_REQUIRED }) + @IsUUID("4", { message: FinancialMessage.USER_ID_SHOULD_BE_A_UUID }) + userId: string; +} diff --git a/src/modules/users/entities/user-financial.entity.ts b/src/modules/users/entities/user-financial.entity.ts new file mode 100644 index 0000000..e1c9909 --- /dev/null +++ b/src/modules/users/entities/user-financial.entity.ts @@ -0,0 +1,31 @@ +import { Column, Entity, ManyToOne, Unique } from "typeorm"; + +import { User } from "./user.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; +import { FinancialType } from "../enums/financial-type.enum"; + +@Unique(["user", "type"]) +@Entity() +export class UserFinancial extends BaseEntity { + @Column({ + type: "enum", + enum: FinancialType, + default: FinancialType.REAL, + }) + type: FinancialType; + + @Column({ nullable: true }) + economicCode: string; + + @Column({ nullable: true }) + registrationId: string; + + @Column({ nullable: true }) + nationalId: string; + + @Column({ nullable: true }) + number: string; + + @ManyToOne(() => User, (user) => user.userFinancials) + user: User; +} diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 7256286..3e872ff 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -2,6 +2,7 @@ import { Exclude } from "class-transformer"; import { Column, Entity, ManyToMany, ManyToOne, OneToMany, OneToOne } from "typeorm"; import { Role } from "./role.entity"; +import { UserFinancial } from "./user-financial.entity"; import { UserGroup } from "./user-group.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity"; @@ -82,6 +83,9 @@ export class User extends BaseEntity { @OneToMany(() => LearningProgress, (learningProgress) => learningProgress.learning) learningProgress: LearningProgress[]; + + @OneToMany(() => UserFinancial, (userFinancial) => userFinancial.user) + userFinancials: UserFinancial[]; } // @ManyToMany(() => DanakService, (danakService) => danakService.users) diff --git a/src/modules/users/enums/financial-type.enum.ts b/src/modules/users/enums/financial-type.enum.ts new file mode 100644 index 0000000..fd8e668 --- /dev/null +++ b/src/modules/users/enums/financial-type.enum.ts @@ -0,0 +1,4 @@ +export enum FinancialType { + REAL = "REAL", + LEGAL = "LEGAL", +} diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 06fad80..5f0e7c3 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -2,17 +2,19 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import slugify from "slugify"; import { In, Not, QueryRunner } from "typeorm"; -import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; +import { CommonMessage, FinancialMessage, UserMessage } from "../../../common/enums/message.enum"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { UserSettingsService } from "../../settings/providers/user-settings.service"; import { WalletsService } from "../../wallets/providers/wallets.service"; import { CheckValidityDTO } from "../DTO/check-validity.dto"; +import { CreateFinancialDto } from "../DTO/create-user-financial.dto"; import { UpdateProfileDto } from "../DTO/update-profile.dto"; import { CreateUserGroupDto } from "../DTO/user-group.dto"; import { Role } from "../entities/role.entity"; import { User } from "../entities/user.entity"; import { RoleEnum } from "../enums/role.enum"; import { ValidityType } from "../enums/validity-type.enum"; +import { UserFinancialRepository } from "../repositories/user-financial.repository"; import { UserGroupRepository } from "../repositories/user-group.repository"; import { UserRepository } from "../repositories/users.repository"; @@ -36,6 +38,7 @@ export class UsersService { private readonly userRepository: UserRepository, private readonly userGroupRepository: UserGroupRepository, private readonly userSettingsService: UserSettingsService, + private readonly userFinancialRepository: UserFinancialRepository, private readonly walletsService: WalletsService, ) {} @@ -121,6 +124,7 @@ export class UsersService { // await this.userSettingsService.createUserSettings(user.id); // return await this.userRepository.save(user); } + /************************************************************ */ async checkValidity(checkDto: CheckValidityDTO, userId: string) { @@ -175,6 +179,7 @@ export class UsersService { } ///****************************************************** */ + async updateUserPassword(userId: string, newPassword: string) { const updatedUser = await this.userRepository.update( { id: userId }, @@ -186,4 +191,72 @@ export class UsersService { updatedUser, }; } + + /************************************************************ */ + + async findAllUsers() { + const users = await this.userRepository.find({ + relations: ["groups"], + }); + + return { users }; + } + + /************************************************************ */ + + async findAllCustomers() { + const customers = await this.userRepository.find({ + where: { + role: { + name: RoleEnum.USER, + }, + }, + relations: ["tickets", "subscriptions", "invoices"], + }); + + return { customers }; + } + + /************************************************************ */ + + async createUserFinancial(createDto: CreateFinancialDto, user: User) { + const userExist = await this.userRepository.findOneBy({ + id: user.role.name === RoleEnum.USER ? user.id : createDto.userId, + }); + + if (!userExist) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + const existingFinancial = await this.userFinancialRepository.findOne({ + where: { user: { id: userExist.id }, type: createDto.type }, + }); + console.log(existingFinancial); + + if (existingFinancial) throw new BadRequestException(FinancialMessage.FINANCIAL_INFO_ALREADY_EXISTS); + + const userFinancial = this.userFinancialRepository.create({ + ...createDto, + user: userExist, + }); + + await this.userFinancialRepository.save(userFinancial); + + return { + message: CommonMessage.CREATED, + userFinancial, + }; + } + + /************************************************************ */ + + async getUserFinancial(userId: string) { + const user = await this.userRepository.findOne({ + where: { + id: userId, + }, + relations: ["userFinancials"], + }); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + return { user }; + } } diff --git a/src/modules/users/repositories/user-financial.repository.ts b/src/modules/users/repositories/user-financial.repository.ts new file mode 100644 index 0000000..004ca93 --- /dev/null +++ b/src/modules/users/repositories/user-financial.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { UserFinancial } from "../entities/user-financial.entity"; + +@Injectable() +export class UserFinancialRepository extends Repository { + constructor(@InjectRepository(UserFinancial) userFinancialRepository: Repository) { + super(userFinancialRepository.target, userFinancialRepository.manager, userFinancialRepository.queryRunner); + } +} diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index e46c896..99fd15c 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Patch, Post } from "@nestj import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { CheckValidityDTO } from "./DTO/check-validity.dto"; +import { CreateFinancialDto } from "./DTO/create-user-financial.dto"; import { UpdateProfileDto } from "./DTO/update-profile.dto"; import { CreateUserGroupDto } from "./DTO/user-group.dto"; import { User } from "./entities/user.entity"; @@ -16,6 +17,8 @@ import { UserDec } from "../../common/decorators/user.decorator"; export class UsersController { constructor(private usersService: UsersService) {} + /************************************************************ */ + @AuthGuards() @ApiOperation({ summary: "Get user profile" }) @Get("me") @@ -23,6 +26,8 @@ export class UsersController { return this.usersService.getMe(user.id); } + /************************************************************ */ + @AuthGuards() @ApiOperation({ summary: "Update user profile" }) @Patch("update-profile") @@ -30,6 +35,8 @@ export class UsersController { return this.usersService.updateProfile(user.id, updateProfileDto); } + /************************************************************ */ + @AuthGuards() @ApiOperation({ summary: "Check validity of user field" }) @HttpCode(HttpStatus.OK) @@ -38,6 +45,8 @@ export class UsersController { return this.usersService.checkValidity(checkValidityDto, user.id); } + /************************************************************ */ + @AuthGuards() @Roles(RoleEnum.ADMIN) @ApiOperation({ summary: "Create user group" }) @@ -47,6 +56,8 @@ export class UsersController { return this.usersService.createUserGroup(createDto); } + /************************************************************ */ + @ApiOperation({ summary: "get all user group" }) @AuthGuards() @Roles(RoleEnum.ADMIN) @@ -54,4 +65,43 @@ export class UsersController { UserGroups() { return this.usersService.getUserGroups(); } + + /************************************************************ */ + + @ApiOperation({ summary: "get all users" }) + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @Get("") + Users() { + return this.usersService.findAllUsers(); + } + + /************************************************************ */ + + @ApiOperation({ summary: "get all customers" }) + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @Get("customers") + customers() { + return this.usersService.findAllCustomers(); + } + + /************************************************************ */ + + @AuthGuards() + @ApiOperation({ summary: "Create user financial" }) + @HttpCode(HttpStatus.CREATED) + @Post("user-financial") + createUserFinancial(@Body() createDto: CreateFinancialDto, @UserDec() user: User) { + return this.usersService.createUserFinancial(createDto, user); + } + + /************************************************************ */ + + @ApiOperation({ summary: "get all user financials" }) + @AuthGuards() + @Get("user-financials") + userFinancials(@UserDec("id") userId: string) { + return this.usersService.getUserFinancial(userId); + } } diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 3491dfb..934712f 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -13,10 +13,20 @@ import { UserSetting } from "../settings/entities/user-setting.entity"; import { UserSettingsService } from "../settings/providers/user-settings.service"; import { UserSettingsRepository } from "../settings/repositories/user-settings.repository"; import { WalletsModule } from "../wallets/wallets.module"; +import { UserFinancial } from "./entities/user-financial.entity"; +import { UserFinancialRepository } from "./repositories/user-financial.repository"; @Module({ - imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting]), WalletsModule], - providers: [UsersService, UserRepository, RoleRepository, UserGroupRepository, UserSettingsRepository, UserSettingsService], + imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting, UserFinancial]), WalletsModule], + providers: [ + UsersService, + UserRepository, + RoleRepository, + UserGroupRepository, + UserSettingsRepository, + UserFinancialRepository, + UserSettingsService, + ], controllers: [UsersController], exports: [UsersService], })