chore: add company request

This commit is contained in:
Mahyargdz
2025-05-19 14:22:41 +03:30
parent 7d0abbeac0
commit f755a3343a
27 changed files with 480 additions and 105 deletions
+6 -1
View File
@@ -548,6 +548,7 @@ export const enum CompanyMessage {
ADDRESS_MUST_BE_STRING = "آدرس شرکت باید یک رشته باشد", ADDRESS_MUST_BE_STRING = "آدرس شرکت باید یک رشته باشد",
ADDRESS_REQUIRED = "آدرس شرکت مورد نیاز است", ADDRESS_REQUIRED = "آدرس شرکت مورد نیاز است",
CHIEF_EXECUTIVE_MUST_BE_STRING = "نام مدیرعامل باید یک رشته باشد", CHIEF_EXECUTIVE_MUST_BE_STRING = "نام مدیرعامل باید یک رشته باشد",
CHIEF_EXECUTIVE_LENGTH = "نام مدیرعامل باید بین ۳ تا ۲۵۵ کاراکتر باشد",
CHIEF_EXECUTIVE_REQUIRED = "نام مدیرعامل مورد نیاز است", CHIEF_EXECUTIVE_REQUIRED = "نام مدیرعامل مورد نیاز است",
COVER_IMAGE_URL_MUST_BE_URL = "آدرس تصویر کاور شرکت باید یک URL معتبر باشد", COVER_IMAGE_URL_MUST_BE_URL = "آدرس تصویر کاور شرکت باید یک URL معتبر باشد",
COVER_IMAGE_URL_REQUIRED = "آدرس تصویر کاور شرکت مورد نیاز است", COVER_IMAGE_URL_REQUIRED = "آدرس تصویر کاور شرکت مورد نیاز است",
@@ -574,7 +575,6 @@ export const enum CompanyMessage {
WEBSITE_URL_MUST_BE_URL = "آدرس وبسایت شرکت باید یک URL معتبر باشد", WEBSITE_URL_MUST_BE_URL = "آدرس وبسایت شرکت باید یک URL معتبر باشد",
WEBSITE_URL_REQUIRED = "آدرس وبسایت شرکت مورد نیاز است", WEBSITE_URL_REQUIRED = "آدرس وبسایت شرکت مورد نیاز است",
NAME_LENGTH = "نام شرکت باید بین ۳ تا ۱۰۰ کاراکتر باشد", NAME_LENGTH = "نام شرکت باید بین ۳ تا ۱۰۰ کاراکتر باشد",
CHIEF_EXECUTIVE_LENGTH = "نام مدیرعامل باید بین ۳ تا ۱۰۰ کاراکتر باشد",
COMPANY_CREATED_SUCCESSFULLY = "شرکت با موفقیت ایجاد شد", COMPANY_CREATED_SUCCESSFULLY = "شرکت با موفقیت ایجاد شد",
COMPANY_ALREADY_EXISTS = "این شرکت قبلا ثبت شده است", COMPANY_ALREADY_EXISTS = "این شرکت قبلا ثبت شده است",
COMPANY_ALREADY_EXISTS_NAME = "این نام شرکت قبلا ثبت شده است", COMPANY_ALREADY_EXISTS_NAME = "این نام شرکت قبلا ثبت شده است",
@@ -600,9 +600,14 @@ export const enum CompanyMessage {
SERVICES_MUST_BE_ARRAY = "خدمات باید آرایه باشد", SERVICES_MUST_BE_ARRAY = "خدمات باید آرایه باشد",
COMPANY_ID_REQUIRED = "شرکت مورد نیاز است", COMPANY_ID_REQUIRED = "شرکت مورد نیاز است",
COMPANY_ID_SHOULD_BE_A_UUID = "شناسه شرکت باید یک UUID معتبر باشد", COMPANY_ID_SHOULD_BE_A_UUID = "شناسه شرکت باید یک UUID معتبر باشد",
COMPANY_REQUEST_STATUS_REQUIRED = "وضعیت درخواست الزامی است",
COMPANY_REQUEST_STATUS_MUST_BE_ENUM = "وضعیت درخواست باید یک عنصر از جعبه انتخابی وضعیت درخواست باشد",
COMPANY_REQUEST_ALREADY_EXISTS = "درخواست شرکت قبلا ثبت شده است",
} }
export const enum BusinessMessage { export const enum BusinessMessage {
NOT_FOUND = "کسب و کار یافت نشد", NOT_FOUND = "کسب و کار یافت نشد",
BUSINESS_ID_REQUIRED = "شناسه کسب و کار مورد نیاز است", BUSINESS_ID_REQUIRED = "شناسه کسب و کار مورد نیاز است",
SLUG_REQUIRED = "اسلاگ مورد نیاز است",
SLUG_MUST_BE_STRING = "اسلاگ باید یک رشته باشد",
} }
@@ -12,7 +12,7 @@ import { Observable } from "rxjs";
import { BusinessMessage } from "../../common/enums/message.enum"; import { BusinessMessage } from "../../common/enums/message.enum";
import { Business } from "../../modules/businesses/entities/business.entity"; import { Business } from "../../modules/businesses/entities/business.entity";
import { BusinessService } from "../../modules/businesses/services/businesses.service"; import { BusinessesService } from "../../modules/businesses/services/businesses.service";
declare module "fastify" { declare module "fastify" {
interface FastifyRequest { interface FastifyRequest {
@@ -25,7 +25,7 @@ export class BusinessInterceptor implements NestInterceptor {
private readonly headerName = "x-business-id"; private readonly headerName = "x-business-id";
private readonly logger = new Logger(BusinessInterceptor.name); private readonly logger = new Logger(BusinessInterceptor.name);
constructor(private readonly businessService: BusinessService) {} constructor(private readonly businessesService: BusinessesService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest<FastifyRequest>(); const request = context.switchToHttp().getRequest<FastifyRequest>();
@@ -34,7 +34,7 @@ export class BusinessInterceptor implements NestInterceptor {
if (businessId) { if (businessId) {
try { try {
const business = await this.businessService.getBusinessByDanakSubscriptionId(businessId); const business = await this.businessesService.getBusinessByDanakSubscriptionId(businessId);
request.business = business; request.business = business;
} catch (error) { } catch (error) {
+17 -13
View File
@@ -1,4 +1,4 @@
import { Body, Controller, HttpCode, HttpStatus, Patch, Post, UseGuards } from "@nestjs/common"; import { Body, Controller, HttpCode, HttpStatus, Patch, Post, UseGuards, UseInterceptors } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { Throttle, ThrottlerGuard } from "@nestjs/throttler"; import { Throttle, ThrottlerGuard } from "@nestjs/throttler";
@@ -11,54 +11,58 @@ import { VerifyOtpDto } from "./DTO/verify-otp.dto";
import { AuthService } from "./providers/auth.service"; import { AuthService } from "./providers/auth.service";
import { AUTH_THROTTLE_LIMIT, AUTH_THROTTLE_TTL, AUTH__REFRESH_THROTTLE_LIMIT, AUTH__REFRESH_THROTTLE_TTL } from "../../common/constants"; import { AUTH_THROTTLE_LIMIT, AUTH_THROTTLE_TTL, AUTH__REFRESH_THROTTLE_LIMIT, AUTH__REFRESH_THROTTLE_TTL } from "../../common/constants";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { UserDec } from "../../common/decorators/user.decorator"; import { UserDec } from "../../common/decorators/user.decorator";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@ApiTags("Auth") @ApiTags("Auth")
@Controller("auth") @Controller("auth")
@Throttle({ default: { limit: AUTH_THROTTLE_LIMIT, ttl: AUTH_THROTTLE_TTL } }) @Throttle({ default: { limit: AUTH_THROTTLE_LIMIT, ttl: AUTH_THROTTLE_TTL } })
@UseGuards(ThrottlerGuard) @UseGuards(ThrottlerGuard)
@UseInterceptors(BusinessInterceptor)
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} constructor(private readonly authService: AuthService) {}
@ApiOperation({ summary: "Initiate registration" }) @ApiOperation({ summary: "Initiate registration" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("register/initiate") @Post("register/initiate")
register(@Body() registerDto: RequestOtpDto) { register(@Body() registerDto: RequestOtpDto, @BusinessDec("id") businessId: string) {
return this.authService.initiateRegistration(registerDto); return this.authService.initiateRegistration(registerDto, businessId);
} }
@ApiOperation({ summary: "complete registration ==> step 2" }) @ApiOperation({ summary: "complete registration ==> step 2" })
@Post("register/complete") @Post("register/complete")
completeRegistration(@Body() completeRegistrationDto: CompleteRegistrationDto) { completeRegistration(@Body() completeRegistrationDto: CompleteRegistrationDto, @BusinessDec() business: Business) {
return this.authService.completeRegistration(completeRegistrationDto); return this.authService.completeRegistration(completeRegistrationDto, business);
} }
@ApiOperation({ summary: "request to login with otp" }) @ApiOperation({ summary: "request to login with otp" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("otp/send") @Post("otp/send")
sendOtp(@Body() requestOtpDto: RequestOtpDto) { sendOtp(@Body() requestOtpDto: RequestOtpDto, @BusinessDec("id") businessId: string) {
return this.authService.requestLoginOtp(requestOtpDto); return this.authService.requestLoginOtp(requestOtpDto, businessId);
} }
@ApiOperation({ summary: "verify otp for login" }) @ApiOperation({ summary: "verify otp for login" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("otp/verify") @Post("otp/verify")
verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) { verifyOtp(@Body() verifyOtpDto: VerifyOtpDto, @BusinessDec("id") businessId: string) {
return this.authService.verifyLoginOtp(verifyOtpDto); return this.authService.verifyLoginOtp(verifyOtpDto, businessId);
} }
@ApiOperation({ summary: "check if user email exist" }) @ApiOperation({ summary: "check if user email exist" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("check") @Post("check")
checkUserExist(@Body() checkUserExistDto: CheckUserExistDto) { checkUserExist(@Body() checkUserExistDto: CheckUserExistDto, @BusinessDec("id") businessId: string) {
return this.authService.checkUserExist(checkUserExistDto); return this.authService.checkUserExist(checkUserExistDto, businessId);
} }
@ApiOperation({ summary: "login with password" }) @ApiOperation({ summary: "login with password" })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("login/password") @Post("login/password")
loginWithPassword(@Body() loginPasswordDto: LoginPasswordDTO) { loginWithPassword(@Body() loginPasswordDto: LoginPasswordDTO, @BusinessDec("id") businessId: string) {
return this.authService.loginWithPassword(loginPasswordDto); return this.authService.loginWithPassword(loginPasswordDto, businessId);
} }
@AuthGuards() @AuthGuards()
+2 -1
View File
@@ -11,9 +11,10 @@ import { NotificationModule } from "../notifications/notifications.module";
import { UsersModule } from "../users/users.module"; import { UsersModule } from "../users/users.module";
import { ConsoleJwtStrategy } from "./strategies/console-jwt.strategy"; import { ConsoleJwtStrategy } from "./strategies/console-jwt.strategy";
import { LocalJwtStrategy } from "./strategies/local-jwt.strategy"; import { LocalJwtStrategy } from "./strategies/local-jwt.strategy";
import { BusinessesModule } from "../businesses/businesses.module";
@Module({ @Module({
imports: [UtilsModule, UsersModule, PassportModule, JwtModule.registerAsync(jwtConfig()), NotificationModule], imports: [UtilsModule, UsersModule, PassportModule, JwtModule.registerAsync(jwtConfig()), NotificationModule, BusinessesModule],
controllers: [AuthController], controllers: [AuthController],
providers: [AuthService, TokensService, LocalJwtStrategy, ConsoleJwtStrategy], providers: [AuthService, TokensService, LocalJwtStrategy, ConsoleJwtStrategy],
exports: [AuthService], exports: [AuthService],
+17 -17
View File
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import { TokensService } from "./tokens.service"; import { TokensService } from "./tokens.service";
import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { NotificationQueue } from "../../notifications/queue/notification.queue"; import { NotificationQueue } from "../../notifications/queue/notification.queue";
import { Role } from "../../users/entities/role.entity"; import { Role } from "../../users/entities/role.entity";
import { RoleEnum } from "../../users/enums/role.enum"; import { RoleEnum } from "../../users/enums/role.enum";
@@ -15,7 +16,6 @@ import { CompleteRegistrationDto } from "../DTO/complete-register.dto";
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto"; import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
import { RequestOtpDto } from "../DTO/request-otp.dto"; import { RequestOtpDto } from "../DTO/request-otp.dto";
import { VerifyOtpDto } from "../DTO/verify-otp.dto"; import { VerifyOtpDto } from "../DTO/verify-otp.dto";
@Injectable() @Injectable()
export class AuthService { export class AuthService {
constructor( constructor(
@@ -29,9 +29,9 @@ export class AuthService {
) {} ) {}
//****************** */ //****************** */
//****************** */ //****************** */
async initiateRegistration(requestOtpDto: RequestOtpDto) { async initiateRegistration(requestOtpDto: RequestOtpDto, businessId: string) {
const { phone } = requestOtpDto; const { phone } = requestOtpDto;
const existUser = await this.usersService.findOneWithPhone(phone); const existUser = await this.usersService.findOneWithPhone(phone, businessId);
if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS); if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS);
const existCode = await this.otpService.checkExistOtp(phone, "REGISTER"); const existCode = await this.otpService.checkExistOtp(phone, "REGISTER");
@@ -52,7 +52,7 @@ export class AuthService {
} }
//****************** */ //****************** */
//****************** */ //****************** */
async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) { async completeRegistration(completeRegistrationDto: CompleteRegistrationDto, business: Business) {
const { phone, code } = completeRegistrationDto; const { phone, code } = completeRegistrationDto;
const entityManager = this.em.fork(); const entityManager = this.em.fork();
@@ -67,7 +67,7 @@ export class AuthService {
await this.otpService.delOtpFormCache(phone, "REGISTER"); await this.otpService.delOtpFormCache(phone, "REGISTER");
const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password);
const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, entityManager); const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, business, entityManager);
const tokens = await this.tokensService.generateTokens(user, entityManager); const tokens = await this.tokensService.generateTokens(user, entityManager);
@@ -84,10 +84,10 @@ export class AuthService {
} }
//****************** */ //****************** */
//****************** */ //****************** */
async loginWithPassword(loginDto: LoginPasswordDTO) { async loginWithPassword(loginDto: LoginPasswordDTO, businessId: string) {
const { email, password } = loginDto; const { email, password } = loginDto;
const user = await this.checkUserLoginCredentialWithEmail(email, password); const user = await this.checkUserLoginCredentialWithEmail(email, password, businessId);
if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN); if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
@@ -104,8 +104,8 @@ export class AuthService {
//****************** */ //****************** */
//****************** */ //****************** */
async checkUserExist(checkUserExistDto: CheckUserExistDto) { async checkUserExist(checkUserExistDto: CheckUserExistDto, businessId: string) {
const user = await this.usersService.findOneWithEmail(checkUserExistDto.email); const user = await this.usersService.findOneWithEmail(checkUserExistDto.email, businessId);
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return { message: UserMessage.USER_EXISTS }; return { message: UserMessage.USER_EXISTS };
} }
@@ -113,9 +113,9 @@ export class AuthService {
//****************** */ //****************** */
//****************** */ //****************** */
async requestLoginOtp(requestOtpDto: RequestOtpDto, isAdmin: boolean = false) { async requestLoginOtp(requestOtpDto: RequestOtpDto, businessId: string, isAdmin: boolean = false) {
const { phone } = requestOtpDto; const { phone } = requestOtpDto;
const user = await this.usersService.findOneWithPhone(phone); const user = await this.usersService.findOneWithPhone(phone, businessId);
if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND); if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND);
//check the if the method call is from admin or not //check the if the method call is from admin or not
@@ -144,10 +144,10 @@ export class AuthService {
//****************** */ //****************** */
//****************** */ //****************** */
async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) { async verifyLoginOtp(verifyOtpDto: VerifyOtpDto, businessId: string) {
const { code, phone } = verifyOtpDto; const { code, phone } = verifyOtpDto;
const user = await this.checkUserLoginCredentialWithPhone(phone, code); const user = await this.checkUserLoginCredentialWithPhone(phone, code, businessId);
if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN); if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
@@ -193,8 +193,8 @@ export class AuthService {
//****************** */ //****************** */
private async checkUserLoginCredentialWithEmail(email: string, password: string) { private async checkUserLoginCredentialWithEmail(email: string, password: string, businessId: string) {
const user = await this.usersService.findOneWithEmail(email); const user = await this.usersService.findOneWithEmail(email, businessId);
if (!user) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); if (!user) throw new BadRequestException(AuthMessage.INVALID_PASSWORD);
const passCompare = await this.passwordService.comparePassword(password, user.password); const passCompare = await this.passwordService.comparePassword(password, user.password);
@@ -205,13 +205,13 @@ export class AuthService {
//****************** */ //****************** */
//****************** */ //****************** */
private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string) { private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string, businessId: string) {
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN"); const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN");
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
await this.otpService.delOtpFormCache(phone, "LOGIN"); await this.otpService.delOtpFormCache(phone, "LOGIN");
const user = await this.usersService.findOneWithPhone(phone); const user = await this.usersService.findOneWithPhone(phone, businessId);
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return user; return user;
@@ -0,0 +1,11 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsString } from "class-validator";
import { BusinessMessage } from "../../../common/enums/message.enum";
export class BusinessSlugParamDto {
@IsNotEmpty({ message: BusinessMessage.SLUG_REQUIRED })
@IsString({ message: BusinessMessage.SLUG_MUST_BE_STRING })
@ApiProperty({ description: "The slug of the business", example: "example-business" })
slug: string;
}
@@ -0,0 +1,14 @@
import { Controller, Get, Param } from "@nestjs/common";
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
import { BusinessesService } from "./services/businesses.service";
@Controller("business")
export class BusinessesController {
constructor(private readonly businessesService: BusinessesService) {}
@Get("slug/:slug")
async getBusinessBySlug(@Param() params: BusinessSlugParamDto) {
return this.businessesService.getBusinessBySlug(params.slug);
}
}
+5 -3
View File
@@ -2,10 +2,11 @@ import { MikroOrmModule } from "@mikro-orm/nestjs";
import { BullModule } from "@nestjs/bullmq"; import { BullModule } from "@nestjs/bullmq";
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { BusinessesController } from "./businesses.controller";
import { SUBSCRIPTIONS } from "./constant"; import { SUBSCRIPTIONS } from "./constant";
import { Business } from "./entities/business.entity"; import { Business } from "./entities/business.entity";
import { BusinessProvisioningProcessor } from "./queue/business-provisioning.processor"; import { BusinessProvisioningProcessor } from "./queue/business-provisioning.processor";
import { BusinessService } from "./services/businesses.service"; import { BusinessesService } from "./services/businesses.service";
@Module({ @Module({
imports: [ imports: [
@@ -15,7 +16,8 @@ import { BusinessService } from "./services/businesses.service";
prefix: SUBSCRIPTIONS.PROVISIONING_QUEUE_PREFIX, prefix: SUBSCRIPTIONS.PROVISIONING_QUEUE_PREFIX,
}), }),
], ],
providers: [BusinessService, BusinessProvisioningProcessor], providers: [BusinessesService, BusinessProvisioningProcessor],
exports: [BusinessService], exports: [BusinessesService],
controllers: [BusinessesController],
}) })
export class BusinessesModule {} export class BusinessesModule {}
@@ -8,6 +8,7 @@ import { Industry } from "../../industries/entities/industry.entity";
import { Invoice } from "../../invoices/entities/invoice.entity"; import { Invoice } from "../../invoices/entities/invoice.entity";
import { TicketCategory } from "../../tickets/entities/ticket-category.entity"; import { TicketCategory } from "../../tickets/entities/ticket-category.entity";
import { Ticket } from "../../tickets/entities/ticket.entity"; import { Ticket } from "../../tickets/entities/ticket.entity";
import { User } from "../../users/entities/user.entity";
import { BusinessRepository } from "../repositories/business.repository"; import { BusinessRepository } from "../repositories/business.repository";
@Entity({ repository: () => BusinessRepository }) @Entity({ repository: () => BusinessRepository })
@@ -19,9 +20,17 @@ export class Business extends BaseEntity {
@Property({ type: "varchar", length: 255, nullable: false }) @Property({ type: "varchar", length: 255, nullable: false })
name!: string; name!: string;
@Property({ type: "varchar", length: 255, nullable: false })
slug!: string;
@Property({ type: "varchar", length: 255, nullable: true })
domain?: string;
@Property({ type: "varchar", length: 255, nullable: true }) @Property({ type: "varchar", length: 255, nullable: true })
logoUrl?: string; logoUrl?: string;
//=========================
@OneToMany(() => Company, (company) => company.business, { cascade: [Cascade.ALL] }) @OneToMany(() => Company, (company) => company.business, { cascade: [Cascade.ALL] })
companies = new Collection<Company>(this); companies = new Collection<Company>(this);
@@ -43,5 +52,8 @@ export class Business extends BaseEntity {
@OneToMany(() => Criticism, (criticism) => criticism.business) @OneToMany(() => Criticism, (criticism) => criticism.business)
criticisms = new Collection<Criticism>(this); criticisms = new Collection<Criticism>(this);
@OneToMany(() => User, (user) => user.business)
users = new Collection<User>(this);
[EntityRepositoryType]?: BusinessRepository; [EntityRepositoryType]?: BusinessRepository;
} }
@@ -29,7 +29,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 } = job.data; const { subscriptionId, serviceId, serviceName, businessName, slug } = 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) {
@@ -43,7 +43,7 @@ export class BusinessProvisioningProcessor extends WorkerProcessor {
business = em.create(Business, { business = em.create(Business, {
danakSubscriptionId: subscriptionId, danakSubscriptionId: subscriptionId,
name: businessName, name: businessName,
// logoUrl: job.data.logoUrl, // Uncomment if logoUrl is provided in job data slug: slug,
}); });
await em.persistAndFlush(business); await em.persistAndFlush(business);
this.logger.log(`Created business for subscription ${subscriptionId}`); this.logger.log(`Created business for subscription ${subscriptionId}`);
@@ -4,7 +4,7 @@ import { BusinessMessage } from "../../../common/enums/message.enum";
import { BusinessRepository } from "../repositories/business.repository"; import { BusinessRepository } from "../repositories/business.repository";
@Injectable() @Injectable()
export class BusinessService { export class BusinessesService {
constructor(private readonly businessRepository: BusinessRepository) {} constructor(private readonly businessRepository: BusinessRepository) {}
async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) { async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) {
@@ -13,4 +13,11 @@ export class BusinessService {
return business; return business;
} }
async getBusinessBySlug(slug: string) {
const business = await this.businessRepository.findOne({ slug, deletedAt: null });
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
return { business };
}
} }
@@ -38,3 +38,10 @@ export class CompanyListQueryDto extends PaginationDto {
@ApiPropertyOptional({ description: "تاریخ ثبت", example: "2021-01-01" }) @ApiPropertyOptional({ description: "تاریخ ثبت", example: "2021-01-01" })
registrationDate?: string; registrationDate?: string;
} }
export class CompanyListPublicQueryDto {
@IsOptional()
@IsString({ message: CompanyMessage.INDUSTRY_ID_SHOULD_BE_UUID })
@ApiPropertyOptional({ description: "شناسه صنعت" })
industryId?: string;
}
@@ -6,6 +6,7 @@ import {
IsBoolean, IsBoolean,
IsDateString, IsDateString,
IsEmail, IsEmail,
IsEnum,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsString, IsString,
@@ -19,6 +20,7 @@ import { CreateCompanyProductDto } from "./create-company-product.dto";
import { CreateCompanyServiceDto } from "./create-company-service.dto"; import { CreateCompanyServiceDto } from "./create-company-service.dto";
import { CompanyMessage } from "../../../common/enums/message.enum"; import { CompanyMessage } from "../../../common/enums/message.enum";
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
import { CompanyRequestStatus } from "../enums/company-request-status.enum";
export class CreateCompanyDto extends PickType(CompleteRegistrationDto, ["phone", "firstName", "lastName", "password", "nationalCode"]) { export class CreateCompanyDto extends PickType(CompleteRegistrationDto, ["phone", "firstName", "lastName", "password", "nationalCode"]) {
@IsNotEmpty({ message: CompanyMessage.NAME_REQUIRED }) @IsNotEmpty({ message: CompanyMessage.NAME_REQUIRED })
@@ -99,3 +101,36 @@ export class CreateCompanyDto extends PickType(CompleteRegistrationDto, ["phone"
@ApiProperty({ description: "خدمات شرکت", type: [CreateCompanyServiceDto], required: false }) @ApiProperty({ description: "خدمات شرکت", type: [CreateCompanyServiceDto], required: false })
services: CreateCompanyServiceDto[]; services: CreateCompanyServiceDto[];
} }
export class CreateCompanyRequestDto extends PickType(CreateCompanyDto, [
"name",
"email",
"phone",
"identificationNumber",
"dateOfEstablishment",
"address",
"mapAddressLink",
"description",
"websiteUrl",
"profileImageUrl",
"coverImageUrl",
"industryId",
"products",
"services",
]) {
@IsNotEmpty({ message: CompanyMessage.CHIEF_EXECUTIVE_REQUIRED })
@IsString({ message: CompanyMessage.CHIEF_EXECUTIVE_MUST_BE_STRING })
@Length(3, 255, { message: CompanyMessage.CHIEF_EXECUTIVE_MUST_BE_STRING })
@ApiProperty({ description: "نام مدیر عامل", example: "محمد حسین حسینی" })
chiefExecutiveOfficer: string;
}
export class ApproveCompanyRequestDto {
@IsNotEmpty({ message: CompanyMessage.COMPANY_REQUEST_STATUS_REQUIRED })
@IsEnum(CompanyRequestStatus)
@ApiProperty({ enum: CompanyRequestStatus, description: "وضعیت درخواست" })
requestStatus: CompanyRequestStatus;
@ApiProperty({ required: false, description: "دلیل رد درخواست" })
rejectReason?: string;
}
+44 -2
View File
@@ -1,15 +1,18 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common"; import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger"; import { ApiOperation } from "@nestjs/swagger";
import { CompanyListQueryDto } from "./DTO/company-list-query.dto"; import { CompanyListPublicQueryDto, CompanyListQueryDto } from "./DTO/company-list-query.dto";
import { CreateCompanyDto } from "./DTO/create-company.dto"; import { ApproveCompanyRequestDto, CreateCompanyDto, CreateCompanyRequestDto } from "./DTO/create-company.dto";
import { UpdateCompanyDto } from "./DTO/update-company.dto"; import { UpdateCompanyDto } from "./DTO/update-company.dto";
import { CompaniesService } from "./services/companies.service"; import { CompaniesService } from "./services/companies.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator"; import { BusinessDec } from "../../common/decorators/business.decorator";
import { UserDec } from "../../common/decorators/user.decorator";
import { ParamDto } from "../../common/DTO/param.dto"; import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity"; import { Business } from "../businesses/entities/business.entity";
import { User } from "../users/entities/user.entity";
@Controller("companies") @Controller("companies")
@UseInterceptors(BusinessInterceptor) @UseInterceptors(BusinessInterceptor)
export class CompaniesController { export class CompaniesController {
@@ -43,6 +46,18 @@ export class CompaniesController {
return this.companiesService.getCompaniesList(query, businessId); return this.companiesService.getCompaniesList(query, businessId);
} }
@Get("list/public")
@ApiOperation({ summary: "get companies list (public)" })
getCompaniesListPublic(@Query() queryDto: CompanyListPublicQueryDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompaniesListPublic(queryDto, businessId);
}
@Get("public/:id")
@ApiOperation({ summary: "get company by id (public)" })
getCompanyByIdPublic(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompanyByIdPublic(paramDto.id, businessId);
}
@Get(":id") @Get(":id")
@AuthGuards() @AuthGuards()
@ApiOperation({ summary: "get company by id" }) @ApiOperation({ summary: "get company by id" })
@@ -56,4 +71,31 @@ export class CompaniesController {
toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) { toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.toggleStatus(paramDto.id, businessId); return this.companiesService.toggleStatus(paramDto.id, businessId);
} }
@Post("/company-requests")
@AuthGuards()
@ApiOperation({ summary: "submit company registration request (user)" })
createCompanyRequest(@Body() createDto: CreateCompanyRequestDto, @UserDec() user: User, @BusinessDec() business: Business) {
return this.companiesService.createCompanyRequest(createDto, user, business);
}
@Get("/company-requests/user")
@ApiOperation({ summary: "get all company registration requests (user)" })
getCompanyRequestsUser(@UserDec("id") userId: string, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompanyRequestsUser(userId, businessId);
}
@Get("/company-requests")
@AuthGuards()
@ApiOperation({ summary: "get all company registration requests (admin)" })
getCompanyRequests(@BusinessDec("id") businessId: string) {
return this.companiesService.getCompanyRequests(businessId);
}
@Patch("/company-requests/:id/approve")
@AuthGuards()
@ApiOperation({ summary: "approve/reject company registration request (admin)" })
approveCompanyRequest(@Param() paramDto: ParamDto, @Body() approveDto: ApproveCompanyRequestDto, @BusinessDec("id") businessId: string) {
return this.companiesService.approveCompanyRequest(paramDto.id, approveDto, businessId);
}
} }
+7 -1
View File
@@ -3,6 +3,7 @@ import { Module } from "@nestjs/common";
import { CompaniesController } from "./companies.controller"; import { CompaniesController } from "./companies.controller";
import { CompanyProduct } from "./entities/company-product.entity"; import { CompanyProduct } from "./entities/company-product.entity";
import { CompanyRequest } from "./entities/company-request.enitiy";
import { CompanyService } from "./entities/company-service.entity"; import { CompanyService } from "./entities/company-service.entity";
import { Company } from "./entities/company.entity"; import { Company } from "./entities/company.entity";
import { CompaniesService } from "./services/companies.service"; import { CompaniesService } from "./services/companies.service";
@@ -11,7 +12,12 @@ import { Industry } from "../industries/entities/industry.entity";
import { UsersModule } from "../users/users.module"; import { UsersModule } from "../users/users.module";
import { UtilsModule } from "../utils/utils.module"; import { UtilsModule } from "../utils/utils.module";
@Module({ @Module({
imports: [MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService]), UsersModule, UtilsModule, BusinessesModule], imports: [
MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService, CompanyRequest]),
UsersModule,
UtilsModule,
BusinessesModule,
],
providers: [CompaniesService], providers: [CompaniesService],
controllers: [CompaniesController], controllers: [CompaniesController],
exports: [CompaniesService], exports: [CompaniesService],
@@ -0,0 +1,28 @@
import { Entity, EntityRepositoryType, Enum, ManyToOne, Opt, Property } from "@mikro-orm/core";
import { Company } from "./company.entity";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { User } from "../../users/entities/user.entity";
import { CompanyRequestStatus } from "../enums/company-request-status.enum";
import { CompanyRequestRepository } from "../repositories/company-request.repository";
@Entity({ repository: () => CompanyRequestRepository })
export class CompanyRequest extends BaseEntity {
@Property({ type: "text", nullable: true })
rejectReason?: string;
@Enum({ items: () => CompanyRequestStatus, nativeEnumName: "company_request_status", default: CompanyRequestStatus.PENDING })
requestStatus!: CompanyRequestStatus & Opt;
@ManyToOne(() => Company, { nullable: false })
company!: Company;
@ManyToOne(() => Business, { nullable: false })
business!: Business;
@ManyToOne(() => User, { nullable: false })
user!: User;
[EntityRepositoryType]?: CompanyRequestRepository;
}
@@ -1,4 +1,16 @@
import { Cascade, Collection, Entity, EntityRepositoryType, Enum, Formula, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core"; import {
Cascade,
Collection,
Entity,
EntityRepositoryType,
Enum,
Formula,
ManyToOne,
OneToMany,
OneToOne,
Opt,
Property,
} from "@mikro-orm/core";
import { CompanyProduct } from "./company-product.entity"; import { CompanyProduct } from "./company-product.entity";
import { CompanyService } from "./company-service.entity"; import { CompanyService } from "./company-service.entity";
@@ -15,6 +27,15 @@ export class Company extends BaseEntity {
@Property({ type: "varchar", length: 255, unique: true }) @Property({ type: "varchar", length: 255, unique: true })
name!: string; name!: string;
@Property({ type: "varchar", length: 255 })
chiefExecutiveOfficer!: string;
@Property({ type: "varchar", length: 255, unique: true })
email!: string;
@Property({ type: "varchar", length: 255, unique: true })
phone!: string;
@Property({ type: "varchar", length: 255, unique: true }) @Property({ type: "varchar", length: 255, unique: true })
identificationNumber!: string; identificationNumber!: string;
@@ -45,6 +66,9 @@ export class Company extends BaseEntity {
@Enum({ items: () => CompanyStatus, nativeEnumName: "company_status", nullable: false }) @Enum({ items: () => CompanyStatus, nativeEnumName: "company_status", nullable: false })
status!: CompanyStatus; status!: CompanyStatus;
@Formula((alias) => `(SELECT COUNT(*)::int FROM invoice i WHERE i.company_id = ${alias}.id)`, { persist: false })
invoiceCount?: number;
//----------------------------------- //-----------------------------------
@OneToMany(() => CompanyProduct, (product) => product.company, { cascade: [Cascade.ALL] }) @OneToMany(() => CompanyProduct, (product) => product.company, { cascade: [Cascade.ALL] })
@@ -56,19 +80,18 @@ export class Company extends BaseEntity {
@OneToMany(() => Invoice, (invoice) => invoice.company, { cascade: [Cascade.ALL] }) @OneToMany(() => Invoice, (invoice) => invoice.company, { cascade: [Cascade.ALL] })
invoices = new Collection<Invoice>(this); invoices = new Collection<Invoice>(this);
@Formula((alias) => `(SELECT COUNT(*)::int FROM invoice i WHERE i.company_id = ${alias}.id)`, { persist: false })
invoiceCount?: number;
//----------------------------------- //-----------------------------------
@ManyToOne(() => Industry, { deleteRule: "restrict" }) @ManyToOne(() => Industry, { deleteRule: "restrict" })
industry!: Industry; industry!: Industry;
@ManyToOne(() => User, { deleteRule: "restrict" })
user!: User;
@ManyToOne(() => Business, { deleteRule: "restrict" }) @ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business; business!: Business;
//-----------------------------------
@OneToOne(() => User, (user) => user.company, { owner: true })
user!: User;
[EntityRepositoryType]?: CompanyRepository; [EntityRepositoryType]?: CompanyRepository;
} }
@@ -0,0 +1,5 @@
export enum CompanyRequestStatus {
PENDING = "PENDING",
APPROVED = "APPROVED",
REJECTED = "REJECTED",
}
@@ -0,0 +1,5 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { CompanyRequest } from "../entities/company-request.enitiy";
export class CompanyRequestRepository extends EntityRepository<CompanyRequest> {}
@@ -1,9 +1,9 @@
import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql"; import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql";
import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { CompanyListQueryDto } from "../DTO/company-list-query.dto"; import { CompanyListPublicQueryDto, CompanyListQueryDto } from "../DTO/company-list-query.dto";
import { Company } from "../entities/company.entity"; import { Company } from "../entities/company.entity";
import { CompanyStatus } from "../enums/company-status.enum";
export class CompanyRepository extends EntityRepository<Company> { export class CompanyRepository extends EntityRepository<Company> {
// //
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto, businessId: string) { async getCompaniesListForAdmin(queryDto: CompanyListQueryDto, businessId: string) {
@@ -48,6 +48,7 @@ export class CompanyRepository extends EntityRepository<Company> {
"dateOfEstablishment", "dateOfEstablishment",
"status", "status",
"invoiceCount", "invoiceCount",
"chiefExecutiveOfficer",
"createdAt", "createdAt",
"industry.id", "industry.id",
"industry.title", "industry.title",
@@ -58,4 +59,23 @@ export class CompanyRepository extends EntityRepository<Company> {
], ],
}); });
} }
async getCompaniesListForPublic(queryDto: CompanyListPublicQueryDto, businessId: string) {
const where: FilterQuery<Company> = {
deletedAt: null,
business: { id: businessId },
isActive: true,
status: CompanyStatus.APPROVED,
};
if (queryDto.industryId) {
where.industry = queryDto.industryId;
}
return this.find(where, {
populate: ["industry"],
orderBy: { createdAt: "DESC" },
fields: ["id", "name", "description", "profileImageUrl", "coverImageUrl", "industry.id", "industry.title", "createdAt"],
});
}
} }
@@ -8,13 +8,16 @@ import { Industry } from "../../industries/entities/industry.entity";
import { User } from "../../users/entities/user.entity"; import { User } from "../../users/entities/user.entity";
import { UsersService } from "../../users/services/users.service"; import { UsersService } from "../../users/services/users.service";
import { PasswordService } from "../../utils/providers/password.service"; import { PasswordService } from "../../utils/providers/password.service";
import { CompanyListQueryDto } from "../DTO/company-list-query.dto"; import { CompanyListPublicQueryDto, CompanyListQueryDto } from "../DTO/company-list-query.dto";
import { CreateCompanyDto } from "../DTO/create-company.dto"; import { ApproveCompanyRequestDto, CreateCompanyDto, CreateCompanyRequestDto } from "../DTO/create-company.dto";
import { UpdateCompanyDto } from "../DTO/update-company.dto"; import { UpdateCompanyDto } from "../DTO/update-company.dto";
import { CompanyProduct } from "../entities/company-product.entity"; import { CompanyProduct } from "../entities/company-product.entity";
import { CompanyRequest } from "../entities/company-request.enitiy";
import { CompanyService } from "../entities/company-service.entity"; import { CompanyService } from "../entities/company-service.entity";
import { Company } from "../entities/company.entity"; import { Company } from "../entities/company.entity";
import { CompanyRequestStatus } from "../enums/company-request-status.enum";
import { CompanyStatus } from "../enums/company-status.enum"; import { CompanyStatus } from "../enums/company-status.enum";
import { CompanyRequestRepository } from "../repositories/company-request.repository";
import { CompanyRepository } from "../repositories/company.repository"; import { CompanyRepository } from "../repositories/company.repository";
@Injectable() @Injectable()
@@ -24,6 +27,7 @@ export class CompaniesService {
private readonly usersService: UsersService, private readonly usersService: UsersService,
private readonly passwordService: PasswordService, private readonly passwordService: PasswordService,
private readonly em: EntityManager, private readonly em: EntityManager,
private readonly companyRequestRepository: CompanyRequestRepository,
) {} ) {}
async create(createDto: CreateCompanyDto, business: Business) { async create(createDto: CreateCompanyDto, business: Business) {
@@ -31,45 +35,12 @@ export class CompaniesService {
try { try {
await em.begin(); await em.begin();
const industry = await em.findOne(Industry, { id: createDto.industryId, deletedAt: null });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
const [existingCompany, existIdentityNumber] = await Promise.all([
em.findOne(Company, { name: createDto.name, business: { id: business.id } }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber, business: { id: business.id } }),
]);
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
if (existIdentityNumber) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
const hashedPassword = await this.passwordService.hashPassword(createDto.password); const hashedPassword = await this.passwordService.hashPassword(createDto.password);
const user = await this.usersService.createUser(createDto, hashedPassword, em); const user = await this.usersService.createUser(createDto, hashedPassword, business, em);
const products = createDto.products.map((productData) => { const company = await this.createCompany(createDto, user, business, em);
const product = new CompanyProduct();
product.title = productData.title;
product.imageUrl = productData.imageUrl;
return product;
});
const services = createDto.services.map((serviceData) => { await em.flush();
const service = new CompanyService();
service.title = serviceData.title;
service.imageUrl = serviceData.imageUrl;
return service;
});
const company = em.create(Company, {
...createDto,
industry,
status: CompanyStatus.APPROVED,
products,
services,
user,
business,
});
await em.persistAndFlush(company);
await em.commit(); await em.commit();
return { return {
@@ -192,6 +163,19 @@ export class CompaniesService {
const company = await this.findCompanyById(id, businessId); const company = await this.findCompanyById(id, businessId);
return { company }; return { company };
} }
//-----------------------------------
async getCompanyByIdPublic(id: string, businessId: string) {
const company = await this.companyRepository.findOne(
{ id, deletedAt: null, isActive: true, status: CompanyStatus.APPROVED, business: { id: businessId } },
{
populate: ["industry", "user", "products", "services"],
fields: ["*", "industry.id", "industry.title", "user.id"],
},
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
return { company };
}
//----------------------------------- //-----------------------------------
@@ -203,7 +187,12 @@ export class CompaniesService {
paginate: true, paginate: true,
}; };
} }
//-----------------------------------
async getCompaniesListPublic(queryDto: CompanyListPublicQueryDto, businessId: string) {
const companies = await this.companyRepository.getCompaniesListForPublic(queryDto, businessId);
return { companies };
}
//----------------------------------- //-----------------------------------
async deleteCompanyById(id: string, businessId: string) { async deleteCompanyById(id: string, businessId: string) {
@@ -253,4 +242,136 @@ export class CompaniesService {
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND); if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
return company; return company;
} }
//-----------------------------------
async createCompanyRequest(createDto: CreateCompanyRequestDto, user: User, business: Business) {
const em = this.em.fork();
try {
await em.begin();
const existRequest = await em.findOne(CompanyRequest, {
user: { id: user.id },
business: { id: business.id },
requestStatus: CompanyRequestStatus.PENDING,
deletedAt: null,
});
if (existRequest) throw new BadRequestException(CompanyMessage.COMPANY_REQUEST_ALREADY_EXISTS);
const company = await this.createCompany(createDto, user, business, em);
company.status = CompanyStatus.PENDING;
const companyRequest = em.create(CompanyRequest, {
user,
business,
company,
});
await em.persistAndFlush(companyRequest);
await em.flush();
await em.commit();
return { message: CompanyMessage.COMPANY_CREATED_SUCCESSFULLY, companyId: company.id, requestId: companyRequest.id };
} catch (error) {
await em.rollback();
throw error;
}
}
//-----------------------------------
async getCompanyRequestsUser(userId: string, businessId: string) {
const requests = await this.companyRequestRepository.find(
{ user: { id: userId }, business: { id: businessId }, deletedAt: null },
{ populate: ["company"], orderBy: { createdAt: "DESC" } },
);
return { requests };
}
//-----------------------------------
async getCompanyRequests(businessId: string) {
const requests = await this.companyRequestRepository.find(
{ business: { id: businessId }, deletedAt: null },
{
populate: ["user", "company"],
orderBy: { createdAt: "DESC" },
},
);
return { requests };
}
//-----------------------------------
async approveCompanyRequest(id: string, dto: ApproveCompanyRequestDto, businessId: string) {
const em = this.em.fork();
try {
await em.begin();
const request = await em.findOneOrFail(CompanyRequest, { id, business: { id: businessId } }, { populate: ["company"] });
request.requestStatus = dto.requestStatus;
if (dto.requestStatus === CompanyRequestStatus.REJECTED) {
request.rejectReason = dto.rejectReason || "";
request.company.status = CompanyStatus.REJECTED;
} else {
request.rejectReason = undefined;
request.company.status = CompanyStatus.APPROVED;
}
await em.persistAndFlush(request);
await em.flush();
await em.commit();
return { message: CompanyMessage.UPDATED_SUCCESSFULLY, status: request.requestStatus };
} catch (error) {
await em.rollback();
throw error;
}
}
//-----------------------------------
private async createCompany(createDto: CreateCompanyDto | CreateCompanyRequestDto, user: User, business: Business, em: EntityManager) {
const industry = await em.findOne(Industry, { id: createDto.industryId, deletedAt: null });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
const [existingCompany, existIdentityNumber] = await Promise.all([
em.findOne(Company, { name: createDto.name, business: { id: business.id } }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber, business: { id: business.id } }),
]);
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
if (existIdentityNumber) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
const products = createDto.products.map((productData) => {
const product = new CompanyProduct();
product.title = productData.title;
product.imageUrl = productData.imageUrl;
return product;
});
const services = createDto.services.map((serviceData) => {
const service = new CompanyService();
service.title = serviceData.title;
service.imageUrl = serviceData.imageUrl;
return service;
});
const company = em.create(Company, {
...createDto,
industry,
status: CompanyStatus.APPROVED,
products,
services,
user,
business,
chiefExecutiveOfficer:
"chiefExecutiveOfficer" in createDto ? createDto.chiefExecutiveOfficer : `${createDto.firstName} ${createDto.lastName}`,
email: createDto.email,
phone: createDto.phone,
});
await em.persistAndFlush(company);
return company;
}
} }
@@ -50,6 +50,12 @@ export class IndustriesController {
return this.industriesService.getIndustriesListForAdmin(query, businessId); return this.industriesService.getIndustriesListForAdmin(query, businessId);
} }
@Get("list/public")
@ApiOperation({ summary: "get industries list for public" })
getIndustriesForPublic(@BusinessDec("id") businessId: string) {
return this.industriesService.getIndustriesListForPublic(businessId);
}
@Patch(":id/toggle-status") @Patch(":id/toggle-status")
@AuthGuards() @AuthGuards()
@ApiOperation({ summary: "toggle industry status" }) @ApiOperation({ summary: "toggle industry status" })
@@ -25,4 +25,11 @@ export class IndustryRepository extends EntityRepository<Industry> {
return queryBuilder.getResultAndCount(); return queryBuilder.getResultAndCount();
} }
//-----------------------------------
async getIndustriesListForPublic(businessId: string) {
return this.find(
{ deletedAt: null, business: { id: businessId }, isActive: true },
{ orderBy: { createdAt: "DESC" }, fields: ["id", "title", "iconUrl", "createdAt"] },
);
}
} }
@@ -78,6 +78,12 @@ export class IndustriesService {
} }
//----------------------------------- //-----------------------------------
async getIndustriesListForPublic(businessId: string) {
const industries = await this.industryRepository.getIndustriesListForPublic(businessId);
return { industries };
}
//-----------------------------------
async toggleStatus(id: string, businessId: string) { async toggleStatus(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } }); const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND); if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
@@ -8,7 +8,7 @@ export class RefreshToken extends BaseEntity {
@Property({ type: "varchar", length: 255 }) @Property({ type: "varchar", length: 255 })
token!: string; token!: string;
@ManyToOne(() => User, { deleteRule: "restrict" }) @ManyToOne(() => User, { deleteRule: "cascade" })
user!: User; user!: User;
@Property({ type: "timestamptz", nullable: true }) @Property({ type: "timestamptz", nullable: true })
+14 -7
View File
@@ -1,13 +1,15 @@
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core"; import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, OneToOne, Opt, Property, Unique } from "@mikro-orm/core";
import { RefreshToken } from "./refresh-token.entity"; import { RefreshToken } from "./refresh-token.entity";
import { Role } from "./role.entity"; import { Role } from "./role.entity";
import { BaseEntity } from "../../../common/entities/base.entity"; import { BaseEntity } from "../../../common/entities/base.entity";
import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity"; import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Company } from "../../companies/entities/company.entity"; import { Company } from "../../companies/entities/company.entity";
import { UserRepository } from "../repositories/user.repository"; import { UserRepository } from "../repositories/user.repository";
@Entity({ repository: () => UserRepository }) @Entity({ repository: () => UserRepository })
@Unique({ properties: ["phone", "business", "userName", "nationalCode"] })
export class User extends BaseEntity { export class User extends BaseEntity {
@Property({ type: "varchar", length: 150 }) @Property({ type: "varchar", length: 150 })
firstName!: string; firstName!: string;
@@ -18,10 +20,10 @@ export class User extends BaseEntity {
@Property({ type: "varchar", length: 150, nullable: true }) @Property({ type: "varchar", length: 150, nullable: true })
email?: string; email?: string;
@Property({ type: "varchar", length: 11, unique: true, nullable: false }) @Property({ type: "varchar", length: 11, nullable: false })
phone!: string; phone!: string;
@Property({ type: "varchar", length: 50, unique: true, nullable: true }) @Property({ type: "varchar", length: 50, nullable: true })
userName!: string; userName!: string;
@Property({ type: "varchar", length: 150 }) @Property({ type: "varchar", length: 150 })
@@ -30,7 +32,7 @@ export class User extends BaseEntity {
@Property({ type: "varchar", length: 12, nullable: true }) @Property({ type: "varchar", length: 12, nullable: true })
birthDate?: string; birthDate?: string;
@Property({ type: "varchar", length: 100, unique: true, nullable: true }) @Property({ type: "varchar", length: 100, nullable: true })
nationalCode?: string; nationalCode?: string;
@Property({ type: "varchar", length: 100, nullable: true }) @Property({ type: "varchar", length: 100, nullable: true })
@@ -49,14 +51,19 @@ export class User extends BaseEntity {
@ManyToOne(() => Role, { deleteRule: "restrict" }) @ManyToOne(() => Role, { deleteRule: "restrict" })
role!: Role; role!: Role;
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
//-----------------------------------
@OneToOne(() => Company, (company) => company.user, { orphanRemoval: true })
company!: Company & Opt;
//----------------------------------- //-----------------------------------
@OneToMany(() => RefreshToken, (token) => token.user) @OneToMany(() => RefreshToken, (token) => token.user)
refreshTokens = new Collection<RefreshToken>(this); refreshTokens = new Collection<RefreshToken>(this);
@OneToMany(() => Company, (company) => company.user)
companies = new Collection<Company>(this);
@OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.user) @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.user)
userAnnouncements = new Collection<UserAnnouncement>(this); userAnnouncements = new Collection<UserAnnouncement>(this);
+7 -6
View File
@@ -4,12 +4,12 @@ import slugify from "slugify";
import { UserMessage } from "../../../common/enums/message.enum"; import { UserMessage } from "../../../common/enums/message.enum";
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
import { Business } from "../../businesses/entities/business.entity";
import { CreateCompanyDto } from "../../companies/DTO/create-company.dto"; import { CreateCompanyDto } from "../../companies/DTO/create-company.dto";
import { Role } from "../entities/role.entity"; import { Role } from "../entities/role.entity";
import { User } from "../entities/user.entity"; import { User } from "../entities/user.entity";
import { RoleEnum } from "../enums/role.enum"; import { RoleEnum } from "../enums/role.enum";
import { UserRepository } from "../repositories/user.repository"; import { UserRepository } from "../repositories/user.repository";
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(
@@ -19,14 +19,14 @@ export class UsersService {
/*******************************/ /*******************************/
async findOneWithEmail(email: string) { async findOneWithEmail(email: string, businessId: string) {
const user = await this.userRepository.findOne({ email }, { populate: ["role"] }); const user = await this.userRepository.findOne({ email, business: { id: businessId } }, { populate: ["role"] });
return user; return user;
} }
/*******************************/ /*******************************/
async findOneWithPhone(phone: string) { async findOneWithPhone(phone: string, businessId: string) {
const user = await this.userRepository.findOne({ phone }, { populate: ["role"] }); const user = await this.userRepository.findOne({ phone, business: { id: businessId } }, { populate: ["role"] });
return user; return user;
} }
/*******************************/ /*******************************/
@@ -85,7 +85,7 @@ export class UsersService {
} }
/*******************************/ /*******************************/
async createUser(registerDto: CompleteRegistrationDto | CreateCompanyDto, hashedPassword: string, em: EntityManager) { async createUser(registerDto: CompleteRegistrationDto | CreateCompanyDto, hashedPassword: string, business: Business, em: EntityManager) {
const [role, existPhone, existUser] = await Promise.all([ const [role, existPhone, existUser] = await Promise.all([
em.findOne(Role, { name: RoleEnum.USER }), em.findOne(Role, { name: RoleEnum.USER }),
em.findOne(User, { phone: registerDto.phone }), em.findOne(User, { phone: registerDto.phone }),
@@ -108,6 +108,7 @@ export class UsersService {
userName, userName,
password: hashedPassword, password: hashedPassword,
role, role,
business,
}); });
await em.persistAndFlush(user); await em.persistAndFlush(user);