diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index f8f53e8..e2aca36 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -113,6 +113,10 @@ export const enum CategoryMessage { CATEGORY_NOT_EXIST = "دسته بندی مورد نظر یافت نشد", IS_ACTIVE_SHOULD_BE_BOOLEAN = "وضعیت دسته بندی باید یک بولین باشد", SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", + IS_SUGGEST_SHOULD_BE_BOOLEAN = "وضعیت پیشنهادی بودن دسته بندی باید یک بولین باشد", + IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت دسته بندی باید یکی از مقادیر ۰ و ۱ باشد", + IS_SUGGEST_SHOULD_BE_1_0 = "وضعیت پیشنهادی بودن دسته بندی باید یکی از مقادیر ۰ و ۱ باشد", + CATEGORY_NOT_EXIST_OR_HAS_NO_SERVICE = "دسته بندی مورد نظر یافت نشد یا هیچ سرویسی در این دسته بندی وجود ندارد", } export const enum ServiceMessage { @@ -159,6 +163,8 @@ export const enum ServiceMessage { REVIEW_NOT_EXIST = "نظری برای این سرویس یافت نشد", REVIEW_APPROVED = "نظر با موفقیت تایید شد", REVIEW_REJECTED = "نظر با موفقیت رد شد", + IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت سرویس باید یکی از مقادیر ۰ و ۱ باشد", + IS_SUGGEST_SHOULD_BE_1_0 = "وضعیت پیشنهادی بودن سرویس باید یکی از مقادیر ۰ و ۱ باشد", } export const enum AnnouncementMessage { @@ -173,6 +179,13 @@ export const enum AnnouncementMessage { USER_MUST_BE_UUID = "شناسه کاربر باید یک UUID معتبر باشد", SERVICE_MUST_BE_ID = "ایدی سرویس اجباری است", NOT_FOUND = "اطلاعیه ای با این ایدی یافت نشد", + USER_IDS_MUST_BE_ARRAY = "شناسه کاربران باید یک آرایه باشد", + IMPORTANT_IS_REQUIRED = "وضعیت اهمیت اطلاعیه مورد نیاز است", + TITLE_LENGTH = "عنوان اطلاعیه باید بین ۵ تا ۱۰۰ کاراکتر باشد", + PUBLISH_AT_MUST_BE_DATE = "تاریخ انتشار باید یک تاریخ معتبر باشد", + IMPORTANT_MUST_BE_BOOLEAN = "وضعیت اهمیت اطلاعیه باید یک بولین باشد", + SOME_SERVICES_NOT_FOUND = "بعضی از سرویس ها یافت نشدند", + SOME_USERS_NOT_FOUND = "بعضی از کاربران یافت نشدند", } export const enum CriticismMessage { @@ -182,6 +195,8 @@ export const enum CriticismMessage { CONTENT_IS_STRING = "متن انتقادات باید رشته باشد", NOT_FOUND = "انتقادی با این ایدی یافت نشد", SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", + FILES_IS_REQUIRED = "فایل های انتقادات مورد نیاز است", + FILES_SHOULD_BE_URL = "فایل های انتقادات باید یک آدرس یو آر ال باشد", } export const enum TicketMessageEnum { TITLE_REQUIRED = "عنوان تیکت مورد نیاز است", @@ -339,11 +354,16 @@ export const enum SubscriptionMessage { SUBS_REQUIRED = "اشتراک مورد نیاز است", SUBS_SHOULD_BE_ARRAY = "اشتراک باید یک آرایه باشد", USER_SUBS_NOT_FOUND = "اشتراک کاربر یافت نشد", - BUSINESS_NAME_REQUIRED = "BUSINESS_NAME_REQUIRED", - BUSINESS_NAME_LENGTH = "BUSINESS_NAME_LENGTH", - BUSINESS_PHONE_INVALID = "BUSINESS_PHONE_INVALID", - DESCRIPTION_LENGTH = "DESCRIPTION_LENGTH", - BUSINESS_NAME_STRING = "BUSINESS_NAME_STRING", + BUSINESS_NAME_REQUIRED = "نام کسب و کار نمی‌تواند خالی باشد", + BUSINESS_NAME_LENGTH = "نام کسب و کار باید بین ۳ تا ۱۰۰ کاراکتر باشد", + BUSINESS_PHONE_INVALID = "شماره تلفن کسب و کار معتبر نیست", + DESCRIPTION_LENGTH = "طول توضیحات باید بین ۱۰ تا ۵۰۰ کاراکتر باشد", + BUSINESS_NAME_STRING = "نام کسب و کار باید یک رشته باشد", + BUSINESS_PHONE_REQUIRED = "شماره تلفن کسب و کار نمی‌تواند خالی باشد", + BUSINESS_DESCRIPTION_REQUIRED = "توضیحات کسب و کار نمی‌تواند خالی باشد", + BUSINESS_DESCRIPTION_LENGTH = "توضیحات کسب و کار باید بین ۱۰ تا ۵۰۰ کاراکتر باشد", + BUSINESS_DESCRIPTION_STRING = "توضیحات کسب و کار باید یک رشته باشد", + IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت اشتراک باید یکی از مقادیر ۰ و ۱ باشد", } export const enum InvoiceMessage { @@ -369,6 +389,8 @@ export const enum InvoiceMessage { INVOICE_PAID = "صورت حساب با موفقیت پرداخت شد", DISCOUNT_APPLIED = "تخفیف با موفقیت اعمال شد", DISCOUNT_CANCELLED = "تخفیف با موفقیت لغو شد", + INVOICE_IS_REJECTED = "فاکتور قبلا رد شده است", + INVOICE_CAN_NOT_PAID = "فاکتور قابل پرداخت نیست", } export const enum LearningMessage { @@ -488,6 +510,7 @@ export const enum AdsMessage { ADS_ACTIVATED = "تبلیغ با موفقیت فعال شد", ADS_DEACTIVATED = "تبلیغ با موفقیت غیرفعال شد", ROLE_NOT_FOUND = "نقشی با این شناسه یافت نشد", + IS_ACTIVE_SHOULD_BE_BOOLEAN = "وضعیت فعال بودن باید یک بولین باشد", } export const enum ProvinceMessage { diff --git a/src/modules/ads/DTO/ads-search-query.dto.ts b/src/modules/ads/DTO/ads-search-query.dto.ts index 91e8e72..6dcedac 100755 --- a/src/modules/ads/DTO/ads-search-query.dto.ts +++ b/src/modules/ads/DTO/ads-search-query.dto.ts @@ -1,15 +1,15 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsDateString, IsEnum, IsOptional, IsString } from "class-validator"; +import { IsDateString, IsEnum, IsIn, IsOptional, IsString } from "class-validator"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; -import { AdsMessage } from "../../../common/enums/message.enum"; +import { AdsMessage, CategoryMessage } from "../../../common/enums/message.enum"; import { AdsDisplayLocation } from "../enums/ads-location.enum"; export class AdsSearchQueryDto extends PaginationDto { @IsOptional() @Type(() => Number) - // @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN }) + @IsIn([1, 0], { message: CategoryMessage.IS_ACTIVE_SHOULD_BE_1_0 }) @ApiPropertyOptional({ description: "ads status", example: 1 }) isActive?: number; diff --git a/src/modules/announcements/DTO/create-announcement.dto.ts b/src/modules/announcements/DTO/create-announcement.dto.ts index 26badc0..ad6917f 100755 --- a/src/modules/announcements/DTO/create-announcement.dto.ts +++ b/src/modules/announcements/DTO/create-announcement.dto.ts @@ -1,77 +1,54 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, MinLength } from "class-validator"; +import { + ArrayMinSize, + IsArray, + IsBoolean, + IsDateString, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + Length, + MinLength, +} from "class-validator"; import { AnnouncementMessage } from "../../../common/enums/message.enum"; export class CreateAnnouncementDto { @IsNotEmpty({ message: AnnouncementMessage.TITLE_IS_REQUIRED }) @IsString({ message: AnnouncementMessage.TITLE_STRING }) - @ApiProperty({ - description: "Title of the announcement", - example: "اطلاعیه جدید", - }) + @Length(5, 100, { message: AnnouncementMessage.TITLE_LENGTH }) + @ApiProperty({ description: "Title of the announcement", example: "اطلاعیه جدید" }) title: string; @IsNotEmpty({ message: AnnouncementMessage.CONTENT_IS_REQUIRED }) @IsString({ message: AnnouncementMessage.CONTENT_IS_STRING }) @MinLength(10, { message: AnnouncementMessage.CONTENT_MUST_BE_LONGER }) - @ApiProperty({ - description: "Content of the announcement", - example: "اطلاعیه جدید برای کاربران", - }) + @ApiProperty({ description: "Content of the announcement", example: "اطلاعیه جدید برای کاربران" }) content: string; @IsOptional() @IsNotEmpty({ message: AnnouncementMessage.PUBLISH_AT_IS_REQUIRED }) - @ApiProperty({ - description: "Publish date of the announcement", - example: "2023-10-01T10:00:00Z", - }) + @IsDateString({}, { message: AnnouncementMessage.PUBLISH_AT_MUST_BE_DATE }) + @ApiProperty({ description: "Publish date of the announcement", example: "2023-10-01T10:00:00Z" }) publishAt: Date; - @IsBoolean() - @ApiProperty({ - description: "Is this announcement public?", - example: false, - }) - isPublic: boolean; - - @IsBoolean() - @ApiProperty({ - description: "Is this announcement important?", - example: false, - }) + @IsNotEmpty({ message: AnnouncementMessage.IMPORTANT_IS_REQUIRED }) + @IsBoolean({ message: AnnouncementMessage.IMPORTANT_MUST_BE_BOOLEAN }) + @ApiProperty({ description: "Is this announcement important?", example: false }) isImportant: boolean; @IsOptional() @IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID }) - @IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID }) - @ApiProperty({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" }) - serviceId: string; + @ArrayMinSize(1) + @IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID, each: true }) + @ApiProperty({ type: [String], description: "Service IDs", example: ["d290f1ee-6c54-4b01-90e6-d701748f0851"] }) + serviceIds: string[]; @IsOptional() - @IsArray() - @IsUUID("4", { - each: true, - message: AnnouncementMessage.GROUP_MUST_BE_UUID, - }) - @ApiProperty({ - type: [String], - description: "Array of group ids", - example: ["f7b1b3b1-1b1b-4b1b-8b1b-1b1b1b1b1b1b"], - }) - groupIds: string[]; - - @IsOptional() - @IsArray() - @IsUUID("4", { - each: true, - message: AnnouncementMessage.USER_MUST_BE_UUID, - }) - @ApiProperty({ - type: [String], - description: "Array of users ids", - example: ["f7b1b3b1-1b1b-4b1b-8b1b-1b1b1b1b1b1b"], - }) + @ArrayMinSize(1) + @IsArray({ message: AnnouncementMessage.USER_IDS_MUST_BE_ARRAY }) + @IsUUID("4", { each: true, message: AnnouncementMessage.USER_MUST_BE_UUID }) + @ApiProperty({ type: [String], description: "Array of users ids", example: ["f7b1b3b1-1b1b-4b1b-8b1b-1b1b1b1b1b1b"] }) userIds: string[]; } diff --git a/src/modules/announcements/announcement.controller.ts b/src/modules/announcements/announcement.controller.ts index 50d3908..8270965 100755 --- a/src/modules/announcements/announcement.controller.ts +++ b/src/modules/announcements/announcement.controller.ts @@ -1,7 +1,6 @@ -import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common"; +import { Controller, Get, Param, Query } from "@nestjs/common"; import { ApiProperty, ApiTags } from "@nestjs/swagger"; -import { CreateAnnouncementDto } from "./DTO/create-announcement.dto"; import { AnnouncementService } from "./providers/announcement.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; @@ -16,13 +15,13 @@ import { PermissionEnum } from "../users/enums/permission.enum"; export class AnnouncementController { constructor(private readonly announcementService: AnnouncementService) {} - @AuthGuards() - @PermissionsDec(PermissionEnum.ANNOUNCEMENTS) - @ApiProperty({ description: "Create a new announcement ===> login as admin" }) - @Post() - async create(@Body() createAnnouncementDto: CreateAnnouncementDto) { - return await this.announcementService.createAnnouncement(createAnnouncementDto); - } + // @AuthGuards() + // @PermissionsDec(PermissionEnum.ANNOUNCEMENTS) + // @ApiProperty({ description: "Create a new announcement ===> login as admin" }) + // @Post() + // async create(@Body() createAnnouncementDto: CreateAnnouncementDto) { + // return await this.announcementService.createAnnouncement(createAnnouncementDto); + // } @AuthGuards() @ApiProperty({ description: "Get all announcements ===> login as admin" }) @@ -33,9 +32,7 @@ export class AnnouncementController { } @AuthGuards() - @ApiProperty({ - description: "Get all announcements ===> login as user", - }) + @ApiProperty({ description: "Get all announcements ===> login as user" }) @Get("user") async getAllAnnouncementsByUser(@Query() queryDto: SearchCriticismQueryDto, @UserDec() user: User) { return await this.announcementService.getAllAnnouncementsByUser(queryDto, user.id); diff --git a/src/modules/announcements/entities/announcement.entity.ts b/src/modules/announcements/entities/announcement.entity.ts index 694191e..32c6006 100755 --- a/src/modules/announcements/entities/announcement.entity.ts +++ b/src/modules/announcements/entities/announcement.entity.ts @@ -1,10 +1,8 @@ -import { Column, Entity, Index, JoinTable, ManyToMany, ManyToOne, OneToMany } from "typeorm"; +import { Column, Entity, Index, ManyToOne, OneToMany } from "typeorm"; import { UserAnnouncement } from "./user-announcement.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; -import { UserGroup } from "../../users/entities/user-group.entity"; -import { User } from "../../users/entities/user.entity"; @Entity() @Index(["isPublic", "createdAt"]) @@ -15,30 +13,22 @@ export class Announcement extends BaseEntity { @Column({ type: "text" }) content: string; - @Column({ type: "timestamp", default: null, nullable: true }) - publishAt: Date; + @Column({ type: "timestamp", nullable: true, default: null }) + publishAt: Date | null; - //TODO: Add customer here @ManyToOne(() => DanakService, (service) => service.announcements, { eager: true, onDelete: "CASCADE" }) - service: DanakService; + targetService: DanakService; @Column({ type: "boolean", default: false }) isImportant: boolean; - @ManyToMany(() => UserGroup, (group) => group.announcements) - targetGroups: UserGroup[]; + @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.announcement) + userAnnouncements: UserAnnouncement[]; - @Column({ default: false }) + @Column({ type: "boolean", default: false }) isPublic: boolean; isPublicAnnouncement(): boolean { return this.isPublic; } - - @ManyToMany(() => User, (user) => user.announcements) - @JoinTable({ name: "announcement_users" }) - users: User[]; - - @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.announcement) - userAnnouncements: UserAnnouncement[]; } diff --git a/src/modules/announcements/entities/user-announcement.entity.ts b/src/modules/announcements/entities/user-announcement.entity.ts index 05ca0c8..cec8cf8 100755 --- a/src/modules/announcements/entities/user-announcement.entity.ts +++ b/src/modules/announcements/entities/user-announcement.entity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Column, Entity, ManyToOne } from "typeorm"; import { Announcement } from "./announcement.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; @@ -7,11 +7,9 @@ import { User } from "../../users/entities/user.entity"; // Assuming you have a @Entity() export class UserAnnouncement extends BaseEntity { @ManyToOne(() => User, (user) => user.userAnnouncements, { onDelete: "CASCADE" }) - @JoinColumn({ name: "userId" }) user: User; @ManyToOne(() => Announcement, (announcement) => announcement.userAnnouncements, { onDelete: "CASCADE" }) - @JoinColumn({ name: "announcementId" }) announcement: Announcement; @Column({ type: "boolean", default: false }) diff --git a/src/modules/announcements/providers/announcement.service.ts b/src/modules/announcements/providers/announcement.service.ts index b28c27f..740a364 100755 --- a/src/modules/announcements/providers/announcement.service.ts +++ b/src/modules/announcements/providers/announcement.service.ts @@ -1,10 +1,7 @@ import { BadRequestException, Injectable } from "@nestjs/common"; -import { AnnouncementMessage, CommonMessage } from "../../../common/enums/message.enum"; -import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; -import { UsersService } from "../../users/providers/users.service"; +import { AnnouncementMessage } from "../../../common/enums/message.enum"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; -import { CreateAnnouncementDto } from "../DTO/create-announcement.dto"; import { SearchAnnouncementQueryDto } from "../DTO/search-announcement-query.dto"; import { AnnouncementRepository } from "../repositories/announcement.repository"; import { UserAnnouncementRepository } from "../repositories/user-announcement.repository"; @@ -14,39 +11,8 @@ export class AnnouncementService { constructor( private readonly announcementRepository: AnnouncementRepository, private readonly userAnnouncementRepository: UserAnnouncementRepository, - private readonly danakServicesService: DanakServicesService, - private readonly usersService: UsersService, ) {} - /***************************************** */ - async createAnnouncement(createAnnouncementDto: CreateAnnouncementDto) { - let service; - let groups; - let users; - if (createAnnouncementDto.serviceId) { - service = await this.danakServicesService.findServiceById(createAnnouncementDto.serviceId); - } - if (createAnnouncementDto.groupIds) { - groups = await this.usersService.getUserGroupsByIds(createAnnouncementDto.groupIds); - } - if (createAnnouncementDto.userIds) { - users = await this.usersService.getUsersByIds(createAnnouncementDto.userIds); - } - //TODO: Create Queue for publishAT - const announcement = this.announcementRepository.create({ - ...createAnnouncementDto, - service, - targetGroups: groups?.userGroups, - users, - }); - - await this.announcementRepository.save(announcement); - return { - message: CommonMessage.CREATED, - announcement, - }; - } - /***************************************** */ async getAllAnnouncements(queryDto: SearchAnnouncementQueryDto, userId: string) { @@ -81,58 +47,6 @@ export class AnnouncementService { /***************************************** */ - // async getAllAnnouncementsByUser(queryDto: SearchAnnouncementQueryDto, userId: string) { - // const { limit, skip } = PaginationUtils(queryDto); - // const queryBuilder = this.announcementRepository - // .createQueryBuilder("announcement") - // .leftJoin("announcement.service", "danak_service") - // .addSelect(["danak_service.id", "danak_service.name"]) - // .leftJoin("announcement.targetGroups", "group") - // .addSelect(["group.id", "group.name"]) - // .leftJoin("group.users", "groupUser") - // .leftJoinAndSelect("announcement.userAnnouncements", "userAnnouncements", "userAnnouncements.userId = :userId", { userId }) - // .where("announcement.isPublic = :isPublic", { isPublic: true }) // Check if the announcement is public - // .orWhere("groupUser.id = :userId", { userId }); - - // if (queryDto.since) { - // queryBuilder.andWhere("announcement.createdAt >= :since", { since: queryDto.since }); - // } - - // if (queryDto.publishAt) { - // queryBuilder.andWhere("announcement.publishAt >= :publishAt", { publishAt: queryDto.publishAt }); - // } - - // if (queryDto.q) { - // queryBuilder.andWhere("announcement.title LIKE :q", { q: `%${queryDto.q}%` }); - // } - - // queryBuilder.orderBy("announcement.createdAt", "DESC"); - // const rawResults = await queryBuilder.getRawMany(); - - // const [, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); - - // const announcements = rawResults.map((raw) => ({ - // id: raw.announcement_id, - // createdAt: raw.announcement_createdAt, - // updatedAt: raw.announcement_updatedAt, - // title: raw.announcement_title, - // content: raw.announcement_content, - // publishAt: raw.announcement_publishAt, - // service: { - // id: raw.danak_service_id, - // name: raw.danak_service_name, - // }, - // isImportant: raw.announcement_isImportant, - // targetGroups: raw.group_id ? [{ id: raw.group_id, name: raw.group_name }] : [], - // isPublic: raw.announcement_isPublic, - // isRead: raw.userAnnouncements_isRead ? true : false, - // })); - - // return { announcements, count, paginate: true }; - // } - - /***************************************** */ - async getAllAnnouncementsByUser(queryDto: SearchAnnouncementQueryDto, userId: string) { const { limit, skip } = PaginationUtils(queryDto); diff --git a/src/modules/criticisms/DTO/create-criticism.dto.ts b/src/modules/criticisms/DTO/create-criticism.dto.ts index e34bf8e..dbffc4b 100755 --- a/src/modules/criticisms/DTO/create-criticism.dto.ts +++ b/src/modules/criticisms/DTO/create-criticism.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsNotEmpty, IsOptional, IsString } from "class-validator"; +import { IsNotEmpty, IsOptional, IsString, IsUrl } from "class-validator"; import { CriticismMessage } from "../../../common/enums/message.enum"; @@ -15,8 +15,8 @@ export class CreateCriticismDto { content: string; @IsOptional() - @IsNotEmpty() - @IsString({ each: true }) + @IsNotEmpty({ message: CriticismMessage.FILES_IS_REQUIRED }) + @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: CriticismMessage.FILES_SHOULD_BE_URL, each: true }) @ApiProperty({ description: "The ulr of the criticism files", example: ["https://example.com/image1.png", "https://example.com/image2.png"], diff --git a/src/modules/criticisms/criticisms.controller.ts b/src/modules/criticisms/criticisms.controller.ts index 59cd319..034fcf4 100755 --- a/src/modules/criticisms/criticisms.controller.ts +++ b/src/modules/criticisms/criticisms.controller.ts @@ -8,7 +8,6 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { ParamDto } from "../../common/DTO/param.dto"; -import { User } from "../users/entities/user.entity"; import { PermissionEnum } from "../users/enums/permission.enum"; @Controller("criticisms") @@ -19,8 +18,8 @@ export class CriticismController { @AuthGuards() @ApiOperation({ summary: "Create a new criticism" }) @Post() - create(@Body() createDto: CreateCriticismDto, @UserDec() user: User) { - return this.criticismService.create(createDto, user.id); + create(@Body() createDto: CreateCriticismDto, @UserDec("id") userId: string) { + return this.criticismService.create(createDto, userId); } @AuthGuards() diff --git a/src/modules/criticisms/providers/criticisms.service.ts b/src/modules/criticisms/providers/criticisms.service.ts index 795d3ab..d5e8f52 100755 --- a/src/modules/criticisms/providers/criticisms.service.ts +++ b/src/modules/criticisms/providers/criticisms.service.ts @@ -16,9 +16,10 @@ export class CriticismsService { async create(createDto: CreateCriticismDto, userId: string) { const { user } = await this.usersService.findOneById(userId); - const files = createDto.files.map((file) => { - return { fileUrl: file }; - }); + const files = + createDto.files?.map((file) => { + return { fileUrl: file }; + }) || []; const criticism = this.criticismRepository.create({ ...createDto, user, diff --git a/src/modules/danak-services/DTO/category-search-query.dto.ts b/src/modules/danak-services/DTO/category-search-query.dto.ts index 2b887f7..1e3dee9 100755 --- a/src/modules/danak-services/DTO/category-search-query.dto.ts +++ b/src/modules/danak-services/DTO/category-search-query.dto.ts @@ -1,6 +1,6 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsOptional, IsString, IsUUID } from "class-validator"; +import { IsIn, IsOptional, IsString, IsUUID } from "class-validator"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { CategoryMessage } from "../../../common/enums/message.enum"; @@ -15,7 +15,7 @@ export class CategorySearchQueryDto { export class CategoryListSearchQueryDto extends PaginationDto { @IsOptional() @Type(() => Number) - // @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN }) + @IsIn([1, 0], { message: CategoryMessage.IS_ACTIVE_SHOULD_BE_1_0 }) @ApiPropertyOptional({ description: "Category status", example: 1 }) isActive?: number; diff --git a/src/modules/danak-services/DTO/danak-services-search-query.dto.ts b/src/modules/danak-services/DTO/danak-services-search-query.dto.ts index eebf6cc..8a21676 100755 --- a/src/modules/danak-services/DTO/danak-services-search-query.dto.ts +++ b/src/modules/danak-services/DTO/danak-services-search-query.dto.ts @@ -1,9 +1,9 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsOptional, IsString } from "class-validator"; +import { IsIn, IsOptional, IsString } from "class-validator"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; -import { CategoryMessage } from "../../../common/enums/message.enum"; +import { CategoryMessage, ServiceMessage } from "../../../common/enums/message.enum"; export class DanakServicesSearchQueryDto extends PaginationDto { @IsOptional() @@ -13,7 +13,7 @@ export class DanakServicesSearchQueryDto extends PaginationDto { @IsOptional() @Type(() => Number) - // @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN }) + @IsIn([1, 0], { message: ServiceMessage.IS_ACTIVE_SHOULD_BE_1_0 }) @ApiPropertyOptional({ description: "service status", example: 1 }) isActive?: number; @@ -24,6 +24,7 @@ export class DanakServicesSearchQueryDto extends PaginationDto { @IsOptional() @Type(() => Number) + @IsIn([1, 0], { message: ServiceMessage.IS_SUGGEST_SHOULD_BE_1_0 }) @ApiPropertyOptional({ description: "is danak suggest this service", example: 1 }) isDanakSuggest?: number; } diff --git a/src/modules/danak-services/entities/danak-service.entity.ts b/src/modules/danak-services/entities/danak-service.entity.ts index ea543b3..a17c3d1 100755 --- a/src/modules/danak-services/entities/danak-service.entity.ts +++ b/src/modules/danak-services/entities/danak-service.entity.ts @@ -7,7 +7,6 @@ import { BaseEntity } from "../../../common/entities/base.entity"; import { Ads } from "../../ads/entities/ads.entity"; import { Announcement } from "../../announcements/entities/announcement.entity"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; -import { UserQuickAccess } from "../../subscriptions/entities/user-quick-access.entity"; import { Ticket } from "../../tickets/entities/ticket.entity"; import { ServicesLanguage } from "../enums/services-language.enum"; @@ -62,7 +61,7 @@ export class DanakService extends BaseEntity { @OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { cascade: true }) images: DanakServiceImage[]; - @OneToMany(() => Announcement, (announcement) => announcement.service) + @OneToMany(() => Announcement, (announcement) => announcement.targetService) announcements: Announcement[]; @OneToMany(() => Ticket, (ticket) => ticket.danakService) @@ -77,8 +76,6 @@ export class DanakService extends BaseEntity { @OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.service) reviews: DanakServiceReview[]; - @OneToMany(() => UserQuickAccess, (quickAccess) => quickAccess.service) - quickAccess: UserQuickAccess[]; // @ManyToMany(() => Discount, (discount) => discount.services, { nullable: true }) // discounts?: Discount[]; } diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index f9322a9..a923658 100755 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -104,7 +104,7 @@ export class DanakServicesService { }, order: { createdAt: "DESC" }, }); - if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); + if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST_OR_HAS_NO_SERVICE); return { category, }; diff --git a/src/modules/invoices/enums/invoice-status.enum.ts b/src/modules/invoices/enums/invoice-status.enum.ts index d60074a..7bfc5ba 100755 --- a/src/modules/invoices/enums/invoice-status.enum.ts +++ b/src/modules/invoices/enums/invoice-status.enum.ts @@ -1,6 +1,8 @@ export enum InvoiceStatus { - APPROVED = "APPROVED", PENDING = "PENDING", + APPROVED = "APPROVED", + REJECTED = "REJECTED", + WAIT_PAYMENT = "WAIT_PAYMENT", PAID = "PAID", EXPIRED = "EXPIRED", CANCELLED = "CANCELLED", diff --git a/src/modules/invoices/invoices.controller.ts b/src/modules/invoices/invoices.controller.ts index bd83f6c..384a071 100755 --- a/src/modules/invoices/invoices.controller.ts +++ b/src/modules/invoices/invoices.controller.ts @@ -18,8 +18,6 @@ import { PermissionEnum } from "../users/enums/permission.enum"; export class InvoicesController { constructor(private readonly invoiceService: InvoicesService) {} - /**************************************** */ - @ApiOperation({ summary: "create an invoice ==> admin route" }) @PermissionsDec(PermissionEnum.INVOICES) @AuthGuards() @@ -28,8 +26,6 @@ export class InvoicesController { return this.invoiceService.createInvoiceAdmin(createDto); } - /**************************************** */ - @ApiOperation({ summary: "get all invoices ==> admin route" }) @AuthGuards() @PermissionsDec(PermissionEnum.INVOICES) @@ -39,8 +35,6 @@ export class InvoicesController { return this.invoiceService.getInvoices(queryDto); } - /**************************************** */ - @ApiOperation({ summary: "get all user invoices" }) @AuthGuards() @Pagination() @@ -49,8 +43,6 @@ export class InvoicesController { return this.invoiceService.getUserInvoices(queryDto, userId); } - /**************************************** */ - @ApiOperation({ summary: "get single invoice by Id " }) @AuthGuards() @Get(":id") @@ -58,8 +50,6 @@ export class InvoicesController { return this.invoiceService.getInvoiceById(paramDto.id, isAdmin, userId); } - /**************************************** */ - @ApiOperation({ summary: "approve request invoice by user" }) @AuthGuards() @Post(":id/approve/request") @@ -67,8 +57,6 @@ export class InvoicesController { return this.invoiceService.approveRequest(paramDto.id, userId, updateDto); } - /**************************************** */ - @ApiOperation({ summary: "approve invoice by user" }) @AuthGuards() @Patch(":id/approve") @@ -76,8 +64,6 @@ export class InvoicesController { return this.invoiceService.approveInvoiceByUser(paramDto.id, userId, verifyOtpDto); } - /**************************************** */ - @ApiOperation({ summary: "apply discount on invoice by user" }) @AuthGuards() @Post(":id/apply-discount") @@ -85,8 +71,6 @@ export class InvoicesController { return this.invoiceService.applyDiscount(paramDto.id, applyDiscountDto, userId); } - /**************************************** */ - @ApiOperation({ summary: "cancel discount by user" }) @AuthGuards() @Post(":id/cancel-discount") @@ -94,8 +78,6 @@ export class InvoicesController { return this.invoiceService.cancelDiscount(paramDto.id, userId); } - /**************************************** */ - @ApiOperation({ summary: "pay invoice by user" }) @AuthGuards() @Post(":id/pay") diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 7c47c04..18c5c2a 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -265,6 +265,11 @@ export class InvoicesService { const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, queryRunner); + if (invoice.status === InvoiceStatus.REJECTED) throw new BadRequestException(InvoiceMessage.INVOICE_IS_REJECTED); + if (invoice.status === InvoiceStatus.EXPIRED) throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); + if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); + if (invoice.status !== InvoiceStatus.APPROVED) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID); + const userWallet = await this.walletsService.getWalletByUserId(user.id, queryRunner); if (new Decimal(userWallet.balance).lessThan(invoice.totalPrice)) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); @@ -297,7 +302,6 @@ export class InvoicesService { async getPendingInvoiceByIdWithQueryRunner(invoiceId: string, userId: string, queryRunner: QueryRunner) { const invoice = await queryRunner.manager.findOneBy(Invoice, { user: { id: userId }, status: InvoiceStatus.PENDING, id: invoiceId }); - console.log(invoice, invoiceId, userId); if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); return invoice; } diff --git a/src/modules/subscriptions/DTO/service-subs-query.dto.ts b/src/modules/subscriptions/DTO/service-subs-query.dto.ts index 9117251..4ddb142 100755 --- a/src/modules/subscriptions/DTO/service-subs-query.dto.ts +++ b/src/modules/subscriptions/DTO/service-subs-query.dto.ts @@ -1,6 +1,6 @@ import { ApiPropertyOptional } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsOptional, IsString } from "class-validator"; +import { IsIn, IsOptional, IsString } from "class-validator"; import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { SubscriptionMessage } from "../../../common/enums/message.enum"; @@ -8,7 +8,7 @@ import { SubscriptionMessage } from "../../../common/enums/message.enum"; export class ServiceSubsQueryDto extends PaginationDto { @IsOptional() @Type(() => Number) - // @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN }) + @IsIn([1, 0], { message: SubscriptionMessage.IS_ACTIVE_SHOULD_BE_1_0 }) @ApiPropertyOptional({ description: "Category status", example: 1 }) isActive?: number; diff --git a/src/modules/subscriptions/DTO/subscribe-service.dto.ts b/src/modules/subscriptions/DTO/subscribe-service.dto.ts index 0816a9b..b62e636 100755 --- a/src/modules/subscriptions/DTO/subscribe-service.dto.ts +++ b/src/modules/subscriptions/DTO/subscribe-service.dto.ts @@ -15,14 +15,14 @@ export class SubscribeServiceDto { @ApiProperty({ description: "Name of business", example: "Test 1" }) businessName: string; - @IsNotEmpty() + @IsNotEmpty({ message: SubscriptionMessage.BUSINESS_PHONE_REQUIRED }) @IsPhoneNumber("IR", { message: SubscriptionMessage.BUSINESS_PHONE_INVALID }) @ApiProperty({ description: "Phone number of the business", example: "+98 234 567 8901", required: false }) businessPhone: string; - @IsNotEmpty() - @IsString() - @Length(5, 500, { message: SubscriptionMessage.DESCRIPTION_LENGTH }) + @IsNotEmpty({ message: SubscriptionMessage.BUSINESS_DESCRIPTION_REQUIRED }) + @IsString({ message: SubscriptionMessage.BUSINESS_DESCRIPTION_STRING }) + @Length(5, 500, { message: SubscriptionMessage.BUSINESS_DESCRIPTION_LENGTH }) @ApiProperty({ description: "A short description of the business or service", example: "We offer the best subscription plans." }) description: string; } diff --git a/src/modules/subscriptions/entities/user-quick-access.entity.ts b/src/modules/subscriptions/entities/user-quick-access.entity.ts index 74ee7e7..a46d20a 100644 --- a/src/modules/subscriptions/entities/user-quick-access.entity.ts +++ b/src/modules/subscriptions/entities/user-quick-access.entity.ts @@ -1,16 +1,20 @@ import { Column, Entity, ManyToOne, Unique } from "typeorm"; +import { UserSubscription } from "./user-subscription.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; import { User } from "../../users/entities/user.entity"; @Entity() -@Unique(["user", "service"]) +@Unique(["user", "userSubscription"]) export class UserQuickAccess extends BaseEntity { - @ManyToOne(() => User, (user) => user.quickAccesses, { nullable: false, onDelete: "SET NULL" }) + @ManyToOne(() => User, (user) => user.quickAccesses, { nullable: false, onDelete: "CASCADE" }) user: User; - @ManyToOne(() => DanakService, { nullable: false, onDelete: "SET NULL" }) + @ManyToOne(() => UserSubscription, { nullable: false, onDelete: "CASCADE" }) + userSubscription: UserSubscription; + + @ManyToOne(() => DanakService, { nullable: false, onDelete: "CASCADE" }) service: DanakService; @Column({ type: "boolean", default: true }) diff --git a/src/modules/subscriptions/entities/user-subscription.entity.ts b/src/modules/subscriptions/entities/user-subscription.entity.ts index 0aac77a..bd206ca 100755 --- a/src/modules/subscriptions/entities/user-subscription.entity.ts +++ b/src/modules/subscriptions/entities/user-subscription.entity.ts @@ -33,4 +33,7 @@ export class UserSubscription extends BaseEntity { @Column({ type: "boolean", default: false }) isCanceled: boolean; + + // @OneToMany(() => UserQuickAccess, (quickAccess) => quickAccess.userSubscription) + // quickAccess: UserQuickAccess[]; } diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index 21167b9..2f0745f 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -225,11 +225,13 @@ export class SubscriptionsService { if (!plan) throw new BadRequestException(SubscriptionMessage.NOT_FOUND); - const startDate = new Date(); - const endDate = new Date(); - endDate.setDate(startDate.getDate() + plan.duration); + const startDate = dayjs().toDate(); + const endDate = dayjs().add(plan.duration, "day").toDate(); + // const startDate = new Date(); + // const endDate = new Date(); + // endDate.setDate(startDate.getDate() + plan.duration); // - const userSubscription = queryRunner.manager.create(UserSubscription, { user, plan, startDate, endDate }); + const userSubscription = queryRunner.manager.create(UserSubscription, { ...subscribeDto, user, plan, startDate, endDate }); await queryRunner.manager.save(UserSubscription, userSubscription); @@ -237,18 +239,16 @@ export class SubscriptionsService { let finalPrice = new Decimal(plan.price); - if (plan.discounts && plan.discounts.length > 0) { - plan.discounts.forEach((discount) => { - if (discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) { - if (discount.calculationType === DiscountCalculationType.PERCENTAGE) { - const discountAmount = finalPrice.mul(discount.amount).div(100); - finalPrice = finalPrice.sub(discountAmount); - } else if (discount.calculationType === DiscountCalculationType.FIXED) { - finalPrice = finalPrice.sub(discount.amount); - } - } - }); - } + const activeDiscounts = + plan.discounts?.filter((discount) => discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) || []; + + activeDiscounts.forEach((discount) => { + if (discount.calculationType === DiscountCalculationType.PERCENTAGE) { + finalPrice = finalPrice.sub(finalPrice.mul(discount.amount).div(100)); + } else if (discount.calculationType === DiscountCalculationType.FIXED) { + finalPrice = finalPrice.sub(discount.amount); + } + }); plan.price = finalPrice; @@ -264,8 +264,7 @@ export class SubscriptionsService { await this.walletsService.createSubscriptionTransaction(finalPrice, userWallet.id, queryRunner); //TODO: need queue handling for notification - const invoiceDueDate = new Date(userSubscription.endDate); - invoiceDueDate.setDate(userSubscription.endDate.getDate() - 3); + const invoiceDueDate = dayjs(userSubscription.endDate).subtract(3, "day").toDate(); const invoice = await this.invoicesService.createInvoiceForSubscription(userId, plan, invoiceDueDate, queryRunner); userSubscription.status = SubscriptionStatus.ACTIVE; @@ -419,19 +418,4 @@ export class SubscriptionsService { const growthRate = ((currentYearCount - lastYearCount) / lastYearCount) * 100; return Math.round(growthRate * 100) / 100; } - - /********************************* */ - - async activeUserSubscription(userId: string, serviceId: string) { - const activeSubscription = await this.userSubscriptionsRepository.findOne({ - where: { - user: { id: userId }, - plan: { service: { id: serviceId } }, - status: SubscriptionStatus.ACTIVE, - }, - relations: ["plan", "plan.service"], - }); - console.log(activeSubscription); - return activeSubscription; - } } diff --git a/src/modules/subscriptions/providers/users-quick-access.service.ts b/src/modules/subscriptions/providers/users-quick-access.service.ts index 838ddef..3a654bd 100644 --- a/src/modules/subscriptions/providers/users-quick-access.service.ts +++ b/src/modules/subscriptions/providers/users-quick-access.service.ts @@ -1,8 +1,7 @@ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; import { SubscriptionsService } from "./subscriptions.service"; -import { CommonMessage, ServiceMessage, UserMessage, UserQuickAccessMessage } from "../../../common/enums/message.enum"; -import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; +import { CommonMessage, UserMessage, UserQuickAccessMessage } from "../../../common/enums/message.enum"; import { UsersService } from "../../users/providers/users.service"; import { UserQuickAccessRepository } from "../repositories/users-quick-access.repository"; @@ -11,32 +10,25 @@ export class UserQuickAccessService { constructor( private readonly quickAccessRepository: UserQuickAccessRepository, private readonly usersService: UsersService, - private readonly danakServicesService: DanakServicesService, private readonly subscriptionService: SubscriptionsService, ) {} - async addQuickAccess(userId: string, serviceId: string) { + async addQuickAccess(userId: string, userSubId: string) { const { user } = await this.usersService.findOneById(userId); if (!user) throw new NotFoundException(UserMessage.USER_NOT_FOUND); - const service = await this.danakServicesService.findServiceById(serviceId); - if (!service) throw new NotFoundException(ServiceMessage.SERVICE_NOT_EXIST); - - const activeSubscription = await this.subscriptionService.activeUserSubscription(user.id, service.id); - if (!activeSubscription) { - throw new BadRequestException(UserQuickAccessMessage.USER_SUBS_NOT_FOUND); - } + const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(userSubId, user.id); const userQuickAccesses = await this.quickAccessRepository.count({ where: { user: { id: userId }, isActive: true } }); - if (userQuickAccesses >= 4) { - throw new BadRequestException(UserQuickAccessMessage.QUICK_ACCESS_LIMIT_REACHED); - } + if (userQuickAccesses >= 4) throw new BadRequestException(UserQuickAccessMessage.QUICK_ACCESS_LIMIT_REACHED); + + const existQuickAccess = await this.quickAccessRepository.findOne({ + where: { user: { id: user.id }, userSubscription: { id: userSubId } }, + }); - const existQuickAccess = await this.quickAccessRepository.findOne({ where: { user: { id: user.id }, service: { id: service.id } } }); - console.log(existQuickAccess); if (existQuickAccess) throw new BadRequestException(UserQuickAccessMessage.QUICK_ACCESS_EXIST); - const quickAccess = this.quickAccessRepository.create({ user, service }); + const quickAccess = this.quickAccessRepository.create({ user, userSubscription, service: userSubscription.plan.service }); await this.quickAccessRepository.save(quickAccess); @@ -45,18 +37,22 @@ export class UserQuickAccessService { quickAccess, }; } - - async removeQuickAccess(userId: string, serviceId: string) { - await this.quickAccessRepository.delete({ user: { id: userId }, service: { id: serviceId } }); + //************************************** */ + async removeQuickAccess(userId: string, userSubId: string) { + await this.quickAccessRepository.delete({ user: { id: userId }, userSubscription: { id: userSubId } }); return { message: CommonMessage.DELETED, }; } + //************************************** */ async getUserQuickAccess(userId: string) { const quickAccesses = await this.quickAccessRepository.find({ where: { user: { id: userId }, isActive: true }, - relations: ["service"], + relations: { + userSubscription: true, + service: true, + }, }); return { quickAccesses }; } diff --git a/src/modules/subscriptions/subscriptions.controller.ts b/src/modules/subscriptions/subscriptions.controller.ts index 46d2b05..5354dfe 100755 --- a/src/modules/subscriptions/subscriptions.controller.ts +++ b/src/modules/subscriptions/subscriptions.controller.ts @@ -86,9 +86,9 @@ export class SubscriptionsController { @ApiOperation({ summary: "add quick access a service ==> user route" }) @AuthGuards() - @Post(":serviceId/add-quick-access") - addQuickAccess(@Param() paramDto: ServiceIdParamDto, @UserDec("id") userId: string) { - return this.userQuickAccessService.addQuickAccess(userId, paramDto.serviceId); + @Post(":id/add-quick-access") + addQuickAccess(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { + return this.userQuickAccessService.addQuickAccess(userId, paramDto.id); } @ApiOperation({ summary: "get quick access a service ==> user route" }) @@ -100,8 +100,8 @@ export class SubscriptionsController { @ApiOperation({ summary: "remove quick access a service ==> user route" }) @AuthGuards() - @Delete(":serviceId/remove-quick-access") - removeQuickAccess(@Param() paramDto: ServiceIdParamDto, @UserDec("id") userId: string) { - return this.userQuickAccessService.removeQuickAccess(userId, paramDto.serviceId); + @Delete(":id/remove-quick-access") + removeQuickAccess(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { + return this.userQuickAccessService.removeQuickAccess(userId, paramDto.id); } } diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index 36711f5..5cd3c1b 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -154,7 +154,7 @@ export class TicketsService { const findOptions = { where: { user: { id: userId }, - ...(queryDto.status && { status: queryDto.status }), + ...(queryDto.status ? { status: queryDto.status } : { status: Not(TicketStatus.CLOSED) }), }, relations: { category: true, diff --git a/src/modules/users/entities/user-group.entity.ts b/src/modules/users/entities/user-group.entity.ts index c0046f0..764d9a2 100755 --- a/src/modules/users/entities/user-group.entity.ts +++ b/src/modules/users/entities/user-group.entity.ts @@ -2,7 +2,6 @@ import { Column, Entity, JoinTable, ManyToMany, OneToMany } from "typeorm"; import { User } from "./user.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; -import { Announcement } from "../../announcements/entities/announcement.entity"; import { TicketCategory } from "../../tickets/entities/ticket-category.entity"; @Entity() @@ -17,9 +16,9 @@ export class UserGroup extends BaseEntity { @JoinTable() users: User[]; - @ManyToMany(() => Announcement, (announcement) => announcement.targetGroups) - @JoinTable() - announcements: Announcement[]; + // @ManyToMany(() => Announcement, (announcement) => announcement.targetGroups) + // @JoinTable() + // announcements: Announcement[]; @OneToMany(() => TicketCategory, (ticketCategory) => ticketCategory.group) ticketCategories: TicketCategory[]; diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 29c8558..33517bb 100755 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -8,7 +8,6 @@ import { UserGroup } from "./user-group.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { Address } from "../../address/entities/address.entity"; import { City } from "../../address/entities/city.entity"; -import { Announcement } from "../../announcements/entities/announcement.entity"; import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity"; import { Criticism } from "../../criticisms/entities/criticism.entity"; import { DanakServiceReview } from "../../danak-services/entities/danak-service-review.entity"; @@ -90,9 +89,6 @@ export class User extends BaseEntity { @OneToMany(() => Criticism, (criticism) => criticism.user) criticisms: Criticism[]; - @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.user) - userAnnouncements: UserAnnouncement[]; - @OneToMany(() => UserSetting, (settings) => settings.user, { nullable: false }) settings: UserSetting; @@ -130,8 +126,8 @@ export class User extends BaseEntity { @ManyToMany(() => UsageDiscount, (usageDiscount) => usageDiscount.users) usageDiscounts: UsageDiscount[]; - @ManyToMany(() => Announcement, (announcement) => announcement.users) - announcements: Announcement[]; + @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.user) + userAnnouncements: UserAnnouncement[]; @OneToMany(() => UserQuickAccess, (quickAccess) => quickAccess.user) quickAccesses: UserQuickAccess[]; diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index e87012a..f33f421 100755 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -374,9 +374,8 @@ export class UsersService { ///****************************************************** */ - async getUsersByIds(ids: string[]) { + async findUsersByIds(ids: string[]) { const users = await this.userRepository.find({ where: { id: In(ids) } }); - if (!users) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return users; } diff --git a/src/modules/wallets/providers/wallets.service.ts b/src/modules/wallets/providers/wallets.service.ts index 969333d..bdc41ae 100755 --- a/src/modules/wallets/providers/wallets.service.ts +++ b/src/modules/wallets/providers/wallets.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable } from "@nestjs/common"; // eslint-disable-next-line import/no-named-as-default +import dayjs from "dayjs"; import Decimal from "decimal.js"; import { QueryRunner } from "typeorm"; @@ -115,6 +116,7 @@ export class WalletsService { .take(limit) .getManyAndCount(); + // Current month totals const totals = await this.walletsTransactionRepo .createQueryBuilder("transaction") .leftJoin("transaction.wallet", "wallet") @@ -125,14 +127,64 @@ export class WalletsService { ]) .getRawOne(); + const now = dayjs(); + const firstDayOfMonth = now.startOf("month").toDate(); + const firstDayOfPrevMonth = now.subtract(1, "month").startOf("month").toDate(); + const lastDayOfPrevMonth = now.subtract(1, "month").endOf("month").toDate(); + + const prevMonthTotals = await this.walletsTransactionRepo + .createQueryBuilder("transaction") + .leftJoin("transaction.wallet", "wallet") + .where("wallet.userId = :userId", { userId }) + .andWhere("transaction.createdAt >= :startDate", { startDate: firstDayOfPrevMonth }) + .andWhere("transaction.createdAt <= :endDate", { endDate: lastDayOfPrevMonth }) + .select([ + `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalDeposits"`, + `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`, + ]) + .getRawOne(); + const wallet = await this.walletsRepository.findOneBy({ user: { id: userId } }); + if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND); + + const prevMonthWallet = await this.walletsTransactionRepo + .createQueryBuilder("transaction") + .leftJoin("transaction.wallet", "wallet") + .where("wallet.userId = :userId", { userId }) + .andWhere("transaction.createdAt < :date", { date: firstDayOfMonth }) + .select([ + `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) - + COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "prevBalance"`, + ]) + .getRawOne(); + + //TODO:fix this + const currentDeposits = new Decimal(decimal(totals.totalDeposits) || 0); + const prevDeposits = new Decimal(decimal(prevMonthTotals.totalDeposits) || 0); + const depositsPercentChange = prevDeposits.isZero() ? null : currentDeposits.minus(prevDeposits).div(prevDeposits).mul(100); + + const currentWithdrawals = new Decimal(decimal(totals.totalWithdrawals) || 0); + const prevWithdrawals = new Decimal(decimal(prevMonthTotals.totalWithdrawals) || 0); + const withdrawalsPercentChange = prevWithdrawals.isZero() + ? null + : currentWithdrawals.minus(prevWithdrawals).div(prevWithdrawals).mul(100); + + const currentBalance = wallet.balance; + const prevBalance = decimal(prevMonthWallet.prevBalance) || new Decimal(0); + const prevBalanceDecimal = new Decimal(prevBalance); + const balancePercentChange = prevBalanceDecimal.isZero() + ? null + : currentBalance.minus(prevBalanceDecimal).div(prevBalanceDecimal).mul(100).toNumber(); return { - balance: wallet?.balance, + balance: currentBalance, + balancePercentChange, transactions, count, - totalDeposits: decimal(totals.totalDeposits) || 0, - totalWithdrawals: decimal(totals.totalWithdrawals) || 0, + totalDeposits: currentDeposits.toNumber(), + totalDepositsPercentChange: depositsPercentChange, + totalWithdrawals: currentWithdrawals.toNumber(), + totalWithdrawalsPercentChange: withdrawalsPercentChange, paginate: true, }; }