chore: add company update

This commit is contained in:
mahyargdz
2025-06-01 15:19:19 +03:30
parent d6650bafd9
commit b0d76d1cd6
7 changed files with 161 additions and 51 deletions
@@ -1,5 +1,7 @@
import { PartialType } from "@nestjs/swagger";
import { CreateCompanyDto } from "./create-company.dto";
import { CreateCompanyDto, CreateCompanyRequestDto } from "./create-company.dto";
export class UpdateCompanyDto extends PartialType(CreateCompanyDto) {}
export class UpdateUserCompanyDto extends PartialType(CreateCompanyRequestDto) {}
+13 -1
View File
@@ -3,7 +3,7 @@ import { ApiOperation } from "@nestjs/swagger";
import { CompanyListPublicQueryDto, CompanyListQueryDto } from "./DTO/company-list-query.dto";
import { ChangeCompanyRequestStatusDto, CreateCompanyDto, CreateCompanyRequestDto } from "./DTO/create-company.dto";
import { UpdateCompanyDto } from "./DTO/update-company.dto";
import { UpdateCompanyDto, UpdateUserCompanyDto } from "./DTO/update-company.dto";
import { CompaniesService } from "./services/companies.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator";
@@ -105,6 +105,18 @@ export class CompaniesController {
return this.companiesService.getCompanyRequestsUser(userId, businessId);
}
@Patch(":id/user")
@AuthGuards()
@ApiOperation({ summary: "update company (user)" })
updateCompanyByUser(
@Param() paramDto: ParamDto,
@Body() updateDto: UpdateUserCompanyDto,
@UserDec("id") userId: string,
@BusinessDec("id") businessId: string,
) {
return this.companiesService.updateCompanyByUser(paramDto.id, updateDto, userId, businessId);
}
@Get("requests")
@AuthGuards()
@ApiOperation({ summary: "get all company registration requests (admin)" })
@@ -59,13 +59,13 @@ export class Company extends BaseEntity {
//-----------------------------------
@OneToMany(() => CompanyProduct, (product) => product.company, { cascade: [Cascade.ALL] })
@OneToMany(() => CompanyProduct, (product) => product.company, { cascade: [Cascade.ALL], orphanRemoval: true })
products = new Collection<CompanyProduct>(this);
@OneToMany(() => CompanyService, (service) => service.company, { cascade: [Cascade.ALL] })
@OneToMany(() => CompanyService, (service) => service.company, { cascade: [Cascade.ALL], orphanRemoval: true })
services = new Collection<CompanyService>(this);
@OneToMany(() => Invoice, (invoice) => invoice.company, { cascade: [Cascade.ALL] })
@OneToMany(() => Invoice, (invoice) => invoice.company, { cascade: [Cascade.ALL], orphanRemoval: true })
invoices = new Collection<Invoice>(this);
//-----------------------------------
@@ -11,7 +11,7 @@ import { UsersService } from "../../users/services/users.service";
import { PasswordService } from "../../utils/providers/password.service";
import { CompanyListPublicQueryDto, CompanyListQueryDto } from "../DTO/company-list-query.dto";
import { ChangeCompanyRequestStatusDto, CreateCompanyDto, CreateCompanyRequestDto } from "../DTO/create-company.dto";
import { UpdateCompanyDto } from "../DTO/update-company.dto";
import { UpdateCompanyDto, UpdateUserCompanyDto } from "../DTO/update-company.dto";
import { CompanyProduct } from "../entities/company-product.entity";
import { CompanyRequest } from "../entities/company-request.enitiy";
import { CompanyService } from "../entities/company-service.entity";
@@ -110,39 +110,25 @@ 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);
}
company.services.removeAll();
const servicesData = services.map((serviceData) => {
const service = new CompanyService();
service.title = serviceData.title;
service.imageUrl = serviceData.imageUrl;
return service;
});
company.services.set(servicesData);
}
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);
}
company.products.removeAll();
const productsData = products.map((productData) => {
const product = new CompanyProduct();
product.title = productData.title;
product.imageUrl = productData.imageUrl;
return product;
});
company.products.set(productsData);
}
await em.flush();
@@ -171,7 +157,7 @@ export class CompaniesService {
{ id, deletedAt: null, isActive: true, status: CompanyStatus.APPROVED, business: { id: businessId } },
{
populate: ["industry", "user", "products", "services"],
fields: ["*", "industry.id", "industry.title", "user.id", "user.firstName", "user.lastName", "user.phone", "user.email"],
fields: ["*", "industry.id", "industry.title"],
},
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
@@ -228,7 +214,7 @@ export class CompaniesService {
{ user: { id: userId }, business: { id: businessId }, deletedAt: null },
{
populate: ["industry", "user", "products", "services"],
fields: ["*", "industry.id", "industry.title", "user.id", "user.firstName", "user.lastName", "user.phone", "user.email"],
fields: ["*", "industry.id", "industry.title"],
},
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
@@ -297,6 +283,62 @@ export class CompaniesService {
}
//-----------------------------------
async updateCompanyByUser(id: string, updateDto: UpdateUserCompanyDto, userId: string, businessId: string) {
const em = this.em.fork();
try {
await em.begin();
const { services, products, ...rest } = updateDto;
const company = await em.findOne(Company, { id, business: { id: businessId }, user: { id: userId }, deletedAt: null });
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
const [existingCompany, existIdentityNumber, existingEmail, existingPhone] = await Promise.all([
em.findOne(Company, { name: updateDto.name, business: { id: businessId }, id: { $ne: company.id } }),
em.findOne(Company, { identificationNumber: updateDto.identificationNumber, business: { id: businessId }, id: { $ne: company.id } }),
em.findOne(Company, { email: updateDto.email, business: { id: businessId }, id: { $ne: company.id } }),
em.findOne(Company, { phone: updateDto.phone, business: { id: businessId }, id: { $ne: company.id } }),
]);
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
if (existIdentityNumber) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
if (existingEmail) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_EMAIL);
if (existingPhone) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_PHONE);
em.assign(company, rest);
if (updateDto.services && updateDto.services.length > 0) {
company.services.removeAll();
const services = updateDto.services.map((serviceData) => {
const service = new CompanyService();
service.title = serviceData.title;
service.imageUrl = serviceData.imageUrl;
return service;
});
company.services.set(services);
}
if (updateDto.products && updateDto.products.length > 0) {
company.products.removeAll();
const products = updateDto.products.map((productData) => {
const product = new CompanyProduct();
product.title = productData.title;
product.imageUrl = productData.imageUrl;
return product;
});
company.products.set(products);
}
em.persist(company);
await em.flush();
await em.commit();
return { message: CompanyMessage.UPDATED_SUCCESSFULLY, companyId: company.id };
} catch (error) {
await em.rollback();
throw error;
}
}
//-----------------------------------
async getCompanyRequestById(id: string, role: RoleEnum, userId: string, businessId: string) {
let request = null;
@@ -358,7 +400,7 @@ export class CompaniesService {
request.company.status = CompanyStatus.APPROVED;
}
await em.persistAndFlush(request);
em.persist(request);
await em.flush();
await em.commit();
return { message: CompanyMessage.UPDATED_SUCCESSFULLY, status: request.requestStatus };
@@ -373,13 +415,17 @@ export class CompaniesService {
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([
const [existingCompany, existIdentityNumber, existingEmail, existingPhone] = await Promise.all([
em.findOne(Company, { name: createDto.name, business: { id: business.id } }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber, business: { id: business.id } }),
em.findOne(Company, { email: createDto.email, business: { id: business.id } }),
em.findOne(Company, { phone: createDto.phone, 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);
if (existingEmail) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_EMAIL);
if (existingPhone) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_PHONE);
const products = createDto.products.map((productData) => {
const product = new CompanyProduct();
@@ -58,7 +58,7 @@ export class Invoice extends BaseEntity {
//-----------------------------------
@OneToMany(() => InvoiceItem, (item) => item.invoice, { cascade: [Cascade.PERSIST, Cascade.REMOVE] })
@OneToMany(() => InvoiceItem, (item) => item.invoice, { cascade: [Cascade.PERSIST, Cascade.REMOVE], orphanRemoval: true })
items = new Collection<InvoiceItem>(this);
@OneToMany(() => Payment, (payment) => payment.invoice, { cascade: [Cascade.PERSIST, Cascade.REMOVE] })
@@ -1,7 +1,7 @@
export enum RecurringPeriodEnum {
WEEKLY = 1,
MONTHLY,
QUARTERLY,
BIANNUALLY,
ANNUALLY,
WEEKLY = 1, // هفتگی
MONTHLY, // ماهانه
QUARTERLY, // سه‌ماهه
SEMIANNUALLY, // شش‌ماهه
ANNUALLY, // سالانه
}
@@ -131,8 +131,9 @@ export class InvoicesService {
if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_UPDATE);
if (updateDto.items) {
this.validateInvoiceItems(updateDto.items);
// Remove existing items
await em.nativeDelete(InvoiceItem, { invoice: invoice.id });
invoice.items.removeAll();
// Create new items
const invoiceItems = updateDto.items.map((item) => {
@@ -161,6 +162,15 @@ export class InvoicesService {
invoice.company = company;
}
if (updateDto.isRecurring) {
this.validateRecurringInvoice(updateDto);
await this.updateScheduleInvoiceJobs(invoice, updateDto);
invoice.isRecurring = updateDto.isRecurring;
invoice.recurringPeriod = updateDto.recurringPeriod;
invoice.maxRecurringCycles = updateDto.maxRecurringCycles ? updateDto.maxRecurringCycles : invoice.maxRecurringCycles;
}
// Save the updated invoice
await em.persistAndFlush(invoice);
@@ -443,7 +453,7 @@ export class InvoicesService {
case RecurringPeriodEnum.QUARTERLY:
delayMs = dayjs().add(3, "month").subtract(7, "day").diff(dayjs());
break;
case RecurringPeriodEnum.BIANNUALLY:
case RecurringPeriodEnum.SEMIANNUALLY:
delayMs = dayjs().add(6, "month").subtract(7, "day").diff(dayjs());
break;
case RecurringPeriodEnum.ANNUALLY:
@@ -458,7 +468,7 @@ export class InvoicesService {
}
//*********************************** */
private async scheduleInvoiceJobs(invoice: Invoice, createDto?: CreateInvoiceDto) {
private async scheduleInvoiceJobs(invoice: Invoice, createDto?: CreateInvoiceDto | UpdateInvoiceDto) {
// Add to queue for billing reminder
await this.invoiceQueue.add(
INVOICE.REMINDER_JOB_NAME,
@@ -489,7 +499,7 @@ export class InvoicesService {
},
);
this.logger.log(`Scheduled recurring invoice for user ${createDto.companyId} with interval ${createDto.recurringPeriod}`);
this.logger.log(`Scheduled recurring invoice for company ${invoice.company.id} with interval ${createDto.recurringPeriod}`);
}
}
//*********************************** */
@@ -507,7 +517,7 @@ export class InvoicesService {
}
///********************************** */
private validateRecurringInvoice(createDto: CreateInvoiceDto): void {
private validateRecurringInvoice(createDto: CreateInvoiceDto | UpdateInvoiceDto): void {
if (createDto.isRecurring) {
if (!createDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED);
@@ -516,4 +526,44 @@ export class InvoicesService {
}
}
}
//*********************************** */
private async updateScheduleInvoiceJobs(invoice: Invoice, updateDto: UpdateInvoiceDto) {
//
await this.removeRecurringInvoiceJobs(invoice.id);
await this.removeReminderInvoiceJobs(invoice.id);
if (updateDto.isRecurring) {
if (!updateDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED);
if (updateDto.maxRecurringCycles && updateDto.maxRecurringCycles < 1) {
throw new BadRequestException(InvoiceMessage.MAX_RECURRING_CYCLES_MUST_BE_POSITIVE);
}
await this.scheduleInvoiceJobs(invoice, updateDto);
this.logger.log(`Updated recurring invoice for company ${updateDto.companyId} with interval ${updateDto.recurringPeriod}`);
}
}
//*********************************** */
private async removeRecurringInvoiceJobs(invoiceId: string) {
const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]);
for (const job of existingJobs) {
if (job.name === INVOICE.RECURRING_JOB_NAME && job.data.invoiceId === invoiceId) {
await job.remove();
}
}
}
//*********************************** */
private async removeReminderInvoiceJobs(invoiceId: string) {
const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]);
for (const job of existingJobs) {
if (job.name === INVOICE.REMINDER_JOB_NAME && job.data.invoiceId === invoiceId) {
await job.remove();
}
}
}
}