chore: add company update
This commit is contained in:
@@ -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) {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user