diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java index 25cf20bffd9..ffe8e9400cc 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java @@ -80,6 +80,7 @@ import org.apache.fineract.client.models.PostWorkingCapitalLoanTransactionsPaymentDetailRequest; import org.apache.fineract.client.models.PostWorkingCapitalLoanTransactionsRequest; import org.apache.fineract.client.models.PostWorkingCapitalLoanTransactionsResponse; +import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdDisbursementPaymentDetails; import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdRequest; import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdResponse; import org.apache.fineract.client.models.PostWorkingCapitalLoansRequest; @@ -1399,6 +1400,23 @@ public void disburseWCLoan(String actualDisbursementDate, String transactionAmou checkChangesExpectedStatus(TestContextKey.LOAN_DISBURSE_RESPONSE, ACTIVE); } + @And("Admin successfully disburse the Working Capital loan on {string} with {string} EUR transaction amount and {string} payment type") + public void disburseWCLoanWithPaymentType(final String actualDisbursementDate, final String transactionAmount, + final String paymentTypeName) { + final long paymentTypeId = paymentTypeResolver.resolve(DefaultPaymentType.valueOf(paymentTypeName)); + final PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory + .defaultWorkingCapitalLoanDisburseRequest() // + .actualDisbursementDate(actualDisbursementDate) // + .transactionAmount(new BigDecimal(transactionAmount)) // + .paymentDetails( + new PostWorkingCapitalLoansLoanIdDisbursementPaymentDetails().paymentTypeId(Math.toIntExact(paymentTypeId))); + testContext().set(TestContextKey.LOAN_DISBURSE_REQUEST, disburseRequest); + + executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, false); + verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement"); + checkChangesExpectedStatus(TestContextKey.LOAN_DISBURSE_RESPONSE, ACTIVE); + } + @And("Admin successfully disburse the Working Capital loan by externalId on {string} with {string} EUR transaction amount") public void disburseWCLoanByExternalId(String actualDisbursementDate, String transactionAmount) { PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest() diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature index 999b4afe405..f4e8818a1c1 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanAccount.feature @@ -1353,7 +1353,7 @@ Feature: WorkingCapitalLoanAccount And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount and "1000" discount amount And Working capital loan account has the correct data: | product.name | submittedOnDate | expectedDisbursementDate | status | proposedPrincipal | approvedPrincipal | totalPaymentVolume | periodPaymentRate | discount | - | WCLP_ADVANCED_ACCOUNTING | 2026-01-01 | 2026-01-01 | Active | 9000.0 | 9000.0 | 100000.0 | 18.0 | 1000.0 | + | WCLP_ADVANCED_ACCOUNTING | 2026-01-01 | 2026-01-01 | Active | 9000.0 | 9000.0 | 100000.0 | 18.0 | 1000.0 | Then Admin update Working Capital period payment rate failed with "0.01" value cause unable to calculate EIR And Working capital loan account has the correct data: | product.name | submittedOnDate | expectedDisbursementDate | status | proposedPrincipal | approvedPrincipal | totalPaymentVolume | periodPaymentRate | discount | @@ -1378,3 +1378,167 @@ Feature: WorkingCapitalLoanAccount Then Working capital loan account has the correct data: | product.name | submittedOnDate | expectedDisbursementDate | status | proposedPrincipal | approvedPrincipal | totalPaymentVolume | periodPaymentRate | discountProposed | | WCLP_DISCOUNT | 2026-01-01 | 2026-01-01 | Submitted and pending approval | 9000.0 | 0.0 | 100000.0 | 18.0 | 0.0 | + + @TestRailId:C106685 + Scenario: Verify journal entries are not created for Disbursement & undo when accounting is disabled + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data and creates-approves-disburses a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin stores the last Working Capital loan disbursement transaction id for later reference + And Working capital loan account has the correct data: + | status | principal | totalDiscountFee | unrealizedIncome | + | Active | 9000.0 | 0.0 | 0.0 | + + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + + And Admin sets the business date to "05 January 2026" + And Admin runs inline COB job for Working Capital Loan + + And Admin successfully undo Working Capital disbursal + + Then Working Capital Loan Transactions tab has a reversed "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + + @TestRailId:C106686 + Scenario: Verify journal entries created for Disbursement & undo when accounting is enabled + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 1000 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Working capital loan approval was successful + When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount and "1000" discount amount + Then Working Capital Loan has transactions: + | transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed | + | 01 January 2026 | Disbursement | 9000.0 | 9000.0 | 0.0 | 0.0 | false | + | 01 January 2026 | Discount Fee | 1000.0 | 1000.0 | 0.0 | 0.0 | false | + And Working capital loan account has the correct data: + | status | principal | discount | totalDiscountFee | unrealizedIncome | + | Active | 10000.0 | 1000.0 | 1000.0 | 1000.0 | + + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + + And Admin sets the business date to "03 January 2026" + And Admin runs inline COB job for Working Capital Loan + And Admin successfully undo Working Capital disbursal + + Then Working Capital Loan Transactions tab has a reversed "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + | ASSET | 112601 | Loans Receivable | | 9000.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 9000.0 | | + + @TestRailId:C106687 + Scenario: Verify Working Capital loan disbursement GL entries - UC1: ACC_DEF_REV_AM, loan portfolio and fund source net to zero after full repayment + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ACC_DEF_REV_AM | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + When Admin sets the business date to "02 January 2026" + And Customer makes repayment on "02 January 2026" with 50.0 transaction amount on Working Capital loan + Then Working Capital Loan Transactions tab has a "REPAYMENT" transaction with date "02 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + | ASSET | 112601 | Loans Receivable | | 50.0 | + When Admin sets the business date to "03 January 2026" + And Customer makes repayment on "03 January 2026" with 8950.0 transaction amount on Working Capital loan + Then Working Capital loan status will be "CLOSED_OBLIGATIONS_MET" +# 9000 Dr - 50 Cr - 8950 Cr = 0.00 on 112601; 9000 Cr - 50 Dr - 8950 Dr = 0.00 on 145023 + And Working Capital Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | LIABILITY | 145023 | Suspense/Clearing account | 8950.0 | | + | ASSET | 112601 | Loans Receivable | | 8950.0 | + + @TestRailId:C106688 + Scenario: Verify Working Capital loan disbursement GL entries - UC2: Advanced Accounting, payment channel fund source mapping, undo disbursal on a later date + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount and "MONEY_TRANSFER" payment type + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | ASSET | 987654 | Fund Receivables | | 9000.0 | + When Admin sets the business date to "05 January 2026" + And Admin successfully undo Working Capital disbursal + Then Working Capital Loan Transactions tab has a reversed "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | ASSET | 987654 | Fund Receivables | | 9000.0 | + | ASSET | 112601 | Loans Receivable | | 9000.0 | + | ASSET | 987654 | Fund Receivables | 9000.0 | | + + @TestRailId:C106689 + Scenario: Verify Working Capital loan disbursement GL entries - UC3: Advanced Accounting, unmapped payment type falls back to the default fund source + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount and "AUTOPAY" payment type + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + + @TestRailId:C106690 + Scenario: Verify Working Capital loan disbursement GL entries - UC4: disbursement with discount fee in the same call posts both sets + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 1000 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount and "1000" discount amount + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | + And Working Capital Loan Transactions tab has a "DISCOUNT_FEE" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 240005 | Deferred Interest Revenue | | 1000.0 | + + @TestRailId:C106691 + Scenario: Verify Working Capital loan disbursement GL entries - UC5: partial disbursement, undo, re-disbursement with a different amount + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ACC_DEF_REV_AM | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "8000" EUR transaction amount + Then Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 8000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 8000.0 | + When Admin successfully undo Working Capital disbursal + And Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital Loan Transactions tab has a reversed "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 8000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 8000.0 | + | ASSET | 112601 | Loans Receivable | | 8000.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 8000.0 | | + And Working Capital Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 9000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 9000.0 | diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualWithDeferredRevenueAmortizationAccountingProcessorForWorkingCapitalLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualWithDeferredRevenueAmortizationAccountingProcessorForWorkingCapitalLoan.java index 1d1d4ebcb9e..883584a6306 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualWithDeferredRevenueAmortizationAccountingProcessorForWorkingCapitalLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualWithDeferredRevenueAmortizationAccountingProcessorForWorkingCapitalLoan.java @@ -118,6 +118,7 @@ private List plannedPostings(final WorkingCapitalLoan loan, final final BigDecimal overpaymentPortion = MathUtil.nullToZero(allocation == null ? null : allocation.getOverpaymentPortion()); return switch (txn.getTypeOf()) { + case LoanTransactionType.DISBURSEMENT -> disbursementPostings(txn, principalPortion); case LoanTransactionType.REPAYMENT -> repaymentPostings(txn, principalPortion, feesPortion, penaltiesPortion, overpaymentPortion, isChargedOff); case LoanTransactionType.GOODWILL_CREDIT -> @@ -163,6 +164,11 @@ private List repaymentPostings(final WorkingCapitalLoanTransactio LedgerPosting.debit(CashAccountsForLoan.FUND_SOURCE, txn.getTransactionAmount())); } + private List disbursementPostings(final WorkingCapitalLoanTransaction txn, final BigDecimal principalPortion) { + return List.of(LedgerPosting.debit(CashAccountsForLoan.LOAN_PORTFOLIO, principalPortion), + LedgerPosting.credit(CashAccountsForLoan.FUND_SOURCE, txn.getTransactionAmount())); + } + private List chargeAdjustmentPostings(final WorkingCapitalLoanTransaction txn, final BigDecimal principalPortion, final BigDecimal feesPortion, final BigDecimal penaltiesPortion, final BigDecimal overpaymentPortion, final boolean isChargedOff) { diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java index e8962b68503..bcbad2fbeda 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java @@ -398,6 +398,10 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand amortizationScheduleWriteService.generateAndSaveAmortizationScheduleOnDisbursement(loan, transactionAmount, actualDisbursementDate); generateInitialDelinquencyAndBreachPeriods(loan); + if (loan.getLoanProduct().getAccountingRule().isAccrualWithDeferredRevenueAmortization()) { + accountingProcessor.postJournalEntries(loan, disbursementTransaction, allocation, false); + } + this.loanRepository.saveAndFlush(loan); changes.put("status", loan.getLoanStatus()); handleNote(loan, command, changes); @@ -1304,6 +1308,11 @@ private WorkingCapitalLoanTransaction reverseDisbursementTransactionAndResetBala .filter(t -> t.getTypeOf() == LoanTransactionType.ACCRUAL && !t.isReversed()).toList(); transactions.forEach(this::markReversed); + + if (loan.getLoanProduct().getAccountingRule().isAccrualWithDeferredRevenueAmortization()) { + accountingProcessor.postReversalJournalEntries(loan, txn); + } + this.transactionRepository.saveAll(transactions); this.transactionRepository.flush();