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 a8cc27c0d..f9c223ca5 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 @@ -493,26 +493,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf); } downPaymentAmount = Money.of(currency, downPaymentAmt); - Money autoPayFromOverpayment = overpaymentHolder.getMoneyObject(); downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount); - Money paidAmount = downPaymentInstallment.payPrincipalComponent(disbursementTransaction.getTransactionDate(), - autoPayFromOverpayment); - disbursementTransaction.setOverPayments(paidAmount); - overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(paidAmount)); } + disbursementTransaction.setOverPayments(overpaymentHolder.getMoneyObject()); Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount); - Money overpaidAmount = overpaymentHolder.getMoneyObject(); - if (overpaidAmount.isGreaterThanZero()) { - if (amortizableAmount.isGreaterThan(overpaidAmount)) { - overpaymentHolder.setMoneyObject(Money.zero(currency)); - amortizableAmount = amortizableAmount.minus(overpaidAmount); - disbursementTransaction.setOverPayments(disbursementTransaction.getOverPaymentPortion(currency).add(overpaidAmount)); - } else { - overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(amortizableAmount)); - amortizableAmount = Money.zero(currency); - disbursementTransaction.setOverPayments(disbursementTransaction.getOverPaymentPortion(currency).add(amortizableAmount)); - } - } if (amortizableAmount.isGreaterThanZero()) { Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode()); @@ -530,6 +514,33 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1) .addToPrincipal(disbursementTransaction.getTransactionDate(), remainingAmount); } + + allocateOverpayment(disbursementTransaction, currency, installments, overpaymentHolder); + } + + private void allocateOverpayment(LoanTransaction loanTransaction, MonetaryCurrency currency, + List installments, MoneyHolder overpaymentHolder) { + List transactionMappings = new ArrayList<>(); + List paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules(); + LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream() + .filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow(); + + Money transactionAmountUnprocessed = null; + Money zero = Money.zero(currency); + Balances balances = new Balances(zero, zero, zero, zero); + if (LoanScheduleProcessingType.HORIZONTAL + .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) { + transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments, + overpaymentHolder.getMoneyObject(), defaultPaymentAllocationRule, transactionMappings, Set.of(), balances); + } else if (LoanScheduleProcessingType.VERTICAL + .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) { + transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, + overpaymentHolder.getMoneyObject(), defaultPaymentAllocationRule, transactionMappings, Set.of(), balances); + } + if (transactionAmountUnprocessed != null && transactionAmountUnprocessed.isGreaterThanZero()) { + overpaymentHolder.setMoneyObject(transactionAmountUnprocessed); + } + loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings); } private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency currency, 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 b682c2250..603f10015 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 @@ -3131,7 +3131,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan // 1. Create a Loan product with Adv. Pment. Alloc. // 2. Submit Loan and approve // 3. Disburse only 100 from 1000 - // 4. Overpay the loan (110) + // 4. Overpay the loan (150) // 5. Disburse again 100 @Test public void uc122() { @@ -3166,9 +3166,9 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertTrue(loanDetails.getStatus().getActive()); loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest() - .dateFormat(DATETIME_PATTERN).transactionDate("23 November 2023").locale("en").transactionAmount(110.0)); + .dateFormat(DATETIME_PATTERN).transactionDate("23 November 2023").locale("en").transactionAmount(150.0)); loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); - validateLoanSummaryBalances(loanDetails, 0.0, 100.0, 0.0, 100.0, 10.0); + 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, 25.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); @@ -3179,27 +3179,27 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan new PostLoansLoanIdRequest().actualDisbursementDate("24 November 2023").dateFormat(DATETIME_PATTERN) .transactionAmount(BigDecimal.valueOf(100.0)).locale("en")); loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); - validateLoanSummaryBalances(loanDetails, 90.0, 110.0, 90.0, 110.0, null); + validateLoanSummaryBalances(loanDetails, 50.0, 150.0, 50.0, 150.0, null); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), 25.0, 25.0, 0.0, 0.0, 25.0); - validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 11, 24), 25.0, 10.0, 15.0, 0.0, 0.0); - validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 7), 50.0, 25.0, 25.0, 25.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 11, 24), 25.0, 25.0, 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 7), 50.0, 50.0, 0.0, 50.0, 0.0); validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 12, 22), 50.0, 25.0, 25.0, 25.0, 0.0); validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 1, 6), 50.0, 25.0, 25.0, 25.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(110, "Repayment", "23 November 2023", 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 10.0), // - transaction(100, "Disbursement", "24 November 2023", 90.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0) // + transaction(150, "Repayment", "23 November 2023", 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0), // + transaction(100, "Disbursement", "24 November 2023", 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 50.0) // ); // verify journal entries verifyJournalEntries(loanResponse.getLoanId(), journalEntry(100.0, loansReceivableAccount, "DEBIT"), // journalEntry(100.0, suspenseClearingAccount, "CREDIT"), // journalEntry(100.0, loansReceivableAccount, "CREDIT"), // - journalEntry(10.0, overpaymentAccount, "CREDIT"), // - journalEntry(110.0, suspenseClearingAccount, "DEBIT"), // - journalEntry(90.0, loansReceivableAccount, "DEBIT"), // - journalEntry(10.0, overpaymentAccount, "DEBIT"), // + journalEntry(50.0, overpaymentAccount, "CREDIT"), // + journalEntry(150.0, suspenseClearingAccount, "DEBIT"), // + journalEntry(50.0, loansReceivableAccount, "DEBIT"), // + journalEntry(50.0, overpaymentAccount, "DEBIT"), // journalEntry(100.0, suspenseClearingAccount, "CREDIT") // ); });