feat: add delete route for the invoice

This commit is contained in:
mahyargdz
2025-08-21 11:35:51 +03:30
parent 6c1f8d6358
commit 1d76138ead
5 changed files with 161 additions and 4 deletions
+1
View File
@@ -561,6 +561,7 @@ export const enum InvoiceMessage {
DANAK_SUBSCRIPTION_NOT_FOUND = "خرید داناک با این شناسه یافت نشد",
BUSINESS_ID_REQUIRED = "شناسه کسب و کار مورد نیاز است",
BUSINESS_ID_SHOULD_BE_A_STRING = "شناسه کسب و کار باید یک رشته باشد",
DELETED = "صورت حساب با موفقیت حذف شد",
}
export const enum LearningMessage {
+8 -1
View File
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { ApplyDiscountDto } from "./DTO/apply-discount.dto";
@@ -34,6 +34,13 @@ export class InvoicesController {
return this.invoiceService.updateInvoiceAdmin(paramDto.id, updateDto);
}
@ApiOperation({ summary: "delete an invoice ==> admin route" })
@PermissionsDec(PermissionEnum.INVOICES)
@Delete(":id")
deleteInvoice(@Param() paramDto: ParamDto) {
return this.invoiceService.deleteInvoiceAdmin(paramDto.id);
}
@ApiOperation({ summary: "get all invoices ==> admin route" })
@PermissionsDec(PermissionEnum.INVOICES)
@Get()
@@ -202,6 +202,105 @@ export class InvoicesService {
await queryRunner.release();
}
}
//********************************** */
async deleteInvoiceAdmin(invoiceId: string) {
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const invoice = await queryRunner.manager.findOne(this.invoiceRepository.target, {
where: { id: invoiceId },
});
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
this.logger.log(`Deleting invoice: ${invoiceId} (numeric ID: ${invoice.numericId}, status: ${invoice.status})`);
this.logger.log(`Starting cleanup of queue jobs for invoice: ${invoiceId}`);
await this.cleanupInvoiceQueueJobs(invoiceId);
this.logger.log(`Completed cleanup of queue jobs for invoice: ${invoiceId}`);
await queryRunner.manager.remove(this.invoiceRepository.target, invoice);
await queryRunner.commitTransaction();
return {
message: InvoiceMessage.DELETED,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
/**
* Clean up all queue jobs related to a specific invoice
*/
private async cleanupInvoiceQueueJobs(invoiceId: string): Promise<void> {
try {
this.logger.log(`Starting cleanup of queue jobs for invoice: ${invoiceId}`);
this.logger.log(`Removing recurring invoice jobs for invoice: ${invoiceId}`);
await this.removeRecurringInvoiceJobs(invoiceId);
this.logger.log(`Removing reminder invoice jobs for invoice: ${invoiceId}`);
await this.removeReminderInvoiceJobs(invoiceId);
this.logger.log(`Removing admin notification jobs for invoice: ${invoiceId}`);
await this.removeAdminNotificationJobs(invoiceId);
this.logger.log(`Checking for additional main queue jobs for invoice: ${invoiceId}`);
const mainQueueJobs = await this.invoiceQueue.getJobs(["active", "waiting", "delayed", "failed", "completed"]);
let mainJobsRemoved = 0;
for (const job of mainQueueJobs) {
const jobData = job.data;
if (jobData && (jobData.invoiceId === invoiceId || (jobData.invoice && jobData.invoice.id === invoiceId))) {
if (
job.name !== INVOICE.RECURRING_JOB_NAME &&
job.name !== INVOICE.REMINDER_JOB_NAME &&
job.name !== INVOICE.SUBSCRIPTION_ADMIN_NOTIFICATION_JOB_NAME
) {
await job.remove();
mainJobsRemoved++;
this.logger.log(`Removed additional invoice queue job: ${job.name} (ID: ${job.id}) for invoice: ${invoiceId}`);
}
}
}
this.logger.log(`Checking for external invoice queue jobs for invoice: ${invoiceId}`);
const externalQueueJobs = await this.externalInvoiceQueue.getJobs(["active", "waiting", "delayed", "failed", "completed"]);
let externalJobsRemoved = 0;
for (const job of externalQueueJobs) {
const jobData = job.data;
if (
jobData &&
(jobData.invoiceId === invoiceId ||
(jobData.invoice && jobData.invoice.id === invoiceId) ||
(jobData.invoice && jobData.invoice.numericId === invoiceId))
) {
await job.remove();
externalJobsRemoved++;
this.logger.log(`Removed external invoice queue job: ${job.name} (ID: ${job.id}) for invoice: ${invoiceId}`);
}
}
this.logger.log(
`Successfully cleaned up queue jobs for invoice: ${invoiceId}. Main jobs removed: ${mainJobsRemoved}, External jobs removed: ${externalJobsRemoved}`,
);
} catch (error) {
this.logger.error(`Error cleaning up queue jobs for invoice ${invoiceId}:`, error);
// don't throw error here to avoid blocking invoice deletion
}
}
//********************************** */
async approveInvoiceRequest(invoiceId: string, userId: string) {
@@ -922,6 +1021,7 @@ export class InvoicesService {
//
await this.removeRecurringInvoiceJobs(invoice.id);
await this.removeReminderInvoiceJobs(invoice.id);
await this.removeAdminNotificationJobs(invoice.id);
if (updateDto.isRecurring) {
if (!updateDto.recurringPeriod) throw new BadRequestException(InvoiceMessage.RECURRING_PERIOD_REQUIRED);
@@ -964,21 +1064,52 @@ export class InvoicesService {
private async removeRecurringInvoiceJobs(invoiceId: string) {
const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]);
let removedCount = 0;
for (const job of existingJobs) {
if (job.name === INVOICE.RECURRING_JOB_NAME && job.data.invoiceId === invoiceId) {
if (job.name === INVOICE.RECURRING_JOB_NAME && job.data?.invoiceId === invoiceId) {
await job.remove();
removedCount++;
}
}
if (removedCount > 0) {
this.logger.log(`Removed ${removedCount} recurring invoice jobs for invoice: ${invoiceId}`);
}
}
//*********************************** */
private async removeReminderInvoiceJobs(invoiceId: string) {
const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]);
let removedCount = 0;
for (const job of existingJobs) {
if (job.name === INVOICE.REMINDER_JOB_NAME && job.data.invoiceId === invoiceId) {
if (job.name === INVOICE.REMINDER_JOB_NAME && job.data?.invoiceId === invoiceId) {
await job.remove();
removedCount++;
}
}
if (removedCount > 0) {
this.logger.log(`Removed ${removedCount} reminder invoice jobs for invoice: ${invoiceId}`);
}
}
//*********************************** */
private async removeAdminNotificationJobs(invoiceId: string) {
const existingJobs = await this.invoiceQueue.getJobs(["delayed", "waiting", "active"]);
let removedCount = 0;
for (const job of existingJobs) {
if (job.name === INVOICE.SUBSCRIPTION_ADMIN_NOTIFICATION_JOB_NAME && job.data?.invoiceId === invoiceId) {
await job.remove();
removedCount++;
}
}
if (removedCount > 0) {
this.logger.log(`Removed ${removedCount} admin notification jobs for invoice: ${invoiceId}`);
}
}
///********************************** */
@@ -367,6 +367,18 @@ export class SubscriptionsService {
return { workspaces: userSubscriptions };
}
//************************************ */
async checkUserAccessToService(userId: string, serviceId: string) {
const userSubscriptions = await this.userSubscriptionsRepository.findOne({
where: { user: { id: userId }, plan: { service: { id: serviceId } }, status: SubscriptionStatus.ACTIVE },
relations: {
plan: { service: true },
staff: true,
},
});
return userSubscriptions ? true : false;
}
//************************************ */
async addProvisioningJob(userSubscription: UserSubscription, plan: SubscriptionPlan, user: User) {
this.logger.debug(`Adding provisioning job for user ${user.id} and subscription ${userSubscription.id}`);
@@ -94,9 +94,15 @@ export class SubscriptionsController {
return this.userQuickAccessService.removeQuickAccess(userId, paramDto.id);
}
@ApiOperation({ summary: "check user access to a service" })
@Get("check-access/:serviceId")
checkUserAccessToService(@Param() serviceIdParamDto: ServiceIdParamDto, @UserDec("id") userId: string) {
return this.subscriptionService.checkUserAccessToService(userId, serviceIdParamDto.serviceId);
}
@ApiOperation({ summary: "Check if user has access to a service and return all their workspaces for that service (internal use)" })
@Get("workspaces/:serviceId")
async getUserServiceAccess(@Param() serviceIdParamDto: ServiceIdParamDto, @UserDec("id") userId: string) {
getUserServiceAccess(@Param() serviceIdParamDto: ServiceIdParamDto, @UserDec("id") userId: string) {
return this.subscriptionService.getUserSubscriptionsForService(userId, serviceIdParamDto.serviceId);
}
}