From 2a87103bc84f714a21f570cacff83ae0cfa110de Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 24 Feb 2026 02:15:32 +0200 Subject: [PATCH] fix: Add DELETE endpoint for credit notes applied invoices - Add missing DELETE /credit-notes/applied-invoices/:id endpoint - Fix CreditNotesApplyInvoice controller to use correct service methods - Add missing GetCreditNoteAssociatedInvoicesToApply endpoint - Add proper DTO for ApplyCreditNoteToInvoices - Update frontend creditNote hook to use correct API paths Co-Authored-By: Claude Sonnet 4.6 --- .../src/modules/Attachments/LinkAttachment.ts | 7 +-- .../CreditNotesApplyInvoice.controller.ts | 52 ++++++++++++++++++- .../CreditNotesApplyInvoice.module.ts | 4 ++ .../CreditNoteApplySyncInvoices.service.ts | 2 +- .../dtos/ApplyCreditNoteToInvoices.dto.ts | 38 ++++++++++++++ .../CreditNoteApplySyncInvoicesSubscriber.ts | 2 +- .../ApplyVendorCreditSyncBills.service.ts | 2 +- .../webapp/src/hooks/query/creditNote.tsx | 39 ++++++-------- 8 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts diff --git a/packages/server/src/modules/Attachments/LinkAttachment.ts b/packages/server/src/modules/Attachments/LinkAttachment.ts index 4bffeadd5..e1015ac73 100644 --- a/packages/server/src/modules/Attachments/LinkAttachment.ts +++ b/packages/server/src/modules/Attachments/LinkAttachment.ts @@ -1,5 +1,5 @@ import { ModuleRef } from '@nestjs/core'; -import bluebird from 'bluebird'; +import * as bluebird from 'bluebird'; import { Knex } from 'knex'; import { validateLinkModelEntryExists, @@ -53,7 +53,8 @@ export class LinkAttachment { const foundLinkModel = await LinkModel().query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); - const foundLinks = await this.documentLinkModel().query(trx) + const foundLinks = await this.documentLinkModel() + .query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .where('documentId', foundFile.id); @@ -70,7 +71,7 @@ export class LinkAttachment { /** * Links the given file keys to the given model type and id. - * @param {string[]} filekeys - File keys. + * @param {string[]} filekeys - File keys. * @param {string} modelRef - Model reference. * @param {number} modelId - Model id. * @param {Knex.Transaction} trx - Knex transaction. diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts index 4e96ba443..76f7261a9 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Post, @@ -14,6 +15,10 @@ import { PermissionGuard } from '@/modules/Roles/Permission.guard'; import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; import { AbilitySubject } from '@/modules/Roles/Roles.types'; import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types'; +import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; +import { CreditNoteApplyToInvoices } from './commands/CreditNoteApplyToInvoices.service'; +import { DeleteCreditNoteApplyToInvoices } from './commands/DeleteCreditNoteApplyToInvoices.service'; +import { ApplyCreditNoteToInvoicesDto } from './dtos/ApplyCreditNoteToInvoices.dto'; @Controller('credit-notes') @ApiTags('Credit Notes Apply Invoice') @@ -22,6 +27,9 @@ import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types'; export class CreditNotesApplyInvoiceController { constructor( private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices, + private readonly getCreditNoteAssociatedInvoicesToApplyService: GetCreditNoteAssociatedInvoicesToApply, + private readonly creditNoteApplyToInvoicesService: CreditNoteApplyToInvoices, + private readonly deleteCreditNoteApplyToInvoicesService: DeleteCreditNoteApplyToInvoices, ) {} @Get(':creditNoteId/applied-invoices') @@ -39,6 +47,23 @@ export class CreditNotesApplyInvoiceController { ); } + @Get(':creditNoteId/apply-invoices') + @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) + @ApiOperation({ summary: 'Get credit note associated invoices to apply' }) + @ApiResponse({ + status: 200, + description: 'Credit note associated invoices to apply', + }) + @ApiResponse({ status: 404, description: 'Credit note not found' }) + @ApiResponse({ status: 400, description: 'Invalid input data' }) + getCreditNoteAssociatedInvoicesToApply( + @Param('creditNoteId') creditNoteId: number, + ) { + return this.getCreditNoteAssociatedInvoicesToApplyService.getCreditAssociatedInvoicesToApply( + creditNoteId, + ); + } + @Post(':creditNoteId/apply-invoices') @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Apply credit note to invoices' }) @@ -48,9 +73,32 @@ export class CreditNotesApplyInvoiceController { }) @ApiResponse({ status: 404, description: 'Credit note not found' }) @ApiResponse({ status: 400, description: 'Invalid input data' }) - applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) { - return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices( + applyCreditNoteToInvoices( + @Param('creditNoteId') creditNoteId: number, + @Body() applyDto: ApplyCreditNoteToInvoicesDto, + ) { + return this.creditNoteApplyToInvoicesService.applyCreditNoteToInvoices( creditNoteId, + applyDto, + ); + } + + @Delete('applied-invoices/:applyCreditToInvoicesId') + @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) + @ApiOperation({ summary: 'Delete applied credit note to invoice' }) + @ApiResponse({ + status: 200, + description: 'Credit note application successfully deleted', + }) + @ApiResponse({ + status: 404, + description: 'Credit note application not found', + }) + deleteApplyCreditNoteToInvoices( + @Param('applyCreditToInvoicesId') applyCreditToInvoicesId: number, + ) { + return this.deleteCreditNoteApplyToInvoicesService.deleteApplyCreditNoteToInvoices( + applyCreditToInvoicesId, ); } } diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts index 7998e3e63..5dab07c49 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts @@ -9,6 +9,8 @@ import { CreditNotesModule } from '../CreditNotes/CreditNotes.module'; import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service'; import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller'; +import { CreditNoteApplySyncCreditSubscriber } from './subscribers/CreditNoteApplySyncCreditSubscriber'; +import { CreditNoteApplySyncInvoicesCreditedAmountSubscriber } from './subscribers/CreditNoteApplySyncInvoicesSubscriber'; @Module({ providers: [ @@ -19,6 +21,8 @@ import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.con CreditNoteApplySyncCredit, GetCreditNoteAssociatedAppliedInvoices, GetCreditNoteAssociatedInvoicesToApply, + CreditNoteApplySyncCreditSubscriber, + CreditNoteApplySyncInvoicesCreditedAmountSubscriber, ], exports: [DeleteCustomerLinkedCreditNoteService], imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)], diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts index efc14b72f..2629c2a02 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts @@ -1,6 +1,6 @@ import { Knex } from 'knex'; import { Injectable, Inject } from '@nestjs/common'; -import Bluebird from 'bluebird'; +import * as Bluebird from 'bluebird'; import { ICreditNoteAppliedToInvoice } from '../types/CreditNoteApplyInvoice.types'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts b/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts new file mode 100644 index 000000000..e193b164b --- /dev/null +++ b/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + ArrayMinSize, + IsArray, + IsInt, + IsNotEmpty, + IsNumber, + ValidateNested, +} from 'class-validator'; + +export class ApplyCreditNoteInvoiceEntryDto { + @IsNotEmpty() + @IsInt() + @ApiProperty({ description: 'Invoice ID to apply credit to', example: 1 }) + invoiceId: number; + + @IsNotEmpty() + @IsNumber() + @ApiProperty({ description: 'Amount to apply', example: 100.5 }) + amount: number; +} + +export class ApplyCreditNoteToInvoicesDto { + @IsArray() + @ArrayMinSize(1) + @ValidateNested({ each: true }) + @Type(() => ApplyCreditNoteInvoiceEntryDto) + @ApiProperty({ + description: 'Entries of invoice ID and amount to apply', + type: [ApplyCreditNoteInvoiceEntryDto], + example: [ + { invoice_id: 1, amount: 100.5 }, + { invoice_id: 2, amount: 50 }, + ], + }) + entries: ApplyCreditNoteInvoiceEntryDto[]; +} diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts b/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts index 5169364a8..ac653c19e 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts @@ -8,7 +8,7 @@ import { CreditNoteApplySyncInvoicesCreditedAmount } from '../commands/CreditNot import { events } from '@/common/events/events'; @Injectable() -export default class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { +export class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { constructor( private readonly syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount, ) {} diff --git a/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts b/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts index 60a002ac6..eb22d212c 100644 --- a/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts +++ b/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts @@ -1,4 +1,4 @@ -import Bluebird from 'bluebird'; +import * as Bluebird from 'bluebird'; import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; import { IVendorCreditAppliedBill } from '../types/VendorCreditApplyBills.types'; diff --git a/packages/webapp/src/hooks/query/creditNote.tsx b/packages/webapp/src/hooks/query/creditNote.tsx index 5611185f0..678897037 100644 --- a/packages/webapp/src/hooks/query/creditNote.tsx +++ b/packages/webapp/src/hooks/query/creditNote.tsx @@ -58,16 +58,13 @@ export function useCreateCreditNote(props) { const queryClient = useQueryClient(); const apiRequest = useApiRequest(); - return useMutation( - (values) => apiRequest.post('credit-notes', values), - { - onSuccess: (res, values) => { - // Common invalidate queries. - commonInvalidateQueries(queryClient); - }, - ...props, + return useMutation((values) => apiRequest.post('credit-notes', values), { + onSuccess: (res, values) => { + // Common invalidate queries. + commonInvalidateQueries(queryClient); }, - ); + ...props, + }); } /** @@ -218,8 +215,7 @@ export function useCreateRefundCreditNote(props) { const apiRequest = useApiRequest(); return useMutation( - ([id, values]) => - apiRequest.post(`credit-notes/${id}/refunds`, values), + ([id, values]) => apiRequest.post(`credit-notes/${id}/refunds`, values), { onSuccess: (res, [id, values]) => { // Common invalidate queries. @@ -240,19 +236,16 @@ export function useDeleteRefundCreditNote(props) { const queryClient = useQueryClient(); const apiRequest = useApiRequest(); - return useMutation( - (id) => apiRequest.delete(`credit-notes/refunds/${id}`), - { - onSuccess: (res, id) => { - // Common invalidate queries. - commonInvalidateQueries(queryClient); + return useMutation((id) => apiRequest.delete(`credit-notes/refunds/${id}`), { + onSuccess: (res, id) => { + // Common invalidate queries. + commonInvalidateQueries(queryClient); - // Invalidate vendor credit query. - queryClient.invalidateQueries([t.CREDIT_NOTE, id]); - }, - ...props, + // Invalidate vendor credit query. + queryClient.invalidateQueries([t.CREDIT_NOTE, id]); }, - ); + ...props, + }); } /** @@ -301,7 +294,7 @@ export function useReconcileCreditNote(id, props, requestProps) { [t.RECONCILE_CREDIT_NOTE, id], { method: 'get', - url: `credit-notes/${id}/applied-invoices`, + url: `credit-notes/${id}/apply-invoices`, ...requestProps, }, {