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 90b52b80d..25a479a84 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 @@ -2972,10 +2972,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19)); LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn, externalId); - /* - * Currently mapping down payment transaction as repayment transaction to loan repayment schedule for - * consistency. Need to replace below logic for creating new installment for down payment and mapping. - */ LoanEvent event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; validateRepaymentTypeAccountStatus(downPaymentTransaction, event); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index a5edd7abc..d76f75e3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -367,6 +367,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periods.add(installment); + if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.isDownPaymentEnabled()) { + createDownPaymentInstallmentForOverlappingInstallments(loanApplicationTerms, scheduleParams, periods, scheduledDueDate); + } + // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, @@ -427,6 +431,29 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalOutstanding); } + private void createDownPaymentInstallmentForOverlappingInstallments(LoanApplicationTerms loanApplicationTerms, + LoanScheduleParams scheduleParams, Collection periods, LocalDate scheduledDueDate) { + for (Map.Entry disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) { + if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()) && disburseDetail.getKey().isEqual(scheduledDueDate)) { + scheduleParams.incrementPeriodNumber(); + scheduleParams.incrementInstalmentNumber(); + + BigDecimal downPaymentAmount = MathUtil.percentageOf(disburseDetail.getValue().getAmount(), + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19); + Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount); + LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment( + scheduleParams.getInstalmentNumber(), disburseDetail.getKey(), downPayment, scheduleParams.getOutstandingBalance()); + + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + periods.add(installment); + scheduleParams.reduceOutstandingBalance(downPayment); + scheduleParams.addTotalCumulativePrincipal(downPayment); + scheduleParams.addTotalRepaymentExpected(downPayment); + + } + } + } + private void updateCompoundingDetails(final Collection periods, final LoanScheduleParams params, final LoanApplicationTerms loanApplicationTerms) { final Map> compoundingDetails = params.getCompoundingDateVariations(); @@ -998,8 +1025,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periods.add(disbursementPeriod); if (loanApplicationTerms.isDownPaymentEnabled()) { - createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disburseDetail.getKey(), - disburseDetail.getValue().getAmount()); + if (!disburseDetail.getKey().isEqual(scheduledDueDate)) { + createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disburseDetail.getKey(), + disburseDetail.getValue().getAmount()); + } } // updates actual outstanding balance with new @@ -2348,11 +2377,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); if (loanApplicationTerms.isDownPaymentEnabled()) { - loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, - Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), - getPrincipalToBeScheduled(loanApplicationTerms)); - createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(), - disbursementPeriod.principalDue()); + if (!disburseDetail.getKey().isEqual(installment.getDueDate())) { + loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, + Money.of(currency, chargesDueAtTimeOfDisbursement), + loanApplicationTerms.getExpectedDisbursementDate(), + getPrincipalToBeScheduled(loanApplicationTerms)); + createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(), + disbursementPeriod.principalDue()); + } } // updates actual outstanding balance with new // disbursement detail @@ -2369,6 +2401,22 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); periods.add(loanScheduleModelPeriod); + + // downpayment installment for overlapping case + + if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.isDownPaymentEnabled()) { + for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { + if (disburseDetail.getKey().isAfter(installment.getFromDate()) + && disburseDetail.getKey().isEqual(installment.getDueDate())) { + loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, + Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), + getPrincipalToBeScheduled(loanApplicationTerms)); + createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(), + disburseDetail.getValue().getAmount()); + } + } + } + totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); 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 1ce71ca2b..6834d360a 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 @@ -447,7 +447,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa RepaymentTransactionTemplateMapper mapper = new RepaymentTransactionTemplateMapper(sqlGenerator); String sql = "select " + mapper.schema(); LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject(sql, mapper, // NOSONAR - LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.REPAYMENT.getValue(), loanId, loanId); + LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.DOWN_PAYMENT.getValue(), + LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.DOWN_PAYMENT.getValue(), loanId, loanId); final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); return LoanTransactionData.templateOnTop(loanTransactionData, paymentOptions); } @@ -2494,9 +2495,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("(CASE "); sqlBuilder.append( - "WHEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum = ? AND tr.is_reversed = false) > ls.dueDate "); + "WHEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum in (?,?) AND tr.is_reversed = false) > ls.dueDate "); sqlBuilder.append( - "THEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum = ? AND tr.is_reversed = false) "); + "THEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum in (?,?) AND tr.is_reversed = false) "); sqlBuilder.append("ELSE ls.dueDate END) as transactionDate, "); sqlBuilder.append( "ls.principal_amount - coalesce(ls.principal_writtenoff_derived, 0) - coalesce(ls.principal_completed_derived, 0) as principalDue, "); @@ -2516,7 +2517,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa sqlBuilder.append( "JOIN((SELECT ls.loan_id, ls.duedate as datedue FROM m_loan_repayment_schedule ls WHERE ls.loan_id = ? and ls.completed_derived = false ORDER BY ls.duedate LIMIT 1)) asq on asq.loan_id = ls.loan_id "); sqlBuilder.append("AND asq.datedue = ls.duedate "); - sqlBuilder.append("WHERE l.id = ?"); + sqlBuilder.append("WHERE l.id = ? LIMIT 1"); return sqlBuilder.toString(); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java new file mode 100644 index 000000000..f427c198e --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java @@ -0,0 +1,697 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.integrationtests.common.BusinessDateHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(LoanTestLifecycleExtension.class) +public class LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private LoanTransactionHelper loanTransactionHelper; + private ClientHelper clientHelper; + private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + } + + @Test + public void loanAccountWithEnableDownPaymentAndEnableAutoRepaymentForDownPaymentWithOverlappingInstallmentPaymentAllocationTest() { + try { + + // Test with + // Enable Down Payment + // Enable Auto Repayment For Down Payment + // Payment Strategy DEFAULT payment allocation strategy "Penalties, Fees, Interest, Principal order" + // Overlapping down payment and regular installment + + // Set business date + LocalDate disbursementDate = LocalDate.of(2023, 03, 3); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = true; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithEnableDownPaymentAndMultipleDisbursements( + loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment); + + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + // create loan account with DEFAULT payment allocation strategy "Penalties, Fees, Interest, Principal order" + + final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr, LoanProductTestBuilder.DEFAULT_STRATEGY); + + // add charge PENALTY with due date as overlapping installment due date + + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true)); + + LocalDate targetDate = LocalDate.of(2023, 4, 3); + final String penaltyCharge1AddedDate = DATE_FORMATTER.format(targetDate); + + Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10")); + + assertNotNull(penalty1LoanChargeId); + + // add charge FEE with due date as overlapping installment due date + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + targetDate = LocalDate.of(2023, 4, 3); + final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate); + + Integer feeLoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), feeCharge1AddedDate, "5.15")); + + assertNotNull(feeLoanChargeId); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // first disbursement + loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "500"); + + // verify down-payment transaction created + checkDownPaymentTransaction(disbursementDate, 125.0f, 0.0f, 0.0f, 0.0f, loanId); + + // verify loan schedule + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // first period [2023-03-03 to 2023-03-03] down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalPaidForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesPaid()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getComplete()); + + // third period [2023-04-03 to 2023-05-03] regular installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(208.33, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // second disbursement with overlapping installment i.e same due date as regular repayment due date + + disbursementDate = LocalDate.of(2023, 04, 3); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + loanTransactionHelper.disburseLoanWithTransactionAmount("03 April 2023", loanId, "1000"); + + checkDownPaymentTransaction(disbursementDate, 234.85f, 0.0f, 5.15f, 10.0f, loanId); + + // verify loan schedule + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify that overlapping down payment installment is created after regular installment + + // and verify regular repayment type installment gets paid before down payment installment according to + // payment + + // allocation strategy [DEFAULT] + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, penalty and fee charges gets paid first + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(265.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] overlapping down payment installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + // make repayment for fully paying and verify that regular installment gets fully paid on 3rd april + + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 April 2023").locale("en") + .transactionAmount(265.15)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify regular installment gets paid off + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, penalty and fee charges gets paid first + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(515.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] overlapping down payment installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + // make another repayment of 250 and verify downpayment installment gets paid off on 3rd april + final PostLoansLoanIdTransactionsResponse repaymentTransaction_2 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 April 2023").locale("en") + .transactionAmount(250.00)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify down payment installment gets paid off + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, penalty and fee charges gets paid first and + // then principal + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(515.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] down payment installment gets paid + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + + } + + @Test + public void loanAccountWithEnableDownPaymentAndDisableAutoRepaymentForDownPaymentWithOverlappingInstallmentPaymentAllocationTest() { + try { + + // Test with + // Enable Down Payment + // Disable Auto Repayment For Down Payment + // Payment Strategy INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY = + // "interest-principal-penalties-fees-order-strategy" + // Overlapping down payment and regular installment + + // Set business date + LocalDate disbursementDate = LocalDate.of(2023, 03, 3); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = false; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithEnableDownPaymentAndMultipleDisbursements( + loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment); + + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + // create loan account with "interest-principal-penalties-fees-order-strategy" + + final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr, LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY); + + // add charge PENALTY with due date as overlapping installment due date + + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true)); + + LocalDate targetDate = LocalDate.of(2023, 4, 3); + final String penaltyCharge1AddedDate = DATE_FORMATTER.format(targetDate); + + Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10")); + + assertNotNull(penalty1LoanChargeId); + + // add charge FEE with due date as overlapping installment due date + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + targetDate = LocalDate.of(2023, 4, 3); + final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate); + + Integer feeLoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), feeCharge1AddedDate, "5.15")); + + assertNotNull(feeLoanChargeId); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // first disbursement + loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "500"); + + // make repayment on 3rd March + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 March 2023").locale("en") + .transactionAmount(125.0)); + + // verify loan schedule + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // first period [2023-03-03 to 2023-03-03] down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalPaidForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesPaid()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getComplete()); + + // third period [2023-04-03 to 2023-05-03] regular installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(208.33, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // second disbursement with overlapping installment i.e same due date as regular repayment due date + + disbursementDate = LocalDate.of(2023, 04, 3); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + loanTransactionHelper.disburseLoanWithTransactionAmount("03 April 2023", loanId, "1000"); + + // make repayment on 3rd April + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 April 2023").locale("en") + .transactionAmount(250.0)); + + // verify loan schedule + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify that overlapping down payment installment is created after regular installment + + // and verify regular repayment type installment gets paid before down payment installment according to + // payment + + // allocation strategy + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, principal gets paid first + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPrincipalPaid()); + assertEquals(265.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] overlapping down payment installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + // make repayment for fully paying and verify that regular installment gets fully paid on 3rd april + + final PostLoansLoanIdTransactionsResponse repaymentTransaction_2 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 April 2023").locale("en") + .transactionAmount(265.15)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify regular installment gets paid off + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, principal gets paid first + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(515.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(500.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPrincipalPaid()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] overlapping down payment installment + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + // make another repayment of 250 and verify downpayment installment gets paid off on 3rd april + final PostLoansLoanIdTransactionsResponse repaymentTransaction_3 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("3 April 2023").locale("en") + .transactionAmount(250.00)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertNotNull(loanDetails.getRepaymentSchedule()); + + // verify down payment installment gets paid off + + // first period [2023-03-03 to 2023-03-03] first down payment installment + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(125.00, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete()); + + // second period [2023-03-03 to 2023-04-03] regular installment, principal gets paid first + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(515.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalPaidForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalOutstandingForPeriod()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesDue()); + assertEquals(5.15, loanDetails.getRepaymentSchedule().getPeriods().get(3).getFeeChargesPaid()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesDue()); + assertEquals(10.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPenaltyChargesPaid()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getComplete()); + + // third period [2023-04-03 to 2023-04-03] down payment installment gets paid + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalPaidForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(4).getComplete()); + + // fourth period [2023-04-03 to 2023-05-03] regular installment + assertEquals(4, loanDetails.getRepaymentSchedule().getPeriods().get(5).getPeriod()); + assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getFromDate()); + assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(5).getDueDate()); + assertEquals(625.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalInstallmentAmountForPeriod()); + assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(5).getTotalPaidForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(5).getComplete()); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + + } + + private Integer createLoanAccountMultipleRepaymentsDisbursement(final Integer clientID, final Long loanProductID, + final String externalId, final String repaymentStartegy) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("3") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("3").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance() + .withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod() + .withExpectedDisbursementDate("03 March 2023").withSubmittedOnDate("03 March 2023").withLoanType("individual") + .withExternalId(externalId).withRepaymentStrategy(repaymentStartegy) + .build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId, null); + return loanId; + } + + private GetLoanProductsProductIdResponse createLoanProductWithEnableDownPaymentAndMultipleDisbursements( + LoanTransactionHelper loanTransactionHelper, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment, + boolean enableAutoRepaymentForDownPayment) { + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() + .withRepaymentAfterEvery("1").withNumberOfRepayments("3").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .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 void checkDownPaymentTransaction(final LocalDate transactionDate, final Float principalPortion, final Float interestPortion, + final Float feePortion, final Float penaltyPortion, final Integer loanID) { + ArrayList transactions = (ArrayList) loanTransactionHelper.getLoanTransactions(this.requestSpec, + this.responseSpec, loanID); + boolean isTransactionFound = false; + for (int i = 0; i < transactions.size(); i++) { + HashMap transactionType = (HashMap) transactions.get(i).get("type"); + boolean isDownPaymentTransaction = (Boolean) transactionType.get("downPayment"); + + if (isDownPaymentTransaction) { + ArrayList downPaymentDateAsArray = (ArrayList) transactions.get(i).get("date"); + LocalDate downPaymentEntryDate = LocalDate.of(downPaymentDateAsArray.get(0), downPaymentDateAsArray.get(1), + downPaymentDateAsArray.get(2)); + + if (transactionDate.isEqual(downPaymentEntryDate)) { + isTransactionFound = true; + assertEquals(principalPortion, Float.valueOf(String.valueOf(transactions.get(i).get("principalPortion"))), + "Mismatch in transaction amounts"); + assertEquals(interestPortion, Float.valueOf(String.valueOf(transactions.get(i).get("interestPortion"))), + "Mismatch in transaction amounts"); + assertEquals(feePortion, Float.valueOf(String.valueOf(transactions.get(i).get("feeChargesPortion"))), + "Mismatch in transaction amounts"); + assertEquals(penaltyPortion, Float.valueOf(String.valueOf(transactions.get(i).get("penaltyChargesPortion"))), + "Mismatch in transaction amounts"); + break; + } + } + } + assertTrue(isTransactionFound, "No Down Payment entries are posted"); + } +}