update: notification
This commit is contained in:
@@ -601,4 +601,7 @@ export const enum BusinessMessage {
|
|||||||
DOMAIN_VERIFICATION_SUCCESS = "دامنه با موفقیت تایید شد",
|
DOMAIN_VERIFICATION_SUCCESS = "دامنه با موفقیت تایید شد",
|
||||||
DOMAIN_VERIFICATION_METHOD_INVALID = "روش تایید دامنه نامعتبر است",
|
DOMAIN_VERIFICATION_METHOD_INVALID = "روش تایید دامنه نامعتبر است",
|
||||||
DOMAIN_ALREADY_VERIFIED = "این دامنه قبلا تایید شده است",
|
DOMAIN_ALREADY_VERIFIED = "این دامنه قبلا تایید شده است",
|
||||||
|
DOMAIN_REQUIRED = "دامنه مورد نیاز است",
|
||||||
|
DOMAIN_MUST_BE_STRING = "دامنه باید یک رشته باشد",
|
||||||
|
STAFF_NOT_FOUND = "کاربر ادمین یافت نشد",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { getSwaggerDocument } from "./configs/swagger.config";
|
|||||||
import { HttpExceptionFilter } from "./core/filters/http-exception.filters";
|
import { HttpExceptionFilter } from "./core/filters/http-exception.filters";
|
||||||
import { PaginationInterceptor } from "./core/interceptors/pagination.interceptor";
|
import { PaginationInterceptor } from "./core/interceptors/pagination.interceptor";
|
||||||
import { ResponseInterceptor } from "./core/interceptors/response.interceptor";
|
import { ResponseInterceptor } from "./core/interceptors/response.interceptor";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const logger = new Logger("APP");
|
const logger = new Logger("APP");
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { IsNotEmpty, IsOptional, IsString, Matches } from "class-validator";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString, Matches } from "class-validator";
|
||||||
|
|
||||||
|
import { BusinessMessage } from "../../../common/enums/message.enum";
|
||||||
export class UpdateBusinessDomainDto {
|
export class UpdateBusinessDomainDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty({ message: BusinessMessage.DOMAIN_REQUIRED })
|
||||||
@IsString()
|
@IsString({ message: BusinessMessage.DOMAIN_MUST_BE_STRING })
|
||||||
@Matches(/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/, { message: "Domain must be a valid domain name (e.g., example.com)" })
|
@Matches(/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/, { message: "Domain must be a valid domain name (e.g., example.com)" })
|
||||||
|
@ApiProperty({ description: "دامنه بیسنس", example: "example.com" })
|
||||||
domain: string;
|
domain: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VerifyDomainDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
businessId?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
|
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
|
||||||
import { ApiOperation } from "@nestjs/swagger";
|
import { ApiHeader, ApiOperation } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { UpdateBusinessDomainDto } from "./DTO/business-domain.dto";
|
import { UpdateBusinessDomainDto } from "./DTO/business-domain.dto";
|
||||||
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
|
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
|
||||||
@@ -10,6 +10,7 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
|||||||
import { BusinessDec } from "../../common/decorators/business.decorator";
|
import { BusinessDec } from "../../common/decorators/business.decorator";
|
||||||
|
|
||||||
@Controller("business")
|
@Controller("business")
|
||||||
|
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
||||||
export class BusinessesController {
|
export class BusinessesController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly businessesService: BusinessesService,
|
private readonly businessesService: BusinessesService,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Entity, EntityRepositoryType, ManyToOne, Property } from "@mikro-orm/core";
|
||||||
|
|
||||||
|
import { Business } from "./business.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { BusinessStaffRepository } from "../repositories/business-staff.repository";
|
||||||
|
|
||||||
|
@Entity({ repository: () => BusinessStaffRepository })
|
||||||
|
export class BusinessStaff extends BaseEntity {
|
||||||
|
@Property({ type: "uuid", nullable: false })
|
||||||
|
danakUserId: string;
|
||||||
|
|
||||||
|
@Property({ type: "varchar", length: 255, nullable: false })
|
||||||
|
fullName: string;
|
||||||
|
|
||||||
|
@Property({ type: "varchar", length: 255, nullable: false })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Property({ type: "varchar", length: 255, nullable: false })
|
||||||
|
phone: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Business, { deleteRule: "cascade", nullable: false })
|
||||||
|
business: Business;
|
||||||
|
|
||||||
|
[EntityRepositoryType]?: BusinessStaffRepository;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Cascade, Collection, Entity, EntityRepositoryType, OneToMany, Opt, Property, Unique } from "@mikro-orm/core";
|
import { Cascade, Collection, Entity, EntityRepositoryType, OneToMany, Opt, Property, Unique } from "@mikro-orm/core";
|
||||||
|
|
||||||
|
import { BusinessStaff } from "./business-staff.entity";
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { Announcement } from "../../announcements/entities/announcement.entity";
|
import { Announcement } from "../../announcements/entities/announcement.entity";
|
||||||
import { Company } from "../../companies/entities/company.entity";
|
import { Company } from "../../companies/entities/company.entity";
|
||||||
@@ -67,5 +68,8 @@ export class Business extends BaseEntity {
|
|||||||
@OneToMany(() => User, (user) => user.business)
|
@OneToMany(() => User, (user) => user.business)
|
||||||
users = new Collection<User>(this);
|
users = new Collection<User>(this);
|
||||||
|
|
||||||
|
@OneToMany(() => BusinessStaff, (businessStaff) => businessStaff.business)
|
||||||
|
businessStaffs = new Collection<BusinessStaff>(this);
|
||||||
|
|
||||||
[EntityRepositoryType]?: BusinessRepository;
|
[EntityRepositoryType]?: BusinessRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Job } from "bullmq";
|
|||||||
|
|
||||||
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
||||||
import { SUBSCRIPTIONS } from "../constant";
|
import { SUBSCRIPTIONS } from "../constant";
|
||||||
|
import { BusinessStaff } from "../entities/business-staff.entity";
|
||||||
import { Business } from "../entities/business.entity";
|
import { Business } from "../entities/business.entity";
|
||||||
import { IBusinessProvisioningJob } from "../interfaces/IBusiness";
|
import { IBusinessProvisioningJob } from "../interfaces/IBusiness";
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ export class BusinessProvisioningProcessor extends WorkerProcessor {
|
|||||||
|
|
||||||
private async handleBusinessCreated(job: Job<IBusinessProvisioningJob>) {
|
private async handleBusinessCreated(job: Job<IBusinessProvisioningJob>) {
|
||||||
const em = this.em.fork();
|
const em = this.em.fork();
|
||||||
const { subscriptionId, serviceId, serviceName, businessName, slug } = job.data;
|
const { subscriptionId, serviceId, serviceName, businessName, slug, userId } = job.data;
|
||||||
|
|
||||||
// Only act if the event is for this service
|
// Only act if the event is for this service
|
||||||
if (serviceId !== SUBSCRIPTIONS.SERVICE_ID) {
|
if (serviceId !== SUBSCRIPTIONS.SERVICE_ID) {
|
||||||
@@ -50,5 +51,20 @@ export class BusinessProvisioningProcessor extends WorkerProcessor {
|
|||||||
} else {
|
} else {
|
||||||
this.logger.debug(`Business already exists for subscription ${subscriptionId}`);
|
this.logger.debug(`Business already exists for subscription ${subscriptionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = await em.findOne(BusinessStaff, { danakUserId: userId });
|
||||||
|
if (!user) {
|
||||||
|
const user = em.create(BusinessStaff, {
|
||||||
|
danakUserId: userId,
|
||||||
|
fullName: `${businessName} Admin`,
|
||||||
|
email: `${slug}@${businessName}.com`,
|
||||||
|
phone: "09123456789",
|
||||||
|
business: business,
|
||||||
|
});
|
||||||
|
await em.persistAndFlush(user);
|
||||||
|
this.logger.log(`Created business staff for subscription ${subscriptionId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.debug(`Business staff already exists for subscription ${subscriptionId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository } from "@mikro-orm/core";
|
||||||
|
|
||||||
|
import { BusinessStaff } from "../entities/business-staff.entity";
|
||||||
|
|
||||||
|
export class BusinessStaffRepository extends EntityRepository<BusinessStaff> {}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import { EntityManager } from "@mikro-orm/postgresql";
|
||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
import { BusinessMessage } from "../../../common/enums/message.enum";
|
import { BusinessMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { BusinessStaff } from "../entities/business-staff.entity";
|
||||||
import { BusinessRepository } from "../repositories/business.repository";
|
import { BusinessRepository } from "../repositories/business.repository";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BusinessesService {
|
export class BusinessesService {
|
||||||
constructor(private readonly businessRepository: BusinessRepository) {}
|
constructor(
|
||||||
|
private readonly businessRepository: BusinessRepository,
|
||||||
|
private readonly em: EntityManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) {
|
async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) {
|
||||||
const business = await this.businessRepository.findOne({ danakSubscriptionId, deletedAt: null });
|
const business = await this.businessRepository.findOne({ danakSubscriptionId, deletedAt: null });
|
||||||
@@ -27,4 +32,32 @@ export class BusinessesService {
|
|||||||
|
|
||||||
return business;
|
return business;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************/
|
||||||
|
|
||||||
|
async findAdminWithLeastTickets() {
|
||||||
|
// First query to get the ID of the business staff with the least tickets
|
||||||
|
const result = await this.em.getConnection().execute(
|
||||||
|
`
|
||||||
|
SELECT bs.id
|
||||||
|
FROM business_staff bs
|
||||||
|
WHERE bs.deleted_at IS NULL
|
||||||
|
ORDER BY (
|
||||||
|
SELECT COUNT(*) FROM ticket t WHERE t.assigned_to_id = bs.id
|
||||||
|
) ASC
|
||||||
|
LIMIT 1
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.length === 0) return null;
|
||||||
|
|
||||||
|
return this.em.findOne(BusinessStaff, { id: result[0].id });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************/
|
||||||
|
async findOneByIdWithEntityManager(staffId: string, em: EntityManager) {
|
||||||
|
const staff = await em.findOne(BusinessStaff, { id: staffId });
|
||||||
|
if (!staff) throw new BadRequestException(BusinessMessage.STAFF_NOT_FOUND);
|
||||||
|
return staff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ export class CompaniesController {
|
|||||||
return this.companiesService.toggleStatus(paramDto.id, businessId);
|
return this.companiesService.toggleStatus(paramDto.id, businessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("user")
|
||||||
|
@AuthGuards()
|
||||||
|
@ApiOperation({ summary: "get user companies" })
|
||||||
|
getUserCompany(@UserDec("id") userId: string, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.companiesService.getUserCompany(userId, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
@Post("requests")
|
@Post("requests")
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@ApiOperation({ summary: "submit company registration request (user)" })
|
@ApiOperation({ summary: "submit company registration request (user)" })
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export class CompaniesService {
|
|||||||
{ id, deletedAt: null, isActive: true, status: CompanyStatus.APPROVED, business: { id: businessId } },
|
{ id, deletedAt: null, isActive: true, status: CompanyStatus.APPROVED, business: { id: businessId } },
|
||||||
{
|
{
|
||||||
populate: ["industry", "user", "products", "services"],
|
populate: ["industry", "user", "products", "services"],
|
||||||
fields: ["*", "industry.id", "industry.title", "user.id"],
|
fields: ["*", "industry.id", "industry.title", "user.id", "user.firstName", "user.lastName", "user.phone", "user.email"],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||||
@@ -222,6 +222,20 @@ export class CompaniesService {
|
|||||||
}
|
}
|
||||||
//-----------------------------------
|
//-----------------------------------
|
||||||
|
|
||||||
|
async getUserCompany(userId: string, businessId: string) {
|
||||||
|
const company = await this.companyRepository.findOne(
|
||||||
|
{ user: { id: userId }, business: { id: businessId }, deletedAt: null },
|
||||||
|
{
|
||||||
|
populate: ["industry", "user", "products", "services"],
|
||||||
|
fields: ["*", "industry.id", "industry.title", "user.id", "user.firstName", "user.lastName", "user.phone", "user.email"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||||
|
return { company };
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------
|
||||||
|
|
||||||
async findOneByIdWithEntityManager(id: string, em: EntityManager) {
|
async findOneByIdWithEntityManager(id: string, em: EntityManager) {
|
||||||
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user"] });
|
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user"] });
|
||||||
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||||
@@ -288,10 +302,13 @@ export class CompaniesService {
|
|||||||
if (role === RoleEnum.USER) {
|
if (role === RoleEnum.USER) {
|
||||||
request = await this.companyRequestRepository.findOne(
|
request = await this.companyRequestRepository.findOne(
|
||||||
{ id, business: { id: businessId }, user: { id: userId }, deletedAt: null },
|
{ id, business: { id: businessId }, user: { id: userId }, deletedAt: null },
|
||||||
{ populate: ["company"] },
|
{ populate: ["company", "company.services", "company.products", "user"] },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
request = await this.companyRequestRepository.findOne({ id, business: { id: businessId }, deletedAt: null }, { populate: ["company"] });
|
request = await this.companyRequestRepository.findOne(
|
||||||
|
{ id, business: { id: businessId }, deletedAt: null },
|
||||||
|
{ populate: ["company", "company.services", "company.products", "business", "user"] },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request) throw new BadRequestException(CompanyMessage.COMPANY_REQUEST_NOT_FOUND);
|
if (!request) throw new BadRequestException(CompanyMessage.COMPANY_REQUEST_NOT_FOUND);
|
||||||
@@ -303,7 +320,7 @@ export class CompaniesService {
|
|||||||
async getCompanyRequestsUser(userId: string, businessId: string) {
|
async getCompanyRequestsUser(userId: string, businessId: string) {
|
||||||
const requests = await this.companyRequestRepository.findOne(
|
const requests = await this.companyRequestRepository.findOne(
|
||||||
{ user: { id: userId }, business: { id: businessId }, deletedAt: null },
|
{ user: { id: userId }, business: { id: businessId }, deletedAt: null },
|
||||||
{ populate: ["company"], orderBy: { createdAt: "DESC" } },
|
{ populate: ["company", "company.services", "company.products", "user"], orderBy: { createdAt: "DESC" } },
|
||||||
);
|
);
|
||||||
return { requests };
|
return { requests };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ApiOperation } from "@nestjs/swagger";
|
|||||||
import { SearchNotificationQueryDto } from "./DTO/search-notification-query.dto";
|
import { SearchNotificationQueryDto } from "./DTO/search-notification-query.dto";
|
||||||
import { NotificationsService } from "./providers/notifications.service";
|
import { NotificationsService } from "./providers/notifications.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { Pagination } from "../../common/decorators/pagination.decorator";
|
|
||||||
import { UserDec } from "../../common/decorators/user.decorator";
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
import { ParamDto } from "../../common/DTO/param.dto";
|
import { ParamDto } from "../../common/DTO/param.dto";
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ export class NotificationController {
|
|||||||
//********************* */
|
//********************* */
|
||||||
|
|
||||||
@ApiOperation({ summary: "all notifications by user" })
|
@ApiOperation({ summary: "all notifications by user" })
|
||||||
@Pagination()
|
|
||||||
@Get()
|
@Get()
|
||||||
getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) {
|
getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) {
|
||||||
return this.notificationService.getAllNotifications(queryDto, userId);
|
return this.notificationService.getAllNotifications(queryDto, userId);
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ export class NotificationsService {
|
|||||||
//************************ */
|
//************************ */
|
||||||
|
|
||||||
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
||||||
return await this.notificationRepository.getAllNotifications(queryDto, recipientId);
|
const [notifications, count] = await this.notificationRepository.getAllNotifications(queryDto, recipientId);
|
||||||
|
return { notifications, count, paginate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************ */
|
//************************ */
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class NotificationRepository extends EntityRepository<Notification> {
|
|||||||
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
||||||
const { skip, limit } = PaginationUtils(queryDto);
|
const { skip, limit } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
const qb = this.qb("notification").where({ recipientId });
|
const qb = this.qb("notification").where({ recipient: { id: recipientId } });
|
||||||
|
|
||||||
if (queryDto.isRead !== undefined) {
|
if (queryDto.isRead !== undefined) {
|
||||||
qb.andWhere({ isRead: queryDto.isRead === 1 });
|
qb.andWhere({ isRead: queryDto.isRead === 1 });
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Cascade, Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany
|
|||||||
import { TicketMessageAttachment } from "./ticket-message-attachment";
|
import { TicketMessageAttachment } from "./ticket-message-attachment";
|
||||||
import { Ticket } from "./ticket.entity";
|
import { Ticket } from "./ticket.entity";
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { BusinessStaff } from "../../businesses/entities/business-staff.entity";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
import { TicketMessagesRepository } from "../repositories/tickets-message.repository";
|
import { TicketMessagesRepository } from "../repositories/tickets-message.repository";
|
||||||
|
|
||||||
@@ -18,7 +19,10 @@ export class TicketMessage extends BaseEntity {
|
|||||||
ticket!: Ticket;
|
ticket!: Ticket;
|
||||||
|
|
||||||
@ManyToOne(() => User, { deleteRule: "cascade", nullable: true })
|
@ManyToOne(() => User, { deleteRule: "cascade", nullable: true })
|
||||||
author!: User | null;
|
author?: User | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => BusinessStaff, { deleteRule: "cascade", nullable: true })
|
||||||
|
externalAuthor?: BusinessStaff | null;
|
||||||
|
|
||||||
[EntityRepositoryType]?: TicketMessagesRepository;
|
[EntityRepositoryType]?: TicketMessagesRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Cascade, Collection, Entity, EntityRepositoryType, Enum, ManyToOne, One
|
|||||||
import { TicketCategory } from "./ticket-category.entity";
|
import { TicketCategory } from "./ticket-category.entity";
|
||||||
import { TicketMessage } from "./ticket-message.entity";
|
import { TicketMessage } from "./ticket-message.entity";
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { BusinessStaff } from "../../businesses/entities/business-staff.entity";
|
||||||
import { Business } from "../../businesses/entities/business.entity";
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
import { Company } from "../../companies/entities/company.entity";
|
import { Company } from "../../companies/entities/company.entity";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
@@ -27,8 +28,8 @@ export class Ticket extends BaseEntity {
|
|||||||
@ManyToOne(() => Company, { deleteRule: "set null", nullable: true })
|
@ManyToOne(() => Company, { deleteRule: "set null", nullable: true })
|
||||||
company?: Company;
|
company?: Company;
|
||||||
|
|
||||||
@ManyToOne(() => User, { deleteRule: "set null", nullable: true })
|
@ManyToOne(() => BusinessStaff, { deleteRule: "set null", nullable: true })
|
||||||
assignedTo?: User;
|
assignedTo?: BusinessStaff;
|
||||||
|
|
||||||
@ManyToOne(() => User, { deleteRule: "cascade", nullable: false })
|
@ManyToOne(() => User, { deleteRule: "cascade", nullable: false })
|
||||||
user!: User;
|
user!: User;
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { Collection, EntityManager } from "@mikro-orm/postgresql";
|
|||||||
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||||
|
|
||||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||||
import { CommonMessage, CompanyMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum";
|
import { BusinessMessage, CommonMessage, CompanyMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { BusinessStaff } from "../../businesses/entities/business-staff.entity";
|
||||||
import { Business } from "../../businesses/entities/business.entity";
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
|
import { BusinessesService } from "../../businesses/services/businesses.service";
|
||||||
import { Company } from "../../companies/entities/company.entity";
|
import { Company } from "../../companies/entities/company.entity";
|
||||||
import { NotificationQueue } from "../../notifications/queue/notification.queue";
|
import { NotificationQueue } from "../../notifications/queue/notification.queue";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
@@ -34,6 +36,7 @@ export class TicketsService {
|
|||||||
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
||||||
private readonly ticketMessagesRepository: TicketMessagesRepository,
|
private readonly ticketMessagesRepository: TicketMessagesRepository,
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
|
private readonly businessesService: BusinessesService,
|
||||||
private readonly notificationQueue: NotificationQueue,
|
private readonly notificationQueue: NotificationQueue,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) {}
|
||||||
@@ -209,14 +212,20 @@ export class TicketsService {
|
|||||||
// if (role !== RoleEnum.ADMIN && ticket.assignedTo?.id !== userId) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
|
// if (role !== RoleEnum.ADMIN && ticket.assignedTo?.id !== userId) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
|
||||||
|
|
||||||
let user = null;
|
let user = null;
|
||||||
|
let externalAuthor = null;
|
||||||
if (role === RoleEnum.USER) {
|
if (role === RoleEnum.USER) {
|
||||||
user = await em.findOne(User, { id: userId });
|
user = await em.findOne(User, { id: userId });
|
||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
} else {
|
||||||
|
externalAuthor = await em.findOne(BusinessStaff, { id: userId });
|
||||||
|
if (!externalAuthor) throw new BadRequestException(BusinessMessage.STAFF_NOT_FOUND);
|
||||||
|
await this.sendNotificationForAdminResponse(ticket, externalAuthor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ticketMessage = em.create(TicketMessage, {
|
const ticketMessage = em.create(TicketMessage, {
|
||||||
content: createDto.content,
|
content: createDto.content,
|
||||||
author: user,
|
author: user,
|
||||||
|
externalAuthor,
|
||||||
ticket,
|
ticket,
|
||||||
});
|
});
|
||||||
await em.persistAndFlush(ticketMessage);
|
await em.persistAndFlush(ticketMessage);
|
||||||
@@ -228,13 +237,6 @@ export class TicketsService {
|
|||||||
this.updateTicketStatus(ticket, role === RoleEnum.ADMIN);
|
this.updateTicketStatus(ticket, role === RoleEnum.ADMIN);
|
||||||
await em.persistAndFlush(ticket);
|
await em.persistAndFlush(ticket);
|
||||||
|
|
||||||
if (role !== RoleEnum.USER) {
|
|
||||||
//TODO Fix this
|
|
||||||
console.log(user, "user");
|
|
||||||
// await this.sendNotificationForAdminResponse(ticket, user);
|
|
||||||
console.log(this.sendNotificationForAdminResponse, "sendNotificationForAdminResponse");
|
|
||||||
}
|
|
||||||
|
|
||||||
await em.commit();
|
await em.commit();
|
||||||
return {
|
return {
|
||||||
message: TicketMessageEnum.MESSAGE_CREATED,
|
message: TicketMessageEnum.MESSAGE_CREATED,
|
||||||
@@ -310,7 +312,7 @@ export class TicketsService {
|
|||||||
const ticket = await this.ticketsRepository.findTicketById(ticketId, businessId);
|
const ticket = await this.ticketsRepository.findTicketById(ticketId, businessId);
|
||||||
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||||
|
|
||||||
const admin = await this.usersService.findOneByIdWithEntityManager(referDto.adminId, em);
|
const admin = await this.businessesService.findOneByIdWithEntityManager(referDto.adminId, em);
|
||||||
|
|
||||||
ticket.assignedTo = admin;
|
ticket.assignedTo = admin;
|
||||||
await em.flush();
|
await em.flush();
|
||||||
@@ -355,7 +357,7 @@ export class TicketsService {
|
|||||||
|
|
||||||
//***************************** */
|
//***************************** */
|
||||||
private async assignTicketAndNotify(ticket: Ticket, user: User, em: EntityManager) {
|
private async assignTicketAndNotify(ticket: Ticket, user: User, em: EntityManager) {
|
||||||
const assignedUser = await this.usersService.findAdminWithLeastTickets();
|
const assignedUser = await this.businessesService.findAdminWithLeastTickets();
|
||||||
//
|
//
|
||||||
|
|
||||||
if (assignedUser) {
|
if (assignedUser) {
|
||||||
@@ -412,7 +414,7 @@ export class TicketsService {
|
|||||||
}
|
}
|
||||||
//***************************** */
|
//***************************** */
|
||||||
|
|
||||||
private async sendNotificationForAdminResponse(ticket: Ticket, admin: User) {
|
private async sendNotificationForAdminResponse(ticket: Ticket, admin: BusinessStaff) {
|
||||||
await this.notificationQueue.addAnswerTicketNotification(ticket.user.id, {
|
await this.notificationQueue.addAnswerTicketNotification(ticket.user.id, {
|
||||||
ticketId: ticket.numericId.toString(),
|
ticketId: ticket.numericId.toString(),
|
||||||
subject: ticket.subject,
|
subject: ticket.subject,
|
||||||
@@ -421,6 +423,4 @@ export class TicketsService {
|
|||||||
userEmail: admin.email,
|
userEmail: admin.email,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//***************************** */
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ export class TicketsRepository extends EntityRepository<Ticket> {
|
|||||||
"user.firstName",
|
"user.firstName",
|
||||||
"user.lastName",
|
"user.lastName",
|
||||||
"assignedTo.id",
|
"assignedTo.id",
|
||||||
"assignedTo.firstName",
|
"assignedTo.fullName",
|
||||||
"assignedTo.lastName",
|
|
||||||
"company.id",
|
"company.id",
|
||||||
"company.name",
|
"company.name",
|
||||||
"category.id",
|
"category.id",
|
||||||
@@ -97,8 +96,7 @@ export class TicketsRepository extends EntityRepository<Ticket> {
|
|||||||
"user.firstName",
|
"user.firstName",
|
||||||
"user.lastName",
|
"user.lastName",
|
||||||
"assignedTo.id",
|
"assignedTo.id",
|
||||||
"assignedTo.firstName",
|
"assignedTo.fullName",
|
||||||
"assignedTo.lastName",
|
|
||||||
"company.id",
|
"company.id",
|
||||||
"company.name",
|
"company.name",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -50,39 +50,7 @@ export class UsersService {
|
|||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
/*******************************/
|
|
||||||
|
|
||||||
async findAdminWithLeastTickets() {
|
|
||||||
// // Find all admins with their assigned tickets
|
|
||||||
// const admins = await this.userRepository.find(
|
|
||||||
// { role: { name: RoleEnum.ADMIN } },
|
|
||||||
// { populate: ['role', 'assignedTickets'] }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (!admins.length) return null;
|
|
||||||
|
|
||||||
// // Find the admin with the fewest assigned tickets
|
|
||||||
// return admins.reduce((minAdmin, admin) =>
|
|
||||||
// (minAdmin.assignedTickets.length <= admin.assignedTickets.length ? minAdmin : admin)
|
|
||||||
// );
|
|
||||||
const result = await this.em.getConnection().execute(
|
|
||||||
`
|
|
||||||
SELECT u.*
|
|
||||||
FROM "user" u
|
|
||||||
INNER JOIN "role" r ON u.role_id = r.id
|
|
||||||
WHERE r.name = ?
|
|
||||||
ORDER BY (
|
|
||||||
SELECT COUNT(*) FROM "ticket" t WHERE t.assigned_to_id = u.id
|
|
||||||
) ASC
|
|
||||||
LIMIT 1
|
|
||||||
`,
|
|
||||||
[RoleEnum.ADMIN],
|
|
||||||
);
|
|
||||||
|
|
||||||
// If you want a User entity, load it by id:
|
|
||||||
if (result.length === 0) return null;
|
|
||||||
return this.userRepository.findOne({ id: result[0].id }, { populate: ["role"] });
|
|
||||||
}
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
|
|
||||||
async createUser(registerDto: CompleteRegistrationDto | CreateCompanyDto, hashedPassword: string, business: Business, em: EntityManager) {
|
async createUser(registerDto: CompleteRegistrationDto | CreateCompanyDto, hashedPassword: string, business: Business, em: EntityManager) {
|
||||||
|
|||||||
Reference in New Issue
Block a user