chore: add business logic

This commit is contained in:
Mahyargdz
2025-05-18 10:19:00 +03:30
parent 479c9c390a
commit 9eeab529a9
25 changed files with 350 additions and 74 deletions
-2
View File
@@ -49,7 +49,6 @@
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0", "@nestjs/jwt": "^11.0.0",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-fastify": "^11.1.0", "@nestjs/platform-fastify": "^11.1.0",
"@nestjs/swagger": "^11.2.0", "@nestjs/swagger": "^11.2.0",
"@nestjs/throttler": "^6.4.0", "@nestjs/throttler": "^6.4.0",
@@ -80,7 +79,6 @@
"@swc/cli": "^0.6.0", "@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7", "@swc/core": "^1.10.7",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
+15 -10
View File
@@ -65,9 +65,6 @@ importers:
'@nestjs/passport': '@nestjs/passport':
specifier: ^11.0.5 specifier: ^11.0.5
version: 11.0.5(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) version: 11.0.5(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
'@nestjs/platform-express':
specifier: ^11.0.1
version: 11.1.0(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)
'@nestjs/platform-fastify': '@nestjs/platform-fastify':
specifier: ^11.1.0 specifier: ^11.1.0
version: 11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0) version: 11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)
@@ -153,9 +150,6 @@ importers:
'@types/bcrypt': '@types/bcrypt':
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2 version: 5.0.2
'@types/express':
specifier: ^5.0.0
version: 5.0.1
'@types/jest': '@types/jest':
specifier: ^29.5.14 specifier: ^29.5.14
version: 29.5.14 version: 29.5.14
@@ -8048,6 +8042,7 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
optional: true
'@nestjs/platform-fastify@11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)': '@nestjs/platform-fastify@11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)':
dependencies: dependencies:
@@ -9423,6 +9418,7 @@ snapshots:
busboy@1.6.0: busboy@1.6.0:
dependencies: dependencies:
streamsearch: 1.1.0 streamsearch: 1.1.0
optional: true
bytes@3.1.2: {} bytes@3.1.2: {}
@@ -9642,6 +9638,7 @@ snapshots:
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 2.3.8 readable-stream: 2.3.8
typedarray: 0.0.6 typedarray: 0.0.6
optional: true
concat-stream@2.0.0: concat-stream@2.0.0:
dependencies: dependencies:
@@ -11165,7 +11162,8 @@ snapshots:
is-docker: 2.2.1 is-docker: 2.2.1
optional: true optional: true
isarray@1.0.0: {} isarray@1.0.0:
optional: true
isarray@2.0.5: {} isarray@2.0.5: {}
@@ -12302,6 +12300,7 @@ snapshots:
mkdirp@0.5.6: mkdirp@0.5.6:
dependencies: dependencies:
minimist: 1.2.8 minimist: 1.2.8
optional: true
mkdirp@1.0.4: {} mkdirp@1.0.4: {}
@@ -12334,6 +12333,7 @@ snapshots:
object-assign: 4.1.1 object-assign: 4.1.1
type-is: 1.6.18 type-is: 1.6.18
xtend: 4.0.2 xtend: 4.0.2
optional: true
mute-stream@2.0.0: {} mute-stream@2.0.0: {}
@@ -12776,7 +12776,8 @@ snapshots:
uuid: 9.0.1 uuid: 9.0.1
optional: true optional: true
process-nextick-args@2.0.1: {} process-nextick-args@2.0.1:
optional: true
process-warning@4.0.1: {} process-warning@4.0.1: {}
@@ -12930,6 +12931,7 @@ snapshots:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
string_decoder: 1.1.1 string_decoder: 1.1.1
util-deprecate: 1.0.2 util-deprecate: 1.0.2
optional: true
readable-stream@3.6.2: readable-stream@3.6.2:
dependencies: dependencies:
@@ -13066,7 +13068,8 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.1.2: {} safe-buffer@5.1.2:
optional: true
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
@@ -13290,7 +13293,8 @@ snapshots:
statuses@2.0.1: {} statuses@2.0.1: {}
streamsearch@1.1.0: {} streamsearch@1.1.0:
optional: true
streamx@2.22.0: streamx@2.22.0:
dependencies: dependencies:
@@ -13350,6 +13354,7 @@ snapshots:
string_decoder@1.1.1: string_decoder@1.1.1:
dependencies: dependencies:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
optional: true
string_decoder@1.3.0: string_decoder@1.3.0:
dependencies: dependencies:
+9
View File
@@ -15,6 +15,7 @@ import { databaseConfig } from "./configs/mikro-orm.config";
import { rateLimitConfig } from "./configs/rateLimit.config"; import { rateLimitConfig } from "./configs/rateLimit.config";
import { HTTPLogger } from "./core/middlewares/logger.middleware"; import { HTTPLogger } from "./core/middlewares/logger.middleware";
import { AuthModule } from "./modules/auth/auth.module"; import { AuthModule } from "./modules/auth/auth.module";
import { BusinessesModule } from "./modules/businesses/businesses.module";
import { CompaniesModule } from "./modules/companies/companies.module"; import { CompaniesModule } from "./modules/companies/companies.module";
import { IndustriesModule } from "./modules/industries/industries.module"; import { IndustriesModule } from "./modules/industries/industries.module";
import { InvoicesModule } from "./modules/invoices/invoices.module"; import { InvoicesModule } from "./modules/invoices/invoices.module";
@@ -23,6 +24,7 @@ import { TicketsModule } from "./modules/tickets/tickets.module";
import { UploaderModule } from "./modules/uploader/uploader.module"; import { UploaderModule } from "./modules/uploader/uploader.module";
import { UsersModule } from "./modules/users/users.module"; import { UsersModule } from "./modules/users/users.module";
import { UtilsModule } from "./modules/utils/utils.module"; import { UtilsModule } from "./modules/utils/utils.module";
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ isGlobal: true, cache: true }), ConfigModule.forRoot({ isGlobal: true, cache: true }),
@@ -50,7 +52,14 @@ import { UtilsModule } from "./modules/utils/utils.module";
UploaderModule, UploaderModule,
TicketsModule, TicketsModule,
InvoicesModule, InvoicesModule,
BusinessesModule,
], ],
// providers: [
// {
// provide: APP_INTERCEPTOR,
// useClass: BusinessInterceptor,
// },
// ],
}) })
export class AppModule implements NestModule { export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
@@ -0,0 +1,17 @@
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { FastifyRequest } from "fastify";
/**
* Extracts the business ID from the request header
* @example
* ```typescript
* @Get()
* findAll(@BusinessIdHeader() businessId: string) {
* // Use businessId
* }
* ```
*/
export const BusinessIdHeader = createParamDecorator((headerName: string = "X-Business-Id", ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
return req.headers[headerName.toLocaleLowerCase()];
});
@@ -0,0 +1,11 @@
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { Business } from "../../modules/businesses/entities/business.entity";
export const BusinessDec = createParamDecorator((data: keyof Business | undefined, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
const business = req.business;
return data ? business?.[data] : business;
});
+5
View File
@@ -597,3 +597,8 @@ export const enum CompanyMessage {
COMPANY_ID_REQUIRED = "شرکت مورد نیاز است", COMPANY_ID_REQUIRED = "شرکت مورد نیاز است",
COMPANY_ID_SHOULD_BE_A_UUID = "شناسه شرکت باید یک UUID معتبر باشد", COMPANY_ID_SHOULD_BE_A_UUID = "شناسه شرکت باید یک UUID معتبر باشد",
} }
export const enum BusinessMessage {
NOT_FOUND = "کسب و کار یافت نشد",
BUSINESS_ID_REQUIRED = "شناسه کسب و کار مورد نیاز است",
}
+1 -1
View File
@@ -10,7 +10,7 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
}, },
prefix: "dzone", prefix: "dzone",
defaultJobOptions: { defaultJobOptions: {
removeOnComplete: false, removeOnComplete: true,
removeOnFail: false, removeOnFail: false,
attempts: 5, attempts: 5,
}, },
@@ -0,0 +1,50 @@
import {
BadRequestException,
CallHandler,
ExecutionContext,
ForbiddenException,
Injectable,
Logger,
NestInterceptor,
} from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { Observable } from "rxjs";
import { BusinessMessage } from "../../common/enums/message.enum";
import { Business } from "../../modules/businesses/entities/business.entity";
import { BusinessService } from "../../modules/businesses/services/businesses.service";
declare module "fastify" {
interface FastifyRequest {
business?: Business;
}
}
@Injectable()
export class BusinessInterceptor implements NestInterceptor {
private readonly headerName = "x-business-id";
private readonly logger = new Logger(BusinessInterceptor.name);
constructor(private readonly businessService: BusinessService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const businessId = request.headers[this.headerName.toLocaleLowerCase()] as string;
if (businessId) {
try {
const business = await this.businessService.getBusinessByDanakSubscriptionId(businessId);
request.business = business;
} catch (error) {
this.logger.error(error);
throw new BadRequestException(BusinessMessage.NOT_FOUND);
}
} else {
throw new ForbiddenException(BusinessMessage.BUSINESS_ID_REQUIRED);
}
return next.handle();
}
}
@@ -0,0 +1,21 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { BullModule } from "@nestjs/bullmq";
import { Module } from "@nestjs/common";
import { SUBSCRIPTIONS } from "./constant";
import { Business } from "./entities/business.entity";
import { BusinessProvisioningProcessor } from "./queue/business-provisioning.processor";
import { BusinessService } from "./services/businesses.service";
@Module({
imports: [
MikroOrmModule.forFeature([Business]),
BullModule.registerQueue({
name: SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME,
prefix: SUBSCRIPTIONS.PROVISIONING_QUEUE_PREFIX,
}),
],
providers: [BusinessService, BusinessProvisioningProcessor],
exports: [BusinessService],
})
export class BusinessesModule {}
+9
View File
@@ -0,0 +1,9 @@
export const SUBSCRIPTIONS = Object.freeze({
PROVISIONING_QUEUE_PREFIX: "provisioning",
PROVISIONING_QUEUE_NAME: "provisioning",
PROVISIONING_JOB_NAME: "business.created",
//
SERVICE_SLUG: "Dzonea",
SERVICE_ID: "7e3c2f08-b7e7-4402-ae5f-fea99cff56bd",
});
@@ -0,0 +1,27 @@
import { Cascade, Collection, Entity, EntityRepositoryType, OneToMany, Property, Unique } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Company } from "../../companies/entities/company.entity";
import { Industry } from "../../industries/entities/industry.entity";
import { BusinessRepository } from "../repositories/business.repository";
@Entity({ repository: () => BusinessRepository })
export class Business extends BaseEntity {
@Unique()
@Property({ type: "uuid", nullable: false })
danakSubscriptionId!: string;
@Property({ type: "varchar", length: 255, nullable: false })
name!: string;
@Property({ type: "varchar", length: 255, nullable: true })
logoUrl?: string;
@OneToMany(() => Company, (company) => company.business, { cascade: [Cascade.ALL] })
companies = new Collection<Company>(this);
@OneToMany(() => Industry, (industry) => industry.business)
industries = new Collection<Industry>(this);
[EntityRepositoryType]?: BusinessRepository;
}
@@ -0,0 +1,8 @@
export interface IBusinessProvisioningJob {
subscriptionId: string;
serviceId: string;
serviceName: string;
businessName: string;
userId: string;
slug: string;
}
@@ -0,0 +1,54 @@
// src/business/queue/business-provisioning.processor.ts
import { EntityManager } from "@mikro-orm/postgresql";
import { Processor } from "@nestjs/bullmq";
import { Logger } from "@nestjs/common";
import { Job } from "bullmq";
import { WorkerProcessor } from "../../../common/queues/worker.processor";
import { SUBSCRIPTIONS } from "../constant";
import { Business } from "../entities/business.entity";
import { IBusinessProvisioningJob } from "../interfaces/IBusiness";
@Processor(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME)
export class BusinessProvisioningProcessor extends WorkerProcessor {
protected readonly logger = new Logger(BusinessProvisioningProcessor.name);
constructor(private readonly em: EntityManager) {
super();
}
async process(job: Job) {
switch (job.name) {
case SUBSCRIPTIONS.PROVISIONING_JOB_NAME:
await this.handleBusinessCreated(job);
break;
default:
this.logger.warn(`Unknown job name: ${job.name}`);
}
}
private async handleBusinessCreated(job: Job<IBusinessProvisioningJob>) {
const em = this.em.fork();
const { subscriptionId, serviceId, serviceName, businessName } = job.data;
// Only act if the event is for this service
if (serviceId !== SUBSCRIPTIONS.SERVICE_ID) {
this.logger.debug(`Skipping job for service: ${serviceName}`);
return;
}
// Check if already exists
let business = await em.findOne(Business, { danakSubscriptionId: subscriptionId });
if (!business) {
business = em.create(Business, {
danakSubscriptionId: subscriptionId,
name: businessName,
// logoUrl: job.data.logoUrl, // Uncomment if logoUrl is provided in job data
});
await em.persistAndFlush(business);
this.logger.log(`Created business for subscription ${subscriptionId}`);
} else {
this.logger.debug(`Business already exists for subscription ${subscriptionId}`);
}
}
}
@@ -0,0 +1,5 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { Business } from "../entities/business.entity";
export class BusinessRepository extends EntityRepository<Business> {}
@@ -0,0 +1,16 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import { BusinessMessage } from "../../../common/enums/message.enum";
import { BusinessRepository } from "../repositories/business.repository";
@Injectable()
export class BusinessService {
constructor(private readonly businessRepository: BusinessRepository) {}
async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) {
const business = await this.businessRepository.findOne({ danakSubscriptionId, deletedAt: null });
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
return business;
}
}
+17 -13
View File
@@ -1,48 +1,52 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } 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 { CompanyListQueryDto } from "./DTO/company-list-query.dto";
import { CreateCompanyDto } from "./DTO/create-company.dto"; import { CreateCompanyDto } 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 { BusinessDec } from "../../common/decorators/business.decorator";
import { ParamDto } from "../../common/DTO/param.dto"; import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@Controller("companies") @Controller("companies")
@UseInterceptors(BusinessInterceptor)
export class CompaniesController { export class CompaniesController {
constructor(private readonly companiesService: CompaniesService) {} constructor(private readonly companiesService: CompaniesService) {}
@Post() @Post()
@ApiOperation({ summary: "create company (admin)" }) @ApiOperation({ summary: "create company (admin)" })
create(@Body() createCompanyDto: CreateCompanyDto) { create(@Body() createCompanyDto: CreateCompanyDto, @BusinessDec() business: Business) {
return this.companiesService.create(createCompanyDto); return this.companiesService.create(createCompanyDto, business);
} }
@Patch(":id") @Patch(":id")
@ApiOperation({ summary: "update company (admin)" }) @ApiOperation({ summary: "update company (admin)" })
update(@Param() paramDto: ParamDto, @Body() updateCompanyDto: UpdateCompanyDto) { update(@Param() paramDto: ParamDto, @Body() updateCompanyDto: UpdateCompanyDto, @BusinessDec("id") businessId: string) {
return this.companiesService.updateCompanyById(paramDto.id, updateCompanyDto); return this.companiesService.updateCompanyById(paramDto.id, updateCompanyDto, businessId);
} }
@Delete(":id") @Delete(":id")
@ApiOperation({ summary: "delete company (admin)" }) @ApiOperation({ summary: "delete company (admin)" })
delete(@Param() paramDto: ParamDto) { delete(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.deleteCompanyById(paramDto.id); return this.companiesService.deleteCompanyById(paramDto.id, businessId);
} }
@Get("list") @Get("list")
@ApiOperation({ summary: "get companies list (admin)" }) @ApiOperation({ summary: "get companies list (admin)" })
getCompaniesList(@Query() query: CompanyListQueryDto) { getCompaniesList(@Query() query: CompanyListQueryDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompaniesList(query); return this.companiesService.getCompaniesList(query, businessId);
} }
@Get(":id") @Get(":id")
@ApiOperation({ summary: "get company by id" }) @ApiOperation({ summary: "get company by id" })
get(@Param() paramDto: ParamDto) { get(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompanyById(paramDto.id); return this.companiesService.getCompanyById(paramDto.id, businessId);
} }
@Patch(":id/toggle-status") @Patch(":id/toggle-status")
@ApiOperation({ summary: "toggle company status (admin)" }) @ApiOperation({ summary: "toggle company status (admin)" })
toggleStatus(@Param() paramDto: ParamDto) { toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.toggleStatus(paramDto.id); return this.companiesService.toggleStatus(paramDto.id, businessId);
} }
} }
+2 -1
View File
@@ -6,11 +6,12 @@ import { CompanyProduct } from "./entities/company-product.entity";
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";
import { BusinessesModule } from "../businesses/businesses.module";
import { Industry } from "../industries/entities/industry.entity"; 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], imports: [MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService]), UsersModule, UtilsModule, BusinessesModule],
providers: [CompaniesService], providers: [CompaniesService],
controllers: [CompaniesController], controllers: [CompaniesController],
exports: [CompaniesService], exports: [CompaniesService],
@@ -3,6 +3,7 @@ import { Cascade, Collection, Entity, EntityRepositoryType, Enum, Formula, ManyT
import { CompanyProduct } from "./company-product.entity"; import { CompanyProduct } from "./company-product.entity";
import { CompanyService } from "./company-service.entity"; import { CompanyService } from "./company-service.entity";
import { BaseEntity } from "../../../common/entities/base.entity"; import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Industry } from "../../industries/entities/industry.entity"; import { Industry } from "../../industries/entities/industry.entity";
import { Invoice } from "../../invoices/entities/invoice.entity"; import { Invoice } from "../../invoices/entities/invoice.entity";
import { User } from "../../users/entities/user.entity"; import { User } from "../../users/entities/user.entity";
@@ -66,5 +67,8 @@ export class Company extends BaseEntity {
@ManyToOne(() => User, { deleteRule: "restrict" }) @ManyToOne(() => User, { deleteRule: "restrict" })
user!: User; user!: User;
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
[EntityRepositoryType]?: CompanyRepository; [EntityRepositoryType]?: CompanyRepository;
} }
@@ -6,11 +6,12 @@ import { Company } from "../entities/company.entity";
export class CompanyRepository extends EntityRepository<Company> { export class CompanyRepository extends EntityRepository<Company> {
// //
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto) { async getCompaniesListForAdmin(queryDto: CompanyListQueryDto, businessId: string) {
const { limit, skip } = PaginationUtils(queryDto); const { limit, skip } = PaginationUtils(queryDto);
const where: FilterQuery<Company> = { const where: FilterQuery<Company> = {
deletedAt: null, deletedAt: null,
business: { id: businessId },
}; };
if (queryDto.status) { if (queryDto.status) {
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { CompanyMessage, IndustryMessage } from "../../../common/enums/message.enum"; import { CompanyMessage, IndustryMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { Industry } from "../../industries/entities/industry.entity"; 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";
@@ -25,7 +26,7 @@ export class CompaniesService {
private readonly em: EntityManager, private readonly em: EntityManager,
) {} ) {}
async create(createDto: CreateCompanyDto) { async create(createDto: CreateCompanyDto, business: Business) {
const em = this.em.fork(); const em = this.em.fork();
try { try {
await em.begin(); await em.begin();
@@ -34,8 +35,8 @@ export class CompaniesService {
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND); if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
const [existingCompany, existIdentityNumber] = await Promise.all([ const [existingCompany, existIdentityNumber] = await Promise.all([
em.findOne(Company, { name: createDto.name }), em.findOne(Company, { name: createDto.name, business: { id: business.id } }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber }), em.findOne(Company, { identificationNumber: createDto.identificationNumber, business: { id: business.id } }),
]); ]);
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME); if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
@@ -65,6 +66,7 @@ export class CompaniesService {
products, products,
services, services,
user, user,
business,
}); });
await em.persistAndFlush(company); await em.persistAndFlush(company);
@@ -82,14 +84,18 @@ export class CompaniesService {
//----------------------------------- //-----------------------------------
async updateCompanyById(id: string, updateDto: UpdateCompanyDto) { async updateCompanyById(id: string, updateDto: UpdateCompanyDto, businessId: string) {
const em = this.em.fork(); const em = this.em.fork();
try { try {
await em.begin(); await em.begin();
const { services, products, ...rest } = updateDto; const { services, products, ...rest } = updateDto;
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user", "services", "products"] }); const company = await em.findOne(
Company,
{ id, deletedAt: null, business: { id: businessId } },
{ populate: ["user", "services", "products"] },
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND); if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
const updatedCompany = em.assign(company, rest); const updatedCompany = em.assign(company, rest);
@@ -101,8 +107,14 @@ export class CompaniesService {
} }
if (updateDto.name) { if (updateDto.name) {
const existingCompany = await em.findOne(Company, { name: updateDto.name, deletedAt: null, id: { $ne: company.id } }); const existingCompany = await em.findOne(Company, {
name: updateDto.name,
deletedAt: null,
id: { $ne: company.id },
business: { id: businessId },
});
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME); if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
updatedCompany.name = updateDto.name;
} }
if (updateDto.identificationNumber) { if (updateDto.identificationNumber) {
@@ -110,8 +122,10 @@ export class CompaniesService {
identificationNumber: updateDto.identificationNumber, identificationNumber: updateDto.identificationNumber,
deletedAt: null, deletedAt: null,
id: { $ne: company.id }, id: { $ne: company.id },
business: { id: businessId },
}); });
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER); if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
updatedCompany.identificationNumber = updateDto.identificationNumber;
} }
const userUpdateData: Partial<User> = {}; const userUpdateData: Partial<User> = {};
@@ -174,15 +188,15 @@ export class CompaniesService {
//----------------------------------- //-----------------------------------
async getCompanyById(id: string) { async getCompanyById(id: string, businessId: string) {
const company = await this.findCompanyById(id); const company = await this.findCompanyById(id, businessId);
return { company }; return { company };
} }
//----------------------------------- //-----------------------------------
async getCompaniesList(queryDto: CompanyListQueryDto) { async getCompaniesList(queryDto: CompanyListQueryDto, businessId: string) {
const [companies, count] = await this.companyRepository.getCompaniesListForAdmin(queryDto); const [companies, count] = await this.companyRepository.getCompaniesListForAdmin(queryDto, businessId);
return { return {
companies, companies,
count, count,
@@ -192,8 +206,8 @@ export class CompaniesService {
//----------------------------------- //-----------------------------------
async deleteCompanyById(id: string) { async deleteCompanyById(id: string, businessId: string) {
const company = await this.findCompanyById(id); const company = await this.findCompanyById(id, businessId);
company.deletedAt = dayjs().toDate(); company.deletedAt = dayjs().toDate();
@@ -205,8 +219,8 @@ export class CompaniesService {
//----------------------------------- //-----------------------------------
async toggleStatus(id: string) { async toggleStatus(id: string, businessId: string) {
const company = await this.findCompanyById(id); const company = await this.findCompanyById(id, businessId);
company.isActive = !company.isActive; company.isActive = !company.isActive;
@@ -231,9 +245,9 @@ export class CompaniesService {
return company; return company;
} }
//----------------------------------- //-----------------------------------
private async findCompanyById(id: string) { private async findCompanyById(id: string, businessId: string) {
const company = await this.companyRepository.findOne( const company = await this.companyRepository.findOne(
{ id, deletedAt: null }, { id, deletedAt: null, business: { id: businessId } },
{ populate: ["user", "industry", "services", "products"] }, { populate: ["user", "industry", "services", "products"] },
); );
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND); if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
@@ -1,6 +1,7 @@
import { Collection, Entity, EntityRepositoryType, Formula, OneToMany, Opt, Property } from "@mikro-orm/core"; import { Collection, Entity, EntityRepositoryType, Formula, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity"; import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Company } from "../../companies/entities/company.entity"; import { Company } from "../../companies/entities/company.entity";
import { IndustryRepository } from "../repositories/industry.repository"; import { IndustryRepository } from "../repositories/industry.repository";
@@ -21,5 +22,8 @@ export class Industry extends BaseEntity {
@OneToMany(() => Company, (company) => company.industry) @OneToMany(() => Company, (company) => company.industry)
companies = new Collection<Company>(this); companies = new Collection<Company>(this);
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
[EntityRepositoryType]?: IndustryRepository; [EntityRepositoryType]?: IndustryRepository;
} }
+17 -13
View File
@@ -1,49 +1,53 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } 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 { CreateIndustryDto } from "./DTO/create-industry.dto"; import { CreateIndustryDto } from "./DTO/create-industry.dto";
import { IndustryListQueryDto } from "./DTO/industry-list-query.dto"; import { IndustryListQueryDto } from "./DTO/industry-list-query.dto";
import { UpdateIndustryDto } from "./DTO/update-industry.dto"; import { UpdateIndustryDto } from "./DTO/update-industry.dto";
import { IndustriesService } from "./services/industries.service"; import { IndustriesService } from "./services/industries.service";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { ParamDto } from "../../common/DTO/param.dto"; import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@Controller("industries") @Controller("industries")
@UseInterceptors(BusinessInterceptor)
export class IndustriesController { export class IndustriesController {
constructor(private readonly industriesService: IndustriesService) {} constructor(private readonly industriesService: IndustriesService) {}
@Post() @Post()
@ApiOperation({ summary: "create industry" }) @ApiOperation({ summary: "create industry" })
create(@Body() createIndustryDto: CreateIndustryDto) { create(@Body() createIndustryDto: CreateIndustryDto, @BusinessDec() business: Business) {
return this.industriesService.create(createIndustryDto); return this.industriesService.create(createIndustryDto, business);
} }
@Get(":id") @Get(":id")
@ApiOperation({ summary: "get industry by id" }) @ApiOperation({ summary: "get industry by id" })
getIndustryById(@Param() paramDto: ParamDto) { getIndustryById(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.getIndustriesById(paramDto.id); return this.industriesService.getIndustriesById(paramDto.id, businessId);
} }
@Patch(":id") @Patch(":id")
@ApiOperation({ summary: "update industry" }) @ApiOperation({ summary: "update industry" })
updateIndustry(@Param() paramDto: ParamDto, @Body() updateIndustryDto: UpdateIndustryDto) { updateIndustry(@Param() paramDto: ParamDto, @Body() updateIndustryDto: UpdateIndustryDto, @BusinessDec("id") businessId: string) {
return this.industriesService.updateIndustry(paramDto.id, updateIndustryDto); return this.industriesService.updateIndustry(paramDto.id, updateIndustryDto, businessId);
} }
@Delete(":id") @Delete(":id")
@ApiOperation({ summary: "delete industry" }) @ApiOperation({ summary: "delete industry" })
deleteIndustry(@Param() paramDto: ParamDto) { deleteIndustry(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.deleteIndustry(paramDto.id); return this.industriesService.deleteIndustry(paramDto.id, businessId);
} }
@Get("list") @Get("list")
@ApiOperation({ summary: "get industries list for admin" }) @ApiOperation({ summary: "get industries list for admin" })
getIndustriesForAdmin(@Query() query: IndustryListQueryDto) { getIndustriesForAdmin(@Query() query: IndustryListQueryDto, @BusinessDec("id") businessId: string) {
return this.industriesService.getIndustriesListForAdmin(query); return this.industriesService.getIndustriesListForAdmin(query, businessId);
} }
@Patch(":id/toggle-status") @Patch(":id/toggle-status")
@ApiOperation({ summary: "toggle industry status" }) @ApiOperation({ summary: "toggle industry status" })
toggleStatus(@Param() paramDto: ParamDto) { toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.toggleStatus(paramDto.id); return this.industriesService.toggleStatus(paramDto.id, businessId);
} }
} }
+2 -1
View File
@@ -1,12 +1,13 @@
import { MikroOrmModule } from "@mikro-orm/nestjs"; import { MikroOrmModule } from "@mikro-orm/nestjs";
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { BusinessesModule } from "../businesses/businesses.module";
import { Industry } from "./entities/industry.entity"; import { Industry } from "./entities/industry.entity";
import { IndustriesController } from "./industries.controller"; import { IndustriesController } from "./industries.controller";
import { IndustriesService } from "./services/industries.service"; import { IndustriesService } from "./services/industries.service";
@Module({ @Module({
imports: [MikroOrmModule.forFeature([Industry])], imports: [MikroOrmModule.forFeature([Industry]), BusinessesModule],
controllers: [IndustriesController], controllers: [IndustriesController],
providers: [IndustriesService], providers: [IndustriesService],
exports: [IndustriesService], exports: [IndustriesService],
@@ -6,10 +6,14 @@ import { Industry } from "../entities/industry.entity";
export class IndustryRepository extends EntityRepository<Industry> { export class IndustryRepository extends EntityRepository<Industry> {
// //
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto) { async getIndustriesListForAdmin(queryDto: IndustryListQueryDto, businessId: string) {
const { limit, skip } = PaginationUtils(queryDto); const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.createQueryBuilder("i").where({ deletedAt: null }).orderBy({ createdAt: "DESC" }).limit(limit).offset(skip); const queryBuilder = this.createQueryBuilder("i")
.where({ deletedAt: null, business: { id: businessId } })
.orderBy({ createdAt: "DESC" })
.limit(limit)
.offset(skip);
if (queryDto.q) { if (queryDto.q) {
queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } }); queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } });
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { IndustryMessage } from "../../../common/enums/message.enum"; import { IndustryMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { CreateIndustryDto } from "../DTO/create-industry.dto"; import { CreateIndustryDto } from "../DTO/create-industry.dto";
import { IndustryListQueryDto } from "../DTO/industry-list-query.dto"; import { IndustryListQueryDto } from "../DTO/industry-list-query.dto";
import { UpdateIndustryDto } from "../DTO/update-industry.dto"; import { UpdateIndustryDto } from "../DTO/update-industry.dto";
@@ -17,11 +18,14 @@ export class IndustriesService {
//----------------------------------- //-----------------------------------
async create(createIndustryDto: CreateIndustryDto) { async create(createIndustryDto: CreateIndustryDto, business: Business) {
const existingIndustry = await this.industryRepository.findOne({ title: createIndustryDto.title }); const existingIndustry = await this.industryRepository.findOne({
title: createIndustryDto.title,
business: { id: business.id },
});
if (existingIndustry) throw new BadRequestException(IndustryMessage.INDUSTRY_ALREADY_EXISTS); if (existingIndustry) throw new BadRequestException(IndustryMessage.INDUSTRY_ALREADY_EXISTS);
const industry = this.industryRepository.create(createIndustryDto); const industry = this.industryRepository.create({ ...createIndustryDto, business });
this.em.persistAndFlush(industry); this.em.persistAndFlush(industry);
return { return {
@@ -31,16 +35,16 @@ export class IndustriesService {
} }
//----------------------------------- //-----------------------------------
async getIndustriesById(id: string) { async getIndustriesById(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null }); 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);
return { industry }; return { industry };
} }
//----------------------------------- //-----------------------------------
async updateIndustry(id: string, updateIndustryDto: UpdateIndustryDto) { async updateIndustry(id: string, updateIndustryDto: UpdateIndustryDto, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null }); 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);
this.industryRepository.assign(industry, updateIndustryDto); this.industryRepository.assign(industry, updateIndustryDto);
@@ -54,8 +58,8 @@ export class IndustriesService {
//----------------------------------- //-----------------------------------
async deleteIndustry(id: string) { async deleteIndustry(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null }); 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);
industry.deletedAt = dayjs().toDate(); industry.deletedAt = dayjs().toDate();
@@ -68,14 +72,14 @@ export class IndustriesService {
} }
//----------------------------------- //-----------------------------------
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto) { async getIndustriesListForAdmin(queryDto: IndustryListQueryDto, businessId: string) {
const [industries, count] = await this.industryRepository.getIndustriesListForAdmin(queryDto); const [industries, count] = await this.industryRepository.getIndustriesListForAdmin(queryDto, businessId);
return { industries, count, paginate: true }; return { industries, count, paginate: true };
} }
//----------------------------------- //-----------------------------------
async toggleStatus(id: string) { async toggleStatus(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null }); 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);
industry.isActive = !industry.isActive; industry.isActive = !industry.isActive;