chore: company and industy related logix

This commit is contained in:
Mahyargdz
2025-05-17 10:45:53 +03:30
parent 1efc3a2795
commit 479c9c390a
10 changed files with 117 additions and 77 deletions
@@ -1,5 +1,5 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsNotEmpty, IsString, IsUrl } from "class-validator";
import { IsNotEmpty, IsString, IsUrl } from "class-validator";
import { CompanyMessage } from "../../../common/enums/message.enum";
@@ -10,8 +10,7 @@ export class CreateCompanyProductDto {
title: string;
@IsNotEmpty({ message: CompanyMessage.PRODUCT_IMAGES_REQUIRED })
@IsArray({ message: CompanyMessage.PRODUCT_IMAGES_MUST_BE_ARRAY })
@IsUrl({ protocols: ["http", "https"] }, { each: true, message: CompanyMessage.PRODUCT_IMAGES_MUST_BE_URL })
@ApiProperty({ description: "تصاویر محصول", example: ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] })
imagesUrl: string[];
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.PRODUCT_IMAGES_MUST_BE_URL })
@ApiProperty({ description: "تصاویر محصول", example: "https://example.com/image1.jpg" })
imageUrl: string;
}
@@ -1,5 +1,5 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsNotEmpty, IsString, IsUrl } from "class-validator";
import { IsNotEmpty, IsString, IsUrl } from "class-validator";
import { CompanyMessage } from "../../../common/enums/message.enum";
@@ -10,8 +10,7 @@ export class CreateCompanyServiceDto {
title: string;
@IsNotEmpty({ message: CompanyMessage.SERVICE_IMAGES_REQUIRED })
@IsArray({ message: CompanyMessage.SERVICE_IMAGES_MUST_BE_ARRAY })
@IsUrl({ protocols: ["http", "https"] }, { each: true, message: CompanyMessage.SERVICE_IMAGES_MUST_BE_URL })
@ApiProperty({ description: "تصاویر خدمت", example: ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] })
imagesUrl: string[];
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.SERVICE_IMAGES_MUST_BE_URL })
@ApiProperty({ description: "تصاویر خدمت", example: "https://example.com/image1.jpg" })
imageUrl: string;
}
@@ -8,8 +8,8 @@ export class CompanyProduct extends BaseEntity {
@Property({ type: "varchar", length: 250, nullable: false })
title!: string;
@Property({ type: "array", nullable: false })
imagesUrl!: string[];
@Property({ type: "varchar", length: 250, nullable: false })
imageUrl!: string;
@ManyToOne(() => Company, { deleteRule: "cascade" })
company!: Company;
@@ -8,8 +8,8 @@ export class CompanyService extends BaseEntity {
@Property({ type: "varchar", length: 250, nullable: false })
title!: string;
@Property({ type: "array", nullable: false })
imagesUrl!: string[];
@Property({ type: "varchar", length: 250, nullable: false })
imageUrl!: string;
@ManyToOne(() => Company, { deleteRule: "cascade" })
company!: Company;
@@ -1,4 +1,4 @@
import { Cascade, Collection, Entity, EntityRepositoryType, Enum, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
import { Cascade, Collection, Entity, EntityRepositoryType, Enum, Formula, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
import { CompanyProduct } from "./company-product.entity";
import { CompanyService } from "./company-service.entity";
@@ -52,9 +52,12 @@ export class Company extends BaseEntity {
@OneToMany(() => CompanyService, (service) => service.company, { cascade: [Cascade.ALL] })
services = new Collection<CompanyService>(this);
@OneToMany(() => Invoice, (invoice) => invoice.company)
@OneToMany(() => Invoice, (invoice) => invoice.company, { cascade: [Cascade.ALL] })
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" })
@@ -1,4 +1,4 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { CompanyListQueryDto } from "../DTO/company-list-query.dto";
@@ -9,57 +9,52 @@ export class CompanyRepository extends EntityRepository<Company> {
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const qb = this.createQueryBuilder("company")
.select([
"company.id",
"company.name",
"company.status",
"company.isActive",
"company.createdAt",
const where: FilterQuery<Company> = {
deletedAt: null,
};
if (queryDto.status) {
where.status = queryDto.status;
}
if (queryDto.isActive !== undefined) {
where.isActive = queryDto.isActive === 1;
}
if (queryDto.q) {
where.name = { $ilike: `%${queryDto.q}%` };
}
if (queryDto.industryId) {
where.industry = queryDto.industryId;
}
if (queryDto.registrationDate) {
where.createdAt = {
$gte: queryDto.registrationDate,
$lte: queryDto.registrationDate,
};
}
if (queryDto.name) {
where.name = { $ilike: `%${queryDto.name}%` };
}
return this.findAndCount(where, {
populate: ["industry", "user"],
limit,
offset: skip,
orderBy: { createdAt: "DESC" },
fields: [
"id",
"name",
"isActive",
"dateOfEstablishment",
"status",
"invoiceCount",
"createdAt",
"industry.id",
"industry.title",
"user.id",
"user.firstName",
"user.lastName",
"(SELECT COUNT(*) FROM invoice WHERE invoice.company_id = company.id) as invoiceCount",
])
.leftJoin("company.industry", "industry")
.leftJoin("company.user", "user")
.where({
deletedAt: null,
...(queryDto.status && { status: queryDto.status }),
...(queryDto.isActive !== undefined && { isActive: queryDto.isActive === 1 }),
});
if (queryDto.q) {
qb.andWhere({ name: { $ilike: `%${queryDto.q}%` } });
}
if (queryDto.industryId) {
qb.andWhere({ industry: { id: queryDto.industryId } });
}
if (queryDto.registrationDate) {
qb.andWhere({
createdAt: {
$gte: queryDto.registrationDate,
$lte: queryDto.registrationDate,
},
});
}
if (queryDto.name) {
qb.andWhere({ name: { $ilike: `%${queryDto.name}%` } });
}
qb.orderBy({ createdAt: "DESC" }).limit(limit).offset(skip);
return qb.getResultAndCount();
// // Transform the result to include invoiceCount as a number
// const transformedCompanies = companies.map((company) => ({
// ...company,
// invoiceCount: parseInt(company.invoiceCount, 10),
// }));
"user.fullName",
],
});
}
}
@@ -47,14 +47,14 @@ export class CompaniesService {
const products = createDto.products.map((productData) => {
const product = new CompanyProduct();
product.title = productData.title;
product.imagesUrl = productData.imagesUrl;
product.imageUrl = productData.imageUrl;
return product;
});
const services = createDto.services.map((serviceData) => {
const service = new CompanyService();
service.title = serviceData.title;
service.imagesUrl = serviceData.imagesUrl;
service.imageUrl = serviceData.imageUrl;
return service;
});
@@ -87,10 +87,12 @@ export class CompaniesService {
try {
await em.begin();
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user"] });
const { services, products, ...rest } = updateDto;
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user", "services", "products"] });
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
const updatedCompany = em.assign(company, updateDto);
const updatedCompany = em.assign(company, rest);
if (updateDto.industryId) {
const industry = await em.findOne(Industry, { id: updateDto.industryId, deletedAt: null });
@@ -121,12 +123,48 @@ export class CompaniesService {
if (Object.keys(userUpdateData).length > 0) await this.usersService.updateUser(company.user.id, userUpdateData, em);
if (services && services.length > 0) {
// Remove all old services from the database
for (const oldService of company.services.getItems()) {
em.remove(oldService);
}
await em.flush(); // flush removals before adding new
// Add new services
for (const service of services) {
const serviceEntity = em.create(CompanyService, {
title: service.title,
imageUrl: service.imageUrl,
company: company,
});
updatedCompany.services.add(serviceEntity);
}
}
if (products && products.length > 0) {
// Remove all old products from the database
for (const oldProduct of company.products.getItems()) {
em.remove(oldProduct);
}
await em.flush(); // flush removals before adding new
// Add new products
for (const product of products) {
const productEntity = em.create(CompanyProduct, {
title: product.title,
imageUrl: product.imageUrl,
company: company,
});
updatedCompany.products.add(productEntity);
}
}
await em.flush();
await em.commit();
return {
message: CompanyMessage.UPDATED_SUCCESSFULLY,
updatedCompany,
company: updatedCompany.name,
};
} catch (error) {
await em.rollback();
@@ -194,7 +232,10 @@ export class CompaniesService {
}
//-----------------------------------
private async findCompanyById(id: string) {
const company = await this.companyRepository.findOne({ id, deletedAt: null });
const company = await this.companyRepository.findOne(
{ id, deletedAt: null },
{ populate: ["user", "industry", "services", "products"] },
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
return company;
}
@@ -1,4 +1,4 @@
import { Collection, Entity, EntityRepositoryType, OneToMany, Opt, Property } from "@mikro-orm/core";
import { Collection, Entity, EntityRepositoryType, Formula, OneToMany, Opt, Property } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Company } from "../../companies/entities/company.entity";
@@ -15,6 +15,9 @@ export class Industry extends BaseEntity {
@Property({ type: "boolean", default: true })
isActive!: boolean & Opt;
@Formula((alias) => `(SELECT COUNT(*)::int FROM company c WHERE c.industry_id = ${alias}.id)`, { persist: false })
companiesCount?: number;
@OneToMany(() => Company, (company) => company.industry)
companies = new Collection<Company>(this);
@@ -9,12 +9,7 @@ export class IndustryRepository extends EntityRepository<Industry> {
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.createQueryBuilder("i")
.leftJoinAndSelect("i.companies", "c")
.where({ deletedAt: null })
.orderBy({ createdAt: "DESC" })
.limit(limit)
.offset(skip);
const queryBuilder = this.createQueryBuilder("i").where({ deletedAt: null }).orderBy({ createdAt: "DESC" }).limit(limit).offset(skip);
if (queryDto.q) {
queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } });
@@ -38,6 +38,11 @@ export class User extends BaseEntity {
@Property({ type: "boolean", default: false })
emailVerified!: boolean & Opt;
@Property({ persist: false })
get fullName(): string | null {
return `${this.firstName} ${this.lastName}`;
}
//-----------------------------------
@ManyToOne(() => Role, { deleteRule: "restrict" })