mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-25 00:59:50 +00:00
145 lines
5.3 KiB
TypeScript
145 lines
5.3 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
import { Knex } from 'knex';
|
|
import { sumBy } from 'lodash';
|
|
import {
|
|
ICreditNoteAppliedToInvoice,
|
|
ICreditNoteAppliedToInvoiceModel,
|
|
IApplyCreditToInvoicesDTO,
|
|
IApplyCreditToInvoicesCreatedPayload,
|
|
} from '../types/CreditNoteApplyInvoice.types';
|
|
import { ERRORS } from '../../CreditNotes/constants';
|
|
import { PaymentReceivedValidators } from '@/modules/PaymentReceived/commands/PaymentReceivedValidators.service';
|
|
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
import { events } from '@/common/events/events';
|
|
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
|
import { ServiceError } from '@/modules/Items/ServiceError';
|
|
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
|
|
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
|
|
import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service';
|
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|
import { ApplyCreditNoteToInvoicesDto } from '../dtos/ApplyCreditNoteToInvoices.dto';
|
|
|
|
@Injectable()
|
|
export class CreditNoteApplyToInvoices {
|
|
/**
|
|
* @param {PaymentReceivedValidators} paymentReceiveValidators - The payment received validators service.
|
|
* @param {UnitOfWork} uow - The unit of work service.
|
|
* @param {EventEmitter2} eventPublisher - The event emitter service.
|
|
* @param {typeof CreditNoteAppliedInvoice} creditNoteAppliedInvoiceModel - The credit note applied invoice model.
|
|
*/
|
|
constructor(
|
|
private readonly paymentReceiveValidators: PaymentReceivedValidators,
|
|
private readonly uow: UnitOfWork,
|
|
private readonly eventPublisher: EventEmitter2,
|
|
private readonly creditNoteDTOTransform: CommandCreditNoteDTOTransform,
|
|
|
|
@Inject(CreditNoteAppliedInvoice.name)
|
|
private readonly creditNoteAppliedInvoiceModel: TenantModelProxy<
|
|
typeof CreditNoteAppliedInvoice
|
|
>,
|
|
|
|
@Inject(CreditNote.name)
|
|
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
|
|
) {}
|
|
|
|
/**
|
|
* Apply credit note to the given invoices.
|
|
* @param {number} creditNoteId
|
|
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
|
|
*/
|
|
public async applyCreditNoteToInvoices(
|
|
creditNoteId: number,
|
|
applyCreditToInvoicesDTO: ApplyCreditNoteToInvoicesDto,
|
|
): Promise<CreditNoteAppliedInvoice[]> {
|
|
// Saves the credit note or throw not found service error.
|
|
const creditNote = await this.creditNoteModel()
|
|
.query()
|
|
.findById(creditNoteId)
|
|
.throwIfNotFound();
|
|
|
|
// Retrieve the applied invoices that associated to the credit note customer.
|
|
const appliedInvoicesEntries =
|
|
await this.paymentReceiveValidators.validateInvoicesIDsExistance(
|
|
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.entries,
|
|
);
|
|
// Validate the credit note remaining amount.
|
|
this.creditNoteDTOTransform.validateCreditRemainingAmount(
|
|
creditNote,
|
|
creditNoteAppliedModel.amount,
|
|
);
|
|
// Creates credit note apply to invoice transaction.
|
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
|
// Saves the credit note apply to invoice graph to the storage layer.
|
|
const creditNoteAppliedInvoices =
|
|
await this.creditNoteAppliedInvoiceModel()
|
|
.query()
|
|
.insertGraph(creditNoteAppliedModel.entries);
|
|
|
|
// Triggers `onCreditNoteApplyToInvoiceCreated` event.
|
|
await this.eventPublisher.emitAsync(
|
|
events.creditNote.onApplyToInvoicesCreated,
|
|
{
|
|
creditNote,
|
|
creditNoteAppliedInvoices,
|
|
trx,
|
|
} as IApplyCreditToInvoicesCreatedPayload,
|
|
);
|
|
|
|
return creditNoteAppliedInvoices;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Transformes apply DTO to model.
|
|
* @param {IApplyCreditToInvoicesDTO} applyDTO
|
|
* @param {ICreditNote} creditNote
|
|
* @returns
|
|
*/
|
|
private transformApplyDTOToModel = (
|
|
applyDTO: IApplyCreditToInvoicesDTO,
|
|
creditNote: CreditNote,
|
|
): ICreditNoteAppliedToInvoiceModel => {
|
|
const entries = applyDTO.entries.map((entry) => ({
|
|
invoiceId: entry.invoiceId,
|
|
amount: entry.amount,
|
|
creditNoteId: creditNote.id,
|
|
}));
|
|
return {
|
|
amount: sumBy(entries, 'amount'),
|
|
entries,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Validate each invoice has sufficient remaining amount for the applied credit.
|
|
* @param {ISaleInvoice[]} invoices
|
|
* @param {ICreditNoteAppliedToInvoice[]} entries
|
|
*/
|
|
private validateInvoicesRemainingAmount = (
|
|
invoices: SaleInvoice[],
|
|
entries: ICreditNoteAppliedToInvoice[],
|
|
) => {
|
|
const invoiceMap = new Map(invoices.map((inv) => [inv.id, inv]));
|
|
const invalidEntries = entries.filter((entry) => {
|
|
const invoice = invoiceMap.get(entry.invoiceId);
|
|
return invoice != null && invoice.dueAmount < entry.amount;
|
|
});
|
|
if (invalidEntries.length > 0) {
|
|
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
|
|
}
|
|
};
|
|
}
|