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;