Files
bigcapital/packages/server/src/services/CreditNotes/CreditNoteApplyToInvoices.ts
2024-08-13 13:41:09 +02:00

136 lines
4.1 KiB
TypeScript

import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import { sumBy } from 'lodash';
import {
ICreditNote,
ICreditNoteAppliedToInvoice,
ICreditNoteAppliedToInvoiceModel,
ISaleInvoice,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import { PaymentReceivedValidators } from '../Sales/PaymentReceived/PaymentReceivedValidators';
import BaseCreditNotes from './CreditNotes';
import {
IApplyCreditToInvoicesDTO,
IApplyCreditToInvoicesCreatedPayload,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import events from '@/subscribers/events';
import { ERRORS } from './constants';
import HasTenancyService from '../Tenancy/TenancyService';
@Service()
export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private paymentReceiveValidators: PaymentReceivedValidators;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* Apply credit note to the given invoices.
* @param {number} tenantId
* @param {number} creditNoteId
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
*/
public applyCreditNoteToInvoices = async (
tenantId: number,
creditNoteId: number,
applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO
): Promise<ICreditNoteAppliedToInvoice[]> => {
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
// Saves the credit note or throw not found service error.
const creditNote = await this.getCreditNoteOrThrowError(
tenantId,
creditNoteId
);
// Retrieve the applied invoices that associated to the credit note customer.
const appliedInvoicesEntries =
await this.paymentReceiveValidators.validateInvoicesIDsExistance(
tenantId,
creditNote.customerId,
applyCreditToInvoicesDTO.entries
);
// Transformes apply DTO to model.
const creditNoteAppliedModel = this.transformApplyDTOToModel(
applyCreditToInvoicesDTO,
creditNote
);
// Validate invoices has remaining amount to apply.
this.validateInvoicesRemainingAmount(
appliedInvoicesEntries,
creditNoteAppliedModel.amount
);
// Validate the credit note remaining amount.
this.validateCreditRemainingAmount(
creditNote,
creditNoteAppliedModel.amount
);
// Creates credit note apply to invoice transaction.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Saves the credit note apply to invoice graph to the storage layer.
const creditNoteAppliedInvoices =
await CreditNoteAppliedInvoice.query().insertGraph(
creditNoteAppliedModel.entries
);
// Triggers `onCreditNoteApplyToInvoiceCreated` event.
await this.eventPublisher.emitAsync(
events.creditNote.onApplyToInvoicesCreated,
{
tenantId,
creditNote,
creditNoteAppliedInvoices,
trx,
} as IApplyCreditToInvoicesCreatedPayload
);
return creditNoteAppliedInvoices;
});
};
/**
* Transformes apply DTO to model.
* @param {IApplyCreditToInvoicesDTO} applyDTO
* @param {ICreditNote} creditNote
* @returns
*/
private transformApplyDTOToModel = (
applyDTO: IApplyCreditToInvoicesDTO,
creditNote: ICreditNote
): ICreditNoteAppliedToInvoiceModel => {
const entries = applyDTO.entries.map((entry) => ({
invoiceId: entry.invoiceId,
amount: entry.amount,
creditNoteId: creditNote.id,
}));
return {
amount: sumBy(entries, 'amount'),
entries,
};
};
/**
* Validate the invoice remaining amount.
* @param {ISaleInvoice[]} invoices
* @param {number} amount
*/
private validateInvoicesRemainingAmount = (
invoices: ISaleInvoice[],
amount: number
) => {
const invalidInvoices = invoices.filter(
(invoice) => invoice.dueAmount < amount
);
if (invalidInvoices.length > 0) {
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
}
};
}