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 8803c554f FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)
8803c554f is described below

commit 8803c554f54bd97f4b7fd7b24e58720ad4671361
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Mar 1 11:55:36 2024 +0100

    FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)
---
 .../portfolio/loanaccount/domain/Loan.java         |  33 +-
 .../loanaccount/domain/LoanTransaction.java        |  15 +-
 .../domain/LoanAccountDomainServiceJpa.java        |   4 +-
 .../service/LoanReadPlatformServiceImpl.java       |   2 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |  25 +-
 .../integrationtests/BaseLoanIntegrationTest.java  |  11 +-
 .../LoanRepaymentScheduleWithDownPaymentTest.java  | 578 +++++++++++++--------
 7 files changed, 428 insertions(+), 240 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 f069466f6..deeb280ce 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
@@ -2967,8 +2967,24 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         Money downPaymentMoney = Money.of(getCurrency(),
                 MathUtil.percentageOf(disbursementTransaction.getAmount(), 
disbursedAmountPercentageForDownPayment, 19));
 
-        Money adjustedDownPaymentMoney = MathUtil
-                
.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
+        Money adjustedDownPaymentMoney = switch 
(getLoanProductRelatedDetail().getLoanScheduleType()) {
+            // For Cumulative loan: To check whether the loan was overpaid 
when the disbursement happened and to get the
+            // proper amount after the disbursement we are using two balances:
+            // 1. Whether the loan is still overpaid after the disbursement,
+            // 2. if the loan is not overpaid anymore after the disbursement, 
but was it more overpaid than the
+            // calculated down-payment amount?
+            case CUMULATIVE -> {
+                if (getTotalOverpaidAsMoney().isGreaterThanZero()) {
+                    yield Money.zero(getCurrency());
+                }
+                yield 
MathUtil.negativeToZero(downPaymentMoney.minus(MathUtil.negativeToZero(disbursementTransaction
+                        
.getAmount(getCurrency()).minus(disbursementTransaction.getOutstandingLoanBalanceMoney(getCurrency())))));
+            }
+            // For Progressive loan: Disbursement transaction portion balances 
are enough to see whether the overpayment
+            // amount was more than the calculated down-payment amount
+            case PROGRESSIVE ->
+                
MathUtil.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
+        };
         if (adjustedDownPaymentMoney.isGreaterThanZero()) {
             LoanTransaction downPaymentTransaction = 
LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null, 
disbursedOn,
                     externalId);
@@ -3602,7 +3618,11 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         final LocalDate currentTransactionDate = 
loanTransaction.getTransactionDate();
         for (final LoanTransaction previousTransaction : loanTransactions) {
             if (!previousTransaction.isDisbursement() && 
previousTransaction.isNotReversed()
-                    && !DateUtils.isAfter(currentTransactionDate, 
previousTransaction.getTransactionDate())) {
+                    && (DateUtils.isBefore(currentTransactionDate, 
previousTransaction.getTransactionDate())
+                            || (DateUtils.isEqual(currentTransactionDate, 
previousTransaction.getTransactionDate())
+                                    && ((loanTransaction.getId() == null && 
previousTransaction.getId() == null)
+                                            || (loanTransaction.getId() != 
null && (previousTransaction.getId() == null
+                                                    || 
loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) {
                 isChronologicallyLatestRepaymentOrWaiver = false;
                 break;
             }
@@ -5958,7 +5978,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             if (loanTransaction.isDisbursement() || 
loanTransaction.isIncomePosting()) {
                 outstanding = 
outstanding.plus(loanTransaction.getAmount(getCurrency()))
                         
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
-                
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+                
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
             } else if (loanTransaction.isChargeback() || 
loanTransaction.isCreditBalanceRefund()) {
                 Money transactionOutstanding = 
loanTransaction.getPrincipalPortion(getCurrency());
                 if 
(!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
@@ -5977,8 +5997,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                     }
                 }
                 outstanding = outstanding.plus(transactionOutstanding);
-                
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
-
+                
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
             } else {
                 if (this.loanInterestRecalculationDetails != null
                         && 
this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
@@ -5987,7 +6006,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 } else {
                     outstanding = 
outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
                 }
-                
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+                
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
             }
         }
     }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index f14835273..960ef6138 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -46,6 +46,7 @@ import org.apache.fineract.organisation.office.domain.Office;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
 import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -147,14 +148,16 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
                 penaltyChargesPortion, overPaymentPortion, reversed, 
paymentDetail, externalId);
     }
 
-    public static LoanTransaction disbursement(final Office office, final 
Money amount, final PaymentDetail paymentDetail,
+    public static LoanTransaction disbursement(final Loan loan, final Money 
amount, final PaymentDetail paymentDetail,
             final LocalDate disbursementDate, final ExternalId externalId, 
final Money loanTotalOverpaid) {
         // We need to set the overpayment amount because it could happen the 
transaction got saved before the proper
         // portion calculation and side effect would be reverse-replay
-        Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ? 
loanTotalOverpaid : amount;
-        LoanTransaction disbursement = new LoanTransaction(null, office, 
LoanTransactionType.DISBURSEMENT, paymentDetail,
+        LoanTransaction disbursement = new LoanTransaction(null, 
loan.getOffice(), LoanTransactionType.DISBURSEMENT, paymentDetail,
                 amount.getAmount(), disbursementDate, externalId);
-        disbursement.setOverPayments(overPaymentPortion);
+        if 
(LoanScheduleType.PROGRESSIVE.equals(loan.getLoanProductRelatedDetail().getLoanScheduleType()))
 {
+            Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) 
? loanTotalOverpaid : amount;
+            disbursement.setOverPayments(overPaymentPortion);
+        }
         return disbursement;
     }
 
@@ -911,6 +914,10 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
         return outstandingLoanBalance;
     }
 
+    public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency 
currency) {
+        return Money.of(currency, this.outstandingLoanBalance);
+    }
+
     public PaymentDetail getPaymentDetail() {
         return this.paymentDetail;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 8039a6479..d6c542e28 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -534,8 +534,8 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
         final List<Long> existingTransactionIds = new ArrayList<>();
         final List<Long> existingReversedTransactionIds = new ArrayList<>();
         final Money amount = Money.of(loan.getCurrency(), transactionAmount);
-        LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, 
transactionDate,
-                txnExternalId, loan.getTotalOverpaidAsMoney());
+        LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan, amount, paymentDetail, transactionDate, 
txnExternalId,
+                loan.getTotalOverpaidAsMoney());
 
         // Subtract Previous loan outstanding balance from netDisbursalAmount
         loan.deductFromNetDisbursalAmount(transactionAmount);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 6d6643f9d..9b0be1385 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1715,7 +1715,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
     public Collection<DisbursementData> retrieveLoanDisbursementDetails(final 
Long loanId) {
         final LoanDisbursementDetailMapper rm = new 
LoanDisbursementDetailMapper(sqlGenerator);
         final String sql = "select " + rm.schema()
-                + " where dd.loan_id=? and dd.is_reversed=false group by 
dd.id, lc.amount_waived_derived order by 
dd.expected_disburse_date,dd.disbursedon_date";
+                + " where dd.loan_id=? and dd.is_reversed=false group by 
dd.id, lc.amount_waived_derived order by 
dd.expected_disburse_date,dd.disbursedon_date,dd.id";
         return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 479370353..42d4ff675 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -435,8 +435,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             } else {
                 
existingTransactionIds.addAll(loan.findExistingTransactionIds());
                 
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
-                disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail,
-                        actualDisbursementDate, txnExternalId, 
loan.getTotalOverpaidAsMoney());
+                disbursementTransaction = LoanTransaction.disbursement(loan, 
amountToDisburse, paymentDetail, actualDisbursementDate,
+                        txnExternalId, loan.getTotalOverpaidAsMoney());
                 disbursementTransaction.updateLoan(loan);
                 loan.addLoanTransaction(disbursementTransaction);
             }
@@ -461,6 +461,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             if (disbursementTransaction != null) {
                 
loanTransactionRepository.saveAndFlush(disbursementTransaction);
             }
+            if (changedTransactionDetail != null) {
+                for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
+                }
+                // Trigger transaction replayed event
+                
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
+            }
             if (loan.isAutoRepaymentForDownPaymentEnabled()) {
                 // updating linked savings account for auto down payment 
transaction for disbursement to savings account
                 if (isAccountTransfer && 
loan.shouldCreateStandingInstructionAtDisbursement()) {
@@ -490,14 +498,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             }
         }
         if (!changes.isEmpty()) {
-            if (changedTransactionDetail != null) {
-                for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
-                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
-                }
-                // Trigger transaction replayed event
-                
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
-            }
+
             loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
             final String noteText = 
command.stringValueOfParameterNamed("note");
@@ -563,8 +564,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             businessEventNotifierService.notifyPostBusinessEvent(new 
LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
         }
 
-        loan.updateLoanSummaryAndStatus();
-
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withEntityId(loan.getId()) //
@@ -751,7 +750,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 } else {
                     
existingTransactionIds.addAll(loan.findExistingTransactionIds());
                     
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
-                    LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
+                    LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan, disburseAmount, paymentDetail,
                             actualDisbursementDate, txnExternalId, 
loan.getTotalOverpaidAsMoney());
                     disbursementTransaction.updateLoan(loan);
                     loan.addLoanTransaction(disbursementTransaction);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 0d74fc161..fc4aaf202 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -159,7 +159,6 @@ public abstract class BaseLoanIntegrationTest {
                 .includeInBorrowerCycle(false)//
                 .currencyCode("USD")//
                 .digitsAfterDecimal(2)//
-                .inMultiplesOf(0)//
                 .installmentAmountInMultiplesOf(1)//
                 .useBorrowerCycle(false)//
                 .minPrincipal(100.0)//
@@ -643,7 +642,14 @@ public abstract class BaseLoanIntegrationTest {
     protected TransactionExt transaction(double amount, String type, String 
date, double outstandingAmount, double principalPortion,
             double interestPortion, double feePortion, double penaltyPortion, 
double unrecognizedIncomePortion, double overpaymentPortion) {
         return new TransactionExt(amount, type, date, outstandingAmount, 
principalPortion, interestPortion, feePortion, penaltyPortion,
-                unrecognizedIncomePortion, overpaymentPortion);
+                unrecognizedIncomePortion, overpaymentPortion, false);
+    }
+
+    protected TransactionExt transaction(double amount, String type, String 
date, double outstandingAmount, double principalPortion,
+            double interestPortion, double feePortion, double penaltyPortion, 
double unrecognizedIncomePortion, double overpaymentPortion,
+            boolean reversed) {
+        return new TransactionExt(amount, type, date, outstandingAmount, 
principalPortion, interestPortion, feePortion, penaltyPortion,
+                unrecognizedIncomePortion, overpaymentPortion, reversed);
     }
 
     protected Installment installment(double principalAmount, Boolean 
completed, String dueDate) {
@@ -766,6 +772,7 @@ public abstract class BaseLoanIntegrationTest {
         Double penaltyPortion;
         Double unrecognizedPortion;
         Double overpaymentPortion;
+        Boolean reversed;
     }
 
     @ToString
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
index 2db85269b..d9cf82d3c 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
@@ -18,37 +18,33 @@
  */
 package org.apache.fineract.integrationtests;
 
-import static 
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.DATETIME_PATTERN;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import io.restassured.builder.RequestSpecBuilder;
-import io.restassured.builder.ResponseSpecBuilder;
-import io.restassured.http.ContentType;
-import io.restassured.specification.RequestSpecification;
-import io.restassured.specification.ResponseSpecification;
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.fineract.client.models.AdvancedPaymentData;
 import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
 import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdSummary;
-import org.apache.fineract.client.models.PaymentAllocationOrder;
 import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
 import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
 import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -59,44 +55,19 @@ import 
org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.integrationtests.common.accounting.Account;
-import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
 import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
-import 
org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
 import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
-import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
 import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
-import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-@ExtendWith(LoanTestLifecycleExtension.class)
-public class LoanRepaymentScheduleWithDownPaymentTest {
-
-    private ResponseSpecification responseSpec;
-    private RequestSpecification requestSpec;
-    private LoanTransactionHelper loanTransactionHelper;
-    private ClientHelper clientHelper;
-    private AccountHelper accountHelper;
-    private JournalEntryHelper journalEntryHelper;
-
-    @BeforeEach
-    public void setup() {
-        Utils.initializeRESTAssured();
-        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
-        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
-        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
-        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
-        clientHelper = new ClientHelper(requestSpec, responseSpec);
-        accountHelper = new AccountHelper(requestSpec, responseSpec);
-        journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
-    }
+
+public class LoanRepaymentScheduleWithDownPaymentTest extends 
BaseLoanIntegrationTest {
 
     @Test
     public void loanRepaymentScheduleWithSimpleDisbursementAndDownPayment() {
@@ -1235,165 +1206,407 @@ public class LoanRepaymentScheduleWithDownPaymentTest 
{
     }
 
     @Test
-    public void downPaymentOnOverpaidLoan() {
-        try {
-
-            // Set business date
+    public void downPaymentOnOverpaidProgressiveLoan() {
+        runAt("03 March 2023", () -> {
             LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
 
-            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+            PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+            final PostLoanProductsRequest loanProductsRequest = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
+
+            PostLoanProductsResponse loanProductsResponse = 
loanTransactionHelper.createLoanProduct(loanProductsRequest);
+
+            String disbursementDateStr = DateUtils.format(disbursementDate, 
DATETIME_PATTERN);
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(client.getResourceId())
+                    
.productId(loanProductsResponse.getResourceId()).loanType("individual").locale("en").dateFormat(DATETIME_PATTERN)
+                    
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+                    .maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
+                    
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                    
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()).expectedDisbursementDate(disbursementDateStr)
+                    
.dateFormat(DATETIME_PATTERN).submittedOnDate(disbursementDateStr).repaymentFrequencyType(0).repaymentEvery(30)
+                    
.numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0).principal(BigDecimal.valueOf(1000))
+                    
.loanType("individual").maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
+
+            loanTransactionHelper.approveLoan(loanResponse.getResourceId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate(disbursementDateStr).locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new 
PostLoansLoanIdRequest().actualDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(750.0, 0.0, 750.0, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+            verifyJournalEntries(loanResponse.getResourceId(), 
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
+                    journalEntry(1000.0, suspenseClearingAccount, "CREDIT"), //
+                    journalEntry(250.0, loansReceivableAccount, "CREDIT"), //
+                    journalEntry(250.0, suspenseClearingAccount, "DEBIT") //
+            );
+
+            
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(), new 
PostLoansLoanIdTransactionsRequest()
+                    .dateFormat("dd MMMM yyyy").transactionDate("03 March 
2023").locale("en").transactionAmount(800.0));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(750.0, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
+            );
+            assertTrue(loanDetails.getStatus().getOverpaid());
+            assertEquals(50.0, loanDetails.getTotalOverpaid());
+
+            // second disbursement
+            disbursementDate = LocalDate.of(2023, 3, 5);
             BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, disbursementDate);
 
-            // Accounts oof periodic accrual
-            final Account assetAccount = accountHelper.createAssetAccount();
-            final Account incomeAccount = accountHelper.createIncomeAccount();
-            final Account expenseAccount = 
accountHelper.createExpenseAccount();
-            final Account overpaymentAccount = 
accountHelper.createLiabilityAccount();
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(20.00)).locale("en"));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(765.0, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 30.0) //
+            );
+            assertTrue(loanDetails.getStatus().getOverpaid());
+            assertEquals(30.0, loanDetails.getTotalOverpaid());
 
-            // Loan ExternalId
-            String loanExternalIdStr = UUID.randomUUID().toString();
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(30.00)).locale("en"));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(787.5, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
-            // down-payment configuration
-            Boolean enableDownPayment = true;
-            BigDecimal disbursedAmountPercentageForDownPayment = 
BigDecimal.valueOf(25);
-            Boolean enableAutoRepaymentForDownPayment = true;
+            assertTrue(loanDetails.getStatus().getClosedObligationsMet());
+            assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
+            assertEquals(null, loanDetails.getTotalOverpaid());
 
-            final Integer clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+            PostLoansLoanIdTransactionsResponse repayment = 
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(),
+                    new PostLoansLoanIdTransactionsRequest().dateFormat("dd 
MMMM yyyy").transactionDate("05 March 2023").locale("en")
+                            .transactionAmount(1.0));
 
-            // Loan Product creation with down-payment configuration
-            final GetLoanProductsProductIdResponse 
getLoanProductsProductResponse = 
createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting(
-                    loanTransactionHelper, enableDownPayment, "25", 
enableAutoRepaymentForDownPayment, assetAccount, incomeAccount,
-                    expenseAccount, overpaymentAccount);
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 1.0) //
+            );
+            assertTrue(loanDetails.getStatus().getOverpaid());
+            assertEquals(1.0, loanDetails.getTotalOverpaid());
 
-            assertNotNull(getLoanProductsProductResponse);
-            assertEquals(enableDownPayment, 
getLoanProductsProductResponse.getEnableDownPayment());
-            assertEquals(0, 
getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
-                    .compareTo(disbursedAmountPercentageForDownPayment));
-            assertEquals(enableAutoRepaymentForDownPayment, 
getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(40.00)).locale("en"));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(40.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(10.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 1.0), //
+                    transaction(40.0, "Disbursement", "05 March 2023", 39.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 1.0), //
+                    transaction(9.0, "Down Payment", "05 March 2023", 30.0, 
9.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
-            final Integer loanId = 
createLoanAccountWithAdvancedPaymentAllocation(clientId, 
getLoanProductsProductResponse.getId(),
-                    loanExternalIdStr);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
 
-            // Retrieve Loan with loanId
+            
loanTransactionHelper.reverseLoanTransaction(repayment.getLoanId(), 
repayment.getResourceId(),
+                    new 
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05
 March 2023")
+                            .transactionAmount(0.0).locale("en"));
 
-            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(40.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(10.0, 0.0, 1.0, false, "05 March 2023"), //
+                    installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 1.0, true), //
+                    transaction(40.0, "Disbursement", "05 March 2023", 40.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(9.0, "Down Payment", "05 March 2023", 31.0, 
9.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
-            // verify down-payment details for Loan
-            assertNotNull(loanDetails);
-            assertEquals(enableDownPayment, 
loanDetails.getEnableDownPayment());
-            assertEquals(0, 
loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
-            assertEquals(enableAutoRepaymentForDownPayment, 
loanDetails.getEnableAutoRepaymentForDownPayment());
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
+        });
+    }
 
-            // first disbursement
-            loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 
2023", loanId, "1000");
+    @Test
+    public void downPaymentOnOverpaidCumulativeLoan() {
+        runAt("03 March 2023", () -> {
+            LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
 
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            // verify down-payment transaction created
-            checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f, 
0.0f, loanId);
+            PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
 
-            // verify journal entries for down-payment
-            journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"03 March 2023",
-                    new JournalEntry(250, 
JournalEntry.TransactionType.CREDIT));
-            journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, 
"03 March 2023",
-                    new JournalEntry(250, JournalEntry.TransactionType.DEBIT));
+            final PostLoanProductsRequest loanProductsRequest = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
+                    
.loanScheduleType(LoanScheduleType.CUMULATIVE.name()).paymentAllocation(null)
+                    .transactionProcessingStrategyCode(
+                            
DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
 
-            // verify installment details
-            assertEquals(LocalDate.of(2023, 3, 3), 
loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate());
-            assertEquals(1000.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding());
-            assertEquals(1, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
-            assertEquals(LocalDate.of(2023, 3, 3), 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
-            assertEquals(250.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
-            assertEquals(true, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod());
-            assertEquals(2, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
-            assertEquals(LocalDate.of(2023, 4, 2), 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
-            assertEquals(750.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
-            assertEquals(false, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod());
+            PostLoanProductsResponse loanProductsResponse = 
loanTransactionHelper.createLoanProduct(loanProductsRequest);
 
-            loanTransactionHelper.makeLoanRepayment((long) loanId, new 
PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
-                    .transactionDate("03 March 
2023").locale("en").transactionAmount(800.0));
+            String disbursementDateStr = DateUtils.format(disbursementDate, 
DATETIME_PATTERN);
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(client.getResourceId())
+                    
.productId(loanProductsResponse.getResourceId()).loanType("individual").locale("en").dateFormat(DATETIME_PATTERN)
+                    
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+                    .maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
+                    .transactionProcessingStrategyCode(
+                            
DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
+                    
.expectedDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN).submittedOnDate(disbursementDateStr)
+                    
.repaymentFrequencyType(0).repaymentEvery(30).numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0)
+                    
.principal(BigDecimal.valueOf(1000)).loanType("individual").maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
 
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            // verify down-payment details for Loan
+            loanTransactionHelper.approveLoan(loanResponse.getResourceId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate(disbursementDateStr).locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new 
PostLoansLoanIdRequest().actualDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+            assertTrue(loanDetails.getStatus().getActive());
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(750.0, 0.0, 750.0, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
+            verifyJournalEntries(loanResponse.getResourceId(), 
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
+                    journalEntry(1000.0, suspenseClearingAccount, "CREDIT"), //
+                    journalEntry(250.0, loansReceivableAccount, "CREDIT"), //
+                    journalEntry(250.0, suspenseClearingAccount, "DEBIT") //
+            );
+
+            String externalId = UUID.randomUUID().toString();
+            
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(),
+                    new PostLoansLoanIdTransactionsRequest().dateFormat("dd 
MMMM yyyy").transactionDate("03 March 2023").locale("en")
+                            .transactionAmount(800.0).externalId(externalId));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(750.0, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
750.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
+            );
             assertTrue(loanDetails.getStatus().getOverpaid());
             assertEquals(50.0, loanDetails.getTotalOverpaid());
 
             // second disbursement
-
             disbursementDate = LocalDate.of(2023, 3, 5);
             BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, disbursementDate);
-            loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 
2023", loanId, "20");
-
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-
-            
assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement());
-            assertEquals(1000.0, 
loanDetails.getTransactions().get(0).getAmount());
-            assertEquals("loanTransactionType.downPayment", 
loanDetails.getTransactions().get(1).getType().getCode());
-            assertEquals(250.0, 
loanDetails.getTransactions().get(1).getAmount());
-            
assertTrue(loanDetails.getTransactions().get(2).getType().getRepayment());
-            assertEquals(800.0, 
loanDetails.getTransactions().get(2).getAmount());
-            
assertTrue(loanDetails.getTransactions().get(3).getType().getDisbursement());
-            assertEquals(20.0, 
loanDetails.getTransactions().get(3).getAmount());
-            assertEquals(0.0, 
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
-            assertEquals(4, loanDetails.getTransactions().size());
 
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(20.00)).locale("en"));
+
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(765.0, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
770.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
             assertTrue(loanDetails.getStatus().getOverpaid());
             assertEquals(30.0, loanDetails.getTotalOverpaid());
 
-            loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 
2023", loanId, "30");
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            
assertTrue(loanDetails.getTransactions().get(4).getType().getDisbursement());
-            assertEquals(30.0, 
loanDetails.getTransactions().get(4).getAmount());
-            assertEquals(0.0, 
loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
-            assertEquals(5, loanDetails.getTransactions().size());
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(30.00)).locale("en"));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(787.5, 0.0, 0.0, true, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
800.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
             assertTrue(loanDetails.getStatus().getClosedObligationsMet());
             assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
             assertEquals(null, loanDetails.getTotalOverpaid());
 
-            PostLoansLoanIdTransactionsResponse repayment = 
loanTransactionHelper.makeLoanRepayment((long) loanId,
-                    new PostLoansLoanIdTransactionsRequest().dateFormat("dd 
MMMM yyyy").transactionDate("05 March 2023").locale("en")
-                            .transactionAmount(1.0));
-
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            assertTrue(loanDetails.getStatus().getOverpaid());
-            assertEquals(1.0, loanDetails.getTotalOverpaid());
-
-            loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 
2023", loanId, "40");
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
-            assertEquals(1.0, 
loanDetails.getTransactions().get(5).getAmount());
-            
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
-            assertEquals(40.0, 
loanDetails.getTransactions().get(6).getAmount());
-            assertEquals(39.0, 
loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
-            assertEquals("loanTransactionType.downPayment", 
loanDetails.getTransactions().get(7).getType().getCode());
-            assertEquals(9.0, 
loanDetails.getTransactions().get(7).getAmount());
-            assertEquals(30.0, 
loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
-            assertEquals(8, loanDetails.getTransactions().size());
+            loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("05 
March 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(40.00)).locale("en"));
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(40.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(10.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
800.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(40.0, "Disbursement", "05 March 2023", 40.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(10.0, "Down Payment", "05 March 2023", 30.0, 
10.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
             assertTrue(loanDetails.getStatus().getActive());
             assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
 
-            
loanTransactionHelper.reverseLoanTransaction(repayment.getLoanId(), 
repayment.getResourceId(),
+            
loanTransactionHelper.reverseLoanTransaction(loanResponse.getLoanId(), 
externalId,
                     new 
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05
 March 2023")
                             .transactionAmount(0.0).locale("en"));
 
-            loanDetails = 
loanTransactionHelper.getLoanDetails(loanId.longValue());
-            
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
-            assertEquals(1.0, 
loanDetails.getTransactions().get(5).getAmount());
-            
assertTrue(loanDetails.getTransactions().get(5).getManuallyReversed());
-            
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
-            assertEquals(40.0, 
loanDetails.getTransactions().get(6).getAmount());
-            assertEquals(40.0, 
loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
-            assertEquals("loanTransactionType.downPayment", 
loanDetails.getTransactions().get(7).getType().getCode());
-            assertEquals(9.0, 
loanDetails.getTransactions().get(7).getAmount());
-            assertEquals(31.0, 
loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
-            assertEquals(8, loanDetails.getTransactions().size());
+            loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+            // Verify Repayment Schedule
+            verifyRepaymentSchedule(loanResponse.getResourceId(), //
+                    installment(1000.0, null, "03 March 2023"), //
+                    installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+                    installment(20.0, null, "05 March 2023"), //
+                    installment(30.0, null, "05 March 2023"), //
+                    installment(40.0, null, "05 March 2023"), //
+                    installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+                    installment(7.5, 0.0, 2.5, false, "05 March 2023"), //
+                    installment(10.0, 0.0, 10.0, false, "05 March 2023"), //
+                    installment(817.5, 0.0, 817.5, false, "02 April 2023") //
+            );
+            // verify transactions
+            verifyTransactions(loanResponse.getResourceId(), //
+                    transaction(1000.0, "Disbursement", "03 March 2023", 
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(250.0, "Down Payment", "03 March 2023", 750.0, 
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(800.0, "Repayment", "03 March 2023", 0.0, 
800.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
+                    transaction(20.0, "Disbursement", "05 March 2023", 770.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(30.0, "Disbursement", "05 March 2023", 800.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(40.0, "Disbursement", "05 March 2023", 840.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(10.0, "Down Payment", "05 March 2023", 830.0, 
10.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+            );
 
             assertTrue(loanDetails.getStatus().getActive());
-            assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
-        } finally {
-            GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
-        }
+            assertEquals(830.0, 
loanDetails.getSummary().getTotalOutstanding());
+        });
     }
 
     private void checkNoDownPaymentTransaction(final Integer loanID) {
@@ -1470,63 +1683,6 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
         return loanTransactionHelper.getLoanProduct(loanProductId);
     }
 
-    private Integer createLoanAccountWithAdvancedPaymentAllocation(final 
Integer clientID, final Long loanProductID,
-            final String externalId) {
-
-        String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30")
-                
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
-                
.withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays()
-                
.withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments()
-                
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03
 March 2023")
-                .withSubmittedOnDate("03 March 
2023").withLoanType("individual").withExternalId(externalId)
-                .build(clientID.toString(), loanProductID.toString(), null);
-
-        final Integer loanId = 
loanTransactionHelper.getLoanId(loanApplicationJSON);
-        loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId, 
null);
-        return loanId;
-    }
-
-    private GetLoanProductsProductIdResponse 
createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting(
-            LoanTransactionHelper loanTransactionHelper, Boolean 
enableDownPayment, String disbursedAmountPercentageForDownPayment,
-            boolean enableAutoRepaymentForDownPayment, final Account... 
accounts) {
-        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
-                
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL).withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
-                
.addAdvancedPaymentAllocation(createDefaultPaymentAllocation()).withRepaymentAfterEvery("1").withNumberOfRepayments("1")
-                
.withRepaymentTypeAsMonth().withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths()
-                
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
-                
.withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30")
-                .withDaysInYear("365").withMoratorium("0", 
"0").withMultiDisburse().withDisallowExpectedDisbursements(true)
-                .withEnableDownPayment(enableDownPayment, 
disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment)
-                .build(null);
-        final Integer loanProductId = 
loanTransactionHelper.getLoanProductId(loanProductJSON);
-        return loanTransactionHelper.getLoanProduct(loanProductId);
-    }
-
-    private List<PaymentAllocationOrder> 
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
-        AtomicInteger integer = new AtomicInteger(1);
-        return Arrays.stream(paymentAllocationTypes).map(pat -> {
-            PaymentAllocationOrder paymentAllocationOrder = new 
PaymentAllocationOrder();
-            paymentAllocationOrder.setPaymentAllocationRule(pat.name());
-            paymentAllocationOrder.setOrder(integer.getAndIncrement());
-            return paymentAllocationOrder;
-        }).toList();
-    }
-
-    private AdvancedPaymentData createDefaultPaymentAllocation() {
-        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
-        advancedPaymentData.setTransactionType("DEFAULT");
-        
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
-
-        List<PaymentAllocationOrder> paymentAllocationOrders = 
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
-                PaymentAllocationType.PAST_DUE_FEE, 
PaymentAllocationType.PAST_DUE_PRINCIPAL, 
PaymentAllocationType.PAST_DUE_INTEREST,
-                PaymentAllocationType.DUE_PENALTY, 
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
-                PaymentAllocationType.DUE_INTEREST, 
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
-                PaymentAllocationType.IN_ADVANCE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_INTEREST);
-
-        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
-        return advancedPaymentData;
-    }
-
     private Integer createApproveAndDisburseLoanAccount(final Integer 
clientID, final Long loanProductID, final String externalId) {
 
         String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")

Reply via email to