feat: add delete route for the invoice
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user