This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 691005167 FINERACT-1981: Fix principal due during disbursement on 
overpaid loan
691005167 is described below

commit 6910051674b1a0cbb3bfbe60d30bccfe9a172f17
Author: Adam Saghy <[email protected]>
AuthorDate: Mon Feb 19 17:18:47 2024 +0100

    FINERACT-1981: Fix principal due during disbursement on overpaid loan
---
 .../portfolio/loanaccount/domain/Loan.java         |  2 +-
 ...dvancedPaymentScheduleTransactionProcessor.java |  9 ++-
 .../AccrualBasedAccountingProcessorForLoan.java    |  6 +-
 .../CashBasedAccountingProcessorForLoan.java       |  6 +-
 ...PaymentAllocationLoanRepaymentScheduleTest.java | 78 ++++++++++++++++++++++
 5 files changed, 94 insertions(+), 7 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 8d9369443..efa3a3ce6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -3838,7 +3838,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             }
             if (loanTransaction.isRefund() || 
loanTransaction.isRefundForActiveLoan()) {
                 totalPaidInRepayments = 
totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
-            } else if (loanTransaction.isCreditBalanceRefund() || 
loanTransaction.isChargeback() || loanTransaction.isDisbursement()) {
+            } else if (loanTransaction.isCreditBalanceRefund() || 
loanTransaction.isChargeback()) {
                 totalPaidInRepayments = 
totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
             }
         }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 297f4efca..1afa4f617 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -522,7 +522,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
             downPaymentAmount = Money.of(currency, downPaymentAmt);
             
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(),
 downPaymentAmount);
         }
-        
disbursementTransaction.setOverPayments(overpaymentHolder.getMoneyObject());
+
         Money amortizableAmount = 
disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
 
         if (amortizableAmount.isGreaterThanZero()) {
@@ -548,6 +548,11 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     private void allocateOverpayment(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, MoneyHolder 
overpaymentHolder) {
         if (overpaymentHolder.getMoneyObject().isGreaterThanZero()) {
+            if 
(overpaymentHolder.getMoneyObject().isGreaterThan(loanTransaction.getAmount(currency)))
 {
+                
loanTransaction.setOverPayments(loanTransaction.getAmount(currency));
+            } else {
+                
loanTransaction.setOverPayments(overpaymentHolder.getMoneyObject());
+            }
             List<LoanTransactionToRepaymentScheduleMapping> 
transactionMappings = new ArrayList<>();
             List<LoanPaymentAllocationRule> paymentAllocationRules = 
loanTransaction.getLoan().getPaymentAllocationRules();
             LoanPaymentAllocationRule defaultPaymentAllocationRule = 
paymentAllocationRules.stream()
@@ -653,7 +658,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     private void handleOverpayment(Money overpaymentPortion, LoanTransaction 
loanTransaction, MoneyHolder overpaymentHolder) {
         if (overpaymentPortion.isGreaterThanZero()) {
             onLoanOverpayment(loanTransaction, overpaymentPortion);
-            overpaymentHolder.setMoneyObject(overpaymentPortion);
+            
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().add(overpaymentPortion));
             loanTransaction.setOverPayments(overpaymentPortion);
         } else {
             overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index 8ba14d78f..f0ed03edb 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -477,9 +477,11 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
 
         // create journal entries for the disbursement (or disbursement
         // reversal)
-        this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
-                loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, principalPortion, isReversed);
+        if (MathUtil.isGreaterThanZero(principalPortion)) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, principalPortion, isReversed);
 
+        }
         if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
             this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
                     loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, overpaymentPortion, isReversed);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
index 5c261725f..4a2f9009c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java
@@ -475,8 +475,10 @@ public class CashBasedAccountingProcessorForLoan 
implements AccountingProcessorF
         final boolean isReversal = loanTransactionDTO.isReversed();
         final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
 
-        this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
-                loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, principalPortion, isReversal);
+        if (MathUtil.isGreaterThanZero(principalPortion)) {
+            this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
+                    loanProductId, paymentTypeId, loanId, transactionId, 
transactionDate, principalPortion, isReversal);
+        }
 
         if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
             this.helper.createDebitJournalEntryOrReversalForLoan(office, 
currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 603f10015..f6723cf77 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -3205,6 +3205,84 @@ public class 
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
         });
     }
 
+    // UC123: Advanced payment allocation, 2nd disbursement on overpaid loan
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc.
+    // 2. Submit Loan and approve
+    // 3. Disburse only 100 from 1000
+    // 4. Overpay the loan (150)
+    // 5. Disburse again 25
+    @Test
+    public void uc123() {
+        runAt("22 November 2023", () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)).enableAutoRepaymentForDownPayment(false);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "22 November 2023",
+                    1000.0, 4);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(3).loanTermFrequency(45)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("22 November 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("22 
November 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 100.0, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 
22), 25.0, 0.0, 25.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 7), 
25.0, 0.0, 25.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 
22), 25.0, 0.0, 25.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 1, 6), 
25.0, 0.0, 25.0, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), 
new PostLoansLoanIdTransactionsRequest()
+                    .dateFormat(DATETIME_PATTERN).transactionDate("22 November 
2023").locale("en").transactionAmount(150.0));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 0.0, 100.0, 0.0, 100.0, 
50.0);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 
22), 25.0, 25.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 7), 
25.0, 25.0, 0.0, 25.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 
22), 25.0, 25.0, 0.0, 25.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 1, 6), 
25.0, 25.0, 0.0, 25.0, 0.0);
+            assertTrue(loanDetails.getStatus().getOverpaid());
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("22 
November 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(28.0)).locale("en"));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 0.0, 128.0, 0.0, 128.0, 
22.0);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 
22), 25.0, 25.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 11, 
22), 7.0, 7.0, 0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 7), 
32.0, 32.0, 0.0, 32.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 12, 
22), 32.0, 32.0, 0.0, 32.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 1, 6), 
32.0, 32.0, 0.0, 32.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+
+            verifyTransactions(loanResponse.getLoanId(), //
+                    transaction(100, "Disbursement", "22 November 2023", 
100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(150, "Repayment", "22 November 2023", 0.0, 
100.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(28, "Disbursement", "22 November 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 28.0) //
+            );
+            // verify journal entries
+            verifyJournalEntries(loanResponse.getLoanId(), journalEntry(100.0, 
loansReceivableAccount, "DEBIT"), //
+                    journalEntry(100.0, suspenseClearingAccount, "CREDIT"), //
+                    journalEntry(100.0, loansReceivableAccount, "CREDIT"), //
+                    journalEntry(50.0, overpaymentAccount, "CREDIT"), //
+                    journalEntry(150.0, suspenseClearingAccount, "DEBIT"), //
+                    journalEntry(28.0, overpaymentAccount, "DEBIT"), //
+                    journalEntry(28.0, suspenseClearingAccount, "CREDIT") //
+            );
+        });
+    }
+
     private static void validateLoanSummaryBalances(GetLoansLoanIdResponse 
loanDetails, Double totalOutstanding, Double totalRepayment,
             Double principalOutstanding, Double principalPaid, Double 
totalOverpaid) {
         assertEquals(totalOutstanding, 
loanDetails.getSummary().getTotalOutstanding());

Reply via email to