Files
bigcapital/packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts

163 lines
4.9 KiB
TypeScript

import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import {
ISystemUser,
ISaleInvoiceDeletePayload,
ISaleInvoiceDeletedPayload,
ISaleInvoiceDeletingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import { UnlinkConvertedSaleEstimate } from '../Estimates/UnlinkConvertedSaleEstimate';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class DeleteSaleInvoice {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private unlockEstimateFromInvoice: UnlinkConvertedSaleEstimate;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private uow: UnitOfWork;
/**
* Validate the sale invoice has no payment entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
*/
private async validateInvoiceHasNoPaymentEntries(
tenantId: number,
saleInvoiceId: number
) {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
// Retrieve the sale invoice associated payment receive entries.
const entries = await PaymentReceiveEntry.query().where(
'invoice_id',
saleInvoiceId
);
if (entries.length > 0) {
throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES);
}
return entries;
}
/**
* Validate the sale invoice has no applied to credit note transaction.
* @param {number} tenantId
* @param {number} invoiceId
* @returns {Promise<void>}
*/
public validateInvoiceHasNoAppliedToCredit = async (
tenantId: number,
invoiceId: number
): Promise<void> => {
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
const appliedTransactions = await CreditNoteAppliedInvoice.query().where(
'invoiceId',
invoiceId
);
if (appliedTransactions.length > 0) {
throw new ServiceError(ERRORS.SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES);
}
};
/**
* Validate whether sale invoice exists on the storage.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
private async getInvoiceOrThrowError(
tenantId: number,
saleInvoiceId: number
) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoice = await saleInvoiceRepository.findOneById(saleInvoiceId, [
'entries',
'paymentMethods',
]);
if (!saleInvoice) {
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
}
return saleInvoice;
}
/**
* Deletes the given sale invoice with associated entries
* and journal transactions.
* @param {number} tenantId - Tenant id.
* @param {Number} saleInvoiceId - The given sale invoice id.
* @param {ISystemUser} authorizedUser -
*/
public async deleteSaleInvoice(
tenantId: number,
saleInvoiceId: number,
authorizedUser: ISystemUser
): Promise<void> {
const { ItemEntry, SaleInvoice } = this.tenancy.models(tenantId);
// Retrieve the given sale invoice with associated entries
// or throw not found error.
const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId,
saleInvoiceId
);
// Validate the sale invoice has no associated payment entries.
await this.validateInvoiceHasNoPaymentEntries(tenantId, saleInvoiceId);
// Validate the sale invoice has applied to credit note transaction.
await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId);
// Triggers `onSaleInvoiceDelete` event.
await this.eventPublisher.emitAsync(events.saleInvoice.onDelete, {
tenantId,
oldSaleInvoice,
saleInvoiceId,
} as ISaleInvoiceDeletePayload);
// Deletes sale invoice transaction and associate transactions with UOW env.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onSaleInvoiceDeleting` event.
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, {
tenantId,
oldSaleInvoice,
saleInvoiceId,
trx,
} as ISaleInvoiceDeletingPayload);
// Unlink the converted sale estimates from the given sale invoice.
await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice(
tenantId,
saleInvoiceId,
trx
);
await ItemEntry.query(trx)
.where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice')
.delete();
await SaleInvoice.query(trx).findById(saleInvoiceId).delete();
// Triggers `onSaleInvoiceDeleted` event.
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleted, {
tenantId,
oldSaleInvoice,
saleInvoiceId,
authorizedUser,
trx,
} as ISaleInvoiceDeletedPayload);
});
}
}