From 75b98c39d82033b91ef04f5621e61075761087b5 Mon Sep 17 00:00:00 2001 From: Yong ke Weng Date: Sat, 21 Feb 2026 09:50:39 -0500 Subject: [PATCH 1/2] fix: validate credit note per-entry amount against each invoice due amount The `validateInvoicesRemainingAmount` method was incorrectly comparing the total credit amount (sum of all entries) against each individual invoice's due amount. This caused valid credit note applications to be rejected when applying to multiple invoices where the total exceeded any single invoice's due amount. Changed the validation to compare each invoice's due amount against only the specific entry amount being applied to that invoice. Co-Authored-By: Claude Opus 4.6 --- .../CreditNoteApplyToInvoices.service.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts index c58254bc7..eb4162d86 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; import { sumBy } from 'lodash'; import { + ICreditNoteAppliedToInvoice, ICreditNoteAppliedToInvoiceModel, IApplyCreditToInvoicesDTO, IApplyCreditToInvoicesCreatedPayload, @@ -71,7 +72,7 @@ export class CreditNoteApplyToInvoices { // Validate invoices has remaining amount to apply. this.validateInvoicesRemainingAmount( appliedInvoicesEntries, - creditNoteAppliedModel.amount, + creditNoteAppliedModel.entries, ); // Validate the credit note remaining amount. this.creditNoteDTOTransform.validateCreditRemainingAmount( @@ -122,17 +123,18 @@ export class CreditNoteApplyToInvoices { }; /** - * Validate the invoice remaining amount. + * Validate each invoice has sufficient remaining amount for the applied credit. * @param {ISaleInvoice[]} invoices - * @param {number} amount + * @param {ICreditNoteAppliedToInvoice[]} entries */ private validateInvoicesRemainingAmount = ( invoices: SaleInvoice[], - amount: number, + entries: ICreditNoteAppliedToInvoice[], ) => { - const invalidInvoices = invoices.filter( - (invoice) => invoice.dueAmount < amount, - ); + const invalidInvoices = invoices.filter((invoice) => { + const entry = entries.find((e) => e.invoiceId === invoice.id); + return entry && invoice.dueAmount < entry.amount; + }); if (invalidInvoices.length > 0) { throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT); } From 688b1bfb566bd3866dbdb3b8fd264484e9ace0cc Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 24 Feb 2026 02:52:28 +0200 Subject: [PATCH 2/2] fix(server): add invoices Map for validateInvoicesRemainingAmount --- .../commands/CreditNoteApplyToInvoices.service.ts | 12 +++++++----- .../types/CreditNoteApplyInvoice.types.ts | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts index eb4162d86..dc042647a 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplyToInvoices.service.ts @@ -18,6 +18,7 @@ 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 { @@ -49,7 +50,7 @@ export class CreditNoteApplyToInvoices { */ public async applyCreditNoteToInvoices( creditNoteId: number, - applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO, + applyCreditToInvoicesDTO: ApplyCreditNoteToInvoicesDto, ): Promise { // Saves the credit note or throw not found service error. const creditNote = await this.creditNoteModel() @@ -131,11 +132,12 @@ export class CreditNoteApplyToInvoices { invoices: SaleInvoice[], entries: ICreditNoteAppliedToInvoice[], ) => { - const invalidInvoices = invoices.filter((invoice) => { - const entry = entries.find((e) => e.invoiceId === invoice.id); - return entry && invoice.dueAmount < entry.amount; + 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 (invalidInvoices.length > 0) { + if (invalidEntries.length > 0) { throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT); } }; diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/types/CreditNoteApplyInvoice.types.ts b/packages/server/src/modules/CreditNotesApplyInvoice/types/CreditNoteApplyInvoice.types.ts index 7172811ae..d65ef2af3 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/types/CreditNoteApplyInvoice.types.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/types/CreditNoteApplyInvoice.types.ts @@ -29,6 +29,7 @@ export interface IApplyCreditToInvoicesDeletedPayload { export interface ICreditNoteAppliedToInvoice { amount: number; creditNoteId: number; + invoiceId: number; } export interface ICreditNoteAppliedToInvoiceModel { amount: number;