Merge pull request #979 from yk-a11y/fix/credit-note-apply-invoice-validation

fix: validate credit note per-entry amount against each invoice due amount
This commit is contained in:
Ahmed Bouhuolia
2026-02-24 02:55:47 +02:00
committed by GitHub
2 changed files with 14 additions and 9 deletions

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { sumBy } from 'lodash'; import { sumBy } from 'lodash';
import { import {
ICreditNoteAppliedToInvoice,
ICreditNoteAppliedToInvoiceModel, ICreditNoteAppliedToInvoiceModel,
IApplyCreditToInvoicesDTO, IApplyCreditToInvoicesDTO,
IApplyCreditToInvoicesCreatedPayload, IApplyCreditToInvoicesCreatedPayload,
@@ -17,6 +18,7 @@ import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service'; import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ApplyCreditNoteToInvoicesDto } from '../dtos/ApplyCreditNoteToInvoices.dto';
@Injectable() @Injectable()
export class CreditNoteApplyToInvoices { export class CreditNoteApplyToInvoices {
@@ -48,7 +50,7 @@ export class CreditNoteApplyToInvoices {
*/ */
public async applyCreditNoteToInvoices( public async applyCreditNoteToInvoices(
creditNoteId: number, creditNoteId: number,
applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO, applyCreditToInvoicesDTO: ApplyCreditNoteToInvoicesDto,
): Promise<CreditNoteAppliedInvoice[]> { ): Promise<CreditNoteAppliedInvoice[]> {
// Saves the credit note or throw not found service error. // Saves the credit note or throw not found service error.
const creditNote = await this.creditNoteModel() const creditNote = await this.creditNoteModel()
@@ -71,7 +73,7 @@ export class CreditNoteApplyToInvoices {
// Validate invoices has remaining amount to apply. // Validate invoices has remaining amount to apply.
this.validateInvoicesRemainingAmount( this.validateInvoicesRemainingAmount(
appliedInvoicesEntries, appliedInvoicesEntries,
creditNoteAppliedModel.amount, creditNoteAppliedModel.entries,
); );
// Validate the credit note remaining amount. // Validate the credit note remaining amount.
this.creditNoteDTOTransform.validateCreditRemainingAmount( this.creditNoteDTOTransform.validateCreditRemainingAmount(
@@ -122,18 +124,20 @@ export class CreditNoteApplyToInvoices {
}; };
/** /**
* Validate the invoice remaining amount. * Validate each invoice has sufficient remaining amount for the applied credit.
* @param {ISaleInvoice[]} invoices * @param {ISaleInvoice[]} invoices
* @param {number} amount * @param {ICreditNoteAppliedToInvoice[]} entries
*/ */
private validateInvoicesRemainingAmount = ( private validateInvoicesRemainingAmount = (
invoices: SaleInvoice[], invoices: SaleInvoice[],
amount: number, entries: ICreditNoteAppliedToInvoice[],
) => { ) => {
const invalidInvoices = invoices.filter( const invoiceMap = new Map(invoices.map((inv) => [inv.id, inv]));
(invoice) => invoice.dueAmount < amount, const invalidEntries = entries.filter((entry) => {
); const invoice = invoiceMap.get(entry.invoiceId);
if (invalidInvoices.length > 0) { return invoice != null && invoice.dueAmount < entry.amount;
});
if (invalidEntries.length > 0) {
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT); throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
} }
}; };

View File

@@ -29,6 +29,7 @@ export interface IApplyCreditToInvoicesDeletedPayload {
export interface ICreditNoteAppliedToInvoice { export interface ICreditNoteAppliedToInvoice {
amount: number; amount: number;
creditNoteId: number; creditNoteId: number;
invoiceId: number;
} }
export interface ICreditNoteAppliedToInvoiceModel { export interface ICreditNoteAppliedToInvoiceModel {
amount: number; amount: number;