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 039bca440 FINERACT-1971: Fix wrong due date calculation when loan got 
submitted
039bca440 is described below

commit 039bca440b8194b8da677aa3a24502ecd54c185c
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Apr 23 18:24:00 2024 +0200

    FINERACT-1971: Fix wrong due date calculation when loan got submitted
---
 .../loanschedule/domain/LoanApplicationTerms.java  |   8 +-
 .../service/LoanScheduleAssembler.java             |  48 ++--
 .../integrationtests/BaseLoanIntegrationTest.java  | 146 ++++++-----
 .../integrationtests/LoanDueCalculationTest.java   | 286 +++++++++++++++++++++
 4 files changed, 390 insertions(+), 98 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 94d990250..df7aa3b70 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -431,10 +431,12 @@ public final class LoanApplicationTerms {
         this.variationsDataWrapper = new 
LoanTermVariationsDataWrapper(loanTermVariations);
         this.actualNumberOfRepayments = numberOfRepayments + 
getLoanTermVariations().adjustNumberOfRepayments();
         this.adjustPrincipalForFlatLoans = principal.zero();
-        if (this.calculatedRepaymentsStartingFromDate == null) {
-            this.seedDate = this.expectedDisbursementDate;
+        // We only change the seed date if `repaymentStartingFromDate was 
provided`
+        if (this.repaymentsStartingFromDate == null) {
+            this.seedDate = repaymentStartDateType.isDisbursementDate() ? 
expectedDisbursementDate : submittedOnDate;
         } else {
-            this.seedDate = this.calculatedRepaymentsStartingFromDate;
+            // When we change the seed date we are taking the 
`repaymentsStartingFromDate`
+            this.seedDate = repaymentsStartingFromDate;
         }
         this.calendarHistoryDataWrapper = calendarHistoryDataWrapper;
         this.isInterestChargedFromDateSameAsDisbursalDateEnabled = 
isInterestChargedFromDateSameAsDisbursalDateEnabled;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index ba296caf0..3ddff00e3 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -274,9 +274,16 @@ public class LoanScheduleAssembler {
          * If user has not passed the first repayments date then then derive 
the same based on loan type.
          */
         if (calculatedRepaymentsStartingFromDate == null) {
+            LocalDate tmpCalculatedRepaymentsStartingFromDate = 
deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
+                    repaymentPeriodFrequencyType, 0, calendar, 
submittedOnDate, repaymentStartDateType);
             calculatedRepaymentsStartingFromDate = 
deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
                     repaymentPeriodFrequencyType, 
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar, 
submittedOnDate,
                     repaymentStartDateType);
+            // If calculated repayment start date does not match due to 
minimum days between disbursal and first
+            // repayment rule, we set repaymentsStartingFromDate (which will 
be used as seed date later)
+            if 
(!tmpCalculatedRepaymentsStartingFromDate.equals(calculatedRepaymentsStartingFromDate))
 {
+                repaymentsStartingFromDate = 
calculatedRepaymentsStartingFromDate;
+            }
         }
 
         /*
@@ -1102,13 +1109,12 @@ public class LoanScheduleAssembler {
             final RepaymentStartDateType repaymentStartDateType) {
         LocalDate derivedFirstRepayment = null;
 
-        final LocalDate 
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(
-                repaymentStartDateType) ? 
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment) 
: submittedOnDate;
-
+        final LocalDate 
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = 
expectedDisbursementDate
+                .plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+        final LocalDate seedDate = repaymentStartDateType.isDisbursementDate() 
? expectedDisbursementDate : submittedOnDate;
         if (calendar != null) {
-            derivedFirstRepayment = 
deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, 
expectedDisbursementDate,
-                    repaymentPeriodFrequencyType, 
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
-                    repaymentStartDateType);
+            derivedFirstRepayment = 
deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, 
seedDate,
+                    repaymentPeriodFrequencyType, 
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate);
         } else { // Individual or group account, or JLG not linked to a meeting
             LocalDate dateBasedOnRepaymentFrequency;
             // Derive the first repayment date as greater date among
@@ -1116,25 +1122,13 @@ public class LoanScheduleAssembler {
             // (disbursement date + minimum between disbursal and first
             // repayment )
             if (repaymentPeriodFrequencyType.isDaily()) {
-                dateBasedOnRepaymentFrequency = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                        ? expectedDisbursementDate.plusDays(repaymentEvery)
-                        : submittedOnDate.plusDays(repaymentEvery);
-
+                dateBasedOnRepaymentFrequency = 
seedDate.plusDays(repaymentEvery);
             } else if (repaymentPeriodFrequencyType.isWeekly()) {
-                dateBasedOnRepaymentFrequency = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                        ? expectedDisbursementDate.plusWeeks(repaymentEvery)
-                        : submittedOnDate.plusWeeks(repaymentEvery);
-
+                dateBasedOnRepaymentFrequency = 
seedDate.plusWeeks(repaymentEvery);
             } else if (repaymentPeriodFrequencyType.isMonthly()) {
-                dateBasedOnRepaymentFrequency = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                        ? expectedDisbursementDate.plusMonths(repaymentEvery)
-                        : submittedOnDate.plusMonths(repaymentEvery);
-
+                dateBasedOnRepaymentFrequency = 
seedDate.plusMonths(repaymentEvery);
             } else { // yearly loan
-                dateBasedOnRepaymentFrequency = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                        ? expectedDisbursementDate.plusYears(repaymentEvery)
-                        : submittedOnDate.plusYears(repaymentEvery);
-
+                dateBasedOnRepaymentFrequency = 
seedDate.plusYears(repaymentEvery);
             }
             derivedFirstRepayment = 
DateUtils.isAfter(dateBasedOnRepaymentFrequency,
                     dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? 
dateBasedOnRepaymentFrequency
@@ -1146,20 +1140,16 @@ public class LoanScheduleAssembler {
 
     private LocalDate deriveFirstRepaymentDateForLoans(final Integer 
repaymentEvery, final LocalDate expectedDisbursementDate,
             final LocalDate refernceDateForCalculatingFirstRepaymentDate, 
final PeriodFrequencyType repaymentPeriodFrequencyType,
-            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final 
Calendar calendar, final LocalDate submittedOnDate,
-            final RepaymentStartDateType repaymentStartDateType) {
+            final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final 
Calendar calendar, final LocalDate submittedOnDate) {
         boolean isMeetingSkipOnFirstDayOfMonth = 
configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
         int numberOfDays = 
configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
         final String frequency = 
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
         final LocalDate derivedFirstRepayment = 
CalendarUtils.getFirstRepaymentMeetingDate(calendar,
                 refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, 
frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
-        final LocalDate minimumFirstRepaymentDate = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                ? 
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment)
-                : submittedOnDate;
+        final LocalDate minimumFirstRepaymentDate = 
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
         return DateUtils.isBefore(minimumFirstRepaymentDate, 
derivedFirstRepayment) ? derivedFirstRepayment
                 : deriveFirstRepaymentDateForLoans(repaymentEvery, 
expectedDisbursementDate, derivedFirstRepayment,
-                        repaymentPeriodFrequencyType, 
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
-                        repaymentStartDateType);
+                        repaymentPeriodFrequencyType, 
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate);
     }
 
     private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final 
LocalDate disbursalDate, final LocalDate firstRepaymentDate,
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 e78b4bba7..0a6bfeee0 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
@@ -91,6 +91,7 @@ import 
org.apache.fineract.integrationtests.common.system.CodeHelper;
 import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
 import 
org.apache.fineract.integrationtests.useradministration.users.UserHelper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 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;
@@ -102,37 +103,21 @@ import org.junit.jupiter.api.extension.ExtendWith;
 @ExtendWith(LoanTestLifecycleExtension.class)
 public abstract class BaseLoanIntegrationTest {
 
+    protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
+
     static {
         Utils.initializeRESTAssured();
     }
 
-    protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
-
     protected final ResponseSpecification responseSpec = 
createResponseSpecification(Matchers.is(200));
     protected final ResponseSpecification responseSpec204 = 
createResponseSpecification(Matchers.is(204));
-
+    protected final LoanProductHelper loanProductHelper = new 
LoanProductHelper();
     private final String fullAdminAuthKey = getFullAdminAuthKey();
-
     protected final RequestSpecification requestSpec = 
createRequestSpecification(fullAdminAuthKey);
     private final String nonByPassUserAuthKey = 
getNonByPassUserAuthKey(requestSpec, responseSpec);
-
     protected final AccountHelper accountHelper = new 
AccountHelper(requestSpec, responseSpec);
-    protected final LoanTransactionHelper loanTransactionHelper = new 
LoanTransactionHelper(requestSpec, responseSpec);
-    protected final LoanProductHelper loanProductHelper = new 
LoanProductHelper();
-    protected JournalEntryHelper journalEntryHelper = new 
JournalEntryHelper(requestSpec, responseSpec);
-    protected ClientHelper clientHelper = new ClientHelper(requestSpec, 
responseSpec);
-    protected SchedulerJobHelper schedulerJobHelper = new 
SchedulerJobHelper(requestSpec);
-    protected final InlineLoanCOBHelper inlineLoanCOBHelper = new 
InlineLoanCOBHelper(requestSpec, responseSpec);
-
-    protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
-
-    protected final LoanAccountLockHelper loanAccountLockHelper = new 
LoanAccountLockHelper(requestSpec,
-            createResponseSpecification(Matchers.is(202)));
-    protected DateTimeFormatter dateTimeFormatter = 
DateTimeFormatter.ofPattern(DATETIME_PATTERN);
-
     // asset
     protected final Account loansReceivableAccount = 
accountHelper.createAssetAccount("loanPortfolio");
-
     protected final Account interestReceivableAccount = 
accountHelper.createAssetAccount("interestReceivable");
     protected final Account feeReceivableAccount = 
accountHelper.createAssetAccount("feeReceivable");
     protected final Account penaltyReceivableAccount = 
accountHelper.createAssetAccount("penaltyReceivable");
@@ -146,7 +131,6 @@ public abstract class BaseLoanIntegrationTest {
     protected final Account penaltyIncomeAccount = 
accountHelper.createIncomeAccount("penaltyIncome");
     protected final Account feeChargeOffAccount = 
accountHelper.createIncomeAccount("feeChargeOff");
     protected final Account penaltyChargeOffAccount = 
accountHelper.createIncomeAccount("penaltyChargeOff");
-
     protected final Account recoveriesAccount = 
accountHelper.createIncomeAccount("recoveries");
     protected final Account interestIncomeChargeOffAccount = 
accountHelper.createIncomeAccount("interestIncomeChargeOff");
     // expense
@@ -154,6 +138,61 @@ public abstract class BaseLoanIntegrationTest {
     protected final Account chargeOffFraudExpenseAccount = 
accountHelper.createExpenseAccount("chargeOffFraud");
     protected final Account writtenOffAccount = 
accountHelper.createExpenseAccount();
     protected final Account goodwillExpenseAccount = 
accountHelper.createExpenseAccount();
+    protected final LoanTransactionHelper loanTransactionHelper = new 
LoanTransactionHelper(requestSpec, responseSpec);
+    protected JournalEntryHelper journalEntryHelper = new 
JournalEntryHelper(requestSpec, responseSpec);
+    protected ClientHelper clientHelper = new ClientHelper(requestSpec, 
responseSpec);
+    protected SchedulerJobHelper schedulerJobHelper = new 
SchedulerJobHelper(requestSpec);
+    protected final InlineLoanCOBHelper inlineLoanCOBHelper = new 
InlineLoanCOBHelper(requestSpec, responseSpec);
+    protected final LoanAccountLockHelper loanAccountLockHelper = new 
LoanAccountLockHelper(requestSpec,
+            createResponseSpecification(Matchers.is(202)));
+    protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
+    protected DateTimeFormatter dateTimeFormatter = 
DateTimeFormatter.ofPattern(DATETIME_PATTERN);
+
+    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, LocalDate dueDate, double principalDue,
+            double principalPaid, double principalOutstanding, double 
paidInAdvance, double paidLate) {
+        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
+                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
+        assertEquals(dueDate, period.getDueDate());
+        assertEquals(principalDue, period.getPrincipalDue());
+        assertEquals(principalPaid, period.getPrincipalPaid());
+        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
+        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
+        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
+    }
+
+    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, double principalDue,
+            double principalPaid, double principalOutstanding, double 
paidInAdvance, double paidLate) {
+        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
+                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
+        assertEquals(principalDue, period.getPrincipalDue());
+        assertEquals(principalPaid, period.getPrincipalPaid());
+        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
+        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
+        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
+    }
+
+    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, LocalDate dueDate, double principalDue,
+            double principalPaid, double principalOutstanding, double feeDue, 
double feePaid, double feeOutstanding, double penaltyDue,
+            double penaltyPaid, double penaltyOutstanding, double interestDue, 
double interestPaid, double interestOutstanding,
+            double paidInAdvance, double paidLate) {
+        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
+                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
+        assertEquals(dueDate, period.getDueDate());
+        assertEquals(principalDue, period.getPrincipalDue());
+        assertEquals(principalPaid, period.getPrincipalPaid());
+        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
+        assertEquals(feeDue, period.getFeeChargesDue());
+        assertEquals(feePaid, period.getFeeChargesPaid());
+        assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
+        assertEquals(penaltyDue, period.getPenaltyChargesDue());
+        assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
+        assertEquals(penaltyOutstanding, 
period.getPenaltyChargesOutstanding());
+        assertEquals(interestDue, period.getInterestDue());
+        assertEquals(interestPaid, period.getInterestPaid());
+        assertEquals(interestOutstanding, period.getInterestOutstanding());
+        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
+        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
+    }
 
     private String getNonByPassUserAuthKey(RequestSpecification requestSpec, 
ResponseSpecification responseSpec) {
         // creates the user
@@ -287,6 +326,27 @@ public abstract class BaseLoanIntegrationTest {
         return advancedPaymentData;
     }
 
+    protected PostLoanProductsRequest 
create4Period1MonthLongWithoutInterestProduct(String repaymentStrategy) {
+        PostLoanProductsRequest productRequest = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)//
+                .disallowExpectedDisbursements(false)//
+                .allowApprovedDisbursedAmountsOverApplied(false)//
+                .overAppliedCalculationType(null)//
+                .overAppliedNumber(null)//
+                .principal(1000.0)//
+                .numberOfRepayments(4)//
+                .repaymentEvery(1)//
+                
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
+                .transactionProcessingStrategyCode(repaymentStrategy)//
+        ;
+        if 
(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(repaymentStrategy))
 {
+            
productRequest.loanScheduleType("PROGRESSIVE").loanScheduleProcessingType("HORIZONTAL")
+                    
.addPaymentAllocationItem(createDefaultPaymentAllocation("NEXT_INSTALLMENT"));
+        } else {
+            
productRequest.loanScheduleType("CUMULATIVE").loanScheduleProcessingType(null).paymentAllocation(null);
+        }
+        return productRequest;
+    }
+
     protected PostLoanProductsRequest 
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
             int interestType, int amortizationType) {
         return 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)//
@@ -773,52 +833,6 @@ public abstract class BaseLoanIntegrationTest {
         assertEquals(totalOverpaid, loanDetails.getTotalOverpaid());
     }
 
-    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, LocalDate dueDate, double principalDue,
-            double principalPaid, double principalOutstanding, double 
paidInAdvance, double paidLate) {
-        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
-                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
-        assertEquals(dueDate, period.getDueDate());
-        assertEquals(principalDue, period.getPrincipalDue());
-        assertEquals(principalPaid, period.getPrincipalPaid());
-        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
-        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
-        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
-    }
-
-    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, double principalDue,
-            double principalPaid, double principalOutstanding, double 
paidInAdvance, double paidLate) {
-        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
-                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
-        assertEquals(principalDue, period.getPrincipalDue());
-        assertEquals(principalPaid, period.getPrincipalPaid());
-        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
-        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
-        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
-    }
-
-    protected static void validateRepaymentPeriod(GetLoansLoanIdResponse 
loanDetails, Integer index, LocalDate dueDate, double principalDue,
-            double principalPaid, double principalOutstanding, double feeDue, 
double feePaid, double feeOutstanding, double penaltyDue,
-            double penaltyPaid, double penaltyOutstanding, double interestDue, 
double interestPaid, double interestOutstanding,
-            double paidInAdvance, double paidLate) {
-        GetLoansLoanIdRepaymentPeriod period = 
loanDetails.getRepaymentSchedule().getPeriods().stream()
-                .filter(p -> Objects.equals(p.getPeriod(), 
index)).findFirst().orElseThrow();
-        assertEquals(dueDate, period.getDueDate());
-        assertEquals(principalDue, period.getPrincipalDue());
-        assertEquals(principalPaid, period.getPrincipalPaid());
-        assertEquals(principalOutstanding, period.getPrincipalOutstanding());
-        assertEquals(feeDue, period.getFeeChargesDue());
-        assertEquals(feePaid, period.getFeeChargesPaid());
-        assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
-        assertEquals(penaltyDue, period.getPenaltyChargesDue());
-        assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
-        assertEquals(penaltyOutstanding, 
period.getPenaltyChargesOutstanding());
-        assertEquals(interestDue, period.getInterestDue());
-        assertEquals(interestPaid, period.getInterestPaid());
-        assertEquals(interestOutstanding, period.getInterestOutstanding());
-        assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
-        assertEquals(paidLate, period.getTotalPaidLateForPeriod());
-    }
-
     protected void checkMaturityDates(long loanId, LocalDate 
expectedMaturityDate, LocalDate actualMaturityDate) {
         GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java
new file mode 100644
index 000000000..132ab6386
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDueCalculationTest.java
@@ -0,0 +1,286 @@
+/**
+ * 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.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.stream.Stream;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class LoanDueCalculationTest extends BaseLoanIntegrationTest {
+
+    private static Stream<Arguments> processingStrategy() {
+        return Stream.of(
+                Arguments.of(Named.of("originalStrategy",
+                        
DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)),
 //
+                Arguments.of(Named.of("advancedStrategy", 
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY))
 //
+        );
+    }
+
+    // Repayment dates are calculated from the provided date (2024-02-29). As 
repayment starting date was provided, it
+    // overrules `repayment start date type` configuration
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void dueDateBasedOnFirstRepaymentDate(String repaymentProcessor) {
+        runAt("2 February 2024", () -> {
+            // Client and Loan account creation
+            final Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest loanProductsRequest = 
create4Period1MonthLongWithoutInterestProduct(repaymentProcessor);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            PostLoansRequest loanRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4,
+                    (postLoansRequest) -> {
+                        
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
+                                
.loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd")
+                                .repaymentsStartingFromDate(LocalDate.of(2024, 
2, 29));
+                    });
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(loanRequest);
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "29 March 2024"), //
+                    installment(250.0, false, "29 April 2024"), //
+                    installment(250.0, false, "29 May 2024")) //
+            ;
+
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(1000.0, "31 January 2024"));
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "29 March 2024"), //
+                    installment(250.0, false, "29 April 2024"), //
+                    installment(250.0, false, "29 May 2024")) //
+            ;
+
+            disburseLoan(postLoansResponse.getLoanId(), 
BigDecimal.valueOf(1000.00), "31 January 2024");
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "29 March 2024"), //
+                    installment(250.0, false, "29 April 2024"), //
+                    installment(250.0, false, "29 May 2024")) //
+            ;
+
+        });
+    }
+
+    // Repayment dates are calculated based on `repayment start date type` 
configuration(=Expected disbursement date).
+    // Expected disbursement date `2024-01-30`,
+    // which is used to generate repayment due date when loan got submitted 
and approved, however the loan got disbursed
+    // on `2024-01-31`,
+    // the repayment schedule reflects the "new date" after it got disbursed
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void dueDateBasedOnExpectedDisbursementDate(String 
repaymentProcessor) {
+        runAt("31 March 2024", () -> {
+            // Client and Loan account creation
+            final Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest loanProductsRequest = 
create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
+                    
.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue());
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            PostLoansRequest loanRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "2024-01-30", 1000.0, 4,
+                    (postLoansRequest) -> {
+                        
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
+                                
.loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd");
+                    });
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(loanRequest);
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "30 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "30 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "30 May 2024")) //
+            ;
+
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(1000.0, "31 January 2024"));
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "30 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "30 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "30 May 2024")) //
+            ;
+
+            disburseLoan(postLoansResponse.getLoanId(), 
BigDecimal.valueOf(1000.00), "31 March 2024");
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "31 May 2024"), //
+                    installment(250.0, false, "30 June 2024"), //
+                    installment(250.0, false, "31 July 2024")) //
+            ;
+        });
+    }
+
+    // Repayment dates are calculated based on `repayment start date type` 
configuration(=Submitted on date). Submitted
+    // on date is `2024-01-31`,
+    // and even the expected disbursement date is `2024-02-01`, the generated 
repayment schedule honors the submitted on
+    // date
+    // when it got disbursed on `2024-02-03`, the repayment schedule due dates 
got no changed.
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) {
+        runAt("03 February 2024", () -> {
+            // Client and Loan account creation
+            final Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest loanProductsRequest = 
create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
+                    
.repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue());
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            PostLoansRequest loanRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "2024-02-01", 1000.0, 4,
+                    (postLoansRequest) -> {
+                        
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
+                                
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
+                    });
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(loanRequest);
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "31 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "31 May 2024")) //
+            ;
+
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(1000.0, "31 January 2024"));
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "31 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "31 May 2024")) //
+            ;
+
+            disburseLoan(postLoansResponse.getLoanId(), 
BigDecimal.valueOf(1000.00), "03 February 2024");
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "01 February 2024"), //
+                    installment(250.0, false, "29 February 2024"), //
+                    installment(250.0, false, "31 March 2024"), //
+                    installment(250.0, false, "30 April 2024"), //
+                    installment(250.0, false, "31 May 2024")) //
+            ;
+        });
+    }
+
+    // Repayment dates are calculated based on `repayment start date type` 
configuration(=Submitted on date). Submitted
+    // on date is `2024-01-31 the expected disbursement date is `2024-02-26`, 
the minimum days between disbursement and
+    // first repayment is 10 days
+    // so the repayment schedule got amended accordingly
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void 
dueDateBasedOnSubmittedOnDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment(String
 repaymentProcessor) {
+        runAt("31 January 2024", () -> {
+            // Client and Loan account creation
+            final Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest loanProductsRequest = 
create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
+                    
.repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue())
+                    .minimumDaysBetweenDisbursalAndFirstRepayment(10);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            PostLoansRequest loanRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "2024-02-26", 1000.0, 4,
+                    (postLoansRequest) -> {
+                        
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
+                                
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
+                    });
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(loanRequest);
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(1000.0, "31 January 2024"));
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+
+            disburseLoan(postLoansResponse.getLoanId(), 
BigDecimal.valueOf(1000.00), "31 January 2024");
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+        });
+    }
+
+    // Repayment dates are calculated based on `repayment start date type` 
configuration(=Disbursement date). Submitted
+    // on date is `2024-01-31 the expected disbursement date is `2024-02-26`, 
the minimum days between disbursement and
+    // first repayment is 36 days
+    // so the repayment schedule got amended accordingly
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void 
dueDateBasedOnExpectedDisbursalDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment(
+            String repaymentProcessor) {
+        runAt("31 January 2024", () -> {
+            // Client and Loan account creation
+            final Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest loanProductsRequest = 
create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
+                    
.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue())
+                    .minimumDaysBetweenDisbursalAndFirstRepayment(36);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            PostLoansRequest loanRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4,
+                    (postLoansRequest) -> {
+                        
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
+                                
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
+                    });
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(loanRequest);
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+
+            
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), 
approveLoanRequest(1000.0, "31 January 2024"));
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+
+            disburseLoan(postLoansResponse.getLoanId(), 
BigDecimal.valueOf(1000.00), "31 January 2024");
+
+            verifyRepaymentSchedule(postLoansResponse.getLoanId(), 
installment(1000.0, null, "31 January 2024"), //
+                    installment(250.0, false, "07 March 2024"), //
+                    installment(250.0, false, "07 April 2024"), //
+                    installment(250.0, false, "07 May 2024"), //
+                    installment(250.0, false, "07 June 2024")) //
+            ;
+        });
+    }
+}


Reply via email to