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 b64b4b710 FINERACT-2065: Schedule handling for fixed length 
configuration
b64b4b710 is described below

commit b64b4b710528b37680f3866feb64389fe7159901
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Tue Mar 19 00:20:24 2024 -0600

    FINERACT-2065: Schedule handling for fixed length configuration
---
 .../loanschedule/domain/LoanApplicationTerms.java  |  39 +++
 .../domain/DefaultScheduledDateGenerator.java      |  17 +-
 .../domain/LoanScheduleModelRepaymentPeriod.java   |   2 +
 ...alculateLoanScheduleQueryFromApiJsonHelper.java |   3 +-
 ...ationWritePlatformServiceJpaRepositoryImpl.java |   5 +-
 .../starter/LoanAccountConfiguration.java          |   4 +-
 .../serialization/LoanProductDataValidator.java    |  65 ++++-
 ...PaymentAllocationLoanRepaymentScheduleTest.java | 277 +++++++++++++++++++++
 .../FixedLengthLoanProductIntegrationTest.java     |  53 ++--
 .../fineract/integrationtests/common/Utils.java    |  15 ++
 .../common/loans/LoanTransactionHelper.java        |   5 +
 11 files changed, 453 insertions(+), 32 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 32f855433..e9d38c438 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
@@ -1832,4 +1832,43 @@ public final class LoanApplicationTerms {
     public Money getDownPaymentAmount() {
         return downPaymentAmount;
     }
+
+    public Integer getFixedLength() {
+        return fixedLength;
+    }
+
+    public LocalDate calculateMaxDateForFixedLength() {
+        final LocalDate startDate = getRepaymentStartDate();
+        LocalDate maxDateForFixedLength = null;
+        if (fixedLength == null) {
+            return maxDateForFixedLength;
+        }
+        switch (repaymentPeriodFrequencyType) {
+            case DAYS:
+                maxDateForFixedLength = startDate.plusDays(fixedLength);
+            break;
+            case WEEKS:
+                maxDateForFixedLength = startDate.plusWeeks(fixedLength);
+            break;
+            case MONTHS:
+                maxDateForFixedLength = startDate.plusMonths(fixedLength);
+            break;
+            case YEARS:
+                maxDateForFixedLength = startDate.plusYears(fixedLength);
+            break;
+            case INVALID:
+            break;
+            case WHOLE_TERM:
+                log.error("TODO Implement repaymentPeriodFrequencyType for 
WHOLE_TERM");
+            break;
+        }
+        return maxDateForFixedLength;
+    }
+
+    public LocalDate getRepaymentStartDate() {
+        final RepaymentStartDateType repaymentStartDateType = 
getRepaymentStartDateType();
+        return 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType) ? 
getExpectedDisbursementDate()
+                : getSubmittedOnDate();
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 5108bff37..fbf889e7a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -34,7 +34,6 @@ import 
org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
-import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -46,16 +45,16 @@ public class DefaultScheduledDateGenerator implements 
ScheduledDateGenerator {
 
         final int numberOfRepayments = 
loanApplicationTerms.getNumberOfRepayments();
 
-        RepaymentStartDateType repaymentStartDateType = 
loanApplicationTerms.getRepaymentStartDateType();
-
-        LocalDate lastRepaymentDate = 
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
-                ? loanApplicationTerms.getExpectedDisbursementDate()
-                : loanApplicationTerms.getSubmittedOnDate();
+        LocalDate lastRepaymentDate = 
loanApplicationTerms.getRepaymentStartDate();
         boolean isFirstRepayment = true;
         for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; 
repaymentPeriod++) {
             lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, 
loanApplicationTerms, isFirstRepayment);
             isFirstRepayment = false;
         }
+        if (loanApplicationTerms.getFixedLength() != null
+                && 
loanApplicationTerms.calculateMaxDateForFixedLength().compareTo(lastRepaymentDate)
 != 0) {
+            lastRepaymentDate = 
loanApplicationTerms.calculateMaxDateForFixedLength();
+        }
         lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, 
loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
         return lastRepaymentDate;
     }
@@ -104,6 +103,12 @@ public class DefaultScheduledDateGenerator implements 
ScheduledDateGenerator {
             }
         }
 
+        final LocalDate maxDateForFixedLength = 
loanApplicationTerms.calculateMaxDateForFixedLength();
+        // Fixed Length validation
+        if (maxDateForFixedLength != null && 
DateUtils.isAfter(dueRepaymentPeriodDate, maxDateForFixedLength)) {
+            dueRepaymentPeriodDate = maxDateForFixedLength;
+        }
+
         return dueRepaymentPeriodDate;
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
index af13f3f3d..a3db61f20 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.HashSet;
 import java.util.Set;
+import lombok.Getter;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
@@ -29,6 +30,7 @@ import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
 /**
  * Domain representation of a Loan Schedule Repayment Period (not used for 
persistence)
  */
+@Getter
 public final class LoanScheduleModelRepaymentPeriod implements 
LoanScheduleModelPeriod {
 
     private final int periodNumber;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
index 8252e309d..affcf2284 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
@@ -70,7 +70,8 @@ public final class 
CalculateLoanScheduleQueryFromApiJsonHelper {
             LoanApiConstants.interestRateDifferentialParameterName, 
LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
             LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, 
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose,
             LoanApiConstants.datatables, 
LoanApiConstants.isEqualAmortizationParam, 
LoanProductConstants.RATES_PARAM_NAME,
-            LoanApiConstants.daysInYearTypeParameterName, 
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
+            LoanApiConstants.daysInYearTypeParameterName, 
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
+            LoanProductConstants.FIXED_LENGTH));
 
     private final FromJsonHelper fromApiJsonHelper;
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index 2759122b0..96ab3a928 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -196,6 +196,7 @@ public class 
LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
     private final LoanRepository loanRepository;
     private final GSIMReadPlatformService gsimReadPlatformService;
     private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
+    private final LoanProductDataValidator loanProductDataValidator;
 
     @Transactional
     @Override
@@ -253,6 +254,9 @@ public class 
LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
             }
 
             final Loan newLoanApplication = 
this.loanAssembler.assembleFrom(command);
+            final LoanApplicationTerms loanApplicationTerms = 
this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson());
+            
loanProductDataValidator.fixedLengthValidations(newLoanApplication.getTransactionProcessingStrategyCode(),
 loanApplicationTerms,
+                    this.fromJsonHelper.parse(command.json()), 
baseDataValidator);
 
             checkForProductMixRestrictions(newLoanApplication);
 
@@ -438,7 +442,6 @@ public class 
LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
                         CalendarEntityType.LOANS.getValue());
                 this.calendarInstanceRepository.save(calendarInstance);
             } else {
-                final LoanApplicationTerms loanApplicationTerms = 
this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson());
                 final Integer repaymentFrequencyNthDayType = 
command.integerValueOfParameterNamed("repaymentFrequencyNthDayType");
                 if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == 
PeriodFrequencyType.MONTHS
                         && repaymentFrequencyNthDayType != null) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index a9cacb79d..501e98582 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -231,7 +231,7 @@ public class LoanAccountConfiguration {
 
             RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService 
glimAccountInfoWritePlatformService,
             GLIMAccountInfoRepository glimRepository, LoanRepository 
loanRepository, GSIMReadPlatformService gsimReadPlatformService,
-            LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
+            LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, 
LoanProductDataValidator loanProductDataValidator) {
         return new 
LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, 
loanApplicationTransitionApiJsonValidator,
                 loanProductCommandFromApiJsonDeserializer, 
fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository,
                 calculationPlatformService, loanAssembler, clientRepository, 
loanProductRepository, loanChargeAssembler,
@@ -241,7 +241,7 @@ public class LoanAccountConfiguration {
                 configurationDomainService, loanScheduleAssembler, 
loanUtilService, calendarReadPlatformService,
                 entityDatatableChecksWritePlatformService, 
globalConfigurationRepository, entityMappingRepository,
                 fineractEntityRelationRepository, 
loanProductReadPlatformService, rateAssembler, 
glimAccountInfoWritePlatformService,
-                glimRepository, loanRepository, gsimReadPlatformService, 
defaultLoanLifecycleStateMachine);
+                glimRepository, loanRepository, gsimReadPlatformService, 
defaultLoanLifecycleStateMachine, loanProductDataValidator);
     }
 
     @Bean
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 625c7de60..433fcf630 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -39,6 +39,7 @@ import 
org.apache.fineract.accounting.common.AccountingValidations;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
 import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -47,6 +48,7 @@ import 
org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
@@ -399,6 +401,7 @@ public final class LoanProductDataValidator {
         }
 
         // interest rates
+        BigDecimal interestRatePerPeriod = null;
         if 
(this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, 
element)
                 && 
this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES,
 element)) {
             if (isEqualAmortization) {
@@ -519,8 +522,7 @@ public final class LoanProductDataValidator {
                         "isFloatingInterestRateCalculationAllowed param is not 
supported when isLinkedToFloatingInterestRates is not supplied or false");
             }
 
-            final BigDecimal interestRatePerPeriod = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD,
-                    element);
+            interestRatePerPeriod = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD,
 element);
             
baseDataValidator.reset().parameter(INTEREST_RATE_PER_PERIOD).value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
 
             final String minInterestRatePerPeriodParameterName = 
MIN_INTEREST_RATE_PER_PERIOD;
@@ -564,6 +566,10 @@ public final class LoanProductDataValidator {
                     4);
         }
 
+        // Fixed Length validation
+        fixedLengthValidations(transactionProcessingStrategyCode, 
interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
+                baseDataValidator);
+
         // Guarantee Funds
         Boolean holdGuaranteeFunds = false;
         if 
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName,
 element)) {
@@ -1238,13 +1244,15 @@ public final class LoanProductDataValidator {
                     .integerGreaterThanZero();
         }
 
+        Integer numberOfRepayments = loanProduct.getNumberOfRepayments();
         if (this.fromApiJsonHelper.parameterExists(NUMBER_OF_REPAYMENTS, 
element)) {
-            final Integer numberOfRepayments = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(NUMBER_OF_REPAYMENTS, 
element);
+            numberOfRepayments = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(NUMBER_OF_REPAYMENTS, 
element);
             
baseDataValidator.reset().parameter(NUMBER_OF_REPAYMENTS).value(numberOfRepayments).notNull().integerGreaterThanZero();
         }
 
+        Integer repaymentEvery = 
loanProduct.getLoanProductRelatedDetail().getRepayEvery();
         if (this.fromApiJsonHelper.parameterExists(REPAYMENT_EVERY, element)) {
-            final Integer repaymentEvery = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(REPAYMENT_EVERY, element);
+            repaymentEvery = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(REPAYMENT_EVERY, element);
             
baseDataValidator.reset().parameter(REPAYMENT_EVERY).value(repaymentEvery).notNull().integerGreaterThanZero();
         }
 
@@ -1364,6 +1372,7 @@ public final class LoanProductDataValidator {
         }
 
         // interest rates
+        BigDecimal interestRatePerPeriod = null;
         boolean isLinkedToFloatingInterestRates = 
loanProduct.isLinkedToFloatingInterestRate();
         if 
(this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, 
element)) {
             isLinkedToFloatingInterestRates = 
this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES,
 element);
@@ -1531,7 +1540,7 @@ public final class LoanProductDataValidator {
             
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
                     .zeroOrPositiveAmount();
 
-            BigDecimal interestRatePerPeriod = 
loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
+            interestRatePerPeriod = 
loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
             if 
(this.fromApiJsonHelper.parameterExists(INTEREST_RATE_PER_PERIOD, element)) {
                 interestRatePerPeriod = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD,
 element);
             }
@@ -1546,6 +1555,10 @@ public final class LoanProductDataValidator {
                     4);
         }
 
+        // Fixed Length validation
+        fixedLengthValidations(transactionProcessingStrategyCode, 
interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
+                baseDataValidator);
+
         // Guarantee Funds
         Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled();
         if 
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName,
 element)) {
@@ -2394,4 +2407,46 @@ public final class LoanProductDataValidator {
 
         }
     }
+
+    public void fixedLengthValidations(final String 
transactionProcessingStrategyCode, final LoanApplicationTerms 
loanApplicationTerms,
+            final JsonElement element, final DataValidatorBuilder 
baseDataValidator) {
+        fixedLengthValidations(transactionProcessingStrategyCode, 
loanApplicationTerms.getAnnualNominalInterestRate(),
+                loanApplicationTerms.getNumberOfRepayments(), 
loanApplicationTerms.getRepaymentEvery(), element, baseDataValidator);
+    }
+
+    public void fixedLengthValidations(final String 
transactionProcessingStrategyCode, final BigDecimal interestRatePerPeriod,
+            final Integer numberOfRepayments, final Integer repayEvery, final 
JsonElement element,
+            final DataValidatorBuilder baseDataValidator) {
+        if 
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, 
element)) {
+            final JsonObject topLevelJsonElement = element.getAsJsonObject();
+            final Locale locale = 
this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+            final Integer fixedLength = 
this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.FIXED_LENGTH, 
element, locale);
+            
baseDataValidator.reset().parameter(LoanProductConstants.FIXED_LENGTH).value(fixedLength).ignoreIfNull()
+                    .integerGreaterThanZero();
+
+            if (fixedLength != null) {
+                if 
(!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+                        .equals(transactionProcessingStrategyCode)) {
+                    final String errorMsg = "Fixed Length configuration is 
only allowed with "
+                            + 
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
 + " stratefy";
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.advanced.payment.allocation",
+                            errorMsg, 
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+                }
+
+                if (interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0) {
+                    final String errorMsg = "Fixed Length configuration is 
only allowed for zero interest products";
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest",
 errorMsg,
+                            interestRatePerPeriod);
+                }
+
+                final Integer valueToCompare = ((numberOfRepayments - 1) * 
repayEvery) + 1;
+                if (fixedLength.compareTo(valueToCompare) < 0) {
+                    final String errorMsg = "Wrong configuration between 
Number Of Repayments: " + numberOfRepayments + " * " + repayEvery
+                            + " and Fixed Length: " + fixedLength + " values";
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.number.repayments.and.fixed.length.configuration.not.valid",
+                            errorMsg, numberOfRepayments, repayEvery, 
valueToCompare, fixedLength);
+                }
+            }
+        }
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 59bc1a45d..9cf4c6791 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -3542,6 +3542,283 @@ public class 
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
         });
     }
 
+    // UC126: Advanced payment allocation with Fixed Length for 40 days and 
Loan Term for 45 days (3 repayments every 15
+    // days)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc126() {
+        runAt("22 November 2023", () -> {
+            final Integer fixedLength = 40; // 40 days
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(3).repaymentEvery(15).fixedLength(fixedLength);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "22 November 2023",
+                    1000.0, 4);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(3).loanTermFrequency(45)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("22 November 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("22 
November 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 100.0, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 12, 7), 
33.0, 0.0, 33.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 
22), 33.0, 0.0, 33.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 1, 01), 
34.0, 0.0, 34.0, 0.0, 0.0);
+            assertEquals(loanDetails.getNumberOfRepayments(), 3);
+            assertEquals(
+                    
Utils.getDifferenceInDays(loanDetails.getTimeline().getActualDisbursementDate(),
 loanDetails.getRepaymentSchedule()
+                            
.getPeriods().get(loanDetails.getRepaymentSchedule().getPeriods().size() - 
1).getDueDate()),
+                    fixedLength.longValue());
+            assertTrue(loanDetails.getStatus().getActive());
+        });
+    }
+
+    // UC127: Advanced payment allocation with Fixed Length for 5 weeks and 
Loan Term for 6 weeks (3 repayments every 2
+    // weeks)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc127() {
+        runAt("22 November 2023", () -> {
+            final Integer fixedLength = 5; // 5 weeks
+            final Integer repaymentFrequencyType = 1; // week
+
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(3).repaymentEvery(2).repaymentFrequencyType(repaymentFrequencyType.longValue())
+                    .fixedLength(fixedLength);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "22 November 2023",
+                    1000.0, 4);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(3).loanTermFrequency(6).loanTermFrequencyType(repaymentFrequencyType)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(2)
+                    .repaymentFrequencyType(repaymentFrequencyType);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("22 November 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("22 
November 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 100.0, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 12, 6), 
33.0, 0.0, 33.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 
20), 33.0, 0.0, 33.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 
27), 34.0, 0.0, 34.0, 0.0, 0.0);
+            assertEquals(loanDetails.getNumberOfRepayments(), 3);
+            assertEquals(
+                    
Utils.getDifferenceInWeeks(loanDetails.getTimeline().getActualDisbursementDate(),
 loanDetails.getRepaymentSchedule()
+                            
.getPeriods().get(loanDetails.getRepaymentSchedule().getPeriods().size() - 
1).getDueDate()),
+                    fixedLength.longValue());
+            assertTrue(loanDetails.getStatus().getActive());
+        });
+    }
+
+    // UC128: Advanced payment allocation with Fixed Length for 11 months and 
Loan Term for 12 months (6 repayments each
+    // 2 months)
+    // every 2 months)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc128() {
+        runAt("22 November 2023", () -> {
+            final Integer fixedLength = 11; // 11 months
+            final Integer repaymentFrequencyType = 2; // month
+
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(6).repaymentEvery(2).repaymentFrequencyType(repaymentFrequencyType.longValue())
+                    .fixedLength(fixedLength);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "22 November 2023",
+                    1000.0, 4);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(6).loanTermFrequency(12)
+                    .loanTermFrequencyType(repaymentFrequencyType)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(2)
+                    .repaymentFrequencyType(repaymentFrequencyType);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("22 November 2023").locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("22 
November 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 100.0, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 22), 
17.0, 0.0, 17.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 22), 
17.0, 0.0, 17.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 5, 22), 
17.0, 0.0, 17.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 7, 22), 
17.0, 0.0, 17.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 9, 22), 
17.0, 0.0, 17.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 10, 
22), 15.0, 0.0, 15.0, 0.0, 0.0);
+            assertEquals(loanDetails.getNumberOfRepayments(), 6);
+            assertEquals(
+                    
Utils.getDifferenceInMonths(loanDetails.getTimeline().getActualDisbursementDate(),
 loanDetails.getRepaymentSchedule()
+                            
.getPeriods().get(loanDetails.getRepaymentSchedule().getPeriods().size() - 
1).getDueDate()),
+                    fixedLength.longValue());
+            assertTrue(loanDetails.getStatus().getActive());
+        });
+    }
+
+    // UC129: Advanced payment allocation with Fixed Length for 5 months and 
Loan Term for 6 months (6 repayments
+    // every month)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve -> expect validation error
+    @Test
+    public void uc129() {
+        final String operationDate = "22 November 2023";
+        runAt(operationDate, () -> {
+            final Integer fixedLength = 5; // 5 months
+            final Integer repaymentFrequencyType = 2; // month
+
+            // Try to create a Loan Product using Fixed Length with a wrong 
Loan Term setup
+            LOG.info("Try to create a Loan Product using Fixed Length with a 
wrong Loan Term setup");
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> loanProductHelper
+                            
.createLoanProduct(createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                                    
.interestRatePerPeriod(0.0).numberOfRepayments(6).repaymentEvery(1)
+                                    
.repaymentFrequencyType(repaymentFrequencyType.longValue()).fixedLength(fixedLength)));
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("error.msg.number.repayments.and.fixed.length.configuration.not.valid"));
+        });
+    }
+
+    // UC130: Advanced payment allocation with Fixed Length for 5 months and 
Loan Term for 6 months (6 repayments
+    // every month)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve -> expect validation error - Fixed Length 
without Advanced Payment Allocation
+    @Test
+    public void uc130() {
+        final String operationDate = "22 November 2023";
+        runAt(operationDate, () -> {
+            final Integer fixedLength = 5; // 5 months
+            final Integer repaymentFrequencyType = 2; // month
+
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(6).repaymentEvery(1).repaymentFrequencyType(repaymentFrequencyType.longValue()).fixedLength(6);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+
+            // Try to use Fixed Length without Advanced Payment Allocation as 
transaction processing strategy code
+            LOG.info("Try to use Fixed Length without Advanced Payment 
Allocation as transaction processing strategy code");
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(client.getClientId())
+                            
.productId(loanProductResponse.getResourceId()).loanType("individual").locale("en")
+                            
.submittedOnDate(operationDate).expectedDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN)
+                            
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+                            
.repaymentEvery(1).repaymentFrequencyType(repaymentFrequencyType).numberOfRepayments(6).loanTermFrequency(6)
+                            
.loanTermFrequencyType(repaymentFrequencyType).principal(BigDecimal.valueOf(1000.0))
+                            
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000)).fixedLength(fixedLength)
+                            
.transactionProcessingStrategyCode(FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
+                            
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name())));
+
+            assertEquals(403, exception.getResponse().code());
+            assertTrue(exception.getMessage()
+                    
.contains("error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation"));
+        });
+    }
+
+    // UC131: Advanced payment allocation with Fixed Length for 5 months and 
Loan Term for 6 months (6 repayments
+    // every month)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve -> expect validation error - Interest Rate 
value higher than Zero
+    @Test
+    public void uc131() {
+        final String operationDate = "22 November 2023";
+        runAt(operationDate, () -> {
+            final Integer fixedLength = 5; // 5 months
+            final Integer repaymentFrequencyType = 2; // month
+
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(6).repaymentEvery(1).repaymentFrequencyType(repaymentFrequencyType.longValue()).fixedLength(6);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+
+            // Try to use Fixed Length with an Interest Rate value higher than 
Zero
+            LOG.info("Try to use Fixed Length with an Interest Rate value 
higher than Zero");
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(client.getClientId())
+                            
.productId(loanProductResponse.getResourceId()).loanType("individual").locale("en")
+                            
.submittedOnDate(operationDate).expectedDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN)
+                            
.amortizationType(1).interestRatePerPeriod(BigDecimal.ONE).interestCalculationPeriodType(1).interestType(0)
+                            
.repaymentEvery(1).repaymentFrequencyType(repaymentFrequencyType).numberOfRepayments(6).loanTermFrequency(6)
+                            
.loanTermFrequencyType(repaymentFrequencyType).principal(BigDecimal.valueOf(1000.0))
+                            
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000)).fixedLength(fixedLength)
+                            .transactionProcessingStrategyCode(
+                                    
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                            
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name())));
+
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("error.msg.fixed.length.only.supported.for.zero.interest"));
+        });
+    }
+
+    // UC132: Advanced payment allocation with Fixed Length for 5 months and 
Loan Term for 6 months (3 repayments
+    // every 2 months)
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and No Interest
+    // 2. Submit Loan and approve -> expect validation error - Loan acount 
Fixed Length with a wrong Loan Term setup
+    @Test
+    public void uc132() {
+        final String operationDate = "22 November 2023";
+        runAt(operationDate, () -> {
+            final Integer fixedLength = 5; // 5 months
+            final Integer repaymentFrequencyType = 2; // month
+
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.numberOfRepayments(6).repaymentEvery(1).repaymentFrequencyType(repaymentFrequencyType.longValue()).fixedLength(6);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+
+            // Try to use Fixed Length with a wrong Loan Term setup
+            LOG.info("Try to use Fixed Length with a wrong Loan Term setup");
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(client.getClientId())
+                            
.productId(loanProductResponse.getResourceId()).loanType("individual").locale("en")
+                            
.submittedOnDate(operationDate).expectedDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN)
+                            
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+                            
.repaymentEvery(2).repaymentFrequencyType(repaymentFrequencyType).numberOfRepayments(6).loanTermFrequency(6)
+                            
.loanTermFrequencyType(repaymentFrequencyType).principal(BigDecimal.valueOf(1000.0))
+                            
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000)).fixedLength(fixedLength)
+                            .transactionProcessingStrategyCode(
+                                    
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                            
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name())));
+
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("error.msg.number.repayments.and.fixed.length.configuration.not.valid"));
+        });
+    }
+
     private static List<PaymentAllocationOrder> 
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
         AtomicInteger integer = new AtomicInteger(1);
         return Arrays.stream(paymentAllocationTypes).map(pat -> {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java
index a85931ed5..eaacc633a 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java
@@ -22,10 +22,16 @@ import 
org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 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.client.models.PutLoanProductsProductIdRequest;
 import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
 import org.apache.fineract.integrationtests.common.ClientHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+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.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -35,25 +41,25 @@ public class FixedLengthLoanProductIntegrationTest extends 
BaseLoanIntegrationTe
 
     @Test
     public void testCreateReadUpdateReadLoanProductWithFixedLength() {
-        // create with 5
-        PostLoanProductsRequest loanProductsRequest = 
fixedLengthLoanProduct(5);
+        // create with 4
+        PostLoanProductsRequest loanProductsRequest = 
fixedLengthLoanProduct(4);
         PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(loanProductsRequest);
         Assertions.assertNotNull(loanProduct.getResourceId());
 
         // read
         GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = 
loanProductHelper
                 .retrieveLoanProductById(loanProduct.getResourceId());
-        Assertions.assertEquals(5, 
getLoanProductsProductIdResponse.getFixedLength());
+        Assertions.assertEquals(4, 
getLoanProductsProductIdResponse.getFixedLength());
 
-        // update to 6
-        PutLoanProductsProductIdRequest updateRequest = new 
PutLoanProductsProductIdRequest().fixedLength(6).locale("en");
+        // update to 5
+        PutLoanProductsProductIdRequest updateRequest = new 
PutLoanProductsProductIdRequest().fixedLength(5).locale("en");
         PutLoanProductsProductIdResponse putLoanProductsProductIdResponse = 
loanProductHelper
                 .updateLoanProductById(loanProduct.getResourceId(), 
updateRequest);
         
Assertions.assertNotNull(putLoanProductsProductIdResponse.getResourceId());
 
         // read again
         getLoanProductsProductIdResponse = 
loanProductHelper.retrieveLoanProductById(loanProduct.getResourceId());
-        Assertions.assertEquals(6, 
getLoanProductsProductIdResponse.getFixedLength());
+        Assertions.assertEquals(5, 
getLoanProductsProductIdResponse.getFixedLength());
 
         // update to null
         
loanTransactionHelper.updateLoanProduct(putLoanProductsProductIdResponse.getResourceId(),
 """
@@ -73,12 +79,18 @@ public class FixedLengthLoanProductIntegrationTest extends 
BaseLoanIntegrationTe
         runAt("01 January 2023", () -> {
             Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
 
-            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(fixedLengthLoanProduct(6));
+            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(fixedLengthLoanProduct(4));
             Assertions.assertNotNull(loanProduct.getResourceId());
 
-            Long loanId = applyAndApproveLoan(clientId, 
loanProduct.getResourceId(), "01 January 2023", 1000.0);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProduct.getResourceId(), "01 January 2023", 1000.0, 4);
+            applicationRequest = applicationRequest
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            Long loanId = loanResponse.getLoanId();
+
             GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
-            Assertions.assertEquals(6, loanDetails.getFixedLength());
+            Assertions.assertEquals(4, loanDetails.getFixedLength());
         });
     }
 
@@ -87,22 +99,29 @@ public class FixedLengthLoanProductIntegrationTest extends 
BaseLoanIntegrationTe
         runAt("01 January 2023", () -> {
             Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
 
-            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(fixedLengthLoanProduct(6));
+            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(fixedLengthLoanProduct(4));
             Assertions.assertNotNull(loanProduct.getResourceId());
 
-            Long loanId = applyAndApproveLoan(clientId, 
loanProduct.getResourceId(), "01 January 2023", 1000.0, 1, //
-                    loanApplication -> loanApplication.fixedLength(5) //
-            );
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProduct.getResourceId(), "01 January 2023", 1000.0, 4);
+            applicationRequest = 
applicationRequest.fixedLength(5).repaymentEvery(1).repaymentFrequencyType(2).loanTermFrequencyType(2)
+                    
.loanTermFrequency(4).transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+            Long loanId = loanResponse.getLoanId();
+
             GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
             Assertions.assertEquals(5, loanDetails.getFixedLength());
         });
     }
 
     private PostLoanProductsRequest fixedLengthLoanProduct(Integer 
fixedLength) {
-        return 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().numberOfRepayments(4)//
-                .repaymentEvery(1)//
-                
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
-                
.transactionProcessingStrategyCode("mifos-standard-strategy").fixedLength(fixedLength);
+        return 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
 //
+                .numberOfRepayments(4).repaymentEvery(1) //
+                
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()) //
+                .loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
+                
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
 //
+                
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()) //
+                .interestRatePerPeriod(0.0).fixedLength(fixedLength);
     }
 
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
index 222bc0d13..5c653d051 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
@@ -19,6 +19,9 @@
 package org.apache.fineract.integrationtests.common;
 
 import static io.restassured.RestAssured.given;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.WEEKS;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -520,4 +523,16 @@ public final class Utils {
     public static LocalDate getDateAsLocalDate(String dateAsString) {
         return LocalDate.parse(dateAsString, dateFormatter);
     }
+
+    public static long getDifferenceInDays(final LocalDate localDateBefore, 
final LocalDate localDateAfter) {
+        return DAYS.between(localDateBefore, localDateAfter);
+    }
+
+    public static long getDifferenceInWeeks(final LocalDate localDateBefore, 
final LocalDate localDateAfter) {
+        return WEEKS.between(localDateBefore, localDateAfter);
+    }
+
+    public static long getDifferenceInMonths(final LocalDate localDateBefore, 
final LocalDate localDateAfter) {
+        return MONTHS.between(localDateBefore, localDateAfter);
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 9f06c03f6..aef2ed245 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -343,6 +343,11 @@ public class LoanTransactionHelper extends IntegrationTest 
{
         return ok(fineract().loanCharges.retrieveTemplate9(loanExternalId));
     }
 
+    public HashMap applyLoan(final String payload, final ResponseSpecification 
responseSpec) {
+        final String postURLForLoan = "/fineract-provider/api/v1/loans?" + 
Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(this.requestSpec, this.responseSpec, 
postURLForLoan, payload, null);
+    }
+
     public List getRepaymentTemplate(final Integer loanId) {
         final String GET_REPAYMENTS_URL = "/fineract-provider/api/v1/loans/" + 
loanId + "/transactions/template?command=repayment&"
                 + Utils.TENANT_IDENTIFIER;

Reply via email to