update: add new field for subscription of user when purchased
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -33,4 +33,7 @@ export class UserSubscription extends BaseEntity {
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
isCanceled: boolean;
|
||||
|
||||
// @OneToMany(() => UserQuickAccess, (quickAccess) => quickAccess.userSubscription)
|
||||
// quickAccess: UserQuickAccess[];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user