From caf235e2b51ee05b0ea94757a444ae1e4b8e0d17 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Dec 2024 18:37:33 +0200 Subject: [PATCH] refactor: migrate credit note and vendor credit services to nestjs --- .../server-nest/src/modules/App/App.module.ts | 8 +- .../Bills/commands/BillsValidators.service.ts | 2 +- .../CreditNoteApplication.service.ts | 57 ++++ .../CreditNotes/CreditNotes.controller.ts | 52 +++ .../modules/CreditNotes/CreditNotes.module.ts | 29 ++ ...reditNoteAssociatedInvoicesToApply copy.ts | 42 +++ .../GetCreditNoteAssociatedInvoicesToApply.ts | 1 + .../CommandCreditNoteDTOTransform.service.ts | 113 +++++++ .../commands/CreateCreditNote.service.ts | 83 +++++ .../CreateRefundCreditNote.service.ts | 113 +++++++ .../CreditNoteApplySyncCredit.service.ts | 45 +++ .../CreditNoteApplySyncInvoices.service.ts | 48 +++ .../CreditNoteApplyToInvoices.service.ts | 131 ++++++++ .../CreditNoteAutoIncrement.service.ts | 28 ++ .../commands/CreditNoteGLEntries.ts | 297 +++++++++++++++++ ...reditNoteWithInvoicesToApplyTransformer.ts | 68 ++++ .../commands/CreditNotesExportable.ts | 35 ++ .../commands/CreditNotesImportable.ts | 44 +++ .../CreditNotesInventoryTransactions.ts | 90 +++++ .../commands/DeleteCreditNote.service.ts | 124 +++++++ ...DeleteCreditNoteApplyToInvoices.service.ts | 63 ++++ .../DeleteCustomerLinkedCreditNote.service.ts | 26 ++ .../DeleteRefundCreditNote.service.ts | 73 +++++ .../commands/EditCreditNote.service.ts | 101 ++++++ .../commands/OpenCreditNote.service.ts | 88 +++++ .../commands/RefundCreditNote.service.ts | 45 +++ .../commands/RefundCreditNoteGLEntries.ts | 160 +++++++++ .../commands/RefundSyncCreditNoteBalance.ts | 48 +++ .../src/modules/CreditNotes/constants.ts | 132 ++++++++ .../models/CreditNoteAppliedInvoice.ts | 9 + .../CreditNotes/models/RefundCreditNote.ts | 63 ++++ .../CreditNoteAppliedInvoiceTransformer.ts | 53 +++ .../CreditNoteBrandingTemplate.service.ts | 45 +++ .../queries/CreditNoteTransformer.ts | 194 +++++++++++ .../queries/GetCreditNote.service.ts | 39 +++ ...itNoteAssociatedAppliedInvoices.service.ts | 42 +++ ...itNoteAssociatedInvoicesToApply.service.ts | 41 +++ .../queries/GetCreditNotePdf.serivce.ts | 99 ++++++ .../queries/GetCreditNoteRefunds.service.ts | 37 +++ .../queries/GetCreditNoteState.service.ts | 25 ++ .../queries/GetCreditNotes.service.ts | 68 ++++ .../GetRefundCreditNoteTransaction.service.ts | 37 +++ .../queries/RefundCreditNoteTransformer.ts | 29 ++ .../CreditNoteApplySyncCreditSubscriber.ts | 67 ++++ .../CreditNoteApplySyncInvoicesSubscriber.ts | 61 ++++ .../CreditNoteAutoSerialSubscriber.ts | 30 ++ .../CreditNoteGLEntriesSubscriber.ts | 113 +++++++ ...editNoteInventoryTransactionsSubscriber.ts | 98 ++++++ .../DeleteCustomerLinkedCreditSubscriber.ts | 48 +++ .../RefundCreditNoteGLEntriesSubscriber.ts | 61 ++++ .../RefundSyncCreditNoteBalanceSubscriber.ts | 62 ++++ .../CreditNotes/types/CreditNotes.types.ts | 307 ++++++++++++++++++ .../src/modules/CreditNotes/utils.ts | 25 ++ .../src/modules/Import/ImportableResources.ts | 2 +- .../SaleEstimates/models/SaleEstimate.ts | 53 ++- .../Tenancy/TenancyModels/Tenancy.module.ts | 2 +- .../VendorCredit/VendorCredits.controller.ts | 33 ++ .../VendorCredit/VendorCredits.module.ts | 23 ++ .../VendorCreditsApplication.service.ts | 64 ++++ .../commands/CreateVendorCredit.service.ts | 90 +++++ .../commands/DeleteVendorCredit.service.ts | 116 +++++++ .../commands/EditVendorCredit.service.ts | 105 ++++++ .../commands/OpenVendorCredit.service.ts | 90 +++++ .../VendorCreditAutoIncrement.service.ts | 32 ++ .../VendorCreditDTOTransform.service.ts | 102 ++++++ .../commands/VendorCreditGLEntries.ts | 252 ++++++++++++++ .../VendorCreditInventoryTransactions.ts | 92 ++++++ .../commands/VendorCreditsExportable.ts | 36 ++ .../commands/VendorCreditsImportable.ts | 45 +++ .../src/modules/VendorCredit/constants.ts | 82 +++++ .../VendorCredit/models/RefundVendorCredit.ts | 63 ++++ .../VendorCredit/models/VendorCredit.ts | 149 +++++---- .../queries/GetVendorCredit.service.ts | 42 +++ .../VendorCredit/queries/ListVendorCredits.ts | 70 ++++ .../queries/VendorCreditTransformer.ts | 179 ++++++++++ .../DeleteVendorAssociatedVendorCredit.ts | 48 +++ ...RefundSyncVendorCreditBalanceSubscriber.ts | 62 ++++ .../RefundVendorCreditGLEntriesSubscriber.ts | 59 ++++ .../VendorCreditAutoSerialSubscriber.ts | 27 ++ .../VendorCreditGLEntriesSubscriber.ts | 110 +++++++ ...orCreditInventoryTransactionsSusbcriber.ts | 93 ++++++ .../VendorCredit/types/VendorCredit.types.ts | 229 +++++++++++++ .../VendorCreditApplyBills.module.ts | 20 ++ .../VendorCreditsApplyBills.constants.ts | 12 + .../ApplyVendorCreditSyncBills.service.ts | 48 +++ .../ApplyVendorCreditSyncInvoiced.service.ts | 43 +++ .../ApplyVendorCreditToBills.service.ts | 129 ++++++++ .../DeleteApplyVendorCreditToBill.service.ts | 66 ++++ .../models/VendorCreditAppliedBill.ts | 13 +- .../GetAppliedBillsToVendorCredit.service.ts | 40 +++ .../GetVendorCreditToApplyBills.service.ts | 47 +++ .../VendorCreditAppliedBillTransformer.ts | 67 ++++ .../VendorCreditToApplyBillTransformer.ts | 69 ++++ .../ApplyVendorCreditSyncBillsSubscriber.ts | 61 ++++ ...ApplyVendorCreditSyncInvoicedSubscriber.ts | 70 ++++ .../types/VendorCreditApplyBills.types.ts | 48 +++ .../RefundCreditSyncBills.ts | 0 .../CreateRefundVendorCredit.service.ts | 122 +++++++ .../DeleteRefundVendorCredit.service.ts | 74 +++++ .../RefundSyncCreditRefundedAmount.service.ts | 48 +++ .../RefundSyncVendorCreditBalance.service.ts | 40 +++ .../commands/RefundVendorCredit.service.ts | 63 ++++ .../commands/RefundVendorCreditGLEntries.ts | 159 +++++++++ .../commands/RefundVendorCreditTransformer.ts | 29 ++ .../modules/VendorCreditsRefund/constants.ts | 5 + .../queries/GetRefundVendorCredit.service.ts | 44 +++ .../queries/GetRefundVendorCredits.service.ts | 39 +++ 107 files changed, 7396 insertions(+), 109 deletions(-) create mode 100644 packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply copy.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreateCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreateRefundCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncCredit.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncInvoices.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplyToInvoices.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteAutoIncrement.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGLEntries.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNoteWithInvoicesToApplyTransformer.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNotesExportable.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNotesImportable.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNoteApplyToInvoices.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/DeleteCustomerLinkedCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/DeleteRefundCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/EditCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/OpenCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNoteGLEntries.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/commands/RefundSyncCreditNoteBalance.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/constants.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/models/RefundCreditNote.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/CreditNoteAppliedInvoiceTransformer.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/CreditNoteBrandingTemplate.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/CreditNoteTransformer.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNote.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedAppliedInvoices.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedInvoicesToApply.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotePdf.serivce.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteRefunds.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteState.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/GetRefundCreditNoteTransaction.service.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/queries/RefundCreditNoteTransformer.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncCreditSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteAutoSerialSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteGLEntriesSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteInventoryTransactionsSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/DeleteCustomerLinkedCreditSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/subscribers/RefundSyncCreditNoteBalanceSubscriber.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts create mode 100644 packages/server-nest/src/modules/CreditNotes/utils.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/CreateVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/DeleteVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/EditVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditAutoIncrement.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGLEntries.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsExportable.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsImportable.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/constants.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/models/RefundVendorCredit.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/queries/ListVendorCredits.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/queries/VendorCreditTransformer.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/DeleteVendorAssociatedVendorCredit.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/RefundSyncVendorCreditBalanceSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/RefundVendorCreditGLEntriesSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditAutoSerialSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditGLEntriesSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts create mode 100644 packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditApplyBills.module.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditsApplyBills.constants.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncInvoiced.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditToBills.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/command/DeleteApplyVendorCreditToBill.service.ts rename packages/server-nest/src/modules/{VendorCredit => VendorCreditsApplyBills}/models/VendorCreditAppliedBill.ts (77%) create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetAppliedBillsToVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetVendorCreditToApplyBills.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditAppliedBillTransformer.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditToApplyBillTransformer.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncBillsSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncInvoicedSubscriber.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsApplyBills/types/VendorCreditApplyBills.types.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/RefundCreditSyncBills.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/CreateRefundVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/DeleteRefundVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncCreditRefundedAmount.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncVendorCreditBalance.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditGLEntries.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditTransformer.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/constants.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredit.service.ts create mode 100644 packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredits.service.ts diff --git a/packages/server-nest/src/modules/App/App.module.ts b/packages/server-nest/src/modules/App/App.module.ts index 04014bcc8..a273b6417 100644 --- a/packages/server-nest/src/modules/App/App.module.ts +++ b/packages/server-nest/src/modules/App/App.module.ts @@ -46,6 +46,9 @@ import { BillsModule } from '../Bills/Bills.module'; import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module'; import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module'; import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module'; +import { VendorCreditsModule } from '../VendorCredit/VendorCredits.module'; +import { CreditNotesModule } from '../CreditNotes/CreditNotes.module'; +import { VendorCreditApplyBillsModule } from '../VendorCreditsApplyBills/VendorCreditApplyBills.module'; // import { BillPaymentsModule } from '../BillPayments/BillPayments.module'; @Module({ @@ -116,7 +119,10 @@ import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module'; SaleEstimatesModule, SaleReceiptsModule, BillsModule, - ManualJournalsModule + ManualJournalsModule, + VendorCreditsModule, + VendorCreditApplyBillsModule, + CreditNotesModule, // BillPaymentsModule, ], controllers: [AppController], diff --git a/packages/server-nest/src/modules/Bills/commands/BillsValidators.service.ts b/packages/server-nest/src/modules/Bills/commands/BillsValidators.service.ts index f8a8ca6cf..343515361 100644 --- a/packages/server-nest/src/modules/Bills/commands/BillsValidators.service.ts +++ b/packages/server-nest/src/modules/Bills/commands/BillsValidators.service.ts @@ -6,7 +6,7 @@ import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; import { Item } from '@/modules/Items/models/Item'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; -import { VendorCreditAppliedBill } from '@/modules/VendorCredit/models/VendorCreditAppliedBill'; +import { VendorCreditAppliedBill } from '@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill'; import { transformToMap } from '@/utils/transform-to-key'; @Injectable() diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts b/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts new file mode 100644 index 000000000..2256599c5 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; +import { CreateCreditNoteService } from './commands/CreateCreditNote.service'; +import { CreateRefundCreditNoteService } from './commands/CreateRefundCreditNote.service'; +import { DeleteCreditNoteService } from './commands/DeleteCreditNote.service'; +import { EditCreditNoteService } from './commands/EditCreditNote.service'; +import { OpenCreditNoteService } from './commands/OpenCreditNote.service'; +import { GetCreditNotePdf } from './queries/GetCreditNotePdf.serivce'; +import { + ICreditNoteEditDTO, + ICreditNoteNewDTO, + ICreditNoteRefundDTO, +} from './types/CreditNotes.types'; + +@Injectable() +export class CreditNoteApplication { + constructor( + private createCreditNoteService: CreateCreditNoteService, + private createRefundCreditNoteService: CreateRefundCreditNoteService, + private editCreditNoteService: EditCreditNoteService, + private openCreditNoteService: OpenCreditNoteService, + private deleteCreditNoteService: DeleteCreditNoteService, + private getCreditNotePdfService: GetCreditNotePdf, + ) {} + + createCreditNote(creditNoteDTO: ICreditNoteNewDTO) { + return this.createCreditNoteService.creditCreditNote(creditNoteDTO); + } + + editCreditNote(creditNoteId: number, creditNoteDTO: ICreditNoteEditDTO) { + return this.editCreditNoteService.editCreditNote( + creditNoteId, + creditNoteDTO, + ); + } + + openCreditNote(creditNoteId: number) { + return this.openCreditNoteService.openCreditNote(creditNoteId); + } + + deleteCreditNote(creditNoteId: number) { + return this.deleteCreditNoteService.deleteCreditNote(creditNoteId); + } + + createRefundCreditNote( + creditNoteId: number, + creditNoteDTO: ICreditNoteRefundDTO, + ) { + return this.createRefundCreditNoteService.createCreditNoteRefund( + creditNoteId, + creditNoteDTO, + ); + } + + getCreditNotePdf(creditNoteId: number) { + return this.getCreditNotePdfService.getCreditNotePdf(creditNoteId); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts b/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts new file mode 100644 index 000000000..4d7f75a4f --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts @@ -0,0 +1,52 @@ +import { Body, Controller, Delete, Param, Post, Put } from '@nestjs/common'; +import { CreditNoteApplication } from './CreditNoteApplication.service'; +import { + ICreditNoteEditDTO, + ICreditNoteNewDTO, + ICreditNoteRefundDTO, +} from './types/CreditNotes.types'; + +@Controller('credit-notes') +export class CreditNotesController { + /** + * @param {CreditNoteApplication} creditNoteApplication - The credit note application service. + */ + constructor(private creditNoteApplication: CreditNoteApplication) {} + + @Post() + createCreditNote(@Body() creditNoteDTO: ICreditNoteNewDTO) { + return this.creditNoteApplication.createCreditNote(creditNoteDTO); + } + + @Put(':id') + editCreditNote( + @Param('id') creditNoteId: number, + @Body() creditNoteDTO: ICreditNoteEditDTO, + ) { + return this.creditNoteApplication.editCreditNote( + creditNoteId, + creditNoteDTO, + ); + } + + @Post(':id/open') + openCreditNote(@Param('id') creditNoteId: number) { + return this.creditNoteApplication.openCreditNote(creditNoteId); + } + + @Delete(':id') + deleteCreditNote(@Param('id') creditNoteId: number) { + return this.creditNoteApplication.deleteCreditNote(creditNoteId); + } + + @Post(':id/refund') + createRefundCreditNote( + @Param('id') creditNoteId: number, + @Body() creditNoteDTO: ICreditNoteRefundDTO, + ) { + return this.creditNoteApplication.createRefundCreditNote( + creditNoteId, + creditNoteDTO, + ); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts b/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts new file mode 100644 index 000000000..a83408403 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { CreateCreditNoteService } from './commands/CreateCreditNote.service'; +import { CommandCreditNoteDTOTransform } from './commands/CommandCreditNoteDTOTransform.service'; +import { CreateRefundCreditNoteService } from './commands/CreateRefundCreditNote.service'; +import { EditCreditNoteService } from './commands/EditCreditNote.service'; +import { OpenCreditNoteService } from './commands/OpenCreditNote.service'; +import { DeleteCreditNoteService } from './commands/DeleteCreditNote.service'; +import { CreditNoteApplySyncCredit } from './commands/CreditNoteApplySyncCredit.service'; +import { DeleteCustomerLinkedCreditNoteService } from './commands/DeleteCustomerLinkedCreditNote.service'; +import { CreditNoteAutoIncrementService } from './commands/CreditNoteAutoIncrement.service'; +import { CreditNoteApplication } from './CreditNoteApplication.service'; +import { CreditNotesController } from './CreditNotes.controller'; + +@Module({ + providers: [ + CreateCreditNoteService, + CommandCreditNoteDTOTransform, + CreateRefundCreditNoteService, + EditCreditNoteService, + OpenCreditNoteService, + DeleteCreditNoteService, + CreditNoteApplySyncCredit, + DeleteCustomerLinkedCreditNoteService, + CreditNoteAutoIncrementService, + CreditNoteApplication + ], + controllers: [CreditNotesController], +}) +export class CreditNotesModule {} diff --git a/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply copy.ts b/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply copy.ts new file mode 100644 index 000000000..ef44818d8 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply copy.ts @@ -0,0 +1,42 @@ +import { Service, Inject } from 'typedi'; +import BaseCreditNotes from '../CreditNotes'; +import { ISaleInvoice } from '@/interfaces'; +import { CreditNoteWithInvoicesToApplyTransformer } from './commands/CreditNoteWithInvoicesToApplyTransformer'; +import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; + +@Service() +export default class GetCreditNoteAssociatedInvoicesToApply extends BaseCreditNotes { + @Inject() + private transformer: TransformerInjectable; + + /** + * Retrieve credit note associated invoices to apply. + * @param {number} tenantId + * @param {number} creditNoteId + * @returns {Promise} + */ + public getCreditAssociatedInvoicesToApply = async ( + tenantId: number, + creditNoteId: number + ): Promise => { + const { SaleInvoice } = this.tenancy.models(tenantId); + + // Retireve credit note or throw not found service error. + const creditNote = await this.getCreditNoteOrThrowError( + tenantId, + creditNoteId + ); + // Retrieves the published due invoices that associated to the given customer. + const saleInvoices = await SaleInvoice.query() + .where('customerId', creditNote.customerId) + .modify('dueInvoices') + .modify('published'); + + // Transformes the sale invoices models to POJO. + return this.transformer.transform( + tenantId, + saleInvoices, + new CreditNoteWithInvoicesToApplyTransformer() + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply.ts b/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply.ts new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/GetCreditNoteAssociatedInvoicesToApply.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts new file mode 100644 index 000000000..c900e693d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts @@ -0,0 +1,113 @@ +import { Injectable } from '@nestjs/common'; +import moment from 'moment'; +import { omit } from 'lodash'; +import * as R from 'ramda'; +import composeAsync from 'async/compose'; +import { ERRORS } from '../constants'; +import { + ICreditNote, + ICreditNoteEditDTO, + ICreditNoteEntryNewDTO, + ICreditNoteNewDTO, +} from '../types/CreditNotes.types'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; +import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; +import { BrandingTemplateDTOTransformer } from '../../PdfTemplate/BrandingTemplateDTOTransformer'; +import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; +import { CreditNoteAutoIncrementService } from './CreditNoteAutoIncrement.service'; +import { CreditNote } from '../models/CreditNote'; + +@Injectable() +export class CommandCreditNoteDTOTransform { + /** + * @param {ItemsEntriesService} itemsEntriesService - The items entries service. + * @param {BranchTransactionDTOTransformer} branchDTOTransform - The branch transaction DTO transformer. + * @param {WarehouseTransactionDTOTransform} warehouseDTOTransform - The warehouse transaction DTO transformer. + * @param {BrandingTemplateDTOTransformer} brandingTemplatesTransformer - The branding template DTO transformer. + * @param {CreditNoteAutoIncrementService} creditNoteAutoIncrement - The credit note auto increment service. + */ + constructor( + private readonly itemsEntriesService: ItemsEntriesService, + private readonly branchDTOTransform: BranchTransactionDTOTransformer, + private readonly warehouseDTOTransform: WarehouseTransactionDTOTransform, + private readonly brandingTemplatesTransformer: BrandingTemplateDTOTransformer, + private readonly creditNoteAutoIncrement: CreditNoteAutoIncrementService + ) {} + + /** + * Transformes the credit/edit DTO to model. + * @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO + * @param {string} customerCurrencyCode - + */ + public transformCreateEditDTOToModel = async ( + creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO, + customerCurrencyCode: string, + oldCreditNote?: CreditNote, + ): Promise => { + // Retrieve the total amount of the given items entries. + const amount = this.itemsEntriesService.getTotalItemsEntries( + creditNoteDTO.entries, + ); + const entries = R.compose( + // Associate the default index to each item entry. + assocItemEntriesDefaultIndex, + + // Associate the reference type to credit note entries. + R.map((entry: ICreditNoteEntryNewDTO) => ({ + ...entry, + referenceType: 'CreditNote', + })), + )(creditNoteDTO.entries); + + // Retreive the next credit note number. + const autoNextNumber = this.creditNoteAutoIncrement.getNextCreditNumber(); + + // Detarmines the credit note number. + const creditNoteNumber = + creditNoteDTO.creditNoteNumber || + oldCreditNote?.creditNoteNumber || + autoNextNumber; + + const initialDTO = { + ...omit(creditNoteDTO, ['open', 'attachments']), + creditNoteNumber, + amount, + currencyCode: customerCurrencyCode, + exchangeRate: creditNoteDTO.exchangeRate || 1, + entries, + ...(creditNoteDTO.open && + !oldCreditNote?.openedAt && { + openedAt: moment().toMySqlDateTime(), + }), + refundedAmount: 0, + invoicesAmount: 0, + }; + const initialAsyncDTO = await composeAsync( + // Assigns the default branding template id to the invoice DTO. + this.brandingTemplatesTransformer.assocDefaultBrandingTemplate( + 'CreditNote', + ), + )(initialDTO); + + return R.compose( + this.branchDTOTransform.transformDTO, + this.warehouseDTOTransform.transformDTO, + )(initialAsyncDTO); + }; + + /** + * Validate the credit note remaining amount. + * @param {ICreditNote} creditNote + * @param {number} amount + */ + public validateCreditRemainingAmount = ( + creditNote: CreditNote, + amount: number, + ) => { + if (creditNote.creditsRemaining < amount) { + throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT); + } + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreateCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreateCreditNote.service.ts new file mode 100644 index 000000000..c83bd1f58 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreateCreditNote.service.ts @@ -0,0 +1,83 @@ +import { Knex } from 'knex'; +import { Injectable } from '@nestjs/common'; +import { + ICreditNoteCreatedPayload, + ICreditNoteCreatingPayload, + ICreditNoteNewDTO, +} from '../types/CreditNotes.types'; +import { CreditNote } from '../models/CreditNote'; +import { Contact } from '../../Contacts/models/Contact'; +import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; + +@Injectable() +export class CreateCreditNoteService { + constructor( + private readonly uow: UnitOfWork, + private readonly itemsEntriesService: ItemsEntriesService, + private readonly eventPublisher: EventEmitter2, + private readonly creditNoteModel: typeof CreditNote, + private readonly contactModel: typeof Contact, + private readonly commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform, + ) {} + + /** + * Creates a new credit note. + * @param creditNoteDTO + */ + public creditCreditNote = async ( + creditNoteDTO: ICreditNoteNewDTO, + trx?: Knex.Transaction, + ) => { + // Triggers `onCreditNoteCreate` event. + await this.eventPublisher.emitAsync(events.creditNote.onCreate, { + creditNoteDTO, + }); + // Validate customer existance. + const customer = await this.contactModel + .query() + .modify('customer') + .findById(creditNoteDTO.customerId) + .throwIfNotFound(); + + // Validate items ids existance. + await this.itemsEntriesService.validateItemsIdsExistance( + creditNoteDTO.entries, + ); + // Validate items should be sellable items. + await this.itemsEntriesService.validateNonSellableEntriesItems( + creditNoteDTO.entries, + ); + // Transformes the given DTO to storage layer data. + const creditNoteModel = + await this.commandCreditNoteDTOTransform.transformCreateEditDTOToModel( + creditNoteDTO, + customer.currencyCode, + ); + // Creates a new credit card transactions under unit-of-work envirement. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onCreditNoteCreating` event. + await this.eventPublisher.emitAsync(events.creditNote.onCreating, { + creditNoteDTO, + trx, + } as ICreditNoteCreatingPayload); + + // Upsert the credit note graph. + const creditNote = await this.creditNoteModel.query(trx).upsertGraph({ + ...creditNoteModel, + }); + // Triggers `onCreditNoteCreated` event. + await this.eventPublisher.emitAsync(events.creditNote.onCreated, { + creditNoteDTO, + creditNote, + creditNoteId: creditNote.id, + trx, + } as ICreditNoteCreatedPayload); + + return creditNote; + }, trx); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreateRefundCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreateRefundCreditNote.service.ts new file mode 100644 index 000000000..4533bc091 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreateRefundCreditNote.service.ts @@ -0,0 +1,113 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { + ICreditNoteRefundDTO, + IRefundCreditNoteCreatedPayload, + IRefundCreditNoteCreatingPayload, +} from '../types/CreditNotes.types'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { RefundCreditNote } from '../models/RefundCreditNote'; +import { events } from '@/common/events/events'; +import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service'; +import { CreditNote } from '../models/CreditNote'; + +@Injectable() +export class CreateRefundCreditNoteService { + /** + * @param {UnitOfWork} uow - The unit of work service. + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - The command credit note DTO transform service. + * @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model. + * @param {typeof Account} accountModel - The account model. + * @param {typeof CreditNote} creditNoteModel - The credit note model. + */ + constructor( + private uow: UnitOfWork, + private eventPublisher: EventEmitter2, + private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform, + + @Inject(RefundCreditNote.name) + private refundCreditNoteModel: typeof RefundCreditNote, + + @Inject(Account.name) private accountModel: typeof Account, + @Inject(CreditNote.name) private creditNoteModel: typeof CreditNote, + ) {} + + /** + * Retrieve the credit note graph. + * @param {number} creditNoteId + * @param {ICreditNoteRefundDTO} newCreditNoteDTO + * @returns {Promise} + */ + public async createCreditNoteRefund( + creditNoteId: number, + newCreditNoteDTO: ICreditNoteRefundDTO, + ): Promise { + // Retrieve the credit note or throw not found service error. + const creditNote = await this.creditNoteModel + .query() + .findById(creditNoteId) + .throwIfNotFound(); + + // Retrieve the withdrawal account or throw not found service error. + const fromAccount = await this.accountModel + .query() + .findById(newCreditNoteDTO.fromAccountId) + .throwIfNotFound(); + + // Validate the credit note remaining amount. + this.commandCreditNoteDTOTransform?.validateCreditRemainingAmount( + creditNote, + newCreditNoteDTO.amount, + ); + // Validate the refund withdrawal account type. + this.commandCreditNoteDTOTransform.validateRefundWithdrawwalAccountType( + fromAccount, + ); + // Creates a refund credit note transaction. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onCreditNoteRefundCreating` event. + await this.eventPublisher.emitAsync(events.creditNote.onRefundCreating, { + trx, + creditNote, + newCreditNoteDTO, + } as IRefundCreditNoteCreatingPayload); + + // Stores the refund credit note graph to the storage layer. + const refundCreditNote = await this.refundCreditNoteModel + .query(trx) + .insertAndFetch({ + ...this.transformDTOToModel(creditNote, newCreditNoteDTO), + }); + + // Triggers `onCreditNoteRefundCreated` event. + await this.eventPublisher.emitAsync(events.creditNote.onRefundCreated, { + trx, + refundCreditNote, + creditNote, + } as IRefundCreditNoteCreatedPayload); + + return refundCreditNote; + }); + } + + /** + * Transformes the refund credit note DTO to model. + * @param {number} creditNoteId + * @param {ICreditNoteRefundDTO} creditNoteDTO + * @returns {ICreditNote} + */ + private transformDTOToModel = ( + creditNote: CreditNote, + creditNoteDTO: ICreditNoteRefundDTO, + ): RefundCreditNote => { + return { + creditNoteId: creditNote.id, + currencyCode: creditNote.currencyCode, + ...creditNoteDTO, + exchangeRate: creditNoteDTO.exchangeRate || 1, + }; + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncCredit.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncCredit.service.ts new file mode 100644 index 000000000..d32abc4a4 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncCredit.service.ts @@ -0,0 +1,45 @@ +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNote } from '../models/CreditNote'; + +@Injectable() +export class CreditNoteApplySyncCredit { + constructor( + @Inject(CreditNote.name) + private creditNoteModel: typeof CreditNote, + ) {} + + /** + * Increment credit note invoiced amount. + * @param {number} creditNoteId + * @param {number} invoicesAppliedAmount + * @param {Knex.Transaction} [trx] + */ + public async incrementCreditNoteInvoicedAmount( + creditNoteId: number, + invoicesAppliedAmount: number, + trx?: Knex.Transaction, + ): Promise { + await this.creditNoteModel + .query(trx) + .findById(creditNoteId) + .increment('invoicesAmount', invoicesAppliedAmount); + } + + /** + * Decrement credit note invoiced amount. + * @param {number} creditNoteId + * @param {number} invoicesAppliedAmount + * @param {Knex.Transaction} [trx] + */ + public async decrementCreditNoteInvoicedAmount( + creditNoteId: number, + invoicesAppliedAmount: number, + trx?: Knex.Transaction, + ): Promise { + await this.creditNoteModel + .query(trx) + .findById(creditNoteId) + .decrement('invoicesAmount', invoicesAppliedAmount); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncInvoices.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncInvoices.service.ts new file mode 100644 index 000000000..ade0f54a6 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplySyncInvoices.service.ts @@ -0,0 +1,48 @@ +import { Knex } from 'knex'; +import { Injectable, Inject } from '@nestjs/common'; +import Bluebird from 'bluebird'; +import { ICreditNoteAppliedToInvoice } from '../types/CreditNotes.types'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; + +@Injectable() +export class CreditNoteApplySyncInvoicesCreditedAmount { + constructor( + @Inject(SaleInvoice.name) + private readonly saleInvoiceModel: typeof SaleInvoice, + ) {} + + /** + * Increment invoices credited amount. + * @param {ICreditNoteAppliedToInvoice[]} creditNoteAppliedInvoices - + * @param {Knex.Transaction} trx - + */ + public incrementInvoicesCreditedAmount = async ( + creditNoteAppliedInvoices: ICreditNoteAppliedToInvoice[], + trx?: Knex.Transaction + ) => { + await Bluebird.each( + creditNoteAppliedInvoices, + (creditNoteAppliedInvoice: ICreditNoteAppliedToInvoice) => { + return this.saleInvoiceModel.query(trx) + .where('id', creditNoteAppliedInvoice.invoiceId) + .increment('creditedAmount', creditNoteAppliedInvoice.amount); + } + ); + }; + + /** + * + * @param {number} invoicesIds + * @param {number} amount - + * @param {Knex.Transaction} knex - + */ + public decrementInvoiceCreditedAmount = async ( + invoiceId: number, + amount: number, + trx?: Knex.Transaction + ) => { + await this.saleInvoiceModel.query(trx) + .findById(invoiceId) + .decrement('creditedAmount', amount); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplyToInvoices.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplyToInvoices.service.ts new file mode 100644 index 000000000..5f194b0de --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteApplyToInvoices.service.ts @@ -0,0 +1,131 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { sumBy } from 'lodash'; +import { + ICreditNoteAppliedToInvoice, + ICreditNoteAppliedToInvoiceModel, + IApplyCreditToInvoicesDTO, + IApplyCreditToInvoicesCreatedPayload, + ICreditNote, +} from '../types/CreditNotes.types'; +import { ERRORS } from '../constants'; +import { PaymentReceivedValidators } from '@/modules/PaymentReceived/commands/PaymentReceivedValidators.service'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; +import { events } from '@/common/events/events'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@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, + + @Inject(CreditNoteAppliedInvoice.name) + private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice, + ) {} + + /** + * Apply credit note to the given invoices. + * @param {number} creditNoteId + * @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO + */ + public async applyCreditNoteToInvoices( + creditNoteId: number, + applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO, + ): Promise { + // Saves the credit note or throw not found service error. + const creditNote = await this.getCreditNoteOrThrowError(creditNoteId); + + // 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.amount, + ); + // Validate the credit note remaining amount. + this.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: 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: SaleInvoice[], + amount: number, + ) => { + const invalidInvoices = invoices.filter( + (invoice) => invoice.dueAmount < amount, + ); + if (invalidInvoices.length > 0) { + throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT); + } + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteAutoIncrement.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteAutoIncrement.service.ts new file mode 100644 index 000000000..48401dbcf --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteAutoIncrement.service.ts @@ -0,0 +1,28 @@ +import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CreditNoteAutoIncrementService { + constructor( + private readonly autoIncrementOrdersService: AutoIncrementOrdersService, + ) {} + + /** + * Retrieve the next unique credit number. + * @return {string} + */ + public getNextCreditNumber(): string { + return this.autoIncrementOrdersService.getNextTransactionNumber( + 'credit_note', + ); + } + + /** + * Increment the credit note serial next number. + */ + public incrementSerialNumber() { + return this.autoIncrementOrdersService.incrementSettingsNextNumber( + 'credit_note', + ); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGLEntries.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGLEntries.ts new file mode 100644 index 000000000..d7662438d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGLEntries.ts @@ -0,0 +1,297 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import * as R from 'ramda'; +// import { +// AccountNormal, +// IItemEntry, +// ILedgerEntry, +// ICreditNote, +// ILedger, +// ICreditNoteGLCommonEntry, +// } from '@/interfaces'; +// import HasTenancyService from '@/services/Tenancy/TenancyService'; +// import Ledger from '@/services/Accounting/Ledger'; +// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; +// import { SaleReceipt } from '@/models'; + +// @Service() +// export default class CreditNoteGLEntries { +// @Inject() +// private tenancy: HasTenancyService; + +// @Inject() +// private ledgerStorage: LedgerStorageService; + +// /** +// * Retrieves the credit note GL. +// * @param {ICreditNote} creditNote +// * @param {number} receivableAccount +// * @returns {Ledger} +// */ +// private getCreditNoteGLedger = ( +// creditNote: ICreditNote, +// receivableAccount: number, +// discountAccount: number, +// adjustmentAccount: number +// ): Ledger => { +// const ledgerEntries = this.getCreditNoteGLEntries( +// creditNote, +// receivableAccount, +// discountAccount, +// adjustmentAccount +// ); +// return new Ledger(ledgerEntries); +// }; + +// /** +// * Saves credit note GL entries. +// * @param {number} tenantId - +// * @param {ICreditNote} creditNote - Credit note model. +// * @param {number} payableAccount - Payable account id. +// * @param {Knex.Transaction} trx +// */ +// public saveCreditNoteGLEntries = async ( +// tenantId: number, +// creditNote: ICreditNote, +// payableAccount: number, +// discountAccount: number, +// adjustmentAccount: number, +// trx?: Knex.Transaction +// ): Promise => { +// const ledger = this.getCreditNoteGLedger( +// creditNote, +// payableAccount, +// discountAccount, +// adjustmentAccount +// ); + +// await this.ledgerStorage.commit(tenantId, ledger, trx); +// }; + +// /** +// * Reverts the credit note associated GL entries. +// * @param {number} tenantId +// * @param {number} vendorCreditId +// * @param {Knex.Transaction} trx +// */ +// public revertVendorCreditGLEntries = async ( +// tenantId: number, +// creditNoteId: number, +// trx?: Knex.Transaction +// ): Promise => { +// await this.ledgerStorage.deleteByReference( +// tenantId, +// creditNoteId, +// 'CreditNote', +// trx +// ); +// }; + +// /** +// * Writes vendor credit associated GL entries. +// * @param {number} tenantId - Tenant id. +// * @param {number} creditNoteId - Credit note id. +// * @param {Knex.Transaction} trx - Knex transactions. +// */ +// public createVendorCreditGLEntries = async ( +// tenantId: number, +// creditNoteId: number, +// trx?: Knex.Transaction +// ): Promise => { +// const { CreditNote } = this.tenancy.models(tenantId); +// const { accountRepository } = this.tenancy.repositories(tenantId); + +// // Retrieve the credit note with associated entries and items. +// const creditNoteWithItems = await CreditNote.query(trx) +// .findById(creditNoteId) +// .withGraphFetched('entries.item'); + +// // Retreive the the `accounts receivable` account based on the given currency. +// const ARAccount = await accountRepository.findOrCreateAccountReceivable( +// creditNoteWithItems.currencyCode +// ); +// const discountAccount = await accountRepository.findOrCreateDiscountAccount( +// {} +// ); +// const adjustmentAccount = +// await accountRepository.findOrCreateOtherChargesAccount({}); +// // Saves the credit note GL entries. +// await this.saveCreditNoteGLEntries( +// tenantId, +// creditNoteWithItems, +// ARAccount.id, +// discountAccount.id, +// adjustmentAccount.id, +// trx +// ); +// }; + +// /** +// * Edits vendor credit associated GL entries. +// * @param {number} tenantId +// * @param {number} creditNoteId +// * @param {Knex.Transaction} trx +// */ +// public editVendorCreditGLEntries = async ( +// tenantId: number, +// creditNoteId: number, +// trx?: Knex.Transaction +// ): Promise => { +// // Reverts vendor credit GL entries. +// await this.revertVendorCreditGLEntries(tenantId, creditNoteId, trx); + +// // Creates vendor credit Gl entries. +// await this.createVendorCreditGLEntries(tenantId, creditNoteId, trx); +// }; + +// /** +// * Retrieve the credit note common entry. +// * @param {ICreditNote} creditNote - +// * @returns {ICreditNoteGLCommonEntry} +// */ +// private getCreditNoteCommonEntry = ( +// creditNote: ICreditNote +// ): ICreditNoteGLCommonEntry => { +// return { +// date: creditNote.creditNoteDate, +// userId: creditNote.userId, +// currencyCode: creditNote.currencyCode, +// exchangeRate: creditNote.exchangeRate, + +// transactionType: 'CreditNote', +// transactionId: creditNote.id, + +// transactionNumber: creditNote.creditNoteNumber, +// referenceNumber: creditNote.referenceNo, + +// createdAt: creditNote.createdAt, +// indexGroup: 10, + +// credit: 0, +// debit: 0, + +// branchId: creditNote.branchId, +// }; +// }; + +// /** +// * Retrieves the creidt note A/R entry. +// * @param {ICreditNote} creditNote - +// * @param {number} ARAccountId - +// * @returns {ILedgerEntry} +// */ +// private getCreditNoteAREntry = ( +// creditNote: ICreditNote, +// ARAccountId: number +// ): ILedgerEntry => { +// const commonEntry = this.getCreditNoteCommonEntry(creditNote); + +// return { +// ...commonEntry, +// credit: creditNote.totalLocal, +// accountId: ARAccountId, +// contactId: creditNote.customerId, +// index: 1, +// accountNormal: AccountNormal.DEBIT, +// }; +// }; + +// /** +// * Retrieve the credit note item entry. +// * @param {ICreditNote} creditNote +// * @param {IItemEntry} entry +// * @param {number} index +// * @returns {ILedgerEntry} +// */ +// private getCreditNoteItemEntry = R.curry( +// ( +// creditNote: ICreditNote, +// entry: IItemEntry, +// index: number +// ): ILedgerEntry => { +// const commonEntry = this.getCreditNoteCommonEntry(creditNote); +// const totalLocal = entry.totalExcludingTax * creditNote.exchangeRate; + +// return { +// ...commonEntry, +// debit: totalLocal, +// accountId: entry.sellAccountId || entry.item.sellAccountId, +// note: entry.description, +// index: index + 2, +// itemId: entry.itemId, +// itemQuantity: entry.quantity, +// accountNormal: AccountNormal.CREDIT, +// }; +// } +// ); + +// /** +// * Retrieves the credit note discount entry. +// * @param {ICreditNote} creditNote +// * @param {number} discountAccountId +// * @returns {ILedgerEntry} +// */ +// private getDiscountEntry = ( +// creditNote: ICreditNote, +// discountAccountId: number +// ): ILedgerEntry => { +// const commonEntry = this.getCreditNoteCommonEntry(creditNote); + +// return { +// ...commonEntry, +// credit: creditNote.discountAmountLocal, +// accountId: discountAccountId, +// index: 1, +// accountNormal: AccountNormal.CREDIT, +// }; +// }; + +// /** +// * Retrieves the credit note adjustment entry. +// * @param {ICreditNote} creditNote +// * @param {number} adjustmentAccountId +// * @returns {ILedgerEntry} +// */ +// private getAdjustmentEntry = ( +// creditNote: ICreditNote, +// adjustmentAccountId: number +// ): ILedgerEntry => { +// const commonEntry = this.getCreditNoteCommonEntry(creditNote); +// const adjustmentAmount = Math.abs(creditNote.adjustmentLocal); + +// return { +// ...commonEntry, +// credit: creditNote.adjustmentLocal < 0 ? adjustmentAmount : 0, +// debit: creditNote.adjustmentLocal > 0 ? adjustmentAmount : 0, +// accountId: adjustmentAccountId, +// accountNormal: AccountNormal.CREDIT, +// index: 1, +// }; +// }; + +// /** +// * Retrieve the credit note GL entries. +// * @param {ICreditNote} creditNote - Credit note. +// * @param {IAccount} receivableAccount - Receviable account. +// * @returns {ILedgerEntry[]} - Ledger entries. +// */ +// public getCreditNoteGLEntries = ( +// creditNote: ICreditNote, +// ARAccountId: number, +// discountAccountId: number, +// adjustmentAccountId: number +// ): ILedgerEntry[] => { +// const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId); + +// const getItemEntry = this.getCreditNoteItemEntry(creditNote); +// const itemsEntries = creditNote.entries.map(getItemEntry); + +// const discountEntry = this.getDiscountEntry(creditNote, discountAccountId); +// const adjustmentEntry = this.getAdjustmentEntry( +// creditNote, +// adjustmentAccountId +// ); + +// return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries]; +// }; +// } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteWithInvoicesToApplyTransformer.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteWithInvoicesToApplyTransformer.ts new file mode 100644 index 000000000..db3821019 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteWithInvoicesToApplyTransformer.ts @@ -0,0 +1,68 @@ +import { Transformer } from "@/modules/Transformer/Transformer"; + +export class CreditNoteWithInvoicesToApplyTransformer extends Transformer { + /** + * Include these attributes to sale invoice object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'formattedInvoiceDate', + 'formattedDueDate', + 'formattedAmount', + 'formattedDueAmount', + 'formattedPaymentAmount', + ]; + }; + + /** + * Retrieve formatted invoice date. + * @param {ISaleInvoice} invoice + * @returns {String} + */ + protected formattedInvoiceDate = (invoice): string => { + return this.formatDate(invoice.invoiceDate); + }; + + /** + * Retrieve formatted due date. + * @param {ISaleInvoice} invoice + * @returns {string} + */ + protected formattedDueDate = (invoice): string => { + return this.formatDate(invoice.dueDate); + }; + + /** + * Retrieve formatted invoice amount. + * @param {ISaleInvoice} invoice + * @returns {string} + */ + protected formattedAmount = (invoice): string => { + return this.formatNumber(invoice.balance, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieve formatted invoice due amount. + * @param {ISaleInvoice} invoice + * @returns {string} + */ + protected formattedDueAmount = (invoice): string => { + return this.formatNumber(invoice.dueAmount, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieve formatted payment amount. + * @param {ISaleInvoice} invoice + * @returns {string} + */ + protected formattedPaymentAmount = (invoice): string => { + return this.formatNumber(invoice.paymentAmount, { + currencyCode: invoice.currencyCode, + }); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesExportable.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesExportable.ts new file mode 100644 index 000000000..8fd00ff7e --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesExportable.ts @@ -0,0 +1,35 @@ +// import { Inject, Service } from 'typedi'; +// import { ICreditNotesQueryDTO } from '@/interfaces'; +// import { Exportable } from '@/services/Export/Exportable'; +// import ListCreditNotes from '../ListCreditNotes'; + +// @Service() +// export class CreditNotesExportable extends Exportable { +// @Inject() +// private getCreditNotes: ListCreditNotes; + +// /** +// * Retrieves the accounts data to exportable sheet. +// * @param {number} tenantId - +// * @param {IVendorCreditsQueryDTO} query - +// * @returns {} +// */ +// public exportable(tenantId: number, query: ICreditNotesQueryDTO) { +// const filterQuery = (query) => { +// query.withGraphFetched('branch'); +// query.withGraphFetched('warehouse'); +// }; +// const parsedQuery = { +// sortOrder: 'desc', +// columnSortBy: 'created_at', +// ...query, +// page: 1, +// pageSize: 12000, +// filterQuery, +// } as ICreditNotesQueryDTO; + +// return this.getCreditNotes +// .getCreditNotesList(tenantId, parsedQuery) +// .then((output) => output.creditNotes); +// } +// } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesImportable.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesImportable.ts new file mode 100644 index 000000000..ba9e4cac8 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesImportable.ts @@ -0,0 +1,44 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import { ICreditNoteNewDTO } from '@/interfaces'; +// import { Importable } from '../Import/Importable'; +// import CreateCreditNote from './commands/CreateCreditNote.service'; + +// @Service() +// export class CreditNotesImportable extends Importable { +// @Inject() +// private createCreditNoteImportable: CreateCreditNote; + +// /** +// * Importing to account service. +// * @param {number} tenantId +// * @param {IAccountCreateDTO} createAccountDTO +// * @returns +// */ +// public importable( +// tenantId: number, +// createAccountDTO: ICreditNoteNewDTO, +// trx?: Knex.Transaction +// ) { +// return this.createCreditNoteImportable.newCreditNote( +// tenantId, +// createAccountDTO, +// trx +// ); +// } + +// /** +// * Concurrrency controlling of the importing process. +// * @returns {number} +// */ +// public get concurrency() { +// return 1; +// } + +// /** +// * Retrieves the sample data that used to download accounts sample sheet. +// */ +// public sampleData(): any[] { +// return []; +// } +// } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts new file mode 100644 index 000000000..ec7deda66 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts @@ -0,0 +1,90 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import { ICreditNote } from '@/interfaces'; +// import InventoryService from '@/services/Inventory/Inventory'; +// import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; + +// @Service() +// export default class CreditNoteInventoryTransactions { +// @Inject() +// inventoryService: InventoryService; + +// @Inject() +// itemsEntriesService: ItemsEntriesService; + +// /** +// * Creates credit note inventory transactions. +// * @param {number} tenantId +// * @param {ICreditNote} creditNote +// */ +// public createInventoryTransactions = async ( +// tenantId: number, +// creditNote: ICreditNote, +// trx?: Knex.Transaction +// ): Promise => { +// // Loads the inventory items entries of the given sale invoice. +// const inventoryEntries = +// await this.itemsEntriesService.filterInventoryEntries( +// tenantId, +// creditNote.entries +// ); +// const transaction = { +// transactionId: creditNote.id, +// transactionType: 'CreditNote', +// transactionNumber: creditNote.creditNoteNumber, +// exchangeRate: creditNote.exchangeRate, +// date: creditNote.creditNoteDate, +// direction: 'IN', +// entries: inventoryEntries, +// createdAt: creditNote.createdAt, +// warehouseId: creditNote.warehouseId, +// }; +// // Writes inventory tranactions. +// await this.inventoryService.recordInventoryTransactionsFromItemsEntries( +// tenantId, +// transaction, +// false, +// trx +// ); +// }; + +// /** +// * Edits vendor credit associated inventory transactions. +// * @param {number} tenantId +// * @param {number} creditNoteId +// * @param {ICreditNote} creditNote +// * @param {Knex.Transactions} trx +// */ +// public editInventoryTransactions = async ( +// tenantId: number, +// creditNoteId: number, +// creditNote: ICreditNote, +// trx?: Knex.Transaction +// ): Promise => { +// // Deletes inventory transactions. +// await this.deleteInventoryTransactions(tenantId, creditNoteId, trx); + +// // Re-write inventory transactions. +// await this.createInventoryTransactions(tenantId, creditNote, trx); +// }; + +// /** +// * Deletes credit note associated inventory transactions. +// * @param {number} tenantId - Tenant id. +// * @param {number} creditNoteId - Credit note id. +// * @param {Knex.Transaction} trx - +// */ +// public deleteInventoryTransactions = async ( +// tenantId: number, +// creditNoteId: number, +// trx?: Knex.Transaction +// ): Promise => { +// // Deletes the inventory transactions by the given reference id and type. +// await this.inventoryService.deleteInventoryTransactions( +// tenantId, +// creditNoteId, +// 'CreditNote', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts new file mode 100644 index 000000000..6c74a6a27 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts @@ -0,0 +1,124 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { + ICreditNoteDeletedPayload, + ICreditNoteDeletingPayload, +} from '../types/CreditNotes.types'; +import { ERRORS } from '../constants'; +import { CreditNote } from '../models/CreditNote'; +import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; +import { + RefundCreditNote as RefundCreditNoteModel, +} from '../models/RefundCreditNote'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { events } from '@/common/events/events'; + +@Injectable() +export class DeleteCreditNoteService { + /** + * @param {UnitOfWork} uow - Unit of work. + * @param {EventEmitter2} eventPublisher - Event emitter. + * @param {typeof CreditNote} creditNoteModel - Credit note model. + * @param {typeof ItemEntry} itemEntryModel - Item entry model. + * @param {typeof CreditNoteAppliedInvoice} creditNoteAppliedInvoiceModel - Credit note applied invoice model. + * @param {typeof RefundCreditNote} refundCreditNoteModel - Refund credit note model. + */ + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + + @Inject(ItemEntry.name) + private readonly itemEntryModel: typeof ItemEntry, + + @Inject(CreditNoteAppliedInvoice.name) + private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice, + + @Inject(RefundCreditNoteModel.name) + private readonly refundCreditNoteModel: typeof RefundCreditNoteModel, + ) {} + + /** + * Deletes the given credit note transactions. + * @param {number} creditNoteId + * @returns {Promise} + */ + public async deleteCreditNote(creditNoteId: number): Promise { + // Retrieve the credit note or throw not found service error. + const oldCreditNote = await this.creditNoteModel + .query() + .findById(creditNoteId) + .throwIfNotFound(); + + // Validate credit note has no refund transactions. + await this.validateCreditNoteHasNoRefundTransactions(creditNoteId); + + // Validate credit note has no applied invoices transactions. + await this.validateCreditNoteHasNoApplyInvoiceTransactions(creditNoteId); + + // Deletes the credit note transactions under unit-of-work transaction. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onCreditNoteDeleting` event. + await this.eventPublisher.emitAsync(events.creditNote.onDeleting, { + trx, + oldCreditNote, + } as ICreditNoteDeletingPayload); + + // Deletes the associated credit note entries. + await this.itemEntryModel + .query(trx) + .where('reference_id', creditNoteId) + .where('reference_type', 'CreditNote') + .delete(); + + // Deletes the credit note transaction. + await this.creditNoteModel.query(trx).findById(creditNoteId).delete(); + + // Triggers `onCreditNoteDeleted` event. + await this.eventPublisher.emitAsync(events.creditNote.onDeleted, { + oldCreditNote, + creditNoteId, + trx, + } as ICreditNoteDeletedPayload); + }); + } + + /** + * Validates credit note has no associated refund transactions. + * @param {number} creditNoteId + * @returns {Promise} + */ + private async validateCreditNoteHasNoRefundTransactions( + creditNoteId: number, + ): Promise { + const refundTransactions = await this.refundCreditNoteModel + .query() + .where('creditNoteId', creditNoteId); + + if (refundTransactions.length > 0) { + throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS); + } + } + + /** + * Validate credit note has no associated applied invoices transactions. + * @param {number} creditNoteId - Credit note id. + * @returns {Promise} + */ + private async validateCreditNoteHasNoApplyInvoiceTransactions( + creditNoteId: number, + ): Promise { + const appliedTransactions = await this.creditNoteAppliedInvoiceModel + .query() + .where('creditNoteId', creditNoteId); + + if (appliedTransactions.length > 0) { + throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_APPLIED_INVOICES); + } + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNoteApplyToInvoices.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNoteApplyToInvoices.service.ts new file mode 100644 index 000000000..623a532bf --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNoteApplyToInvoices.service.ts @@ -0,0 +1,63 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { IApplyCreditToInvoicesDeletedPayload } from '../types/CreditNotes.types'; +import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { events } from '@/common/events/events'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { CreditNote } from '../models/CreditNote'; +import { ERRORS } from '../constants'; + +@Injectable() +export default class DeleteCreditNoteApplyToInvoices { + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + ) {} + + /** + * Apply credit note to the given invoices. + * @param {number} creditNoteId + * @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO + */ + public deleteApplyCreditNoteToInvoices = async ( + applyCreditToInvoicesId: number, + ): Promise => { + const creditNoteAppliedToInvoice = await this.creditNoteAppliedInvoiceModel + .query() + .findById(applyCreditToInvoicesId); + + if (!creditNoteAppliedToInvoice) { + throw new ServiceError(ERRORS.CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND); + } + // Retrieve the credit note or throw not found service error. + const creditNote = await this.creditNoteModel + .query() + .findById(creditNoteAppliedToInvoice.creditNoteId) + .throwIfNotFound(); + + // Creates credit note apply to invoice transaction. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Delete credit note applied to invoices. + await this.creditNoteAppliedInvoiceModel + .query(trx) + .findById(applyCreditToInvoicesId) + .delete(); + + // Triggers `onCreditNoteApplyToInvoiceDeleted` event. + await this.eventPublisher.emitAsync( + events.creditNote.onApplyToInvoicesDeleted, + { + creditNote, + creditNoteAppliedToInvoice, + trx, + } as IApplyCreditToInvoicesDeletedPayload, + ); + }); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/DeleteCustomerLinkedCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCustomerLinkedCreditNote.service.ts new file mode 100644 index 000000000..149047e68 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCustomerLinkedCreditNote.service.ts @@ -0,0 +1,26 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNote } from '../models/CreditNote'; +import { ERRORS } from '../constants'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class DeleteCustomerLinkedCreditNoteService { + constructor( + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + ) {} + + /** + * Validate the given customer has no linked credit note transactions. + * @param {number} customerId - The customer identifier. + */ + public async validateCustomerHasNoCreditTransaction(customerId: number) { + const associatedCredits = await this.creditNoteModel + .query() + .where('customerId', customerId); + + if (associatedCredits.length > 0) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_LINKED_CREDIT_NOTES); + } + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/DeleteRefundCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/DeleteRefundCreditNote.service.ts new file mode 100644 index 000000000..2e9c684bb --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/DeleteRefundCreditNote.service.ts @@ -0,0 +1,73 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Knex } from 'knex'; +import { + IRefundCreditNoteDeletedPayload, + IRefundCreditNoteDeletingPayload, + IRefundVendorCreditDeletedPayload, +} from '../types/CreditNotes.types'; +import { RefundCreditNote } from '../models/RefundCreditNote'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { events } from '@/common/events/events'; + +@Injectable() +export default class DeleteRefundCreditNote { + /** + * @param {UnitOfWork} uow + * @param {EventEmitter2} eventPublisher + * @param {typeof RefundCreditNoteModel} refundCreditNoteModel + */ + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + + @Inject(RefundCreditNote.name) + private readonly refundCreditNoteModel: typeof RefundCreditNote, + ) {} + + /** + * Retrieve the credit note graph. + * @param {number} refundCreditId + * @returns + */ + public deleteCreditNoteRefund = async (refundCreditId: number) => { + // Retrieve the old credit note or throw not found service error. + const oldRefundCredit = await this.refundCreditNoteModel + .query() + .findById(refundCreditId) + .throwIfNotFound(); + + // Triggers `onCreditNoteRefundDeleted` event. + await this.eventPublisher.emitAsync(events.creditNote.onRefundDelete, { + refundCreditId, + oldRefundCredit, + } as IRefundCreditNoteDeletedPayload); + + // Deletes refund credit note transactions with associated entries. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + const eventPayload = { + trx, + refundCreditId, + oldRefundCredit, + } as IRefundCreditNoteDeletedPayload | IRefundCreditNoteDeletingPayload; + + // Triggers `onCreditNoteRefundDeleting` event. + await this.eventPublisher.emitAsync( + events.creditNote.onRefundDeleting, + eventPayload, + ); + + // Deletes the refund credit note graph from the storage. + await this.refundCreditNoteModel + .query(trx) + .findById(refundCreditId) + .delete(); + + // Triggers `onCreditNoteRefundDeleted` event. + await this.eventPublisher.emitAsync( + events.creditNote.onRefundDeleted, + eventPayload as IRefundVendorCreditDeletedPayload, + ); + }); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/EditCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/EditCreditNote.service.ts new file mode 100644 index 000000000..330561a6c --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/EditCreditNote.service.ts @@ -0,0 +1,101 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + ICreditNoteEditDTO, + ICreditNoteEditedPayload, + ICreditNoteEditingPayload, +} from '../types/CreditNotes.types'; +import { Knex } from 'knex'; +import { CreditNote } from '../models/CreditNote'; +import { Contact } from '../../Contacts/models/Contact'; +import { ItemsEntriesService } from '../../Items/ItemsEntries.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service'; +import { events } from '@/common/events/events'; +import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service'; + +@Injectable() +export class EditCreditNoteService { + /** + * @param {typeof CreditNote} creditNoteModel - The credit note model. + * @param {typeof Contact} contactModel - The contact model. + * @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - The command credit note DTO transform service. + * @param {ItemsEntriesService} itemsEntriesService - The items entries service. + * @param {EventEmitter2} eventPublisher - The event publisher. + * @param {UnitOfWork} uow - The unit of work. + */ + constructor( + @Inject(CreditNote.name) private creditNoteModel: typeof CreditNote, + @Inject(Contact.name) private contactModel: typeof Contact, + private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform, + private itemsEntriesService: ItemsEntriesService, + private eventPublisher: EventEmitter2, + private uow: UnitOfWork, + ) {} + + /** + * Edits the given credit note. + * @param {ICreditNoteEditDTO} creditNoteEditDTO - + */ + public async editCreditNote( + creditNoteId: number, + creditNoteEditDTO: ICreditNoteEditDTO, + ) { + // Retrieve the sale invoice or throw not found service error. + const oldCreditNote = await this.creditNoteModel + .query() + .findById(creditNoteId) + .throwIfNotFound(); + + // Validate customer existance. + const customer = await this.contactModel + .query() + .findById(creditNoteEditDTO.customerId); + + // Validate items ids existance. + await this.itemsEntriesService.validateItemsIdsExistance( + creditNoteEditDTO.entries, + ); + // Validate non-sellable entries items. + await this.itemsEntriesService.validateNonSellableEntriesItems( + creditNoteEditDTO.entries, + ); + // Validate the items entries existance. + await this.itemsEntriesService.validateEntriesIdsExistance( + creditNoteId, + 'CreditNote', + creditNoteEditDTO.entries, + ); + // Transformes the given DTO to storage layer data. + const creditNoteModel = + await this.commandCreditNoteDTOTransform.transformCreateEditDTOToModel( + creditNoteEditDTO, + customer.currencyCode, + oldCreditNote, + ); + // Sales the credit note transactions with associated entries. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onCreditNoteEditing` event. + await this.eventPublisher.emitAsync(events.creditNote.onEditing, { + creditNoteEditDTO, + oldCreditNote, + trx, + } as ICreditNoteEditingPayload); + + // Saves the credit note graph to the storage. + const creditNote = await this.creditNoteModel.query(trx).upsertGraph({ + id: creditNoteId, + ...creditNoteModel, + }); + // Triggers `onCreditNoteEdited` event. + await this.eventPublisher.emitAsync(events.creditNote.onEdited, { + trx, + oldCreditNote, + creditNoteId, + creditNote, + creditNoteEditDTO, + } as ICreditNoteEditedPayload); + + return creditNote; + }); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/OpenCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/OpenCreditNote.service.ts new file mode 100644 index 000000000..ed69d4d8a --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/OpenCreditNote.service.ts @@ -0,0 +1,88 @@ +import { + ICreditNoteOpenedPayload, + ICreditNoteOpeningPayload, +} from '../types/CreditNotes.types'; +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { ERRORS } from '../constants'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { CreditNote } from '../models/CreditNote'; +import { events } from '@/common/events/events'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class OpenCreditNoteService { + /** + * @param {EventEmitter2} eventPublisher - The event publisher. + * @param {UnitOfWork} uow - The unit of work. + * @param {typeof CreditNote} creditNoteModel - The credit note model. + */ + constructor( + private readonly eventPublisher: EventEmitter2, + private readonly uow: UnitOfWork, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + ) {} + + /** + * Opens the given credit note. + * @param {number} creditNoteId - + * @returns {Promise} + */ + public openCreditNote = async (creditNoteId: number): Promise => { + // Retrieve the sale invoice or throw not found service error. + const oldCreditNote = await this.creditNoteModel + .query() + .findById(creditNoteId) + .throwIfNotFound(); + + // Throw service error if the credit note is already open. + this.throwErrorIfAlreadyOpen(oldCreditNote); + + // Triggers `onCreditNoteOpen` event. + this.eventPublisher.emitAsync(events.creditNote.onOpen, { + creditNoteId, + oldCreditNote, + }); + // Sales the credit note transactions with associated entries. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + const eventPayload = { + creditNoteId, + oldCreditNote, + trx, + } as ICreditNoteOpeningPayload; + + // Triggers `onCreditNoteOpening` event. + await this.eventPublisher.emitAsync( + events.creditNote.onOpening, + eventPayload, + ); + // Saves the credit note graph to the storage. + const creditNote = await this.creditNoteModel + .query(trx) + .findById(creditNoteId) + .update({ + openedAt: new Date(), + }); + // Triggers `onCreditNoteOpened` event. + await this.eventPublisher.emitAsync(events.creditNote.onOpened, { + ...eventPayload, + creditNote, + } as ICreditNoteOpenedPayload); + + return creditNote; + }); + }; + + /** + * + * @param creditNote + */ + public throwErrorIfAlreadyOpen = (creditNote) => { + if (creditNote.openedAt) { + throw new ServiceError(ERRORS.CREDIT_NOTE_ALREADY_OPENED); + } + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNote.service.ts new file mode 100644 index 000000000..0661efd0b --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNote.service.ts @@ -0,0 +1,45 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ERRORS } from '../constants'; +import { RefundCreditNote } from '../models/RefundCreditNote'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { Account } from '@/modules/Accounts/models/Account.model'; + +@Injectable() +export class RefundCreditNoteService { + /** + * @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model. + */ + constructor( + @Inject(RefundCreditNote.name) + private readonly refundCreditNoteModel: typeof RefundCreditNote, + ) {} + + /** + * Retrieve the credit note graph. + * @param {number} refundCreditId + * @returns {Promise} + */ + public getCreditNoteRefundOrThrowError = async ( + refundCreditId: number, + ): Promise => { + const refundCreditNote = await this.refundCreditNoteModel + .query() + .findById(refundCreditId); + if (!refundCreditNote) { + throw new ServiceError(ERRORS.REFUND_CREDIT_NOTE_NOT_FOUND); + } + return refundCreditNote; + }; + + /** + * Validate the refund account type. + * @param {Account} account + */ + public validateRefundWithdrawwalAccountType = (account: Account): void => { + const supportedTypes = ['bank', 'cash', 'fixed-asset']; + + if (supportedTypes.indexOf(account.accountType) === -1) { + throw new ServiceError(ERRORS.ACCOUNT_INVALID_TYPE); + } + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNoteGLEntries.ts b/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNoteGLEntries.ts new file mode 100644 index 000000000..da014b885 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/RefundCreditNoteGLEntries.ts @@ -0,0 +1,160 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import { AccountNormal, ILedgerEntry, IRefundCreditNote } from '@/interfaces'; +// import HasTenancyService from '@/services/Tenancy/TenancyService'; +// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; +// import Ledger from '@/services/Accounting/Ledger'; + +// @Service() +// export default class RefundCreditNoteGLEntries { +// @Inject() +// ledgerStorage: LedgerStorageService; + +// @Inject() +// tenancy: HasTenancyService; + +// /** +// * Retrieves the refund credit common GL entry. +// * @param {IRefundCreditNote} refundCreditNote +// * @returns +// */ +// private getRefundCreditCommonGLEntry = ( +// refundCreditNote: IRefundCreditNote +// ) => { +// return { +// currencyCode: refundCreditNote.currencyCode, +// exchangeRate: refundCreditNote.exchangeRate, + +// transactionType: 'RefundCreditNote', +// transactionId: refundCreditNote.id, +// date: refundCreditNote.date, +// userId: refundCreditNote.userId, + +// referenceNumber: refundCreditNote.referenceNo, + +// createdAt: refundCreditNote.createdAt, +// indexGroup: 10, + +// credit: 0, +// debit: 0, + +// note: refundCreditNote.description, +// branchId: refundCreditNote.branchId, +// }; +// }; + +// /** +// * Retrieves the refudn credit receivable GL entry. +// * @param {IRefundCreditNote} refundCreditNote +// * @param {number} ARAccountId +// * @returns {ILedgerEntry} +// */ +// private getRefundCreditGLReceivableEntry = ( +// refundCreditNote: IRefundCreditNote, +// ARAccountId: number +// ): ILedgerEntry => { +// const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote); + +// return { +// ...commonEntry, +// debit: refundCreditNote.amount, +// accountId: ARAccountId, +// contactId: refundCreditNote.creditNote.customerId, +// index: 1, +// accountNormal: AccountNormal.DEBIT, +// }; +// }; + +// /** +// * Retrieves the refund credit withdrawal GL entry. +// * @param {number} refundCreditNote +// * @returns {ILedgerEntry} +// */ +// private getRefundCreditGLWithdrawalEntry = ( +// refundCreditNote: IRefundCreditNote +// ): ILedgerEntry => { +// const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote); + +// return { +// ...commonEntry, +// credit: refundCreditNote.amount, +// accountId: refundCreditNote.fromAccountId, +// index: 2, +// accountNormal: AccountNormal.DEBIT, +// }; +// }; + +// /** +// * Retrieve the refund credit note GL entries. +// * @param {IRefundCreditNote} refundCreditNote +// * @param {number} receivableAccount +// * @returns {ILedgerEntry[]} +// */ +// public getRefundCreditGLEntries( +// refundCreditNote: IRefundCreditNote, +// ARAccountId: number +// ): ILedgerEntry[] { +// const receivableEntry = this.getRefundCreditGLReceivableEntry( +// refundCreditNote, +// ARAccountId +// ); +// const withdrawalEntry = +// this.getRefundCreditGLWithdrawalEntry(refundCreditNote); + +// return [receivableEntry, withdrawalEntry]; +// } + +// /** +// * Creates refund credit GL entries. +// * @param {number} tenantId +// * @param {IRefundCreditNote} refundCreditNote +// * @param {Knex.Transaction} trx +// */ +// public createRefundCreditGLEntries = async ( +// tenantId: number, +// refundCreditNoteId: number, +// trx?: Knex.Transaction +// ) => { +// const { Account, RefundCreditNote } = this.tenancy.models(tenantId); + +// // Retrieve the refund with associated credit note. +// const refundCreditNote = await RefundCreditNote.query(trx) +// .findById(refundCreditNoteId) +// .withGraphFetched('creditNote'); + +// // Receivable account A/R. +// const receivableAccount = await Account.query().findOne( +// 'slug', +// 'accounts-receivable' +// ); +// // Retrieve refund credit GL entries. +// const refundGLEntries = this.getRefundCreditGLEntries( +// refundCreditNote, +// receivableAccount.id +// ); +// const ledger = new Ledger(refundGLEntries); + +// // Saves refund ledger entries. +// await this.ledgerStorage.commit(tenantId, ledger, trx); +// }; + +// /** +// * Reverts refund credit note GL entries. +// * @param {number} tenantId +// * @param {number} refundCreditNoteId +// * @param {number} receivableAccount +// * @param {Knex.Transaction} trx +// */ +// public revertRefundCreditGLEntries = async ( +// tenantId: number, +// refundCreditNoteId: number, +// trx?: Knex.Transaction +// ) => { +// await this.ledgerStorage.deleteByReference( +// tenantId, +// refundCreditNoteId, +// 'RefundCreditNote', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/RefundSyncCreditNoteBalance.ts b/packages/server-nest/src/modules/CreditNotes/commands/RefundSyncCreditNoteBalance.ts new file mode 100644 index 000000000..1a1a5433d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/commands/RefundSyncCreditNoteBalance.ts @@ -0,0 +1,48 @@ +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNote } from '../models/CreditNote'; + +@Injectable() +export class RefundSyncCreditNoteBalance { + /** + * @param {typeof CreditNote} creditNoteModel - The credit note model. + */ + constructor( + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + ) {} + + /** + * Increments the refund amount of the credit note. + * @param {number} creditNoteId - The credit note ID. + * @param {number} amount - The amount to increment. + * @param {Knex.Transaction} trx - The knex transaction. + */ + public incrementCreditNoteRefundAmount = async ( + creditNoteId: number, + amount: number, + trx?: Knex.Transaction + ): Promise => { + await this.creditNoteModel + .query(trx) + .findById(creditNoteId) + .increment('refunded_amount', amount); + }; + + /** + * Decrements the refund amount of the credit note. + * @param {number} creditNoteId - The credit note ID. + * @param {number} amount - The amount to decrement. + * @param {Knex.Transaction} trx - The knex transaction. + */ + public decrementCreditNoteRefundAmount = async ( + creditNoteId: number, + amount: number, + trx?: Knex.Transaction + ): Promise => { + await this.creditNoteModel + .query(trx) + .findById(creditNoteId) + .decrement('refunded_amount', amount); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/constants.ts b/packages/server-nest/src/modules/CreditNotes/constants.ts new file mode 100644 index 000000000..4f1d51d30 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/constants.ts @@ -0,0 +1,132 @@ +export const ERRORS = { + CREDIT_NOTE_NOT_FOUND: 'CREDIT_NOTE_NOT_FOUND', + REFUND_CREDIT_NOTE_NOT_FOUND: 'REFUND_CREDIT_NOTE_NOT_FOUND', + CREDIT_NOTE_ALREADY_OPENED: 'CREDIT_NOTE_ALREADY_OPENED', + ACCOUNT_INVALID_TYPE: 'ACCOUNT_INVALID_TYPE', + CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT: 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT', + INVOICES_HAS_NO_REMAINING_AMOUNT: 'INVOICES_HAS_NO_REMAINING_AMOUNT', + CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND: + 'CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND', + CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS: 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS', + CREDIT_NOTE_HAS_APPLIED_INVOICES: 'CREDIT_NOTE_HAS_APPLIED_INVOICES', + CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES', +}; + +export const DEFAULT_VIEW_COLUMNS = []; +export const DEFAULT_VIEWS = [ + { + name: 'credit_note.view.draft', + slug: 'draft', + rolesLogicExpression: '1', + roles: [ + { index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'credit_note.view.published', + slug: 'published', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'published', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'credit_note.view.open', + slug: 'open', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'open', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'credit_note.view.closed', + slug: 'closed', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'closed', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, +]; + +export const defaultCreditNoteBrandingAttributes = { + // # Colors + primaryColor: '', + secondaryColor: '', + + // # Company logo + showCompanyLogo: true, + companyLogoKey: '', + companyLogoUri: '', + + // # Company name + companyName: 'Bigcapital Technology, Inc.', + + // # Customer address + showCustomerAddress: true, + customerAddress: '', + + // # Company address + showCompanyAddress: true, + companyAddress: '', + billedToLabel: 'Billed To', + + // Total + total: '$1000.00', + totalLabel: 'Total', + showTotal: true, + + // Subtotal + subtotal: '1000/00', + subtotalLabel: 'Subtotal', + showSubtotal: true, + + // Customer note + showCustomerNote: true, + customerNote: + 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.', + customerNoteLabel: 'Customer Note', + + // Terms & conditions + showTermsConditions: true, + termsConditions: + 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.', + termsConditionsLabel: 'Terms & Conditions', + + lines: [ + { + item: 'Simply dummy text', + description: 'Simply dummy text of the printing and typesetting', + rate: '1', + quantity: '1000', + total: '$1000.00', + }, + ], + // Credit note number. + showCreditNoteNumber: true, + creditNoteNumberLabel: 'Credit Note Number', + creditNoteNumebr: '346D3D40-0001', + + // Credit note date. + creditNoteDate: 'September 3, 2024', + showCreditNoteDate: true, + creditNoteDateLabel: 'Credit Note Date', +}; diff --git a/packages/server-nest/src/modules/CreditNotes/models/CreditNoteAppliedInvoice.ts b/packages/server-nest/src/modules/CreditNotes/models/CreditNoteAppliedInvoice.ts index 74b8517a0..2c3b23cb7 100644 --- a/packages/server-nest/src/modules/CreditNotes/models/CreditNoteAppliedInvoice.ts +++ b/packages/server-nest/src/modules/CreditNotes/models/CreditNoteAppliedInvoice.ts @@ -4,8 +4,17 @@ import { mixin, Model } from 'objection'; // import CustomViewBaseModel from './CustomViewBaseModel'; // import ModelSearchable from './ModelSearchable'; import { BaseModel } from '@/models/Model'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { CreditNote } from './CreditNote'; export class CreditNoteAppliedInvoice extends BaseModel { + public amount: number; + public creditNoteId: number; + public invoiceId: number; + + public saleInvoice!: SaleInvoice; + public creditNote!: CreditNote; + /** * Table name */ diff --git a/packages/server-nest/src/modules/CreditNotes/models/RefundCreditNote.ts b/packages/server-nest/src/modules/CreditNotes/models/RefundCreditNote.ts new file mode 100644 index 000000000..0d975d74d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/models/RefundCreditNote.ts @@ -0,0 +1,63 @@ +import { Model, mixin } from 'objection'; +// import TenantModel from 'models/TenantModel'; +// import ModelSetting from './ModelSetting'; +// import CustomViewBaseModel from './CustomViewBaseModel'; +// import ModelSearchable from './ModelSearchable'; +import { BaseModel } from '@/models/Model'; + +export class RefundCreditNote extends BaseModel{ + date: Date; + referenceNo: string; + amount: number; + currencyCode: string; + exchangeRate: number; + fromAccountId: number; + description: string; + creditNoteId: number; + + userId?: number; + branchId?: number; + + createdAt?: Date | null; + + /** + * Table name. + */ + static get tableName() { + return 'refund_credit_note_transactions'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['created_at', 'updated_at']; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + const { Account } = require('../../Accounts/models/Account.model'); + const { CreditNote } = require('../../CreditNotes/models/CreditNote'); + + return { + fromAccount: { + relation: Model.BelongsToOneRelation, + modelClass: Account, + join: { + from: 'refund_credit_note_transactions.fromAccountId', + to: 'accounts.id', + }, + }, + creditNote: { + relation: Model.BelongsToOneRelation, + modelClass: CreditNote, + join: { + from: 'refund_credit_note_transactions.creditNoteId', + to: 'credit_notes.id', + }, + }, + }; + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteAppliedInvoiceTransformer.ts b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteAppliedInvoiceTransformer.ts new file mode 100644 index 000000000..b5105c931 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteAppliedInvoiceTransformer.ts @@ -0,0 +1,53 @@ +import { Transformer } from '../../Transformer/Transformer'; +import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; + +export class CreditNoteAppliedInvoiceTransformer extends Transformer { + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return [ + 'formttedAmount', + 'creditNoteNumber', + 'creditNoteDate', + 'invoiceNumber', + 'invoiceReferenceNo', + 'formattedCreditNoteDate', + ]; + }; + + /** + * Exclude attributes. + * @returns {string[]} + */ + public excludeAttributes = (): string[] => { + return ['saleInvoice', 'creditNote']; + }; + + public formttedAmount = (item: CreditNoteAppliedInvoice) => { + return this.formatNumber(item.amount, { + currencyCode: item.creditNote.currencyCode, + }); + }; + + public creditNoteNumber = (item: CreditNoteAppliedInvoice) => { + return item.creditNote.creditNoteNumber; + }; + + public creditNoteDate = (item: CreditNoteAppliedInvoice) => { + return item.creditNote.creditNoteDate; + }; + + public invoiceNumber = (item: CreditNoteAppliedInvoice) => { + return item.saleInvoice.invoiceNo; + }; + + public invoiceReferenceNo = (item: CreditNoteAppliedInvoice) => { + return item.saleInvoice.referenceNo; + }; + + public formattedCreditNoteDate = (item: CreditNoteAppliedInvoice) => { + return this.formatDate(item.creditNote.creditNoteDate); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteBrandingTemplate.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteBrandingTemplate.service.ts new file mode 100644 index 000000000..5d785d75c --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteBrandingTemplate.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { defaultCreditNoteBrandingAttributes } from '../constants'; +import { GetPdfTemplateService } from '../../PdfTemplate/queries/GetPdfTemplate.service'; +import { GetOrganizationBrandingAttributesService } from '../../PdfTemplate/queries/GetOrganizationBrandingAttributes.service'; +import { mergePdfTemplateWithDefaultAttributes } from '../../SaleInvoices/utils'; + +@Injectable() +export class CreditNoteBrandingTemplate { + constructor( + private getPdfTemplateService: GetPdfTemplateService, + private getOrgBrandingAttributes: GetOrganizationBrandingAttributesService, + ) {} + + /** + * Retrieves the credit note branding template. + * @param {number} templateId + * @returns {} + */ + public async getCreditNoteBrandingTemplate(templateId: number) { + const template = + await this.getPdfTemplateService.getPdfTemplate(templateId); + + // Retrieves the organization branding attributes. + const commonOrgBrandingAttrs = + await this.getOrgBrandingAttributes.getOrganizationBrandingAttributes(); + + // Merges the default branding attributes with common organization branding attrs. + const organizationBrandingAttrs = { + ...defaultCreditNoteBrandingAttributes, + ...commonOrgBrandingAttrs, + }; + const brandingTemplateAttrs = { + ...template.attributes, + companyLogoUri: template.companyLogoUri, + }; + const attributes = mergePdfTemplateWithDefaultAttributes( + brandingTemplateAttrs, + organizationBrandingAttrs, + ); + return { + ...template, + attributes, + }; + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteTransformer.ts b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteTransformer.ts new file mode 100644 index 000000000..a15066b5d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/CreditNoteTransformer.ts @@ -0,0 +1,194 @@ +import { AttachmentTransformer } from "@/modules/Attachments/Attachment.transformer"; +import { ItemEntryTransformer } from "@/modules/TransactionItemEntry/ItemEntry.transformer"; +import { Transformer } from "@/modules/Transformer/Transformer"; + +export class CreditNoteTransformer extends Transformer { + /** + * Include these attributes to sale credit note object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'formattedCreditsRemaining', + 'formattedCreditNoteDate', + 'formattedCreatedAt', + 'formattedCreatedAt', + 'formattedAmount', + 'formattedCreditsUsed', + 'formattedSubtotal', + + 'discountAmountFormatted', + 'discountAmountLocalFormatted', + + 'discountPercentageFormatted', + + 'adjustmentFormatted', + 'adjustmentLocalFormatted', + + 'totalFormatted', + 'totalLocalFormatted', + + 'entries', + 'attachments', + ]; + }; + + /** + * Retrieve formatted credit note date. + * @param {ICreditNote} credit + * @returns {String} + */ + protected formattedCreditNoteDate = (credit): string => { + return this.formatDate(credit.creditNoteDate); + }; + + /** + * Retrieve formatted created at date. + * @param credit + * @returns {string} + */ + protected formattedCreatedAt = (credit): string => { + return this.formatDate(credit.createdAt); + }; + + /** + * Retrieve formatted invoice amount. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedAmount = (credit): string => { + return this.formatNumber(credit.amount, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieve formatted credits remaining. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedCreditsRemaining = (credit) => { + return this.formatNumber(credit.creditsRemaining, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieve formatted credits used. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedCreditsUsed = (credit) => { + return this.formatNumber(credit.creditsUsed, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the formatted subtotal. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedSubtotal = (credit): string => { + return this.formatNumber(credit.amount, { money: false }); + }; + + /** + * Retrieves formatted discount amount. + * @param credit + * @returns {string} + */ + protected discountAmountFormatted = (credit): string => { + return this.formatNumber(credit.discountAmount, { + currencyCode: credit.currencyCode, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted discount amount in local currency. + * @param {ICreditNote} credit + * @returns {string} + */ + protected discountAmountLocalFormatted = (credit): string => { + return this.formatNumber(credit.discountAmountLocal, { + currencyCode: credit.currencyCode, + excerptZero: true, + }); + }; + + /** + * Retrieves formatted discount percentage. + * @param credit + * @returns {string} + */ + protected discountPercentageFormatted = (credit): string => { + return credit.discountPercentage ? `${credit.discountPercentage}%` : ''; + }; + + /** + * Retrieves formatted adjustment amount. + * @param credit + * @returns {string} + */ + protected adjustmentFormatted = (credit): string => { + return this.formatMoney(credit.adjustment, { + currencyCode: credit.currencyCode, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted adjustment amount in local currency. + * @param {ICreditNote} credit + * @returns {string} + */ + protected adjustmentLocalFormatted = (credit): string => { + return this.formatNumber(credit.adjustmentLocal, { + currencyCode: this.context.organization.baseCurrency, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted total. + * @param credit + * @returns {string} + */ + protected totalFormatted = (credit): string => { + return this.formatNumber(credit.total, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the formatted total in local currency. + * @param credit + * @returns {string} + */ + protected totalLocalFormatted = (credit): string => { + return this.formatNumber(credit.totalLocal, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the entries of the credit note. + * @param {ICreditNote} credit + * @returns {} + */ + protected entries = (credit) => { + return this.item(credit.entries, new ItemEntryTransformer(), { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the credit note attachments. + * @param {ISaleInvoice} invoice + * @returns + */ + protected attachments = (creditNote) => { + return this.item(creditNote.attachments, new AttachmentTransformer()); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNote.service.ts new file mode 100644 index 000000000..6e2126be2 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNote.service.ts @@ -0,0 +1,39 @@ +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { ERRORS } from '../constants'; +import { CreditNoteTransformer } from './CreditNoteTransformer'; +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNote } from '../models/CreditNote'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class GetCreditNote { + constructor( + private readonly transformer: TransformerInjectable, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + ) {} + + /** + * Retrieve the credit note graph. + * @param {number} tenantId + * @param {number} creditNoteId + * @returns + */ + public async getCreditNote(creditNoteId: number) { + // Retrieve the vendor credit model graph. + const creditNote = await this.creditNoteModel + .query() + .findById(creditNoteId) + .withGraphFetched('entries.item') + .withGraphFetched('customer') + .withGraphFetched('branch') + .withGraphFetched('attachments'); + + if (!creditNote) { + throw new ServiceError(ERRORS.CREDIT_NOTE_NOT_FOUND); + } + // Transforms the credit note model to POJO. + return this.transformer.transform(creditNote, new CreditNoteTransformer()); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedAppliedInvoices.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedAppliedInvoices.service.ts new file mode 100644 index 000000000..94d2b12d3 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedAppliedInvoices.service.ts @@ -0,0 +1,42 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNoteAppliedInvoiceTransformer } from './CreditNoteAppliedInvoiceTransformer'; +import { CreditNote } from '../models/CreditNote'; +import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service'; +import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; + +@Injectable() +export class GetCreditNoteAssociatedAppliedInvoices { + constructor( + private readonly transformer: TransformerInjectable, + private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote + ) { + } + + /** + * Retrieve credit note associated invoices to apply. + * @param {number} creditNoteId + * @returns {Promise} + */ + public async getCreditAssociatedAppliedInvoices( + creditNoteId: number + ): Promise { + // Retrieve credit note or throw not found service error. + const creditNote = await this.creditNoteModel.query() + .findById(creditNoteId) + .throwIfNotFound(); + + const appliedToInvoices = await this.creditNoteAppliedInvoiceModel.query() + .where('credit_note_id', creditNoteId) + .withGraphFetched('saleInvoice') + .withGraphFetched('creditNote'); + + // Transforms credit note applied to invoices. + return this.transformer.transform( + appliedToInvoices, + new CreditNoteAppliedInvoiceTransformer() + ); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedInvoicesToApply.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedInvoicesToApply.service.ts new file mode 100644 index 000000000..e2f275284 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteAssociatedInvoicesToApply.service.ts @@ -0,0 +1,41 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { CreditNoteWithInvoicesToApplyTransformer } from '../commands/CreditNoteWithInvoicesToApplyTransformer'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { GetCreditNote } from './GetCreditNote.service'; + +@Injectable() +export class GetCreditNoteAssociatedInvoicesToApply { + constructor( + private transformer: TransformerInjectable, + private getCreditNote: GetCreditNote, + + @Inject(SaleInvoice.name) + private saleInvoiceModel: typeof SaleInvoice, + ) {} + + /** + * Retrieve credit note associated invoices to apply. + * @param {number} creditNoteId + * @returns {Promise} + */ + public async getCreditAssociatedInvoicesToApply( + creditNoteId: number, + ): Promise { + // Retrieve credit note or throw not found service error. + const creditNote = await this.getCreditNote.getCreditNote(creditNoteId); + + // Retrieves the published due invoices that associated to the given customer. + const saleInvoices = await this.saleInvoiceModel + .query() + .where('customerId', creditNote.customerId) + .modify('dueInvoices') + .modify('published'); + + // Transforms the sale invoices models to POJO. + return this.transformer.transform( + saleInvoices, + new CreditNoteWithInvoicesToApplyTransformer(), + ); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotePdf.serivce.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotePdf.serivce.ts new file mode 100644 index 000000000..c88452024 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotePdf.serivce.ts @@ -0,0 +1,99 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { GetCreditNote } from './GetCreditNote.service'; +import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service'; +import { transformCreditNoteToPdfTemplate } from '../utils'; +import { CreditNote } from '../models/CreditNote'; +import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service'; +import { TemplateInjectable } from '@/modules/TemplateInjectable/TemplateInjectable.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate'; +import { CreditNotePdfTemplateAttributes } from '../types/CreditNotes.types'; +import { events } from '@/common/events/events'; + +@Injectable() +export class GetCreditNotePdf { + constructor( + private readonly chromiumlyTenancy: ChromiumlyTenancy, + private readonly templateInjectable: TemplateInjectable, + private readonly getCreditNoteService: GetCreditNote, + private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate, + private readonly eventPublisher: EventEmitter2, + + @Inject(CreditNote.name) + private readonly creditNoteModel: typeof CreditNote, + + @Inject(PdfTemplateModel.name) + private readonly pdfTemplateModel: typeof PdfTemplateModel, + ) {} + + /** + * Retrieves sale invoice pdf content. + * @param {number} creditNoteId - Credit note id. + * @returns {Promise<[Buffer, string]>} + */ + public async getCreditNotePdf( + creditNoteId: number, + ): Promise<[Buffer, string]> { + const brandingAttributes = + await this.getCreditNoteBrandingAttributes(creditNoteId); + const htmlContent = await this.templateInjectable.render( + 'modules/credit-note-standard', + brandingAttributes, + ); + const filename = await this.getCreditNoteFilename(creditNoteId); + + const document = + await this.chromiumlyTenancy.convertHtmlContent(htmlContent); + const eventPayload = { creditNoteId }; + + // Triggers the `onCreditNotePdfViewed` event. + await this.eventPublisher.emitAsync( + events.creditNote.onPdfViewed, + eventPayload, + ); + return [document, filename]; + } + + /** + * Retrieves the filename pdf document of the given credit note. + * @param {number} creditNoteId + * @returns {Promise} + */ + public async getCreditNoteFilename(creditNoteId: number): Promise { + const creditNote = await this.creditNoteModel + .query() + .findById(creditNoteId); + return `Credit-${creditNote.creditNoteNumber}`; + } + + /** + * Retrieves credit note branding attributes. + * @param {number} creditNoteId - The ID of the credit note. + * @returns {Promise} The credit note branding attributes. + */ + public async getCreditNoteBrandingAttributes( + creditNoteId: number, + ): Promise { + const creditNote = + await this.getCreditNoteService.getCreditNote(creditNoteId); + + // Retrieve the invoice template id of not found get the default template id. + const templateId = + creditNote.pdfTemplateId ?? + ( + await this.pdfTemplateModel.query().findOne({ + resource: 'CreditNote', + default: true, + }) + )?.id; + // Retrieves the credit note branding template. + const brandingTemplate = + await this.creditNoteBrandingTemplate.getCreditNoteBrandingTemplate( + templateId, + ); + return { + ...brandingTemplate.attributes, + ...transformCreditNoteToPdfTemplate(creditNote), + }; + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteRefunds.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteRefunds.service.ts new file mode 100644 index 000000000..36e585014 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteRefunds.service.ts @@ -0,0 +1,37 @@ +import { Inject, Injectable } from '@nestjs/common'; +import RefundCreditNoteTransformer from './RefundCreditNoteTransformer'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { RefundCreditNote } from '../models/RefundCreditNote'; +import { IRefundCreditNotePOJO } from '../types/CreditNotes.types'; + +@Injectable() +export class ListCreditNoteRefunds { + constructor( + private readonly transformer: TransformerInjectable, + + @Inject(RefundCreditNote.name) + private readonly refundCreditNoteModel: typeof RefundCreditNote, + ) {} + + /** + * Retrieve the credit note graph. + * @param {number} creditNoteId + * @returns {Promise} + */ + public async getCreditNoteRefunds( + creditNoteId: number, + ): Promise { + // Retrieve refund credit notes associated to the given credit note. + const refundCreditTransactions = await this.refundCreditNoteModel + .query() + .where('creditNoteId', creditNoteId) + .withGraphFetched('creditNote') + .withGraphFetched('fromAccount'); + + // Transforms refund credit note models to POJO objects. + return this.transformer.transform( + refundCreditTransactions, + new RefundCreditNoteTransformer(), + ); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteState.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteState.service.ts new file mode 100644 index 000000000..1138264d1 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNoteState.service.ts @@ -0,0 +1,25 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ICreditNoteState } from '../types/CreditNotes.types'; +import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate'; + +@Injectable() +export class GetCreditNoteState { + constructor( + @Inject(PdfTemplateModel.name) + private pdfTemplateModel: typeof PdfTemplateModel, + ) {} + + /** + * Retrieves the create/edit initial state of the payment received. + * @return {Promise} + */ + public async getCreditNoteState(): Promise { + const defaultPdfTemplate = await this.pdfTemplateModel.query() + .findOne({ resource: 'CreditNote' }) + .modify('default'); + + return { + defaultTemplateId: defaultPdfTemplate?.id, + }; + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts new file mode 100644 index 000000000..7ca5e5066 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts @@ -0,0 +1,68 @@ +// import { Service, Inject } from 'typedi'; +// import * as R from 'ramda'; +// import { ICreditNotesQueryDTO } from '@/interfaces'; +// import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +// import BaseCreditNotes from './commands/CommandCreditNoteDTOTransform.service'; +// import { CreditNoteTransformer } from './queries/CreditNoteTransformer'; +// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; + +// @Service() +// export default class ListCreditNotes extends BaseCreditNotes { +// @Inject() +// private dynamicListService: DynamicListingService; + +// @Inject() +// private transformer: TransformerInjectable; + +// /** +// * Parses the sale invoice list filter DTO. +// * @param filterDTO +// * @returns +// */ +// private parseListFilterDTO = (filterDTO) => { +// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO); +// }; + +// /** +// * Retrieves the paginated and filterable credit notes list. +// * @param {number} tenantId - +// * @param {ICreditNotesQueryDTO} creditNotesQuery - +// */ +// public getCreditNotesList = async ( +// tenantId: number, +// creditNotesQuery: ICreditNotesQueryDTO +// ) => { +// const { CreditNote } = this.tenancy.models(tenantId); + +// // Parses stringified filter roles. +// const filter = this.parseListFilterDTO(creditNotesQuery); + +// // Dynamic list service. +// const dynamicFilter = await this.dynamicListService.dynamicList( +// tenantId, +// CreditNote, +// filter +// ); +// const { results, pagination } = await CreditNote.query() +// .onBuild((builder) => { +// builder.withGraphFetched('entries.item'); +// builder.withGraphFetched('customer'); +// dynamicFilter.buildQuery()(builder); +// creditNotesQuery?.filterQuery && creditNotesQuery?.filterQuery(builder); +// }) +// .pagination(filter.page - 1, filter.pageSize); + +// // Transforomes the credit notes to POJO. +// const creditNotes = await this.transformer.transform( +// tenantId, +// results, +// new CreditNoteTransformer() +// ); + +// return { +// creditNotes, +// pagination, +// filterMeta: dynamicFilter.getResponseMeta(), +// }; +// }; +// } diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetRefundCreditNoteTransaction.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetRefundCreditNoteTransaction.service.ts new file mode 100644 index 000000000..de9b320b0 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetRefundCreditNoteTransaction.service.ts @@ -0,0 +1,37 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IRefundCreditNote } from '../types/CreditNotes.types'; +import { RefundCreditNote } from '../models/RefundCreditNote'; +import { RefundCreditNoteTransformer } from './RefundCreditNoteTransformer'; + +@Injectable() +export class GetRefundCreditNoteTransaction { + /** + * @param {RefundCreditNoteTransformer} transformer + * @param {typeof RefundCreditNote} refundCreditNoteModel + */ + constructor( + private readonly transformer: RefundCreditNoteTransformer, + + @Inject(RefundCreditNote.name) + private readonly refundCreditNoteModel: typeof RefundCreditNote, + ) { + } + + /** + * Retrieve credit note associated invoices to apply. + * @param {number} refundCreditId + * @returns {Promise} + */ + public async getRefundCreditTransaction( + refundCreditId: number + ): Promise { + const refundCreditNote = await this.refundCreditNoteModel + .query() + .findById(refundCreditId) + .withGraphFetched('fromAccount') + .withGraphFetched('creditNote') + .throwIfNotFound(); + + return this.transformer.transform(refundCreditNote); + } +} diff --git a/packages/server-nest/src/modules/CreditNotes/queries/RefundCreditNoteTransformer.ts b/packages/server-nest/src/modules/CreditNotes/queries/RefundCreditNoteTransformer.ts new file mode 100644 index 000000000..2c3b1a759 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/queries/RefundCreditNoteTransformer.ts @@ -0,0 +1,29 @@ +import { Transformer } from "@/modules/Transformer/Transformer"; + +export class RefundCreditNoteTransformer extends Transformer{ + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['formttedAmount', 'formattedDate']; + }; + + /** + * Formatted amount. + * @returns {string} + */ + protected formttedAmount = (item) => { + return this.formatNumber(item.amount, { + currencyCode: item.currencyCode, + }); + }; + + /** + * Formatted date. + * @returns {string} + */ + protected formattedDate = (item) => { + return this.formatDate(item.date); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncCreditSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncCreditSubscriber.ts new file mode 100644 index 000000000..39ab18462 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncCreditSubscriber.ts @@ -0,0 +1,67 @@ +import { Service, Inject } from 'typedi'; +import { sumBy } from 'lodash'; +import events from '@/subscribers/events'; +import { + IApplyCreditToInvoicesCreatedPayload, + IApplyCreditToInvoicesDeletedPayload, +} from '@/interfaces'; +import CreditNoteApplySyncCredit from '../commands/CreditNoteApplySyncCredit.service'; + +@Service() +export default class CreditNoteApplySyncCreditSubscriber { + @Inject() + syncInvoicedAmountWithCredit: CreditNoteApplySyncCredit; + + /** + * + * @param bus + */ + attach(bus) { + bus.subscribe( + events.creditNote.onApplyToInvoicesCreated, + this.incrementCreditedAmountOnceApplyToInvoicesCreated + ); + bus.subscribe( + events.creditNote.onApplyToInvoicesDeleted, + this.decrementCreditedAmountOnceApplyToInvoicesDeleted + ); + } + + /** + * Increment credited amount of credit note transaction once the transaction created. + * @param {IApplyCreditToInvoicesCreatedPayload} payload - + */ + private incrementCreditedAmountOnceApplyToInvoicesCreated = async ({ + trx, + creditNote, + tenantId, + creditNoteAppliedInvoices, + }: IApplyCreditToInvoicesCreatedPayload) => { + const totalCredited = sumBy(creditNoteAppliedInvoices, 'amount'); + + await this.syncInvoicedAmountWithCredit.incrementCreditNoteInvoicedAmount( + tenantId, + creditNote.id, + totalCredited, + trx + ); + }; + + /** + * Decrement credited amount of credit note transaction once the transaction deleted. + * @param {IApplyCreditToInvoicesDeletedPayload} payload - + */ + private decrementCreditedAmountOnceApplyToInvoicesDeleted = async ({ + tenantId, + creditNote, + creditNoteAppliedToInvoice, + trx, + }: IApplyCreditToInvoicesDeletedPayload) => { + await this.syncInvoicedAmountWithCredit.decrementCreditNoteInvoicedAmount( + tenantId, + creditNote.id, + creditNoteAppliedToInvoice.amount, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts new file mode 100644 index 000000000..8d7f9d2b7 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts @@ -0,0 +1,61 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import { + IApplyCreditToInvoicesCreatedPayload, + IApplyCreditToInvoicesDeletedPayload, +} from '@/interfaces'; +import CreditNoteApplySyncInvoicesCreditedAmount from '../commands/CreditNoteApplySyncInvoices.service'; + +@Service() +export default class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { + @Inject() + private syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount; + + /** + * Attaches events with handlers. + */ + public attach(bus) { + bus.subscribe( + events.creditNote.onApplyToInvoicesCreated, + this.incrementAppliedInvoicesOnceCreditCreated + ); + bus.subscribe( + events.creditNote.onApplyToInvoicesDeleted, + this.decrementAppliedInvoicesOnceCreditDeleted + ); + } + + /** + * Increment invoices credited amount once the credit note apply to invoices transaction + * @param {IApplyCreditToInvoicesCreatedPayload} payload - + */ + private incrementAppliedInvoicesOnceCreditCreated = async ({ + trx, + tenantId, + creditNoteAppliedInvoices, + }: IApplyCreditToInvoicesCreatedPayload) => { + await this.syncInvoicesWithCreditNote.incrementInvoicesCreditedAmount( + tenantId, + creditNoteAppliedInvoices, + trx + ); + }; + + /** + * + * @param {IApplyCreditToInvoicesDeletedPayload} payload - + */ + private decrementAppliedInvoicesOnceCreditDeleted = async ({ + trx, + creditNoteAppliedToInvoice, + tenantId, + }: IApplyCreditToInvoicesDeletedPayload) => { + // Decrement invoice credited amount. + await this.syncInvoicesWithCreditNote.decrementInvoiceCreditedAmount( + tenantId, + creditNoteAppliedToInvoice.invoiceId, + creditNoteAppliedToInvoice.amount, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteAutoSerialSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteAutoSerialSubscriber.ts new file mode 100644 index 000000000..8880789e9 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteAutoSerialSubscriber.ts @@ -0,0 +1,30 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import BaseCreditNotes from '../commands/CommandCreditNoteDTOTransform.service'; +import { ICreditNoteCreatedPayload } from '@/interfaces'; + +@Service() +export default class CreditNoteAutoSerialSubscriber { + @Inject() + creditNotesService: BaseCreditNotes; + + /** + * Attaches events with handlers. + */ + public attach(bus) { + bus.subscribe( + events.creditNote.onCreated, + this.autoSerialIncrementOnceCreated + ); + } + + /** + * Auto serial increment once credit note created. + * @param {ICreditNoteCreatedPayload} payload - + */ + private autoSerialIncrementOnceCreated = async ({ + tenantId, + }: ICreditNoteCreatedPayload) => { + await this.creditNotesService.incrementSerialNumber(tenantId); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteGLEntriesSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteGLEntriesSubscriber.ts new file mode 100644 index 000000000..b29ab57db --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteGLEntriesSubscriber.ts @@ -0,0 +1,113 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import { + ICreditNoteCreatedPayload, + ICreditNoteDeletedPayload, + ICreditNoteEditedPayload, + ICreditNoteOpenedPayload, +} from '@/interfaces'; +import CreditNoteGLEntries from '../commands/CreditNoteGLEntries'; + +@Service() +export default class CreditNoteGLEntriesSubscriber { + @Inject() + private creditNoteGLEntries: CreditNoteGLEntries; + + /** + * Attaches events with handlers. + * @param bus + */ + public attach(bus) { + bus.subscribe( + events.creditNote.onCreated, + this.writeGlEntriesOnceCreditNoteCreated + ); + bus.subscribe( + events.creditNote.onOpened, + this.writeGLEntriesOnceCreditNoteOpened + ); + bus.subscribe( + events.creditNote.onEdited, + this.editVendorCreditGLEntriesOnceEdited + ); + bus.subscribe( + events.creditNote.onDeleted, + this.revertGLEntriesOnceCreditNoteDeleted + ); + } + + /** + * Writes the GL entries once the credit note transaction created or open. + * @private + * @param {ICreditNoteCreatedPayload|ICreditNoteOpenedPayload} payload - + */ + private writeGlEntriesOnceCreditNoteCreated = async ({ + tenantId, + creditNote, + creditNoteId, + trx, + }: ICreditNoteCreatedPayload | ICreditNoteOpenedPayload) => { + // Can't continue if the credit note is not published yet. + if (!creditNote.isPublished) return; + + await this.creditNoteGLEntries.createVendorCreditGLEntries( + tenantId, + creditNoteId, + trx + ); + }; + + /** + * Writes the GL entries once the vendor credit transaction opened. + * @param {ICreditNoteOpenedPayload} payload + */ + private writeGLEntriesOnceCreditNoteOpened = async ({ + tenantId, + creditNoteId, + trx, + }: ICreditNoteOpenedPayload) => { + await this.creditNoteGLEntries.createVendorCreditGLEntries( + tenantId, + creditNoteId, + trx + ); + }; + + /** + * Reverts GL entries once credit note deleted. + */ + private revertGLEntriesOnceCreditNoteDeleted = async ({ + tenantId, + oldCreditNote, + creditNoteId, + trx, + }: ICreditNoteDeletedPayload) => { + // Can't continue if the credit note is not published yet. + if (!oldCreditNote.isPublished) return; + + await this.creditNoteGLEntries.revertVendorCreditGLEntries( + tenantId, + creditNoteId + ); + }; + + /** + * Edits vendor credit associated GL entries once the transaction edited. + * @param {ICreditNoteEditedPayload} payload - + */ + private editVendorCreditGLEntriesOnceEdited = async ({ + tenantId, + creditNote, + creditNoteId, + trx, + }: ICreditNoteEditedPayload) => { + // Can't continue if the credit note is not published yet. + if (!creditNote.isPublished) return; + + await this.creditNoteGLEntries.editVendorCreditGLEntries( + tenantId, + creditNoteId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteInventoryTransactionsSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteInventoryTransactionsSubscriber.ts new file mode 100644 index 000000000..2b594bb4e --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/CreditNoteInventoryTransactionsSubscriber.ts @@ -0,0 +1,98 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import CreditNoteInventoryTransactions from '../commands/CreditNotesInventoryTransactions'; +import { + ICreditNoteCreatedPayload, + ICreditNoteDeletedPayload, + ICreditNoteEditedPayload, +} from '@/interfaces'; + +@Service() +export default class CreditNoteInventoryTransactionsSubscriber { + @Inject() + inventoryTransactions: CreditNoteInventoryTransactions; + + /** + * Attaches events with publisher. + */ + public attach(bus) { + bus.subscribe( + events.creditNote.onCreated, + this.writeInventoryTranscationsOnceCreated + ); + bus.subscribe( + events.creditNote.onEdited, + this.rewriteInventoryTransactionsOnceEdited + ); + bus.subscribe( + events.creditNote.onDeleted, + this.revertInventoryTransactionsOnceDeleted + ); + bus.subscribe( + events.creditNote.onOpened, + this.writeInventoryTranscationsOnceCreated + ); + } + + /** + * Writes inventory transactions once credit note created. + * @param {ICreditNoteCreatedPayload} payload - + * @returns {Promise} + */ + public writeInventoryTranscationsOnceCreated = async ({ + tenantId, + creditNote, + trx, + }: ICreditNoteCreatedPayload) => { + // Can't continue if the credit note is open yet. + if (!creditNote.isOpen) return; + + await this.inventoryTransactions.createInventoryTransactions( + tenantId, + creditNote, + trx + ); + }; + + /** + * Rewrites inventory transactions once credit note edited. + * @param {ICreditNoteEditedPayload} payload - + * @returns {Promise} + */ + public rewriteInventoryTransactionsOnceEdited = async ({ + tenantId, + creditNoteId, + creditNote, + trx, + }: ICreditNoteEditedPayload) => { + // Can't continue if the credit note is open yet. + if (!creditNote.isOpen) return; + + await this.inventoryTransactions.editInventoryTransactions( + tenantId, + creditNoteId, + creditNote, + trx + ); + }; + + /** + * Reverts inventory transactions once credit note deleted. + * @param {ICreditNoteDeletedPayload} payload - + */ + public revertInventoryTransactionsOnceDeleted = async ({ + tenantId, + creditNoteId, + oldCreditNote, + trx, + }: ICreditNoteDeletedPayload) => { + // Can't continue if the credit note is open yet. + if (!oldCreditNote.isOpen) return; + + await this.inventoryTransactions.deleteInventoryTransactions( + tenantId, + creditNoteId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/DeleteCustomerLinkedCreditSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/DeleteCustomerLinkedCreditSubscriber.ts new file mode 100644 index 000000000..826c94e9d --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/DeleteCustomerLinkedCreditSubscriber.ts @@ -0,0 +1,48 @@ +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import TenancyService from '@/services/Tenancy/TenancyService'; +import events from '@/subscribers/events'; +import { ICustomerDeletingPayload } from '@/interfaces'; +import DeleteCustomerLinkedCreidtNote from '../commands/DeleteCustomerLinkedCreditNote.service'; + +const ERRORS = { + CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS', +}; + +@Service() +export default class DeleteCustomerLinkedCreditSubscriber { + @Inject() + tenancy: TenancyService; + + @Inject() + deleteCustomerLinkedCredit: DeleteCustomerLinkedCreidtNote; + + /** + * Attaches events with handlers. + * @param bus + */ + public attach = (bus) => { + bus.subscribe( + events.customers.onDeleting, + this.validateCustomerHasNoLinkedCreditsOnDeleting + ); + }; + + /** + * Validate vendor has no associated credit transaction once the vendor deleting. + * @param {IVendorEventDeletingPayload} payload - + */ + public validateCustomerHasNoLinkedCreditsOnDeleting = async ({ + tenantId, + customerId, + }: ICustomerDeletingPayload) => { + try { + await this.deleteCustomerLinkedCredit.validateCustomerHasNoCreditTransaction( + tenantId, + customerId + ); + } catch (error) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_TRANSACTIONS); + } + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber.ts new file mode 100644 index 000000000..a4d0365a0 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber.ts @@ -0,0 +1,61 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import RefundCreditNoteGLEntries from '../commands/RefundCreditNoteGLEntries'; +import { + IRefundCreditNoteCreatedPayload, + IRefundCreditNoteDeletedPayload, +} from '@/interfaces'; + +@Service() +export default class RefundCreditNoteGLEntriesSubscriber { + @Inject() + refundCreditGLEntries: RefundCreditNoteGLEntries; + + /** + * Attaches events with handlers. + */ + public attach = (bus) => { + bus.subscribe( + events.creditNote.onRefundCreated, + this.writeRefundCreditGLEntriesOnceCreated + ); + bus.subscribe( + events.creditNote.onRefundDeleted, + this.revertRefundCreditGLEntriesOnceDeleted + ); + }; + + /** + * Writes refund credit note GL entries once the transaction created. + * @param {IRefundCreditNoteCreatedPayload} payload - + */ + private writeRefundCreditGLEntriesOnceCreated = async ({ + trx, + refundCreditNote, + creditNote, + tenantId, + }: IRefundCreditNoteCreatedPayload) => { + await this.refundCreditGLEntries.createRefundCreditGLEntries( + tenantId, + refundCreditNote.id, + trx + ); + }; + + /** + * Reverts refund credit note GL entries once the transaction deleted. + * @param {IRefundCreditNoteDeletedPayload} payload - + */ + private revertRefundCreditGLEntriesOnceDeleted = async ({ + trx, + refundCreditId, + oldRefundCredit, + tenantId, + }: IRefundCreditNoteDeletedPayload) => { + await this.refundCreditGLEntries.revertRefundCreditGLEntries( + tenantId, + refundCreditId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/subscribers/RefundSyncCreditNoteBalanceSubscriber.ts b/packages/server-nest/src/modules/CreditNotes/subscribers/RefundSyncCreditNoteBalanceSubscriber.ts new file mode 100644 index 000000000..a4ac93396 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/subscribers/RefundSyncCreditNoteBalanceSubscriber.ts @@ -0,0 +1,62 @@ +import { Inject, Service } from 'typedi'; +import { + IRefundCreditNoteCreatedPayload, + IRefundCreditNoteDeletedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import RefundSyncCreditNoteBalance from '../commands/RefundSyncCreditNoteBalance'; + +@Service() +export default class RefundSyncCreditNoteBalanceSubscriber { + @Inject() + refundSyncCreditBalance: RefundSyncCreditNoteBalance; + + /** + * Attaches events with handlers. + */ + attach(bus) { + bus.subscribe( + events.creditNote.onRefundCreated, + this.incrementRefundedAmountOnceRefundCreated + ); + bus.subscribe( + events.creditNote.onRefundDeleted, + this.decrementRefundedAmountOnceRefundDeleted + ); + return bus; + } + + /** + * Increment credit note refunded amount once associated refund transaction created. + * @param {IRefundCreditNoteCreatedPayload} payload - + */ + private incrementRefundedAmountOnceRefundCreated = async ({ + trx, + refundCreditNote, + tenantId, + }: IRefundCreditNoteCreatedPayload) => { + await this.refundSyncCreditBalance.incrementCreditNoteRefundAmount( + tenantId, + refundCreditNote.creditNoteId, + refundCreditNote.amount, + trx + ); + }; + + /** + * Decrement credit note refunded amount once associated refuned transaction deleted. + * @param {IRefundCreditNoteDeletedPayload} payload - + */ + private decrementRefundedAmountOnceRefundDeleted = async ({ + trx, + oldRefundCredit, + tenantId, + }: IRefundCreditNoteDeletedPayload) => { + await this.refundSyncCreditBalance.decrementCreditNoteRefundAmount( + tenantId, + oldRefundCredit.creditNoteId, + oldRefundCredit.amount, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts new file mode 100644 index 000000000..3a56f73e0 --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts @@ -0,0 +1,307 @@ +import { Knex } from 'knex'; +import { DiscountType, IDynamicListFilter, IItemEntry } from '@/interfaces'; +import { ILedgerEntry } from './Ledger'; +import { AttachmentLinkDTO } from './Attachments'; +import { CreditNote } from '../models/CreditNote'; +import { RefundCreditNote } from '../models/RefundCreditNote'; + +export interface ICreditNoteEntryNewDTO { + index?: number; + itemId: number; + rate: number; + quantity: number; + discount: number; + description: string; + warehouseId?: number; +} +export interface ICreditNoteNewDTO { + customerId: number; + exchangeRate?: number; + creditNoteDate: Date; + creditNoteNumber: string; + note: string; + open: boolean; + entries: ICreditNoteEntryNewDTO[]; + branchId?: number; + warehouseId?: number; + attachments?: AttachmentLinkDTO[]; + discount?: number; + discountType?: DiscountType; + adjustment?: number; +} + +export interface ICreditNoteEditDTO { + customerId: number; + exchangeRate?: number; + creditNoteDate: Date; + creditNoteNumber: string; + note: string; + open: boolean; + entries: ICreditNoteEntryNewDTO[]; + branchId?: number; + warehouseId?: number; + attachments?: AttachmentLinkDTO[]; +} + +export interface ICreditNoteEntry extends IItemEntry {} + +export interface ICreditNote { + id?: number; + customerId: number; + amount: number; + refundedAmount: number; + currencyCode: string; + exchangeRate: number; + creditNoteDate: Date; + creditNoteNumber: string; + referenceNo?: string; + // note?: string; + openedAt: Date | null; + entries: ICreditNoteEntry[]; + isOpen: boolean; + isClosed: boolean; + isDraft: boolean; + isPublished: boolean; + creditsRemaining: number; + localAmount?: number; + branchId?: number; + warehouseId: number; + createdAt?: Date; + termsConditions: string; + note: string; +} + +export enum CreditNoteAction { + Create = 'Create', + Edit = 'Edit', + Delete = 'Delete', + View = 'View', + Refund = 'Refund', +} + +export interface ICreditNoteDeletingPayload { + tenantId: number; + oldCreditNote: CreditNote; + trx: Knex.Transaction; +} + +export interface ICreditNoteDeletedPayload { + tenantId: number; + oldCreditNote: CreditNote; + creditNoteId: number; + trx: Knex.Transaction; +} + +export interface ICreditNoteEditingPayload { + trx: Knex.Transaction; + oldCreditNote: CreditNote; + creditNoteEditDTO: ICreditNoteEditDTO; + tenantId: number; +} + +export interface ICreditNoteEditedPayload { + trx: Knex.Transaction; + oldCreditNote: CreditNote; + // creditNoteId: number; + creditNote: CreditNote; + creditNoteEditDTO: ICreditNoteEditDTO; + // tenantId: number; +} + +export interface ICreditNoteCreatedPayload { + // tenantId: number; + creditNoteDTO: ICreditNoteNewDTO; + creditNote: CreditNote; + // creditNoteId: number; + trx: Knex.Transaction; +} + +export interface ICreditNoteCreatingPayload { + // tenantId: number; + creditNoteDTO: ICreditNoteNewDTO; + trx: Knex.Transaction; +} + +export interface ICreditNoteOpeningPayload { + tenantId: number; + creditNoteId: number; + oldCreditNote: CreditNote; + trx: Knex.Transaction; +} + +export interface ICreditNoteOpenedPayload { + // tenantId: number; + creditNote: CreditNote; + // creditNoteId: number; + oldCreditNote: CreditNote; + trx: Knex.Transaction; +} + +export interface ICreditNotesQueryDTO { + filterQuery?: (query: any) => void; +} + +export interface ICreditNotesQueryDTO extends IDynamicListFilter { + page: number; + pageSize: number; + searchKeyword?: string; +} + +export interface ICreditNoteRefundDTO { + fromAccountId: number; + amount: number; + exchangeRate?: number; + referenceNo: string; + description: string; + date: Date; + branchId?: number; +} + +export interface ICreditNoteApplyInvoiceDTO { + entries: { invoiceId: number; amount: number }[]; +} +export interface IRefundCreditNotePOJO { + formattedAmount: string; +} + +export interface IRefundCreditNoteDeletedPayload { + trx: Knex.Transaction; + refundCreditId: number; + oldRefundCredit: RefundCreditNote; + tenantId: number; +} + +export interface IRefundCreditNoteDeletingPayload { + trx: Knex.Transaction; + refundCreditId: number; + oldRefundCredit: RefundCreditNote; + // tenantId: number; +} + +export interface IRefundCreditNoteCreatingPayload { + trx: Knex.Transaction; + creditNote: CreditNote; + newCreditNoteDTO: ICreditNoteRefundDTO; + // tenantId: number; +} + +export interface IRefundCreditNoteCreatedPayload { + trx: Knex.Transaction; + refundCreditNote: RefundCreditNote; + creditNote: CreditNote; + // tenantId: number; +} + +export interface IRefundCreditNoteOpenedPayload { + // tenantId: number; + creditNoteId: number; + oldCreditNote: CreditNote; + trx: Knex.Transaction; +} + +export interface IApplyCreditToInvoiceEntryDTO { + amount: number; + invoiceId: number; +} + +export interface IApplyCreditToInvoicesDTO { + entries: IApplyCreditToInvoiceEntryDTO[]; +} + +export interface IApplyCreditToInvoicesCreatedPayload { + trx: Knex.Transaction; + creditNote: ICreditNote; + creditNoteAppliedInvoices: ICreditNoteAppliedToInvoice[]; + // tenantId: number; +} +export interface IApplyCreditToInvoicesDeletedPayload { + trx: Knex.Transaction; + creditNote: ICreditNote; + creditNoteAppliedToInvoice: ICreditNoteAppliedToInvoice; + // tenantId: number; +} + +export interface ICreditNoteAppliedToInvoice { + invoiceId: number; + amount: number; + creditNoteId: number; +} +export interface ICreditNoteAppliedToInvoiceModel { + amount: number; + entries: ICreditNoteAppliedToInvoice[]; +} + +export type ICreditNoteGLCommonEntry = Pick< + ILedgerEntry, + | 'date' + | 'userId' + | 'currencyCode' + | 'exchangeRate' + | 'transactionType' + | 'transactionId' + | 'transactionNumber' + | 'referenceNumber' + | 'createdAt' + | 'indexGroup' + | 'credit' + | 'debit' + | 'branchId' +>; + +export interface CreditNotePdfTemplateAttributes { + // # Primary color + primaryColor: string; + secondaryColor: string; + + // # Company logo + showCompanyLogo: boolean; + companyLogo: string; + + // # Company name + companyName: string; + + // # Customer Address + showCustomerAddress: boolean; + customerAddress: string; + + // # Company address + showCompanyAddress: boolean; + companyAddress: string; + billedToLabel: string; + + total: string; + totalLabel: string; + showTotal: boolean; + + subtotal: string; + subtotalLabel: string; + showSubtotal: boolean; + + showCustomerNote: boolean; + customerNote: string; + customerNoteLabel: string; + + showTermsConditions: boolean; + termsConditions: string; + termsConditionsLabel: string; + + lines: Array<{ + item: string; + description: string; + rate: string; + quantity: string; + total: string; + }>; + + showCreditNoteNumber: boolean; + creditNoteNumberLabel: string; + creditNoteNumebr: string; + + creditNoteDate: string; + showCreditNoteDate: boolean; + creditNoteDateLabel: string; +} + +export interface ICreditNoteState { + defaultTemplateId: number; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/CreditNotes/utils.ts b/packages/server-nest/src/modules/CreditNotes/utils.ts new file mode 100644 index 000000000..8936f922c --- /dev/null +++ b/packages/server-nest/src/modules/CreditNotes/utils.ts @@ -0,0 +1,25 @@ +import { CreditNotePdfTemplateAttributes, ICreditNote } from '@/interfaces'; +import { contactAddressTextFormat } from '@/utils/address-text-format'; + +export const transformCreditNoteToPdfTemplate = ( + creditNote: ICreditNote +): Partial => { + return { + creditNoteDate: creditNote.formattedCreditNoteDate, + creditNoteNumebr: creditNote.creditNoteNumber, + + total: creditNote.formattedAmount, + subtotal: creditNote.formattedSubtotal, + + lines: creditNote.entries?.map((entry) => ({ + item: entry.item.name, + description: entry.description, + rate: entry.rateFormatted, + quantity: entry.quantityFormatted, + total: entry.totalFormatted, + })), + customerNote: creditNote.note, + termsConditions: creditNote.termsConditions, + customerAddress: contactAddressTextFormat(creditNote.customer), + }; +}; diff --git a/packages/server-nest/src/modules/Import/ImportableResources.ts b/packages/server-nest/src/modules/Import/ImportableResources.ts index d4170c843..8635ebd29 100644 --- a/packages/server-nest/src/modules/Import/ImportableResources.ts +++ b/packages/server-nest/src/modules/Import/ImportableResources.ts @@ -14,7 +14,7 @@ import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImporta import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable'; import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCreditsImportable'; import { PaymentsReceivedImportable } from '../Sales/PaymentReceived/PaymentsReceivedImportable'; -import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable'; +import { CreditNotesImportable } from '../CreditNotes/commands/CreditNotesImportable'; import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable'; import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable'; diff --git a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts index 7d6dedb93..c5fd413bd 100644 --- a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts +++ b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts @@ -1,15 +1,25 @@ import { BaseModel } from '@/models/Model'; import moment from 'moment'; import { Model } from 'objection'; -// import TenantModel from 'models/TenantModel'; -// import { defaultToTransform } from 'utils'; -// import SaleEstimateSettings from './SaleEstimate.Settings'; -// import ModelSetting from './ModelSetting'; -// import CustomViewBaseModel from './CustomViewBaseModel'; -// import { DEFAULT_VIEWS } from '@/services/Sales/Estimates/constants'; -// import ModelSearchable from './ModelSearchable'; +import { ItemEntry } from '../../Items/models/ItemEntry'; +import { Customer } from '../../Customers/models/Customer'; +import { Branch } from '../../Branches/models/Branch.model'; +import { Warehouse } from '../../Warehouses/models/Warehouse.model'; +import { Document } from '../../ChromiumlyTenancy/models/Document'; +import { Injectable } from '@nestjs/common'; +@Injectable() export class SaleEstimate extends BaseModel { + constructor( + private readonly itemEntryModel: typeof ItemEntry, + private readonly customerModel: typeof Customer, + private readonly branchModel: typeof Branch, + private readonly warehouseModel: typeof Warehouse, + private readonly documentModel: typeof Document, + ) { + super(); + } + exchangeRate!: number; amount!: number; @@ -207,16 +217,10 @@ export class SaleEstimate extends BaseModel { * Relationship mapping. */ static get relationMappings() { - const { ItemEntry } = require('../../Items/models/ItemEntry'); - const { Customer } = require('../../Customers/models/Customer'); - const { Branch } = require('../../Branches/models/Branch.model'); - const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); - const { Document } = require('../../ChromiumlyTenancy/models/Document'); - return { customer: { relation: Model.BelongsToOneRelation, - modelClass: Customer, + modelClass: this.customerModel, join: { from: 'sales_estimates.customerId', to: 'contacts.id', @@ -227,8 +231,7 @@ export class SaleEstimate extends BaseModel { }, entries: { relation: Model.HasManyRelation, - modelClass: ItemEntry, - + modelClass: this.itemEntryModel, join: { from: 'sales_estimates.id', to: 'items_entries.referenceId', @@ -238,37 +241,25 @@ export class SaleEstimate extends BaseModel { builder.orderBy('index', 'ASC'); }, }, - - /** - * Sale estimate may belongs to branch. - */ branch: { relation: Model.BelongsToOneRelation, - modelClass: Branch, + modelClass: this.branchModel, join: { from: 'sales_estimates.branchId', to: 'branches.id', }, }, - - /** - * Sale estimate may has associated warehouse. - */ warehouse: { relation: Model.BelongsToOneRelation, - modelClass: Warehouse, + modelClass: this.warehouseModel, join: { from: 'sales_estimates.warehouseId', to: 'warehouses.id', }, }, - - /** - * Sale estimate transaction may has many attached attachments. - */ attachments: { relation: Model.ManyToManyRelation, - modelClass: Document, + modelClass: this.documentModel, join: { from: 'sales_estimates.id', through: { diff --git a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts index e3deb665a..cb1efe1db 100644 --- a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts +++ b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -25,7 +25,7 @@ import { BillPayment } from '@/modules/BillPayments/models/BillPayment'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillLandedCostEntry } from '@/modules/BillLandedCosts/models/BillLandedCostEntry'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; -import { VendorCreditAppliedBill } from '@/modules/VendorCredit/models/VendorCreditAppliedBill'; +import { VendorCreditAppliedBill } from '@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry'; import { CreditNoteAppliedInvoice } from '@/modules/CreditNotes/models/CreditNoteAppliedInvoice'; diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts new file mode 100644 index 000000000..68fbea67b --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts @@ -0,0 +1,33 @@ +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; +import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; +import { IVendorCreditCreateDTO, IVendorCreditEditDTO } from './types/VendorCredit.types'; + +@Controller('vendor-credits') +export class VendorCreditsController { + constructor( + private readonly vendorCreditsApplication: VendorCreditsApplicationService, + ) {} + + @Post() + async createVendorCredit(@Body() dto: IVendorCreditCreateDTO) { + return this.vendorCreditsApplication.createVendorCredit(dto); + } + + @Put(':id') + async editVendorCredit( + @Param('id') vendorCreditId: number, + @Body() dto: IVendorCreditEditDTO, + ) { + return this.vendorCreditsApplication.editVendorCredit(vendorCreditId, dto); + } + + @Delete(':id') + async deleteVendorCredit(@Param('id') vendorCreditId: number) { + return this.vendorCreditsApplication.deleteVendorCredit(vendorCreditId); + } + + @Get(':id') + async getVendorCredit(@Param('id') vendorCreditId: number) { + return this.vendorCreditsApplication.getVendorCredit(vendorCreditId); + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts new file mode 100644 index 000000000..b18a1fd00 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { CreateVendorCreditService } from './commands/CreateVendorCredit.service'; +import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service'; +import { EditVendorCreditService } from './commands/EditVendorCredit.service'; +import { VendorCreditDTOTransformService } from './commands/VendorCreditDTOTransform.service'; +import { VendorCreditAutoIncrementService } from './commands/VendorCreditAutoIncrement.service'; +import { GetRefundVendorCreditService } from '../VendorCreditsRefund/queries/GetRefundVendorCredit.service'; +import GetVendorCreditService from './queries/GetVendorCredit.service'; + +@Module({ + providers: [ + CreateVendorCreditService, + DeleteVendorCreditService, + EditVendorCreditService, + VendorCreditDTOTransformService, + VendorCreditAutoIncrementService, + GetRefundVendorCreditService, + VendorCreditDTOTransformService, + GetVendorCreditService + ], + controllers: [], +}) +export class VendorCreditsModule {} diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts new file mode 100644 index 000000000..99fa82e18 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts @@ -0,0 +1,64 @@ +import { Knex } from 'knex'; +import { CreateVendorCreditService } from './commands/CreateVendorCredit.service'; +import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service'; +import { EditVendorCreditService } from './commands/EditVendorCredit.service'; +import GetVendorCreditService from './queries/GetVendorCredit.service'; +import { IVendorCreditEditDTO } from './types/VendorCredit.types'; +import { IVendorCreditCreateDTO } from './types/VendorCredit.types'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class VendorCreditsApplicationService { + constructor( + private readonly createVendorCreditService: CreateVendorCreditService, + private readonly editVendorCreditService: EditVendorCreditService, + private readonly deleteVendorCreditService: DeleteVendorCreditService, + private readonly getVendorCreditService: GetVendorCreditService, + ) {} + + /** + * Creates a new vendor credit. + * @param {IVendorCreditCreateDTO} dto - The vendor credit create DTO. + * @param {Knex.Transaction} trx - The transaction. + * @returns {Promise} The created vendor credit. + */ + createVendorCredit(dto: IVendorCreditCreateDTO, trx?: Knex.Transaction) { + return this.createVendorCreditService.newVendorCredit(dto, trx); + } + + /** + * Edits the given vendor credit. + * @param {number} vendorCreditId - The vendor credit id. + * @param {IVendorCreditEditDTO} dto - The vendor credit edit DTO. + * @param {Knex.Transaction} trx - The transaction. + * @returns {Promise} The edited vendor credit. + */ + editVendorCredit( + vendorCreditId: number, + dto: IVendorCreditEditDTO, + trx?: Knex.Transaction, + ) { + return this.editVendorCreditService.editVendorCredit( + vendorCreditId, + dto, + trx, + ); + } + + /** + * Deletes the given vendor credit. + * @param {number} vendorCreditId - The vendor credit id. + * @param {Knex.Transaction} trx - The transaction. + * @returns {Promise} The deleted vendor credit. + */ + deleteVendorCredit(vendorCreditId: number, trx?: Knex.Transaction) { + return this.deleteVendorCreditService.deleteVendorCredit( + vendorCreditId, + trx, + ); + } + + getVendorCredit(vendorCreditId: number, trx?: Knex.Transaction) { + return this.getVendorCreditService.getVendorCredit(vendorCreditId, trx); + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/CreateVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/CreateVendorCredit.service.ts new file mode 100644 index 000000000..dc6c85e16 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/CreateVendorCredit.service.ts @@ -0,0 +1,90 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { + IVendorCreditCreatedPayload, + IVendorCreditCreateDTO, + IVendorCreditCreatingPayload, +} from '@/modules/VendorCredit/types/VendorCredit.types'; +import { VendorCredit } from '../models/VendorCredit'; +import { Vendor } from '@/modules/Vendors/models/Vendor'; +import { events } from '@/common/events/events'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; + +@Injectable() +export class CreateVendorCreditService { + /** + * @param {UnitOfWork} uow - The unit of work service. + * @param {ItemsEntriesService} itemsEntriesService - The items entries service. + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + * @param {typeof Vendor} vendorModel - The vendor model. + */ + constructor( + private readonly uow: UnitOfWork, + private readonly itemsEntriesService: ItemsEntriesService, + private readonly eventPublisher: EventEmitter2, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + + @Inject(Vendor.name) private readonly vendorModel: typeof Vendor, + ) {} + + /** + * Creates a new vendor credit. + * @param {IVendorCreditCreateDTO} vendorCreditCreateDTO - + * @param {Knex.Transaction} trx - + */ + public newVendorCredit = async ( + vendorCreditCreateDTO: IVendorCreditCreateDTO, + trx?: Knex.Transaction, + ) => { + // Triggers `onVendorCreditCreate` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onCreate, { + vendorCreditCreateDTO, + }); + + // Retrieve the given vendor or throw not found service error. + const vendor = await this.vendorModel + .query() + .findById(vendorCreditCreateDTO.vendorId) + .throwIfNotFound(); + + // Validate items should be sellable items. + await this.itemsEntriesService.validateNonSellableEntriesItems( + vendorCreditCreateDTO.entries, + ); + + // Transforms the credit DTO to storage layer. + const vendorCreditModel = this.transformCreateEditDTOToModel( + vendorCreditCreateDTO, + vendor.currencyCode, + ); + // Saves the vendor credit transactions under UOW environment. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onVendorCreditCreating` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onCreating, { + vendorCreditCreateDTO, + trx, + } as IVendorCreditCreatingPayload); + + // Saves the vendor credit graph. + const vendorCredit = await this.vendorCreditModel + .query(trx) + .upsertGraphAndFetch({ + ...vendorCreditModel, + }); + + // Triggers `onVendorCreditCreated` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onCreated, { + vendorCredit, + vendorCreditCreateDTO, + trx, + } as IVendorCreditCreatedPayload); + + return vendorCredit; + }, trx); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/DeleteVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/DeleteVendorCredit.service.ts new file mode 100644 index 000000000..d1763c9dd --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/DeleteVendorCredit.service.ts @@ -0,0 +1,116 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { + IVendorCreditDeletedPayload, + IVendorCreditDeletingPayload, +} from '@/modules/VendorCredit/types/VendorCredit.types'; +import { ERRORS } from '../constants'; +import { VendorCredit } from '../models/VendorCredit'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; +import { VendorCreditAppliedBill } from '../../VendorCreditsApplyBills/models/VendorCreditAppliedBill'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { RefundVendorCredit } from '../models/RefundVendorCredit'; +import { events } from '@/common/events/events'; + +@Injectable() +export class DeleteVendorCreditService { + /** + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + * @param {typeof ItemEntry} itemEntryModel - The item entry model. + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {UnitOfWork} uow - The unit of work service. + * @param {typeof RefundVendorCredit} refundVendorCreditModel - The refund vendor credit model. + * @param {typeof VendorCreditAppliedBill} vendorCreditAppliedBillModel - The vendor credit applied bill model. + */ + constructor( + private vendorCreditModel: typeof VendorCredit, + private itemEntryModel: typeof ItemEntry, + private eventPublisher: EventEmitter2, + private uow: UnitOfWork, + + @Inject(RefundVendorCredit.name) + private refundVendorCreditModel: typeof RefundVendorCredit, + + @Inject(VendorCreditAppliedBill.name) + private vendorCreditAppliedBillModel: typeof VendorCreditAppliedBill, + ) {} + + /** + * Deletes the given vendor credit. + * @param {number} vendorCreditId - Vendor credit id. + */ + public deleteVendorCredit = async ( + vendorCreditId: number, + trx?: Knex.Transaction, + ) => { + // Retrieve the old vendor credit. + const oldVendorCredit = await this.refundVendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Validates vendor credit has no associate refund transactions. + await this.validateVendorCreditHasNoRefundTransactions(vendorCreditId); + + // Validates vendor credit has no associated applied to bills transactions. + await this.validateVendorCreditHasNoApplyBillsTransactions(vendorCreditId); + + // Deletes the vendor credit transactions under UOW environment. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onVendorCreditEditing` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onDeleting, { + oldVendorCredit, + trx, + } as IVendorCreditDeletingPayload); + + // Deletes the associated credit note entries. + await this.itemEntryModel + .query(trx) + .where('reference_id', vendorCreditId) + .where('reference_type', 'VendorCredit') + .delete(); + + // Deletes the credit note transaction. + await this.vendorCreditModel.query(trx).findById(vendorCreditId).delete(); + + // Triggers `onVendorCreditDeleted` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onDeleted, { + vendorCreditId, + oldVendorCredit, + trx, + } as IVendorCreditDeletedPayload); + }); + }; + + /** + * Validates vendor credit has no refund transactions. + * @param {number} vendorCreditId + */ + private validateVendorCreditHasNoRefundTransactions = async ( + vendorCreditId: number, + ): Promise => { + const refundCredits = await this.refundVendorCreditModel + .query() + .where('vendorCreditId', vendorCreditId); + if (refundCredits.length > 0) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS); + } + }; + + /** + * Validate vendor credit has no applied transactions to bills. + * @param {number} vendorCreditId + */ + private validateVendorCreditHasNoApplyBillsTransactions = async ( + vendorCreditId: number, + ): Promise => { + const appliedTransactions = await this.vendorCreditAppliedBillModel + .query() + .where('vendorCreditId', vendorCreditId); + if (appliedTransactions.length > 0) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_APPLIED_BILLS); + } + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/EditVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/EditVendorCredit.service.ts new file mode 100644 index 000000000..89cdf5316 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/EditVendorCredit.service.ts @@ -0,0 +1,105 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + IVendorCreditEditDTO, + IVendorCreditEditedPayload, + IVendorCreditEditingPayload, +} from '../types/VendorCredit.types'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { VendorCredit } from '../models/VendorCredit'; +import { Contact } from '@/modules/Contacts/models/Contact'; +import { events } from '@/common/events/events'; + +@Injectable() +export class EditVendorCreditService { + /** + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {UnitOfWork} uow - The unit of work service. + * @param {ItemsEntriesService} itemsEntriesService - The items entries service. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + * @param {typeof Contact} contactModel - The contact model. + */ + constructor( + private readonly eventPublisher: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly itemsEntriesService: ItemsEntriesService, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + + @Inject(Contact.name) + private readonly contactModel: typeof Contact, + ) {} + + /** + * Deletes the given vendor credit. + * @param {number} vendorCreditId - Vendor credit id. + */ + public editVendorCredit = async ( + vendorCreditId: number, + vendorCreditDTO: IVendorCreditEditDTO, + trx?: Knex.Transaction, + ) => { + // Retrieve the vendor credit or throw not found service error. + const oldVendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Validate customer existance. + const vendor = await this.contactModel + .query() + .modify('vendor') + .findById(vendorCreditDTO.vendorId) + .throwIfNotFound(); + + // Validate items ids existance. + await this.itemsEntriesService.validateItemsIdsExistance( + vendorCreditDTO.entries, + ); + // Validate non-sellable entries items. + await this.itemsEntriesService.validateNonSellableEntriesItems( + vendorCreditDTO.entries, + ); + // Validate the items entries existance. + await this.itemsEntriesService.validateEntriesIdsExistance( + vendorCreditId, + 'VendorCredit', + vendorCreditDTO.entries, + ); + // Transformes edit DTO to model storage layer. + const vendorCreditModel = this.transformCreateEditDTOToModel( + vendorCreditDTO, + vendor.currencyCode, + oldVendorCredit, + ); + // Edits the vendor credit graph under unit-of-work envirement. + return this.uow.withTransaction(async (trx) => { + // Triggers `onVendorCreditEditing` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onEditing, { + oldVendorCredit, + vendorCreditDTO, + trx, + } as IVendorCreditEditingPayload); + + // Saves the vendor credit graph to the storage. + const vendorCredit = await this.vendorCreditModel + .query(trx) + .upsertGraphAndFetch({ + id: vendorCreditId, + ...vendorCreditModel, + }); + // Triggers `onVendorCreditEdited event. + await this.eventPublisher.emitAsync(events.vendorCredit.onEdited, { + oldVendorCredit, + vendorCredit, + vendorCreditId, + vendorCreditDTO, + trx, + } as IVendorCreditEditedPayload); + + return vendorCredit; + }, trx); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts new file mode 100644 index 000000000..d564f0a9b --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts @@ -0,0 +1,90 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + IVendorCreditOpenedPayload, + IVendorCreditOpeningPayload, + IVendorCreditOpenPayload, +} from '../types/VendorCredit.types'; +import { ERRORS } from '../constants'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { VendorCredit } from '../models/VendorCredit'; +import { events } from '@/common/events/events'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class OpenVendorCreditService { + /** + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {UnitOfWork} uow - The unit of work service. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + */ + constructor( + private eventPublisher: EventEmitter2, + private uow: UnitOfWork, + + @Inject(VendorCredit.name) private vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Opens the given credit note. + * @param {number} vendorCreditId - + * @returns {Promise} + */ + public openVendorCredit = async ( + vendorCreditId: number, + ): Promise => { + // Retrieve the vendor credit or throw not found service error. + const oldVendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Throw service error if the credit note is already open. + this.throwErrorIfAlreadyOpen(oldVendorCredit); + + // Triggers `onVendorCreditOpen` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onOpen, { + vendorCreditId, + oldVendorCredit, + } as IVendorCreditOpenPayload); + + // Sales the credit note transactions with associated entries. + return this.uow.withTransaction(async (trx) => { + const eventPayload = { + vendorCreditId, + oldVendorCredit, + trx, + } as IVendorCreditOpeningPayload; + + // Triggers `onCreditNoteOpening` event. + await this.eventPublisher.emitAsync( + events.creditNote.onOpening, + eventPayload as IVendorCreditOpeningPayload, + ); + // Saves the vendor credit graph to the storage. + const vendorCredit = await this.vendorCreditModel + .query(trx) + .findById(vendorCreditId) + .update({ + openedAt: new Date(), + }); + // Triggers `onVendorCreditOpened` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onOpened, { + ...eventPayload, + vendorCredit, + } as IVendorCreditOpenedPayload); + + return vendorCredit; + }); + }; + + /** + * Throw error if the vendor credit is already open. + * @param {IVendorCredit} vendorCredit + */ + public throwErrorIfAlreadyOpen = (vendorCredit: VendorCredit) => { + if (vendorCredit.openedAt) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_ALREADY_OPENED); + } + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditAutoIncrement.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditAutoIncrement.service.ts new file mode 100644 index 000000000..843779838 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditAutoIncrement.service.ts @@ -0,0 +1,32 @@ +import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service'; +import { Injectable } from '@nestjs/common'; + + +@Injectable() +export class VendorCreditAutoIncrementService { + /** + * @param {AutoIncrementOrdersService} autoIncrementOrdersService - Auto increment orders service. + */ + constructor(private autoIncrementOrdersService: AutoIncrementOrdersService) {} + + /** + * Retrieve the next unique credit number. + * @param {number} tenantId - Tenant id. + * @return {string} + */ + public getNextCreditNumber = (): string => { + return this.autoIncrementOrdersService.getNextTransactionNumber( + 'vendor_credit', + ); + }; + + /** + * Increment the vendor credit serial next number. + * @param {number} tenantId - + */ + public incrementSerialNumber = () => { + return this.autoIncrementOrdersService.incrementSettingsNextNumber( + 'vendor_credit', + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts new file mode 100644 index 000000000..f09c17370 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts @@ -0,0 +1,102 @@ +import moment from 'moment'; +import { omit } from 'lodash'; +import * as R from 'ramda'; +import { ERRORS } from '../constants'; +import { + IVendorCreditCreateDTO, + IVendorCreditEditDTO, + IVendorCreditEntryDTO, +} from '../types/VendorCredit.types'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; +import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; +import { VendorCredit } from '../models/VendorCredit'; +import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; +import { VendorCreditAutoIncrementService } from './VendorCreditAutoIncrement.service'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { Injectable } from '@nestjs/common'; + + +@Injectable() +export class VendorCreditDTOTransformService { + /** + * @param {ItemsEntriesService} itemsEntriesService - The items entries service. + * @param {BranchTransactionDTOTransformer} branchDTOTransform - The branch transaction DTO transformer. + * @param {WarehouseTransactionDTOTransform} warehouseDTOTransform - The warehouse transaction DTO transformer. + * @param {VendorCreditAutoIncrementService} vendorCreditAutoIncrement - The vendor credit auto increment service. + */ + constructor( + private itemsEntriesService: ItemsEntriesService, + private branchDTOTransform: BranchTransactionDTOTransformer, + private warehouseDTOTransform: WarehouseTransactionDTOTransform, + private vendorCreditAutoIncrement: VendorCreditAutoIncrementService + ) {} + + /** + * Transformes the credit/edit vendor credit DTO to model. + * @param {IVendorCreditCreateDTO | IVendorCreditEditDTO} vendorCreditDTO + * @param {string} vendorCurrencyCode - + * @param {IVendorCredit} oldVendorCredit - + * @returns {VendorCredit} + */ + public transformCreateEditDTOToModel = ( + vendorCreditDTO: IVendorCreditCreateDTO | IVendorCreditEditDTO, + vendorCurrencyCode: string, + oldVendorCredit?: VendorCredit + ): VendorCredit => { + // Calculates the total amount of items entries. + const amount = this.itemsEntriesService.getTotalItemsEntries( + vendorCreditDTO.entries + ); + const entries = R.compose( + // Associate the default index to each item entry. + assocItemEntriesDefaultIndex, + + // Associate the reference type to item entries. + R.map((entry: IVendorCreditEntryDTO) => ({ + referenceType: 'VendorCredit', + ...entry, + })) + )(vendorCreditDTO.entries); + + // Retreive the next vendor credit number. + const autoNextNumber = this.vendorCreditAutoIncrement.getNextCreditNumber(); + + // Detarmines the credit note number. + const vendorCreditNumber = + vendorCreditDTO.vendorCreditNumber || + oldVendorCredit?.vendorCreditNumber || + autoNextNumber; + + const initialDTO = { + ...omit(vendorCreditDTO, ['open', 'attachments']), + amount, + currencyCode: vendorCurrencyCode, + exchangeRate: vendorCreditDTO.exchangeRate || 1, + vendorCreditNumber, + entries, + ...(vendorCreditDTO.open && + !oldVendorCredit?.openedAt && { + openedAt: moment().toMySqlDateTime(), + }), + }; + return R.compose( + this.branchDTOTransform.transformDTO, + this.warehouseDTOTransform.transformDTO( + )(initialDTO); + }; + + /** + * Validate the credit note remaining amount. + * @param {ICreditNote} creditNote + * @param {number} amount + */ + public validateCreditRemainingAmount = ( + vendorCredit: VendorCredit, + amount: number + ) => { + if (vendorCredit.creditsRemaining < amount) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT); + } + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGLEntries.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGLEntries.ts new file mode 100644 index 000000000..6aced9d90 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGLEntries.ts @@ -0,0 +1,252 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import * as R from 'ramda'; +// import { +// IVendorCredit, +// ILedgerEntry, +// AccountNormal, +// IItemEntry, +// } from '@/interfaces'; +// import HasTenancyService from '@/services/Tenancy/TenancyService'; +// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; +// import Ledger from '@/services/Accounting/Ledger'; + +// @Service() +// export default class VendorCreditGLEntries { +// @Inject() +// private ledgerStorage: LedgerStorageService; + +// @Inject() +// private tenancy: HasTenancyService; + +// /** +// * Retrieve the vendor credit GL common entry. +// * @param {IVendorCredit} vendorCredit +// * @returns {} +// */ +// public getVendorCreditGLCommonEntry = (vendorCredit: IVendorCredit) => { +// return { +// date: vendorCredit.vendorCreditDate, +// currencyCode: vendorCredit.currencyCode, +// exchangeRate: vendorCredit.exchangeRate, + +// transactionId: vendorCredit.id, +// transactionType: 'VendorCredit', +// transactionNumber: vendorCredit.vendorCreditNumber, +// referenceNumber: vendorCredit.referenceNo, + +// credit: 0, +// debit: 0, + +// branchId: vendorCredit.branchId, +// }; +// }; + +// /** +// * Retrieves the vendor credit payable GL entry. +// * @param {IVendorCredit} vendorCredit +// * @param {number} APAccountId +// * @returns {ILedgerEntry} +// */ +// public getVendorCreditPayableGLEntry = ( +// vendorCredit: IVendorCredit, +// APAccountId: number +// ): ILedgerEntry => { +// const commonEntity = this.getVendorCreditGLCommonEntry(vendorCredit); + +// return { +// ...commonEntity, +// debit: vendorCredit.totalLocal, +// accountId: APAccountId, +// contactId: vendorCredit.vendorId, +// accountNormal: AccountNormal.CREDIT, +// index: 1, +// }; +// }; + +// /** +// * Retrieves the vendor credit item GL entry. +// * @param {IVendorCredit} vendorCredit +// * @param {IItemEntry} entry +// * @returns {ILedgerEntry} +// */ +// public getVendorCreditGLItemEntry = R.curry( +// ( +// vendorCredit: IVendorCredit, +// entry: IItemEntry, +// index: number +// ): ILedgerEntry => { +// const commonEntity = this.getVendorCreditGLCommonEntry(vendorCredit); +// const totalLocal = entry.totalExcludingTax * vendorCredit.exchangeRate; + +// return { +// ...commonEntity, +// credit: totalLocal, +// index: index + 2, +// itemId: entry.itemId, +// itemQuantity: entry.quantity, +// accountId: +// 'inventory' === entry.item.type +// ? entry.item.inventoryAccountId +// : entry.costAccountId || entry.item.costAccountId, +// accountNormal: AccountNormal.DEBIT, +// }; +// } +// ); + +// /** +// * Retrieves the vendor credit discount GL entry. +// * @param {IVendorCredit} vendorCredit +// * @param {number} discountAccountId +// * @returns {ILedgerEntry} +// */ +// public getDiscountEntry = ( +// vendorCredit: IVendorCredit, +// purchaseDiscountAccountId: number +// ) => { +// const commonEntry = this.getVendorCreditGLCommonEntry(vendorCredit); + +// return { +// ...commonEntry, +// debit: vendorCredit.discountAmountLocal, +// accountId: purchaseDiscountAccountId, +// accountNormal: AccountNormal.DEBIT, +// index: 1, +// indexGroup: 40, +// }; +// }; + +// /** +// * Retrieves the vendor credit adjustment GL entry. +// * @param {IVendorCredit} vendorCredit +// * @param {number} adjustmentAccountId +// * @returns {ILedgerEntry} +// */ +// public getAdjustmentEntry = ( +// vendorCredit: IVendorCredit, +// otherExpensesAccountId: number +// ) => { +// const commonEntry = this.getVendorCreditGLCommonEntry(vendorCredit); +// const adjustmentAmount = Math.abs(vendorCredit.adjustmentLocal); + +// return { +// ...commonEntry, +// credit: vendorCredit.adjustmentLocal > 0 ? adjustmentAmount : 0, +// debit: vendorCredit.adjustmentLocal < 0 ? adjustmentAmount : 0, +// accountId: otherExpensesAccountId, +// accountNormal: AccountNormal.DEBIT, +// index: 1, +// indexGroup: 40, +// }; +// }; + +// /** +// * Retrieve the vendor credit GL entries. +// * @param {IVendorCredit} vendorCredit - +// * @param {number} receivableAccount - +// * @return {ILedgerEntry[]} +// */ +// public getVendorCreditGLEntries = ( +// vendorCredit: IVendorCredit, +// payableAccountId: number, +// purchaseDiscountAccountId: number, +// otherExpensesAccountId: number +// ): ILedgerEntry[] => { +// const payableEntry = this.getVendorCreditPayableGLEntry( +// vendorCredit, +// payableAccountId +// ); +// const getItemEntry = this.getVendorCreditGLItemEntry(vendorCredit); +// const itemsEntries = vendorCredit.entries.map(getItemEntry); + +// const discountEntry = this.getDiscountEntry( +// vendorCredit, +// purchaseDiscountAccountId +// ); +// const adjustmentEntry = this.getAdjustmentEntry( +// vendorCredit, +// otherExpensesAccountId +// ); +// return [payableEntry, discountEntry, adjustmentEntry, ...itemsEntries]; +// }; + +// /** +// * Reverts the vendor credit associated GL entries. +// * @param {number} tenantId +// * @param {number} vendorCreditId +// * @param {Knex.Transaction} trx +// */ +// public revertVendorCreditGLEntries = async ( +// tenantId: number, +// vendorCreditId: number, +// trx?: Knex.Transaction +// ): Promise => { +// await this.ledgerStorage.deleteByReference( +// tenantId, +// vendorCreditId, +// 'VendorCredit', +// trx +// ); +// }; + +// /** +// * Creates vendor credit associated GL entries. +// * @param {number} tenantId +// * @param {number} vendorCreditId +// * @param {Knex.Transaction} trx +// */ +// public writeVendorCreditGLEntries = async ( +// tenantId: number, +// vendorCreditId: number, +// trx?: Knex.Transaction +// ) => { +// const { accountRepository } = this.tenancy.repositories(tenantId); +// const { VendorCredit } = this.tenancy.models(tenantId); + +// // Vendor credit with entries items. +// const vendorCredit = await VendorCredit.query(trx) +// .findById(vendorCreditId) +// .withGraphFetched('entries.item'); + +// // Retrieve the payable account (A/P) account. +// const APAccount = await accountRepository.findOrCreateAccountsPayable( +// vendorCredit.currencyCode, +// {}, +// trx +// ); +// const purchaseDiscountAccount = +// await accountRepository.findOrCreatePurchaseDiscountAccount({}, trx); + +// const otherExpensesAccount = +// await accountRepository.findOrCreateOtherExpensesAccount({}, trx); +// // Saves the vendor credit GL entries. +// const ledgerEntries = this.getVendorCreditGLEntries( +// vendorCredit, +// APAccount.id, +// purchaseDiscountAccount.id, +// otherExpensesAccount.id +// ); +// const ledger = new Ledger(ledgerEntries); + +// // Commits the ledger entries to the storage. +// await this.ledgerStorage.commit(tenantId, ledger, trx); +// }; + +// /** +// * Edits vendor credit associated GL entries. +// * @param {number} tenantId +// * @param {number} vendorCreditId +// * @param {Knex.Transaction} trx +// */ +// public rewriteVendorCreditGLEntries = async ( +// tenantId: number, +// vendorCreditId: number, +// trx?: Knex.Transaction +// ) => { +// // Reverts the GL entries. +// await this.revertVendorCreditGLEntries(tenantId, vendorCreditId, trx); + +// // Re-write the GL entries. +// await this.writeVendorCreditGLEntries(tenantId, vendorCreditId, trx); +// }; +// } diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts new file mode 100644 index 000000000..f604bcd31 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts @@ -0,0 +1,92 @@ +// import { Knex } from 'knex'; +// import { Service, Inject } from 'typedi'; +// import { IVendorCredit } from '@/interfaces'; +// import InventoryService from '@/services/Inventory/Inventory'; +// import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; + +// @Service() +// export default class VendorCreditInventoryTransactions { +// @Inject() +// inventoryService: InventoryService; + +// @Inject() +// itemsEntriesService: ItemsEntriesService; + +// /** +// * Creates vendor credit associated inventory transactions. +// * @param {number} tenantId +// * @param {IVnedorCredit} vendorCredit +// * @param {Knex.Transaction} trx +// */ +// public createInventoryTransactions = async ( +// tenantId: number, +// vendorCredit: IVendorCredit, +// trx: Knex.Transaction +// ): Promise => { +// // Loads the inventory items entries of the given sale invoice. +// const inventoryEntries = +// await this.itemsEntriesService.filterInventoryEntries( +// tenantId, +// vendorCredit.entries +// ); + +// const transaction = { +// transactionId: vendorCredit.id, +// transactionType: 'VendorCredit', +// transactionNumber: vendorCredit.vendorCreditNumber, +// exchangeRate: vendorCredit.exchangeRate, +// date: vendorCredit.vendorCreditDate, +// direction: 'OUT', +// entries: inventoryEntries, +// warehouseId: vendorCredit.warehouseId, +// createdAt: vendorCredit.createdAt, +// }; +// // Writes inventory tranactions. +// await this.inventoryService.recordInventoryTransactionsFromItemsEntries( +// tenantId, +// transaction, +// false, +// trx +// ); +// }; + +// /** +// * Edits vendor credit associated inventory transactions. +// * @param {number} tenantId +// * @param {number} creditNoteId +// * @param {ICreditNote} creditNote +// * @param {Knex.Transactions} trx +// */ +// public editInventoryTransactions = async ( +// tenantId: number, +// vendorCreditId: number, +// vendorCredit: IVendorCredit, +// trx?: Knex.Transaction +// ): Promise => { +// // Deletes inventory transactions. +// await this.deleteInventoryTransactions(tenantId, vendorCreditId, trx); + +// // Re-write inventory transactions. +// await this.createInventoryTransactions(tenantId, vendorCredit, trx); +// }; + +// /** +// * Deletes credit note associated inventory transactions. +// * @param {number} tenantId - Tenant id. +// * @param {number} creditNoteId - Credit note id. +// * @param {Knex.Transaction} trx - +// */ +// public deleteInventoryTransactions = async ( +// tenantId: number, +// vendorCreditId: number, +// trx?: Knex.Transaction +// ): Promise => { +// // Deletes the inventory transactions by the given reference id and type. +// await this.inventoryService.deleteInventoryTransactions( +// tenantId, +// vendorCreditId, +// 'VendorCredit', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsExportable.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsExportable.ts new file mode 100644 index 000000000..39a688896 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsExportable.ts @@ -0,0 +1,36 @@ +// import { Inject, Service } from 'typedi'; +// import { IVendorCreditsQueryDTO } from '@/interfaces'; +// import ListVendorCredits from '../queries/ListVendorCredits'; +// import { Exportable } from '@/services/Export/Exportable'; +// import { QueryBuilder } from 'knex'; + +// @Service() +// export class VendorCreditsExportable extends Exportable { +// @Inject() +// private getVendorCredits: ListVendorCredits; + +// /** +// * Retrieves the vendor credits data to exportable sheet. +// * @param {number} tenantId - +// * @param {IVendorCreditsQueryDTO} query - +// * @returns {} +// */ +// public exportable(tenantId: number, query: IVendorCreditsQueryDTO) { +// const filterQuery = (query) => { +// query.withGraphFetched('branch'); +// query.withGraphFetched('warehouse'); +// }; +// const parsedQuery = { +// sortOrder: 'desc', +// columnSortBy: 'created_at', +// ...query, +// page: 1, +// pageSize: 12000, +// filterQuery, +// } as IVendorCreditsQueryDTO; + +// return this.getVendorCredits +// .getVendorCredits(tenantId, parsedQuery) +// .then((output) => output.vendorCredits); +// } +// } diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsImportable.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsImportable.ts new file mode 100644 index 000000000..922da3bc6 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditsImportable.ts @@ -0,0 +1,45 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import { Importable } from '@/services/Import/Importable'; +// import CreateVendorCredit from './CreateVendorCredit.service'; +// import { IVendorCreditCreateDTO } from '@/interfaces'; +// import { VendorCreditsSampleData } from '../constants'; + +// @Service() +// export class VendorCreditsImportable extends Importable { +// @Inject() +// private createVendorCreditService: CreateVendorCredit; + +// /** +// * Importing to account service. +// * @param {number} tenantId +// * @param {IAccountCreateDTO} createAccountDTO +// * @returns +// */ +// public importable( +// tenantId: number, +// createPaymentDTO: IVendorCreditCreateDTO, +// trx?: Knex.Transaction +// ) { +// return this.createVendorCreditService.newVendorCredit( +// tenantId, +// createPaymentDTO, +// trx +// ); +// } + +// /** +// * Concurrrency controlling of the importing process. +// * @returns {number} +// */ +// public get concurrency() { +// return 1; +// } + +// /** +// * Retrieves the sample data that used to download accounts sample sheet. +// */ +// public sampleData(): any[] { +// return VendorCreditsSampleData; +// } +// } diff --git a/packages/server-nest/src/modules/VendorCredit/constants.ts b/packages/server-nest/src/modules/VendorCredit/constants.ts new file mode 100644 index 000000000..d686669d8 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/constants.ts @@ -0,0 +1,82 @@ +export const ERRORS = { + VENDOR_CREDIT_NOT_FOUND: 'VENDOR_CREDIT_NOT_FOUND', + VENDOR_CREDIT_ALREADY_OPENED: 'VENDOR_CREDIT_ALREADY_OPENED', + VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT: + 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT', + VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND: + 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND', + BILLS_HAS_NO_REMAINING_AMOUNT: 'BILLS_HAS_NO_REMAINING_AMOUNT', + VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS: + 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS', + VENDOR_CREDIT_HAS_APPLIED_BILLS: 'VENDOR_CREDIT_HAS_APPLIED_BILLS', +}; + +export const DEFAULT_VIEW_COLUMNS = []; +export const DEFAULT_VIEWS = [ + { + name: 'vendor_credit.view.draft', + slug: 'draft', + rolesLogicExpression: '1', + roles: [ + { index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'vendor_credit.view.published', + slug: 'published', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'published', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'vendor_credit.view.open', + slug: 'open', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'open', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'vendor_credit.view.closed', + slug: 'closed', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'status', + comparator: 'equals', + value: 'closed', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, +]; + +export const VendorCreditsSampleData = [ + { + Vendor: 'Randall Kohler VENDOR', + 'Vendor Credit Date': '2024-01-01', + 'Vendor Credit No.': 'VC-0001', + 'Reference No.': 'REF-00001', + 'Exchange Rate': '', + Note: 'Note', + Open: 'T', + 'Item Name': 'Hettinger, Schumm and Bartoletti', + Quantity: 100, + Rate: 100, + }, +]; diff --git a/packages/server-nest/src/modules/VendorCredit/models/RefundVendorCredit.ts b/packages/server-nest/src/modules/VendorCredit/models/RefundVendorCredit.ts new file mode 100644 index 000000000..ae9067a04 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/models/RefundVendorCredit.ts @@ -0,0 +1,63 @@ +import { Model, mixin } from 'objection'; +// import TenantModel from 'models/TenantModel'; +// import ModelSetting from './ModelSetting'; +// import CustomViewBaseModel from './CustomViewBaseModel'; +// import ModelSearchable from './ModelSearchable'; +import { BaseModel } from '@/models/Model'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { VendorCredit } from './VendorCredit'; + +export class RefundVendorCredit extends BaseModel { + public vendorCreditId!: number; + public amount!: number; + public currencyCode!: string; + public exchangeRate!: number; + public referenceNo!: string; + public depositAccountId!: number; + public description!: string; + public branchId!: number; + + public vendorCredit!: VendorCredit; + public depositAccount!: Account; + + /** + * Table name. + */ + static get tableName() { + return 'refund_vendor_credit_transactions'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['created_at', 'updated_at']; + } + + /* + * Relationship mapping. + */ + static get relationMappings() { + const { VendorCredit } = require('./VendorCredit'); + const { Account } = require('../../Accounts/models/Account.model'); + + return { + depositAccount: { + relation: Model.BelongsToOneRelation, + modelClass: Account, + join: { + from: 'refund_vendor_credit_transactions.depositAccountId', + to: 'accounts.id', + }, + }, + vendorCredit: { + relation: Model.BelongsToOneRelation, + modelClass: VendorCredit, + join: { + from: 'refund_vendor_credit_transactions.vendorCreditId', + to: 'vendor_credits.id', + }, + }, + }; + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts index b95d0924a..6ab11b68e 100644 --- a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts +++ b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts @@ -22,6 +22,9 @@ export class VendorCredit extends BaseModel { openedAt: Date; userId: number; + branchId: number; + warehouseId: number; + /** * Table name */ @@ -183,83 +186,85 @@ export class VendorCredit extends BaseModel { /** * Relationship mapping. */ - // static get relationMappings() { - // const Vendor = require('models/Vendor'); - // const ItemEntry = require('models/ItemEntry'); - // const Branch = require('models/Branch'); - // const Document = require('models/Document'); - // const Warehouse = require('models/Warehouse'); + static get relationMappings() { + const { Vendor } = require('../../Vendors/models/Vendor'); + const { + ItemEntry, + } = require('../../TransactionItemEntry/models/ItemEntry'); + const { Branch } = require('../../Branches/models/Branch.model'); + const { Document } = require('../../ChromiumlyTenancy/models/Document'); + const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); - // return { - // vendor: { - // relation: Model.BelongsToOneRelation, - // modelClass: Vendor.default, - // join: { - // from: 'vendor_credits.vendorId', - // to: 'contacts.id', - // }, - // filter(query) { - // query.where('contact_service', 'vendor'); - // }, - // }, + return { + vendor: { + relation: Model.BelongsToOneRelation, + modelClass: Vendor, + join: { + from: 'vendor_credits.vendorId', + to: 'contacts.id', + }, + filter(query) { + query.where('contact_service', 'vendor'); + }, + }, - // entries: { - // relation: Model.HasManyRelation, - // modelClass: ItemEntry.default, - // join: { - // from: 'vendor_credits.id', - // to: 'items_entries.referenceId', - // }, - // filter(builder) { - // builder.where('reference_type', 'VendorCredit'); - // builder.orderBy('index', 'ASC'); - // }, - // }, + entries: { + relation: Model.HasManyRelation, + modelClass: ItemEntry, + join: { + from: 'vendor_credits.id', + to: 'items_entries.referenceId', + }, + filter(builder) { + builder.where('reference_type', 'VendorCredit'); + builder.orderBy('index', 'ASC'); + }, + }, - // /** - // * Vendor credit may belongs to branch. - // */ - // branch: { - // relation: Model.BelongsToOneRelation, - // modelClass: Branch.default, - // join: { - // from: 'vendor_credits.branchId', - // to: 'branches.id', - // }, - // }, + /** + * Vendor credit may belongs to branch. + */ + branch: { + relation: Model.BelongsToOneRelation, + modelClass: Branch, + join: { + from: 'vendor_credits.branchId', + to: 'branches.id', + }, + }, - // /** - // * Vendor credit may has associated warehouse. - // */ - // warehouse: { - // relation: Model.BelongsToOneRelation, - // modelClass: Warehouse.default, - // join: { - // from: 'vendor_credits.warehouseId', - // to: 'warehouses.id', - // }, - // }, + /** + * Vendor credit may has associated warehouse. + */ + warehouse: { + relation: Model.BelongsToOneRelation, + modelClass: Warehouse, + join: { + from: 'vendor_credits.warehouseId', + to: 'warehouses.id', + }, + }, - // /** - // * Vendor credit may has many attached attachments. - // */ - // attachments: { - // relation: Model.ManyToManyRelation, - // modelClass: Document.default, - // join: { - // from: 'vendor_credits.id', - // through: { - // from: 'document_links.modelId', - // to: 'document_links.documentId', - // }, - // to: 'documents.id', - // }, - // filter(query) { - // query.where('model_ref', 'VendorCredit'); - // }, - // }, - // }; - // } + /** + * Vendor credit may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document, + join: { + from: 'vendor_credits.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'VendorCredit'); + }, + }, + }; + } /** * diff --git a/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredit.service.ts new file mode 100644 index 000000000..24467c34c --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredit.service.ts @@ -0,0 +1,42 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { VendorCreditTransformer } from './VendorCreditTransformer'; +import { ERRORS } from '../constants'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { VendorCredit } from '../models/VendorCredit'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { Knex } from 'knex'; + +@Injectable() +export default class GetVendorCreditService { + constructor( + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + private readonly transformer: TransformerInjectable, + ) {} + + /** + * Retrieve the given vendor credit. + * @param {number} vendorCreditId - Vendor credit id. + */ + public async getVendorCredit( + vendorCreditId: number, + trx?: Knex.Transaction, + ) { + // Retrieve the vendor credit model graph. + const vendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .withGraphFetched('entries.item') + .withGraphFetched('vendor') + .withGraphFetched('branch') + .withGraphFetched('attachments'); + + if (!vendorCredit) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_NOT_FOUND); + } + return this.transformer.transform( + vendorCredit, + new VendorCreditTransformer() + ); + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/queries/ListVendorCredits.ts b/packages/server-nest/src/modules/VendorCredit/queries/ListVendorCredits.ts new file mode 100644 index 000000000..854008fa7 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/queries/ListVendorCredits.ts @@ -0,0 +1,70 @@ +// import * as R from 'ramda'; +// import { Service, Inject } from 'typedi'; +// import BaseVendorCredit from '../commands/BaseVendorCredit'; +// import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +// import { IVendorCreditsQueryDTO } from '@/interfaces'; +// import { VendorCreditTransformer } from './VendorCreditTransformer'; +// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; + +// @Service() +// export default class ListVendorCredits extends BaseVendorCredit { +// @Inject() +// private dynamicListService: DynamicListingService; + +// @Inject() +// private transformer: TransformerInjectable; + +// /** +// * Parses the sale invoice list filter DTO. +// * @param {IVendorCreditsQueryDTO} filterDTO +// * @returns +// */ +// private parseListFilterDTO = (filterDTO: IVendorCreditsQueryDTO) => { +// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO); +// }; + +// /** +// * Retrieve the vendor credits list. +// * @param {number} tenantId - Tenant id. +// * @param {IVendorCreditsQueryDTO} vendorCreditQuery - +// */ +// public getVendorCredits = async ( +// tenantId: number, +// vendorCreditQuery: IVendorCreditsQueryDTO +// ) => { +// const { VendorCredit } = this.tenancy.models(tenantId); + +// // Parses stringified filter roles. +// const filter = this.parseListFilterDTO(vendorCreditQuery); + +// // Dynamic list service. +// const dynamicFilter = await this.dynamicListService.dynamicList( +// tenantId, +// VendorCredit, +// filter +// ); +// const { results, pagination } = await VendorCredit.query() +// .onBuild((builder) => { +// builder.withGraphFetched('entries'); +// builder.withGraphFetched('vendor'); +// dynamicFilter.buildQuery()(builder); + +// // Gives ability to inject custom query to filter results. +// vendorCreditQuery?.filterQuery && +// vendorCreditQuery?.filterQuery(builder); +// }) +// .pagination(filter.page - 1, filter.pageSize); + +// // Transformes the vendor credits models to POJO. +// const vendorCredits = await this.transformer.transform( +// tenantId, +// results, +// new VendorCreditTransformer() +// ); +// return { +// vendorCredits, +// pagination, +// filterMeta: dynamicFilter.getResponseMeta(), +// }; +// }; +// } diff --git a/packages/server-nest/src/modules/VendorCredit/queries/VendorCreditTransformer.ts b/packages/server-nest/src/modules/VendorCredit/queries/VendorCreditTransformer.ts new file mode 100644 index 000000000..6b307cdb9 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/queries/VendorCreditTransformer.ts @@ -0,0 +1,179 @@ +import { AttachmentTransformer } from "@/modules/Attachments/Attachment.transformer"; +import { ItemEntryTransformer } from "@/modules/TransactionItemEntry/ItemEntry.transformer"; +import { Transformer } from "@/modules/Transformer/Transformer"; + +export class VendorCreditTransformer extends Transformer { + /** + * Include these attributes to vendor credit object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'formattedAmount', + 'formattedSubtotal', + 'formattedVendorCreditDate', + 'formattedCreatedAt', + 'formattedCreditsRemaining', + 'formattedInvoicedAmount', + + 'discountAmountFormatted', + 'discountPercentageFormatted', + 'discountAmountLocalFormatted', + + 'adjustmentFormatted', + 'adjustmentLocalFormatted', + + 'totalFormatted', + 'entries', + 'attachments', + ]; + }; + + /** + * Retrieve formatted vendor credit date. + * @param {IVendorCredit} credit + * @returns {String} + */ + protected formattedVendorCreditDate = (vendorCredit): string => { + return this.formatDate(vendorCredit.vendorCreditDate); + }; + + /** + * Retireve formatted created at date. + * @param vendorCredit + * @returns {string} + */ + protected formattedCreatedAt = (vendorCredit): string => { + return this.formatDate(vendorCredit.createdAt); + }; + + /** + * Retrieve formatted vendor credit amount. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected formattedAmount = (vendorCredit): string => { + return this.formatNumber(vendorCredit.amount, { + currencyCode: vendorCredit.currencyCode, + }); + }; + + /** + * Retrieves the vendor credit formatted subtotal. + * @param {IVendorCredit} vendorCredit + * @returns {string} + */ + protected formattedSubtotal = (vendorCredit): string => { + return this.formatNumber(vendorCredit.amount, { money: false }); + }; + + /** + * Retrieve formatted credits remaining. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected formattedCreditsRemaining = (credit) => { + return this.formatNumber(credit.creditsRemaining, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the formatted discount amount. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected discountAmountFormatted = (credit): string => { + return this.formatNumber(credit.discountAmount, { + currencyCode: credit.currencyCode, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted discount amount in local currency. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected discountAmountLocalFormatted = (credit): string => { + return this.formatNumber(credit.discountAmountLocal, { + currencyCode: this.context.organization.baseCurrency, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted discount percentage. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected discountPercentageFormatted = (credit): string => { + return credit.discountPercentage ? `${credit.discountPercentage}%` : ''; + }; + + /** + * Retrieves the formatted adjustment amount. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected adjustmentFormatted = (credit): string => { + return this.formatNumber(credit.adjustment, { + currencyCode: credit.currencyCode, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted adjustment amount in local currency. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected adjustmentLocalFormatted = (credit): string => { + return this.formatNumber(credit.adjustmentLocal, { + currencyCode: this.context.organization.baseCurrency, + excerptZero: true, + }); + }; + + /** + * Retrieves the formatted invoiced amount. + * @param credit + * @returns {string} + */ + protected formattedInvoicedAmount = (credit) => { + return this.formatNumber(credit.invoicedAmount, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the formatted total. + * @param {IVendorCredit} credit + * @returns {string} + */ + protected totalFormatted = (credit) => { + return this.formatNumber(credit.total, { + currencyCode: credit.currencyCode, + }); + }; + + /** + * Retrieves the entries of the bill. + * @param {IVendorCredit} vendorCredit + * @returns {} + */ + protected entries = (vendorCredit) => { + return this.item(vendorCredit.entries, new ItemEntryTransformer(), { + currencyCode: vendorCredit.currencyCode, + }); + }; + + /** + * Retrieves the vendor credit attachments. + * @param {IVendorCredit} invoice + * @returns + */ + protected attachments = (vendorCredit) => { + return this.item(vendorCredit.attachments, new AttachmentTransformer()); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/DeleteVendorAssociatedVendorCredit.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/DeleteVendorAssociatedVendorCredit.ts new file mode 100644 index 000000000..33f5c0377 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/DeleteVendorAssociatedVendorCredit.ts @@ -0,0 +1,48 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { VendorCredit } from '../models/VendorCredit'; +import { IVendorEventDeletingPayload } from '@/modules/Vendors/types/Vendors.types'; +import { events } from '@/common/events/events'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +const ERRORS = { + VENDOR_HAS_TRANSACTIONS: 'VENDOR_HAS_TRANSACTIONS', +}; + +@Injectable() +export class DeleteVendorAssociatedVendorCredit { + /** + * @param {typeof VendorCredit} vendorCreditModel - Vendor credit model. + */ + constructor( + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Validate vendor has no associated credit transaction once the vendor deleting. + * @param {IVendorEventDeletingPayload} payload - + */ + @OnEvent(events.vendors.onDeleting) + public async validateVendorHasNoCreditsTransactionsOnceDeleting({ + vendorId, + }: IVendorEventDeletingPayload) { + await this.validateVendorHasNoCreditsTransactions(vendorId); + } + + /** + * Validate the given vendor has no associated vendor credit transactions. + * @param {number} vendorId + */ + public async validateVendorHasNoCreditsTransactions( + vendorId: number, + ): Promise { + const associatedVendors = await this.vendorCreditModel + .query() + .where('vendorId', vendorId); + + if (associatedVendors.length > 0) { + throw new ServiceError(ERRORS.VENDOR_HAS_TRANSACTIONS); + } + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/RefundSyncVendorCreditBalanceSubscriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/RefundSyncVendorCreditBalanceSubscriber.ts new file mode 100644 index 000000000..dbce1c3dc --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/RefundSyncVendorCreditBalanceSubscriber.ts @@ -0,0 +1,62 @@ +import { Service, Inject } from 'typedi'; +import { + IRefundVendorCreditCreatedPayload, + IRefundVendorCreditDeletedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import RefundSyncCreditRefundedAmount from './RefundSyncCreditRefundedAmount'; + +@Service() +export default class RefundSyncVendorCreditBalanceSubscriber { + @Inject() + refundSyncCreditRefunded: RefundSyncCreditRefundedAmount; + + /** + * Attaches events with handlers. + */ + public attach = (bus) => { + bus.subscribe( + events.vendorCredit.onRefundCreated, + this.incrementRefundedAmountOnceRefundCreated + ); + bus.subscribe( + events.vendorCredit.onRefundDeleted, + this.decrementRefundedAmountOnceRefundDeleted + ); + }; + + /** + * Increment refunded vendor credit amount once refund transaction created. + * @param {IRefundVendorCreditCreatedPayload} payload - + */ + private incrementRefundedAmountOnceRefundCreated = async ({ + refundVendorCredit, + vendorCredit, + tenantId, + trx, + }: IRefundVendorCreditCreatedPayload) => { + await this.refundSyncCreditRefunded.incrementCreditRefundedAmount( + tenantId, + refundVendorCredit.vendorCreditId, + refundVendorCredit.amount, + trx + ); + }; + + /** + * Decrement refunded vendor credit amount once refund transaction deleted. + * @param {IRefundVendorCreditDeletedPayload} payload - + */ + private decrementRefundedAmountOnceRefundDeleted = async ({ + trx, + oldRefundCredit, + tenantId, + }: IRefundVendorCreditDeletedPayload) => { + await this.refundSyncCreditRefunded.decrementCreditNoteRefundAmount( + tenantId, + oldRefundCredit.vendorCreditId, + oldRefundCredit.amount, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/RefundVendorCreditGLEntriesSubscriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/RefundVendorCreditGLEntriesSubscriber.ts new file mode 100644 index 000000000..6839c2af1 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/RefundVendorCreditGLEntriesSubscriber.ts @@ -0,0 +1,59 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import RefundVendorCreditGLEntries from '../RefundVendorCredits/commands/RefundVendorCreditGLEntries'; +import { + IRefundCreditNoteDeletedPayload, + IRefundVendorCreditCreatedPayload, +} from '@/interfaces'; + +@Service() +export default class RefundVendorCreditGLEntriesSubscriber { + @Inject() + refundVendorGLEntries: RefundVendorCreditGLEntries; + + /** + * Attaches events with handlers. + */ + attach(bus) { + bus.subscribe( + events.vendorCredit.onRefundCreated, + this.writeRefundVendorCreditGLEntriesOnceCreated + ); + bus.subscribe( + events.vendorCredit.onRefundDeleted, + this.revertRefundVendorCreditOnceDeleted + ); + } + + /** + * Writes refund vendor credit GL entries once the transaction created. + * @param {IRefundCreditNoteCreatedPayload} payload - + */ + private writeRefundVendorCreditGLEntriesOnceCreated = async ({ + tenantId, + trx, + refundVendorCredit, + }: IRefundVendorCreditCreatedPayload) => { + await this.refundVendorGLEntries.saveRefundCreditGLEntries( + tenantId, + refundVendorCredit.id, + trx + ); + }; + + /** + * Reverts refund vendor credit GL entries once the transaction deleted. + * @param {IRefundCreditNoteDeletedPayload} payload - + */ + private revertRefundVendorCreditOnceDeleted = async ({ + tenantId, + trx, + refundCreditId, + }: IRefundCreditNoteDeletedPayload) => { + await this.refundVendorGLEntries.revertRefundCreditGLEntries( + tenantId, + refundCreditId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditAutoSerialSubscriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditAutoSerialSubscriber.ts new file mode 100644 index 000000000..9e2070cff --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditAutoSerialSubscriber.ts @@ -0,0 +1,27 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import BaseVendorCredit from '../commands/VendorCreditDTOTransform.service'; +import { IVendorCreditCreatedPayload } from '@/interfaces'; + +@Service() +export default class VendorCreditAutoSerialSubscriber { + @Inject() + vendorCreditService: BaseVendorCredit; + + /** + * Attaches events with handlers. + */ + public attach(bus) { + bus.subscribe(events.vendorCredit.onCreated, this.autoIncrementOnceCreated); + } + + /** + * Auto serial increment once the vendor credit created. + * @param {IVendorCreditCreatedPayload} payload + */ + private autoIncrementOnceCreated = ({ + tenantId, + }: IVendorCreditCreatedPayload) => { + this.vendorCreditService.incrementSerialNumber(tenantId); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditGLEntriesSubscriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditGLEntriesSubscriber.ts new file mode 100644 index 000000000..250d27a53 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditGLEntriesSubscriber.ts @@ -0,0 +1,110 @@ +import { Service, Inject } from 'typedi'; +import events from '@/subscribers/events'; +import { + IVendorCreditCreatedPayload, + IVendorCreditDeletedPayload, + IVendorCreditEditedPayload, + IVendorCreditOpenedPayload, +} from '@/interfaces'; +import VendorCreditGLEntries from '../commands/VendorCreditGLEntries'; + +@Service() +export default class VendorCreditGlEntriesSubscriber { + @Inject() + private vendorCreditGLEntries: VendorCreditGLEntries; + + /*** + * Attaches events with handlers. + */ + public attach(bus) { + bus.subscribe( + events.vendorCredit.onCreated, + this.writeGLEntriesOnceVendorCreditCreated + ); + bus.subscribe( + events.vendorCredit.onOpened, + this.writeGLEntgriesOnceVendorCreditOpened + ); + bus.subscribe( + events.vendorCredit.onEdited, + this.editGLEntriesOnceVendorCreditEdited + ); + bus.subscribe( + events.vendorCredit.onDeleted, + this.revertGLEntriesOnceDeleted + ); + } + + /** + * Writes GL entries of vendor credit once the transaction created. + * @param {IVendorCreditCreatedPayload} payload - + */ + private writeGLEntriesOnceVendorCreditCreated = async ({ + tenantId, + vendorCredit, + trx, + }: IVendorCreditCreatedPayload): Promise => { + // Can't continue if the vendor credit is not open yet. + if (!vendorCredit.isPublished) return; + + await this.vendorCreditGLEntries.writeVendorCreditGLEntries( + tenantId, + vendorCredit.id, + trx + ); + }; + + /** + * Writes Gl entries of vendor credit once the transaction opened. + * @param {IVendorCreditOpenedPayload} payload - + */ + private writeGLEntgriesOnceVendorCreditOpened = async ({ + tenantId, + vendorCreditId, + trx, + }: IVendorCreditOpenedPayload) => { + await this.vendorCreditGLEntries.writeVendorCreditGLEntries( + tenantId, + vendorCreditId, + trx + ); + }; + + /** + * Edits associated GL entries once vendor credit edited. + * @param {IVendorCreditEditedPayload} payload + */ + private editGLEntriesOnceVendorCreditEdited = async ({ + tenantId, + vendorCreditId, + vendorCredit, + trx, + }: IVendorCreditEditedPayload) => { + // Can't continue if the vendor credit is not open yet. + if (!vendorCredit.isPublished) return; + + await this.vendorCreditGLEntries.rewriteVendorCreditGLEntries( + tenantId, + vendorCreditId, + trx + ); + }; + + /** + * Reverts the GL entries once vendor credit deleted. + * @param {IVendorCreditDeletedPayload} payload - + */ + private revertGLEntriesOnceDeleted = async ({ + vendorCreditId, + tenantId, + oldVendorCredit, + }: IVendorCreditDeletedPayload): Promise => { + // Can't continue of the vendor credit is not open yet. + if (!oldVendorCredit.isPublished) return; + + await this.vendorCreditGLEntries.revertVendorCreditGLEntries( + tenantId, + vendorCreditId + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts new file mode 100644 index 000000000..112868723 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts @@ -0,0 +1,93 @@ +import { Inject, Service } from 'typedi'; +import events from '@/subscribers/events'; +import { + IVendorCreditCreatedPayload, + IVendorCreditDeletedPayload, + IVendorCreditEditedPayload, +} from '@/interfaces'; +import VendorCreditInventoryTransactions from '../commands/VendorCreditInventoryTransactions'; + +@Service() +export default class VendorCreditInventoryTransactionsSubscriber { + @Inject() + private inventoryTransactions: VendorCreditInventoryTransactions; + + /** + * Attaches events with handlers. + * @param bus + */ + attach(bus) { + bus.subscribe( + events.vendorCredit.onCreated, + this.writeInventoryTransactionsOnceCreated + ); + bus.subscribe( + events.vendorCredit.onOpened, + this.writeInventoryTransactionsOnceCreated + ); + bus.subscribe( + events.vendorCredit.onEdited, + this.rewriteInventroyTransactionsOnceEdited + ); + bus.subscribe( + events.vendorCredit.onDeleted, + this.revertInventoryTransactionsOnceDeleted + ); + } + + /** + * Writes inventory transactions once vendor created created. + * @param {IVendorCreditCreatedPayload} payload - + */ + private writeInventoryTransactionsOnceCreated = async ({ + tenantId, + vendorCredit, + trx, + }: IVendorCreditCreatedPayload) => { + // Can't continue if vendor credit is not opened. + if (!vendorCredit.openedAt) return null; + + await this.inventoryTransactions.createInventoryTransactions( + tenantId, + vendorCredit, + trx + ); + }; + + /** + * Rewrites inventory transactions once vendor credit edited. + * @param {IVendorCreditEditedPayload} payload - + */ + private rewriteInventroyTransactionsOnceEdited = async ({ + tenantId, + vendorCreditId, + vendorCredit, + trx, + }: IVendorCreditEditedPayload) => { + // Can't continue if vendor credit is not opened. + if (!vendorCredit.openedAt) return null; + + await this.inventoryTransactions.editInventoryTransactions( + tenantId, + vendorCreditId, + vendorCredit, + trx + ); + }; + + /** + * Reverts inventory transactions once vendor credit deleted. + * @param {IVendorCreditDeletedPayload} payload - + */ + private revertInventoryTransactionsOnceDeleted = async ({ + tenantId, + vendorCreditId, + trx, + }: IVendorCreditDeletedPayload) => { + await this.inventoryTransactions.deleteInventoryTransactions( + tenantId, + vendorCreditId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts b/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts new file mode 100644 index 000000000..97805ec1f --- /dev/null +++ b/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts @@ -0,0 +1,229 @@ +import { DiscountType, IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces'; +import { Knex } from 'knex'; +import { VendorCredit } from '../models/VendorCredit'; + +export enum VendorCreditAction { + Create = 'Create', + Edit = 'Edit', + Delete = 'Delete', + View = 'View', + Refund = 'Refund', +} + + +export interface IVendorCreditEntryDTO extends IItemEntryDTO {} + +export interface IRefundVendorCredit { + id?: number | null; + date: Date; + referenceNo: string; + amount: number; + currencyCode: string; + exchangeRate: number; + depositAccountId: number; + description: string; + vendorCreditId: number; + createdAt: Date | null; + userId: number; + branchId?: number; + + vendorCredit?: VendorCredit +} + +export interface IVendorCreditDTO { + vendorId: number; + exchangeRate?: number; + vendorCreditNumber: string; + referenceNo: string; + vendorCreditDate: Date; + note: string; + open: boolean; + entries: IVendorCreditEntryDTO[]; + + branchId?: number; + warehouseId?: number; + attachments?: AttachmentLinkDTO[]; + + discount?: number; + discountType?: DiscountType; + + adjustment?: number; +} + +export interface IVendorCreditCreateDTO extends IVendorCreditDTO {} +export interface IVendorCreditEditDTO extends IVendorCreditDTO {} +export interface IVendorCreditCreatePayload { + // tenantId: number; + refundVendorCreditDTO: IRefundVendorCreditDTO; + vendorCreditId: number; +} + +export interface IVendorCreditCreatingPayload { + // tenantId: number; + vendorCredit: VendorCredit; + vendorCreditId: number; + vendorCreditCreateDTO: IVendorCreditCreateDTO; + trx: Knex.Transaction; +} + +export interface IVendorCreditCreatedPayload { + // tenantId: number; + vendorCredit: VendorCredit; + vendorCreditCreateDTO: IVendorCreditCreateDTO; + trx: Knex.Transaction; +} + +export interface IVendorCreditCreatedPayload {} +export interface IVendorCreditDeletedPayload { + trx: Knex.Transaction; + // tenantId: number; + vendorCreditId: number; + oldVendorCredit: VendorCredit; +} + +export interface IVendorCreditDeletingPayload { + trx: Knex.Transaction; + // tenantId: number; + oldVendorCredit: VendorCredit; +} + +export interface IVendorCreditsQueryDTO extends IDynamicListFilter { + page: number; + pageSize: number; + searchKeyword?: string; + filterQuery?: (q: any) => void; +} + +export interface IVendorCreditEditingPayload { + // tenantId: number; + oldVendorCredit: VendorCredit; + vendorCreditDTO: IVendorCreditEditDTO; + trx: Knex.Transaction; +} + +export interface IVendorCreditEditedPayload { + // tenantId: number; + oldVendorCredit: VendorCredit; + vendorCredit: VendorCredit; + vendorCreditId: number; + vendorCreditDTO: IVendorCreditEditDTO; + trx: Knex.Transaction; +} + +export interface IRefundVendorCreditDTO { + amount: number; + exchangeRate?: number; + depositAccountId: number; + description: string; + date: Date; + branchId?: number; +} + +export interface IRefundVendorCreditDeletedPayload { + trx: Knex.Transaction; + refundCreditId: number; + oldRefundCredit: IRefundVendorCredit; + // tenantId: number; +} + +export interface IRefundVendorCreditDeletePayload { + trx: Knex.Transaction; + refundCreditId: number; + oldRefundCredit: IRefundVendorCredit; + // tenantId: number; +} +export interface IRefundVendorCreditDeletingPayload { + trx: Knex.Transaction; + refundCreditId: number; + oldRefundCredit: IRefundVendorCredit; + // tenantId: number; +} + +export interface IRefundVendorCreditCreatingPayload { + trx: Knex.Transaction; + vendorCredit: VendorCredit; + refundVendorCreditDTO: IRefundVendorCreditDTO; + // tenantId: number; +} + +export interface IRefundVendorCreditCreatedPayload { + refundVendorCredit: IRefundVendorCredit; + vendorCredit: VendorCredit; + trx: Knex.Transaction; + // tenantId: number; +} +export interface IRefundVendorCreditPOJO {} + +export interface IApplyCreditToBillEntryDTO { + amount: number; + billId: number; +} + +export interface IApplyCreditToBillsDTO { + entries: IApplyCreditToBillEntryDTO[]; +} + +export interface IVendorCreditOpenedPayload { + // tenantId: number; + vendorCreditId: number; + vendorCredit: VendorCredit; + trx: Knex.Transaction; +} + +export interface IVendorCreditOpenPayload { + // tenantId: number; + vendorCreditId: number; + oldVendorCredit: VendorCredit; +} + +export interface IVendorCreditOpeningPayload { + // tenantId: number; + vendorCreditId: number; + oldVendorCredit: VendorCredit; + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToBillsCreatedPayload { + // tenantId: number; + vendorCredit: VendorCredit; + vendorCreditAppliedBills: IVendorCreditAppliedBill[]; + trx: Knex.Transaction; +} +export interface IVendorCreditApplyToBillsCreatingPayload { + trx: Knex.Transaction; +} +export interface IVendorCreditApplyToBillsCreatePayload { + trx: Knex.Transaction; +} +export interface IVendorCreditApplyToBillDeletedPayload { + // tenantId: number; + vendorCredit: VendorCredit; + oldCreditAppliedToBill: IVendorCreditAppliedBill; + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToInvoiceDTO { + amount: number; + billId: number; +} + +export interface IVendorCreditApplyToInvoicesDTO { + entries: IVendorCreditApplyToInvoiceDTO[]; +} + +export interface IVendorCreditApplyToInvoiceModel { + billId: number; + amount: number; + vendorCreditId: number; +} + +export interface IVendorCreditApplyToInvoicesModel { + entries: IVendorCreditApplyToInvoiceModel[]; + amount: number; +} + +export interface IVendorCreditAppliedBill { + billId: number; + amount: number; + vendorCreditId: number; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditApplyBills.module.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditApplyBills.module.ts new file mode 100644 index 000000000..a573817c5 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditApplyBills.module.ts @@ -0,0 +1,20 @@ + + +import { Module } from "@nestjs/common"; +import { ApplyVendorCreditSyncBillsService } from "./command/ApplyVendorCreditSyncBills.service"; +import { ApplyVendorCreditSyncInvoicedService } from "./command/ApplyVendorCreditSyncInvoiced.service"; +import { DeleteApplyVendorCreditToBillService } from "./command/DeleteApplyVendorCreditToBill.service"; +import { ApplyVendorCreditToBillsService } from "./command/ApplyVendorCreditToBills.service"; +import { GetAppliedBillsToVendorCreditService } from "./queries/GetAppliedBillsToVendorCredit.service"; + +@Module({ + imports: [ + ApplyVendorCreditSyncBillsService, + ApplyVendorCreditSyncInvoicedService, + ApplyVendorCreditToBillsService, + DeleteApplyVendorCreditToBillService, + GetAppliedBillsToVendorCreditService + ], + controllers: [], +}) +export class VendorCreditApplyBillsModule {} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditsApplyBills.constants.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditsApplyBills.constants.ts new file mode 100644 index 000000000..dd44b4c37 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/VendorCreditsApplyBills.constants.ts @@ -0,0 +1,12 @@ +export const ERRORS = { + VENDOR_CREDIT_NOT_FOUND: 'VENDOR_CREDIT_NOT_FOUND', + VENDOR_CREDIT_ALREADY_OPENED: 'VENDOR_CREDIT_ALREADY_OPENED', + VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT: + 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT', + VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND: + 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND', + BILLS_HAS_NO_REMAINING_AMOUNT: 'BILLS_HAS_NO_REMAINING_AMOUNT', + VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS: + 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS', + VENDOR_CREDIT_HAS_APPLIED_BILLS: 'VENDOR_CREDIT_HAS_APPLIED_BILLS', +}; diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts new file mode 100644 index 000000000..bf1a37622 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts @@ -0,0 +1,48 @@ +import Bluebird from 'bluebird'; +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { IVendorCreditAppliedBill } from '../types/VendorCreditApplyBills.types'; +import { Bill } from '@/modules/Bills/models/Bill'; + +@Injectable() +export class ApplyVendorCreditSyncBillsService { + constructor( + @Inject(Bill.name) + private readonly billModel: typeof Bill, + ) {} + + /** + * Increment bills credited amount. + * @param {IVendorCreditAppliedBill[]} vendorCreditAppliedBills + * @param {Knex.Transaction} trx + */ + public incrementBillsCreditedAmount = async ( + vendorCreditAppliedBills: IVendorCreditAppliedBill[], + trx?: Knex.Transaction, + ) => { + await Bluebird.each( + vendorCreditAppliedBills, + (vendorCreditAppliedBill: IVendorCreditAppliedBill) => { + return this.billModel + .query(trx) + .where('id', vendorCreditAppliedBill.billId) + .increment('creditedAmount', vendorCreditAppliedBill.amount); + }, + ); + }; + + /** + * Decrement bill credited amount. + * @param {IVendorCreditAppliedBill} vendorCreditAppliedBill + * @param {Knex.Transaction} trx + */ + public decrementBillCreditedAmount = async ( + vendorCreditAppliedBill: IVendorCreditAppliedBill, + trx?: Knex.Transaction, + ) => { + await this.billModel + .query(trx) + .findById(vendorCreditAppliedBill.billId) + .decrement('creditedAmount', vendorCreditAppliedBill.amount); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncInvoiced.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncInvoiced.service.ts new file mode 100644 index 000000000..8cab15c49 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncInvoiced.service.ts @@ -0,0 +1,43 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; + +@Injectable() +export class ApplyVendorCreditSyncInvoicedService { + constructor( + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Increment vendor credit invoiced amount. + * @param {number} vendorCreditId - Vendor credit id. + * @param {number} amount - Amount to increment. + * @param {Knex.Transaction} trx - Knex transaction. + */ + public incrementVendorCreditInvoicedAmount = async ( + vendorCreditId: number, + amount: number, + trx?: Knex.Transaction + ) => { + await this.vendorCreditModel.query(trx) + .findById(vendorCreditId) + .increment('invoicedAmount', amount); + }; + + /** + * Decrement credit note invoiced amount. + * @param {number} vendorCreditId - Vendor credit id. + * @param {number} amount - Amount to decrement. + * @param {Knex.Transaction} trx - Knex transaction. + */ + public decrementVendorCreditInvoicedAmount = async ( + vendorCreditId: number, + amount: number, + trx?: Knex.Transaction + ) => { + await this.vendorCreditModel.query(trx) + .findById(vendorCreditId) + .decrement('invoicedAmount', amount); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditToBills.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditToBills.service.ts new file mode 100644 index 000000000..959a1f25f --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditToBills.service.ts @@ -0,0 +1,129 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { sumBy } from 'lodash'; +import { + IVendorCreditApplyToBillsCreatedPayload, + IVendorCreditApplyToInvoicesDTO, + IVendorCreditApplyToInvoicesModel, +} from '../types/VendorCreditApplyBills.types'; +import { ERRORS } from '../VendorCreditsApplyBills.constants'; +import { VendorCreditAppliedBill } from '../models/VendorCreditAppliedBill'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; +import { BillPaymentValidators } from '@/modules/BillPayments/commands/BillPaymentValidators.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { Bill } from '@/modules/Bills/models/Bill'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class ApplyVendorCreditToBillsService { + /** + * @param {UnitOfWork} uow - The unit of work service. + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {BillPaymentValidators} billPaymentValidators - The bill payment validators service. + * @param {typeof VendorCreditAppliedBill} vendorCreditAppliedBillModel - The vendor credit applied bill model. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + */ + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + private readonly billPaymentValidators: BillPaymentValidators, + private readonly vendorCreditAppliedBillModel: typeof VendorCreditAppliedBill, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Apply credit note to the given invoices. + * @param {number} creditNoteId + * @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO + */ + public applyVendorCreditToBills = async ( + vendorCreditId: number, + applyCreditToBillsDTO: IVendorCreditApplyToInvoicesDTO, + ): Promise => { + // Retrieves the vendor credit or throw not found service error. + const vendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Transfomes credit apply to bills DTO to model object. + const vendorCreditAppliedModel = this.transformApplyDTOToModel( + applyCreditToBillsDTO, + vendorCredit, + ); + + // Validate bills entries existance. + const appliedBills = + await this.billPaymentValidators.validateBillsExistance( + vendorCreditAppliedModel.entries, + vendorCredit.vendorId, + ); + + // Validate bills has remaining amount to apply. + this.validateBillsRemainingAmount( + appliedBills, + vendorCreditAppliedModel.amount, + ); + // Validate vendor credit remaining credit amount. + this.validateCreditRemainingAmount( + vendorCredit, + vendorCreditAppliedModel.amount, + ); + + // Saves vendor credit applied to bills under unit-of-work envirement. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Inserts vendor credit applied to bills graph to the storage layer. + const vendorCreditAppliedBills = await this.vendorCreditAppliedBillModel + .query(trx) + .insertGraph(vendorCreditAppliedModel.entries); + + // Triggers `IVendorCreditApplyToBillsCreatedPayload` event. + await this.eventPublisher.emitAsync( + events.vendorCredit.onApplyToInvoicesCreated, + { + trx, + vendorCredit, + vendorCreditAppliedBills, + } as IVendorCreditApplyToBillsCreatedPayload, + ); + }); + }; + + /** + * Transformes apply DTO to model. + * @param {IApplyCreditToInvoicesDTO} applyDTO + * @param {ICreditNote} creditNote + * @returns {IVendorCreditApplyToInvoicesModel} + */ + private transformApplyDTOToModel = ( + applyDTO: IVendorCreditApplyToInvoicesDTO, + vendorCredit: VendorCredit, + ): IVendorCreditApplyToInvoicesModel => { + const entries = applyDTO.entries.map((entry) => ({ + billId: entry.billId, + amount: entry.amount, + vendorCreditId: vendorCredit.id, + })); + const amount = sumBy(applyDTO.entries, 'amount'); + + return { + amount, + entries, + }; + }; + + /** + * Validate bills remaining amount. + * @param {IBill[]} bills + * @param {number} amount + */ + private validateBillsRemainingAmount = (bills: Bill[], amount: number) => { + const invalidBills = bills.filter((bill) => bill.dueAmount < amount); + if (invalidBills.length > 0) { + throw new ServiceError(ERRORS.BILLS_HAS_NO_REMAINING_AMOUNT); + } + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/command/DeleteApplyVendorCreditToBill.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/DeleteApplyVendorCreditToBill.service.ts new file mode 100644 index 000000000..728030aff --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/command/DeleteApplyVendorCreditToBill.service.ts @@ -0,0 +1,66 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ServiceError } from '@/modules/Items/ServiceError'; +import { events } from '@/common/events/events'; +import { IVendorCreditApplyToBillDeletedPayload } from '../types/VendorCreditApplyBills.types'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; +import { ERRORS } from '../VendorCreditsApplyBills.constants'; +import { VendorCreditAppliedBill } from '../models/VendorCreditAppliedBill'; + +@Injectable() +export class DeleteApplyVendorCreditToBillService { + /** + * @param {UnitOfWork} uow - The unit of work service. + * @param {EventEmitter2} eventPublisher - The event emitter service. + * @param {typeof VendorCreditAppliedBill} vendorCreditAppliedBillModel - The vendor credit applied bill model. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + */ + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + private readonly vendorCreditAppliedBillModel: typeof VendorCreditAppliedBill, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Delete apply vendor credit to bill transaction. + * @param {number} appliedCreditToBillId + * @returns {Promise} + */ + public async deleteApplyVendorCreditToBills(appliedCreditToBillId: number) { + const oldCreditAppliedToBill = await this.vendorCreditAppliedBillModel + .query() + .findById(appliedCreditToBillId); + + if (!oldCreditAppliedToBill) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND); + } + // Retrieve the vendor credit or throw not found service error. + const vendorCredit = await this.vendorCreditModel + .query() + .findById(oldCreditAppliedToBill.vendorCreditId) + .throwIfNotFound(); + + // Deletes vendor credit apply under unit-of-work environment. + return this.uow.withTransaction(async (trx) => { + // Delete vendor credit applied to bill transaction. + await this.vendorCreditAppliedBillModel + .query(trx) + .findById(appliedCreditToBillId) + .delete(); + + // Triggers `onVendorCreditApplyToInvoiceDeleted` event. + await this.eventPublisher.emitAsync( + events.vendorCredit.onApplyToInvoicesDeleted, + { + vendorCredit, + oldCreditAppliedToBill, + trx, + } as IVendorCreditApplyToBillDeletedPayload, + ); + }); + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/models/VendorCreditAppliedBill.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill.ts similarity index 77% rename from packages/server-nest/src/modules/VendorCredit/models/VendorCreditAppliedBill.ts rename to packages/server-nest/src/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill.ts index a74895958..2a4ab9769 100644 --- a/packages/server-nest/src/modules/VendorCredit/models/VendorCreditAppliedBill.ts +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill.ts @@ -1,11 +1,20 @@ -import { mixin, Model } from 'objection'; +import { Model } from 'objection'; // import TenantModel from 'models/TenantModel'; // import ModelSetting from './ModelSetting'; // import CustomViewBaseModel from './CustomViewBaseModel'; // import ModelSearchable from './ModelSearchable'; import { BaseModel } from '@/models/Model'; +import { VendorCredit } from '../../VendorCredit/models/VendorCredit'; +import { Bill } from '@/modules/Bills/models/Bill'; export class VendorCreditAppliedBill extends BaseModel { + public amount!: number; + public billId!: number; + public vendorCreditId!: number; + + public vendorCredit!: VendorCredit; + public bill!: Bill; + /** * Table name */ @@ -16,7 +25,7 @@ export class VendorCreditAppliedBill extends BaseModel { /** * Timestamps columns. */ - get timestamps() { + public get timestamps() { return ['created_at', 'updated_at']; } diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetAppliedBillsToVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetAppliedBillsToVendorCredit.service.ts new file mode 100644 index 000000000..9083d15d1 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetAppliedBillsToVendorCredit.service.ts @@ -0,0 +1,40 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { VendorCreditAppliedBillTransformer } from './VendorCreditAppliedBillTransformer'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { VendorCreditAppliedBill } from '@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; + +@Injectable() +export class GetAppliedBillsToVendorCreditService { + constructor( + private readonly transformer: TransformerInjectable, + private readonly vendorCreditAppliedBillModel: typeof VendorCreditAppliedBill, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Get applied bills to vendor credit. + * @param {number} vendorCreditId + * @returns + */ + public getAppliedBills = async (vendorCreditId: number) => { + const vendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + const appliedToBills = await this.vendorCreditAppliedBillModel + .query() + .where('vendorCreditId', vendorCreditId) + .withGraphFetched('bill') + .withGraphFetched('vendorCredit'); + + // Transforms the models to POJO. + return this.transformer.transform( + appliedToBills, + new VendorCreditAppliedBillTransformer(), + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetVendorCreditToApplyBills.service.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetVendorCreditToApplyBills.service.ts new file mode 100644 index 000000000..c795d4f3c --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/GetVendorCreditToApplyBills.service.ts @@ -0,0 +1,47 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { VendorCreditToApplyBillTransformer } from './VendorCreditToApplyBillTransformer'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; +import { Bill } from '@/modules/Bills/models/Bill'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; + +@Injectable() +export class GetVendorCreditToApplyBills { + /** + * @param {TransformerService} transformerService - The transformer service. + * @param {typeof Bill} billModel - The bill model. + * @param {typeof VendorCredit} vendorCreditModel - The vendor credit model. + */ + constructor( + private readonly transformerService: TransformerInjectable, + @Inject(Bill.name) private readonly billModel: typeof Bill, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Retrieve bills that valid apply to the given vendor credit. + * @param {number} vendorCreditId + * @returns {Promise} + */ + public async getCreditToApplyBills(vendorCreditId: number) { + // Retrieve vendor credit or throw not found service error. + const vendorCredit = await this.vendorCreditModel + .query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Retrieve open bills associated to the given vendor. + const openBills = await this.billModel + .query() + .where('vendor_id', vendorCredit.vendorId) + .modify('dueBills') + .modify('published'); + + // Transform the bills to POJO. + return this.transformerService.transform( + openBills, + new VendorCreditToApplyBillTransformer(), + ); + } +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditAppliedBillTransformer.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditAppliedBillTransformer.ts new file mode 100644 index 000000000..a03fd9249 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditAppliedBillTransformer.ts @@ -0,0 +1,67 @@ +import { Transformer } from "@/modules/Transformer/Transformer"; +import { VendorCreditAppliedBill } from "@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill"; + +export class VendorCreditAppliedBillTransformer extends Transformer { + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return [ + 'formattedAmount', + 'vendorCreditNumber', + 'vendorCreditDate', + 'billNumber', + 'billReferenceNo', + 'formattedVendorCreditDate', + 'formattedBillDate', + ]; + }; + + /** + * Exclude attributes. + * @returns {string[]} + */ + public excludeAttributes = (): string[] => { + return ['bill', 'vendorCredit']; + }; + + /** + * + * @param item + * @returns + */ + protected formattedAmount = (item: VendorCreditAppliedBill) => { + return this.formatNumber(item.amount, { + currencyCode: item.vendorCredit.currencyCode, + }); + }; + + protected vendorCreditNumber = (item) => { + return item.vendorCredit.vendorCreditNumber; + }; + + protected vendorCreditDate = (item) => { + return item.vendorCredit.vendorCreditDate; + }; + + protected formattedVendorCreditDate = (item) => { + return this.formatDate(item.vendorCredit.vendorCreditDate); + }; + + protected billNumber = (item) => { + return item.bill.billNo; + }; + + protected billReferenceNo = (item) => { + return item.bill.referenceNo; + }; + + protected BillDate = (item) => { + return item.bill.billDate; + }; + + protected formattedBillDate = (item) => { + return this.formatDate(item.bill.billDate); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditToApplyBillTransformer.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditToApplyBillTransformer.ts new file mode 100644 index 000000000..834f5119a --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/queries/VendorCreditToApplyBillTransformer.ts @@ -0,0 +1,69 @@ +import { Bill } from '@/modules/Bills/models/Bill'; +import { Transformer } from '@/modules/Transformer/Transformer'; + +export class VendorCreditToApplyBillTransformer extends Transformer { + /** + * Include these attributes to sale invoice object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return [ + 'formattedBillDate', + 'formattedDueDate', + 'formattedAmount', + 'formattedDueAmount', + 'formattedPaymentAmount', + ]; + }; + + /** + * Retrieve formatted bill date. + * @param {Bill} bill + * @returns {String} + */ + protected formattedBillDate = (bill: Bill): string => { + return this.formatDate(bill.billDate); + }; + + /** + * Retrieve formatted due date. + * @param {Bill} bill + * @returns {string} + */ + protected formattedDueDate = (bill: Bill): string => { + return this.formatDate(bill.dueDate); + }; + + /** + * Retrieve formatted bill amount. + * @param {Bill} bill + * @returns {string} + */ + protected formattedAmount = (bill: Bill): string => { + return this.formatNumber(bill.amount, { + currencyCode: bill.currencyCode, + }); + }; + + /** + * Retrieve formatted bill due amount. + * @param {Bill} bill + * @returns {string} + */ + protected formattedDueAmount = (bill: Bill): string => { + return this.formatNumber(bill.dueAmount, { + currencyCode: bill.currencyCode, + }); + }; + + /** + * Retrieve formatted payment amount. + * @param {Bill} bill + * @returns {string} + */ + protected formattedPaymentAmount = (bill: Bill): string => { + return this.formatNumber(bill.paymentAmount, { + currencyCode: bill.currencyCode, + }); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncBillsSubscriber.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncBillsSubscriber.ts new file mode 100644 index 000000000..e13a588ba --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncBillsSubscriber.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/objection'; +import events from '@/subscribers/events'; +import { + IVendorCreditApplyToBillDeletedPayload, + IVendorCreditApplyToBillsCreatedPayload, +} from '@/interfaces'; +import { ApplyVendorCreditSyncBillsService } from '../command/ApplyVendorCreditSyncBills.service'; +import { VendorCreditApplyToBill } from '../models/VendorCreditApplyToBill'; + +@Injectable() +export default class ApplyVendorCreditSyncBillsSubscriber { + constructor( + private readonly syncBillsWithVendorCredit: ApplyVendorCreditSyncBillsService, + @InjectModel(VendorCreditApplyToBill) + private readonly vendorCreditApplyToBillModel: typeof VendorCreditApplyToBill, + ) {} + + /** + * Attaches events with handlers. + */ + attach(bus) { + bus.subscribe( + events.vendorCredit.onApplyToInvoicesCreated, + this.incrementAppliedBillsOnceCreditCreated + ); + bus.subscribe( + events.vendorCredit.onApplyToInvoicesDeleted, + this.decrementAppliedBillsOnceCreditDeleted + ); + } + + /** + * Increment credited amount of applied bills once the vendor credit transaction created. + * @param {IVendorCreditApplyToBillsCreatedPayload} paylaod - + */ + private incrementAppliedBillsOnceCreditCreated = async ({ + vendorCreditAppliedBills, + trx, + }: IVendorCreditApplyToBillsCreatedPayload) => { + await this.syncBillsWithVendorCredit.incrementBillsCreditedAmount( + vendorCreditAppliedBills, + trx + ); + }; + + /** + * Decrement credited amount of applied bills once the vendor credit + * transaction delted. + * @param {IVendorCreditApplyToBillDeletedPayload} payload + */ + private decrementAppliedBillsOnceCreditDeleted = async ({ + oldCreditAppliedToBill, + trx, + }: IVendorCreditApplyToBillDeletedPayload) => { + await this.syncBillsWithVendorCredit.decrementBillCreditedAmount( + oldCreditAppliedToBill, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncInvoicedSubscriber.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncInvoicedSubscriber.ts new file mode 100644 index 000000000..2936f864a --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/subscribers/ApplyVendorCreditSyncInvoicedSubscriber.ts @@ -0,0 +1,70 @@ +import { Service, Inject } from 'typedi'; +import { sumBy } from 'lodash'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import ApplyVendorCreditSyncInvoiced from '../command/ApplyVendorCreditSyncInvoiced.service'; +import events from '@/subscribers/events'; +import { + IVendorCreditApplyToBillDeletedPayload, + IVendorCreditApplyToBillsCreatedPayload, +} from '@/interfaces'; + +@Service() +export default class ApplyVendorCreditSyncInvoicedSubscriber { + @Inject() + tenancy: HasTenancyService; + + @Inject() + syncCreditWithInvoiced: ApplyVendorCreditSyncInvoiced; + + /** + * Attaches events with handlers. + */ + attach(bus) { + bus.subscribe( + events.vendorCredit.onApplyToInvoicesCreated, + this.incrementBillInvoicedOnceCreditApplied + ); + bus.subscribe( + events.vendorCredit.onApplyToInvoicesDeleted, + this.decrementBillInvoicedOnceCreditApplyDeleted + ); + } + + /** + * Increment vendor credit invoiced amount once the apply transaction created. + * @param {IVendorCreditApplyToBillsCreatedPayload} payload - + */ + private incrementBillInvoicedOnceCreditApplied = async ({ + vendorCredit, + tenantId, + vendorCreditAppliedBills, + trx, + }: IVendorCreditApplyToBillsCreatedPayload) => { + const amount = sumBy(vendorCreditAppliedBills, 'amount'); + + await this.syncCreditWithInvoiced.incrementVendorCreditInvoicedAmount( + tenantId, + vendorCredit.id, + amount, + trx + ); + }; + + /** + * Decrement vendor credit invoiced amount once the apply transaction deleted. + * @param {IVendorCreditApplyToBillDeletedPayload} payload - + */ + private decrementBillInvoicedOnceCreditApplyDeleted = async ({ + tenantId, + vendorCredit, + oldCreditAppliedToBill, + trx, + }: IVendorCreditApplyToBillDeletedPayload) => { + await this.syncCreditWithInvoiced.decrementVendorCreditInvoicedAmount( + tenantId, + oldCreditAppliedToBill.vendorCreditId, + oldCreditAppliedToBill.amount, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsApplyBills/types/VendorCreditApplyBills.types.ts b/packages/server-nest/src/modules/VendorCreditsApplyBills/types/VendorCreditApplyBills.types.ts new file mode 100644 index 000000000..aed7e04f7 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsApplyBills/types/VendorCreditApplyBills.types.ts @@ -0,0 +1,48 @@ +import { Knex } from 'knex'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; + +export interface IVendorCreditApplyToBillsCreatedPayload { + vendorCredit: VendorCredit; + vendorCreditAppliedBills: IVendorCreditAppliedBill[]; + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToBillsCreatingPayload { + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToBillsCreatePayload { + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToBillDeletedPayload { + vendorCredit: VendorCredit; + oldCreditAppliedToBill: IVendorCreditAppliedBill; + trx: Knex.Transaction; +} + +export interface IVendorCreditApplyToInvoiceDTO { + amount: number; + billId: number; +} + +export interface IVendorCreditApplyToInvoicesDTO { + entries: IVendorCreditApplyToInvoiceDTO[]; +} + +export interface IVendorCreditApplyToInvoiceModel { + billId: number; + amount: number; + vendorCreditId: number; +} + +export interface IVendorCreditApplyToInvoicesModel { + entries: IVendorCreditApplyToInvoiceModel[]; + amount: number; +} + +export interface IVendorCreditAppliedBill { + billId: number; + amount: number; + vendorCreditId: number; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/RefundCreditSyncBills.ts b/packages/server-nest/src/modules/VendorCreditsRefund/RefundCreditSyncBills.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/CreateRefundVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/CreateRefundVendorCredit.service.ts new file mode 100644 index 000000000..a07a74e1a --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/CreateRefundVendorCredit.service.ts @@ -0,0 +1,122 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import * as R from 'ramda'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { + IRefundVendorCredit, + IRefundVendorCreditCreatedPayload, + IRefundVendorCreditCreatingPayload, + IRefundVendorCreditDTO, + IVendorCreditCreatePayload, +} from '@/modules/VendorCredit/types/VendorCredit.types'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { VendorCredit } from '../../models/VendorCredit'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { RefundVendorCredit } from '../../models/RefundVendorCredit'; +import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; +import { events } from '@/common/events/events'; + +@Injectable() +export class CreateRefundVendorCredit { + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + private readonly branchDTOTransform: BranchTransactionDTOTransformer, + + @Inject(RefundVendorCredit.name) + private readonly refundVendorCreditModel: typeof RefundVendorCredit, + + @Inject(Account.name) + private readonly accountModel: typeof Account, + + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) { + } + + /** + * Creates a refund vendor credit. + * @param {number} vendorCreditId + * @param {IRefundVendorCreditDTO} refundVendorCreditDTO + * @returns {Promise} + */ + public createRefund = async ( + vendorCreditId: number, + refundVendorCreditDTO: IRefundVendorCreditDTO + ): Promise => { + // Retrieve the vendor credit or throw not found service error. + const vendorCredit = await this.vendorCreditModel.query() + .findById(vendorCreditId) + .throwIfNotFound(); + + // Retrieve the deposit account or throw not found service error. + const depositAccount = await this.accountModel.query() + .findById(refundVendorCreditDTO.depositAccountId) + .throwIfNotFound(); + + // Validate vendor credit has remaining credit. + this.validateVendorCreditRemainingCredit( + vendorCredit, + refundVendorCreditDTO.amount + ); + // Validate refund deposit account type. + this.validateRefundDepositAccountType(depositAccount); + + // Triggers `onVendorCreditRefundCreate` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onRefundCreate, { + vendorCreditId, + refundVendorCreditDTO, + } as IVendorCreditCreatePayload); + + const refundCreditObj = this.transformDTOToModel( + vendorCredit, + refundVendorCreditDTO + ); + // Saves refund vendor credit with associated transactions. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + const eventPayload = { + vendorCredit, + trx, + refundVendorCreditDTO, + } as IRefundVendorCreditCreatingPayload; + + // Triggers `onVendorCreditRefundCreating` event. + await this.eventPublisher.emitAsync( + events.vendorCredit.onRefundCreating, + eventPayload as IRefundVendorCreditCreatingPayload + ); + // Inserts refund vendor credit to the storage layer. + const refundVendorCredit = + await this.refundVendorCreditModel.query().insertAndFetch(refundCreditObj); + + // Triggers `onVendorCreditCreated` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onRefundCreated, { + ...eventPayload, + refundVendorCredit, + } as IRefundVendorCreditCreatedPayload); + + return refundVendorCredit; + }); + }; + + /** + * Transformes the refund DTO to refund vendor credit model. + * @param {IVendorCredit} vendorCredit - + * @param {IRefundVendorCreditDTO} vendorCreditDTO + * @returns {IRefundVendorCredit} + */ + public transformDTOToModel = ( + vendorCredit: VendorCredit, + vendorCreditDTO: IRefundVendorCreditDTO + ) => { + const initialDTO = { + vendorCreditId: vendorCredit.id, + ...vendorCreditDTO, + currencyCode: vendorCredit.currencyCode, + exchangeRate: vendorCreditDTO.exchangeRate || 1, + }; + return R.compose(this.branchDTOTransform.transformDTO())( + initialDTO + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/DeleteRefundVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/DeleteRefundVendorCredit.service.ts new file mode 100644 index 000000000..769825279 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/DeleteRefundVendorCredit.service.ts @@ -0,0 +1,74 @@ +import { Knex } from 'knex'; +import { + IRefundVendorCreditDeletedPayload, + IRefundVendorCreditDeletePayload, + IRefundVendorCreditDeletingPayload, +} from '@/modules/VendorCredit/types/VendorCredit.types'; +import { Inject, Injectable } from '@nestjs/common'; +import { RefundVendorCredit } from '../../models/RefundVendorCredit'; +import { RefundVendorCreditService } from './RefundVendorCredit.service'; +import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; + +@Injectable() +export class DeleteRefundVendorCreditService { + /** + * @param {UnitOfWork} uow - Unit of work service. + * @param {EventEmitter2} eventPublisher - Event emitter service. + * @param {typeof RefundVendorCredit} refundVendorCreditModel - Refund vendor credit model. + */ + constructor( + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + + @Inject(RefundVendorCredit.name) + private readonly refundVendorCreditModel: typeof RefundVendorCredit, + ) { + } + + /** + * Deletes the refund vendor credit. + * @param {number} refundCreditId - Refund credit id. + * @returns {Promise} + */ + public async deleteRefundVendorCreditRefund( + refundCreditId: number + ): Promise { + // Retrieve the old credit note or throw not found service error. + const oldRefundCredit = await this.getRefundVendorCreditOrThrowError( + refundCreditId + ); + // Triggers `onVendorCreditRefundDelete` event. + await this.eventPublisher.emitAsync(events.vendorCredit.onRefundDelete, { + refundCreditId, + oldRefundCredit, + } as IRefundVendorCreditDeletePayload); + + // Deletes the refund vendor credit under unit-of-work environment. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + const eventPayload = { + trx, + refundCreditId, + oldRefundCredit, + } as IRefundVendorCreditDeletingPayload; + + // Triggers `onVendorCreditRefundDeleting` event. + await this.eventPublisher.emitAsync( + events.vendorCredit.onRefundDeleting, + eventPayload + ); + // Deletes the refund vendor credit graph from the storage. + await this.refundVendorCreditModel + .query(trx) + .findById(refundCreditId) + .delete(); + + // Triggers `onVendorCreditRefundDeleted` event. + await this.eventPublisher.emitAsync( + events.vendorCredit.onRefundDeleted, + eventPayload as IRefundVendorCreditDeletedPayload + ); + }); + } +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncCreditRefundedAmount.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncCreditRefundedAmount.service.ts new file mode 100644 index 000000000..6618b358b --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncCreditRefundedAmount.service.ts @@ -0,0 +1,48 @@ +import { Transaction } from 'objection'; +import { Inject, Injectable } from '@nestjs/common'; +import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit'; + +@Injectable() +export class RefundSyncCreditRefundedAmount { + /** + * @param {typeof VendorCredit} vendorCreditModel - Vendor credit model. + */ + constructor( + @Inject(VendorCredit.name) + private vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Increment vendor credit refunded amount. + * @param {number} vendorCreditId - Vendor credit id. + * @param {number} amount - Amount. + * @param {Transaction} trx - Objection transaction. + */ + public incrementCreditRefundedAmount = async ( + vendorCreditId: number, + amount: number, + trx?: Transaction, + ): Promise => { + await this.vendorCreditModel + .query(trx) + .findById(vendorCreditId) + .increment('refundedAmount', amount); + }; + + /** + * Decrement vendor credit refunded amount. + * @param {number} vendorCreditId - Vendor credit id. + * @param {number} amount - Amount. + * @param {Transaction} trx - Objection transaction. + */ + public decrementCreditNoteRefundAmount = async ( + vendorCreditId: number, + amount: number, + trx?: Transaction, + ): Promise => { + await this.vendorCreditModel + .query(trx) + .findById(vendorCreditId) + .decrement('refundedAmount', amount); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncVendorCreditBalance.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncVendorCreditBalance.service.ts new file mode 100644 index 000000000..d3a12110f --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundSyncVendorCreditBalance.service.ts @@ -0,0 +1,40 @@ +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { IRefundVendorCredit } from '../../types/VendorCredit.types'; +import { VendorCredit } from '../../models/VendorCredit'; + +@Injectable() +export class RefundSyncVendorCreditBalance { + constructor( + @Inject(VendorCredit.name) + private readonly vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Increment vendor credit refunded amount. + * @param {IRefundVendorCredit} refundCreditNote - + * @param {Knex.Transaction} trx - + */ + public async incrementVendorCreditRefundAmount( + refundVendorCredit: IRefundVendorCredit, + trx?: Knex.Transaction + ): Promise { + await this.vendorCreditModel + .query(trx) + .increment('refundedAmount', refundVendorCredit.amount); + } + + /** + * Decrement vendor credit refunded amount. + * @param {IRefundVendorCredit} refundCreditNote + * @param {Knex.Transaction} trx + */ + public async decrementVendorCreditRefundAmount( + refundVendorCredit: IRefundVendorCredit, + trx?: Knex.Transaction + ): Promise { + await this.vendorCreditModel + .query(trx) + .decrement('refundedAmount', refundVendorCredit.amount); + } +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCredit.service.ts new file mode 100644 index 000000000..70235fc24 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCredit.service.ts @@ -0,0 +1,63 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ERRORS } from '../constants'; +import { RefundVendorCredit } from '../../models/RefundVendorCredit'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { VendorCredit } from '../../models/VendorCredit'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class RefundVendorCreditService { + constructor( + @Inject(RefundVendorCredit.name) + private refundVendorCreditModel: typeof RefundVendorCredit, + + @Inject(Account.name) + private accountModel: typeof Account, + + @Inject(VendorCredit.name) + private vendorCreditModel: typeof VendorCredit, + ) {} + + /** + * Retrieve the vendor credit refund or throw not found service error. + * @param {number} refundVendorCreditId + * @returns {Promise { + const refundCredit = await this.refundVendorCreditModel + .query() + .findById(refundVendorCreditId); + if (!refundCredit) { + throw new ServiceError(ERRORS.REFUND_VENDOR_CREDIT_NOT_FOUND); + } + return refundCredit; + }; + + /** + * Validate the deposit refund account type. + * @param {Account} account + */ + public validateRefundDepositAccountType = (account: Account): void => { + const supportedTypes = ['bank', 'cash', 'fixed-asset']; + + if (supportedTypes.indexOf(account.accountType) === -1) { + throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_INVALID_TYPE); + } + }; + + /** + * Validate vendor credit has remaining credits. + * @param {VendorCredit} vendorCredit + * @param {number} amount + */ + public validateVendorCreditRemainingCredit = ( + vendorCredit: VendorCredit, + amount: number, + ) => { + if (vendorCredit.creditsRemaining < amount) { + throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING); + } + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditGLEntries.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditGLEntries.ts new file mode 100644 index 000000000..c9786c790 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditGLEntries.ts @@ -0,0 +1,159 @@ +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; +// import { AccountNormal, ILedgerEntry } from '@/interfaces'; +// import { IRefundVendorCredit } from '@/interfaces'; +// import JournalPosterService from '@/services/Sales/JournalPosterService'; +// import LedgerRepository from '@/services/Ledger/LedgerRepository'; +// import HasTenancyService from '@/services/Tenancy/TenancyService'; + +// @Service() +// export default class RefundVendorCreditGLEntries { +// @Inject() +// private journalService: JournalPosterService; + +// @Inject() +// private ledgerRepository: LedgerRepository; + +// @Inject() +// private tenancy: HasTenancyService; + +// /** +// * Retrieves the refund credit common GL entry. +// * @param {IRefundVendorCredit} refundCredit +// */ +// private getRefundCreditGLCommonEntry = ( +// refundCredit: IRefundVendorCredit +// ) => { +// return { +// exchangeRate: refundCredit.exchangeRate, +// currencyCode: refundCredit.currencyCode, + +// transactionType: 'RefundVendorCredit', +// transactionId: refundCredit.id, + +// date: refundCredit.date, +// userId: refundCredit.userId, +// referenceNumber: refundCredit.referenceNo, +// createdAt: refundCredit.createdAt, +// indexGroup: 10, + +// credit: 0, +// debit: 0, + +// note: refundCredit.description, +// branchId: refundCredit.branchId, +// }; +// }; + +// /** +// * Retrieves the refund credit payable GL entry. +// * @param {IRefundVendorCredit} refundCredit +// * @param {number} APAccountId +// * @returns {ILedgerEntry} +// */ +// private getRefundCreditGLPayableEntry = ( +// refundCredit: IRefundVendorCredit, +// APAccountId: number +// ): ILedgerEntry => { +// const commonEntry = this.getRefundCreditGLCommonEntry(refundCredit); + +// return { +// ...commonEntry, +// credit: refundCredit.amount, +// accountId: APAccountId, +// contactId: refundCredit.vendorCredit.vendorId, +// index: 1, +// accountNormal: AccountNormal.CREDIT, +// }; +// }; + +// /** +// * Retrieves the refund credit deposit GL entry. +// * @param {IRefundVendorCredit} refundCredit +// * @returns {ILedgerEntry} +// */ +// private getRefundCreditGLDepositEntry = ( +// refundCredit: IRefundVendorCredit +// ): ILedgerEntry => { +// const commonEntry = this.getRefundCreditGLCommonEntry(refundCredit); + +// return { +// ...commonEntry, +// debit: refundCredit.amount, +// accountId: refundCredit.depositAccountId, +// index: 2, +// accountNormal: AccountNormal.DEBIT, +// }; +// }; + +// /** +// * Retrieve refund vendor credit GL entries. +// * @param {IRefundVendorCredit} refundCredit +// * @param {number} APAccountId +// * @returns {ILedgerEntry[]} +// */ +// public getRefundCreditGLEntries = ( +// refundCredit: IRefundVendorCredit, +// APAccountId: number +// ): ILedgerEntry[] => { +// const payableEntry = this.getRefundCreditGLPayableEntry( +// refundCredit, +// APAccountId +// ); +// const depositEntry = this.getRefundCreditGLDepositEntry(refundCredit); + +// return [payableEntry, depositEntry]; +// }; + +// /** +// * Saves refund credit note GL entries. +// * @param {number} tenantId +// * @param {IRefundVendorCredit} refundCredit - +// * @param {Knex.Transaction} trx - +// * @return {Promise} +// */ +// public saveRefundCreditGLEntries = async ( +// tenantId: number, +// refundCreditId: number, +// trx?: Knex.Transaction +// ): Promise => { +// const { Account, RefundVendorCredit } = this.tenancy.models(tenantId); + +// // Retireve refund with associated vendor credit entity. +// const refundCredit = await RefundVendorCredit.query() +// .findById(refundCreditId) +// .withGraphFetched('vendorCredit'); + +// const payableAccount = await Account.query().findOne( +// 'slug', +// 'accounts-payable' +// ); +// // Generates the GL entries of the given refund credit. +// const entries = this.getRefundCreditGLEntries( +// refundCredit, +// payableAccount.id +// ); +// // Saves the ledegr to the storage. +// await this.ledgerRepository.saveLedgerEntries(tenantId, entries, trx); +// }; + +// /** +// * Reverts refund credit note GL entries. +// * @param {number} tenantId +// * @param {number} refundCreditId +// * @param {Knex.Transaction} trx +// * @return {Promise} +// */ +// public revertRefundCreditGLEntries = async ( +// tenantId: number, +// refundCreditId: number, +// trx?: Knex.Transaction +// ) => { +// await this.journalService.revertJournalTransactions( +// tenantId, +// refundCreditId, +// 'RefundVendorCredit', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditTransformer.ts b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditTransformer.ts new file mode 100644 index 000000000..1c211eba6 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/commands/RefundVendorCreditTransformer.ts @@ -0,0 +1,29 @@ +import { Transformer } from "@/modules/Transformer/Transformer"; + +export class RefundVendorCreditTransformer extends Transformer { + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['formttedAmount', 'formattedDate']; + }; + + /** + * Formatted amount. + * @returns {string} + */ + protected formttedAmount = (item) => { + return this.formatNumber(item.amount, { + currencyCode: item.currencyCode, + }); + }; + + /** + * Formatted date. + * @returns {string} + */ + protected formattedDate = (item) => { + return this.formatDate(item.date); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/constants.ts b/packages/server-nest/src/modules/VendorCreditsRefund/constants.ts new file mode 100644 index 000000000..21f355ed3 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/constants.ts @@ -0,0 +1,5 @@ +export const ERRORS = { + REFUND_VENDOR_CREDIT_NOT_FOUND: 'REFUND_VENDOR_CREDIT_NOT_FOUND', + DEPOSIT_ACCOUNT_INVALID_TYPE: 'DEPOSIT_ACCOUNT_INVALID_TYPE', + VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING: 'VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING' +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredit.service.ts new file mode 100644 index 000000000..ed808d1b3 --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredit.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { RefundVendorCreditTransformer } from '../commands/RefundVendorCreditTransformer'; +import { RefundVendorCredit, RefundVendorCredit as RefundVendorCreditModel } from '../../models/RefundVendorCredit'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; + +@Injectable() +export class GetRefundVendorCreditService { + /** + * @param {TransformerInjectable} transformer - Transformer injectable service. + * @param {typeof RefundVendorCreditModel} refundVendorCreditModel - Refund vendor credit model. + */ + constructor( + private readonly transformer: TransformerInjectable, + private readonly refundVendorCreditModel: typeof RefundVendorCreditModel, + ) { + } + + /** + * Retrieve refund vendor credit transaction. + * @param {number} refundId + * @returns {Promise} + */ + public getRefundCreditTransaction = async ( + refundId: number, + ): Promise => { + await this.refundVendorCreditModel + .query() + .findById(refundId) + .throwIfNotFound(); + + // Retrieve refund transactions associated to the given vendor credit. + const refundVendorTransactions = await this.refundVendorCreditModel + .query() + .findById(refundId) + .withGraphFetched('vendorCredit') + .withGraphFetched('depositAccount'); + + // Transforms refund vendor credit models to POJO objects. + return this.transformer.transform( + refundVendorTransactions, + new RefundVendorCreditTransformer(), + ); + }; +} diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredits.service.ts b/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredits.service.ts new file mode 100644 index 000000000..77c4751fa --- /dev/null +++ b/packages/server-nest/src/modules/VendorCreditsRefund/queries/GetRefundVendorCredits.service.ts @@ -0,0 +1,39 @@ +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { RefundVendorCreditTransformer } from '../commands/RefundVendorCreditTransformer'; +import { Injectable } from '@nestjs/common'; +import { RefundVendorCredit } from '../../models/RefundVendorCredit'; +import { IRefundVendorCreditPOJO } from '../../types/VendorCredit.types'; + +@Injectable() +export class GetRefundVendorCreditsService { + /** + * @param {TransformerInjectable} transformer - Transformer injectable service. + * @param {typeof RefundVendorCredit} refundVendorCreditModel - Refund vendor credit model. + */ + constructor( + private readonly transformer: TransformerInjectable, + private readonly refundVendorCreditModel: typeof RefundVendorCredit, + ) {} + + /** + * Retrieve the refund vendor credit graph. + * @param {number} vendorCreditId - Vendor credit id. + * @returns {Promise} + */ + public getVendorCreditRefunds = async ( + vendorCreditId: number, + ): Promise => { + // Retrieve refund transactions associated to the given vendor credit. + const refundVendorTransactions = await this.refundVendorCreditModel + .query() + .where('vendorCreditId', vendorCreditId) + .withGraphFetched('vendorCredit') + .withGraphFetched('depositAccount'); + + // Transformes refund vendor credit models to POJO objects. + return this.transformer.transform( + refundVendorTransactions, + new RefundVendorCreditTransformer(), + ); + }; +}