You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2023/04/27 06:34:44 UTC
[fineract] branch develop updated: FINERACT-1761-Repayment-due-event-configuration-at-product-level
This is an automated email from the ASF dual-hosted git repository.
arnold 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 57a1cb2f8 FINERACT-1761-Repayment-due-event-configuration-at-product-level
57a1cb2f8 is described below
commit 57a1cb2f8c96969f8960c85c8e203ebfefde60e1
Author: Ruchi Dhamankar <ru...@gmail.com>
AuthorDate: Tue Apr 25 12:52:55 2023 +0530
FINERACT-1761-Repayment-due-event-configuration-at-product-level
---
.../loan/CheckLoanRepaymentDueBusinessStep.java | 5 +
.../CheckLoanRepaymentOverdueBusinessStep.java | 5 +
.../loanproduct/LoanProductConstants.java | 4 +
.../loanproduct/api/LoanProductsApiResource.java | 5 +-
.../api/LoanProductsApiResourceSwagger.java | 12 ++
.../loanproduct/data/LoanProductData.java | 30 ++++-
.../portfolio/loanproduct/domain/LoanProduct.java | 42 ++++++-
.../serialization/LoanProductDataValidator.java | 23 +++-
.../LoanProductReadPlatformServiceImpl.java | 6 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
...n_product_add_repayment_overdue_days_config.xml | 37 ++++++
.../CheckLoanRepaymentDueBusinessStepTest.java | 35 ++++++
.../CheckLoanRepaymentOverdueBusinessStepTest.java | 38 ++++++
...ductWithRepaymentDueEventConfigurationTest.java | 135 +++++++++++++++++++++
.../common/loans/LoanProductTestBuilder.java | 20 +++
.../common/loans/LoanTransactionHelper.java | 6 +
16 files changed, 393 insertions(+), 11 deletions(-)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java
index 474142ab5..153a064e4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStep.java
@@ -42,6 +42,11 @@ public class CheckLoanRepaymentDueBusinessStep implements LoanCOBBusinessStep {
public Loan execute(Loan loan) {
log.debug("start processing loan repayment due business step loan for loan with id [{}]", loan.getId());
Long numberOfDaysBeforeDueDateToRaiseEvent = configurationDomainService.retrieveRepaymentDueDays();
+ if (loan.getLoanProduct().getDueDaysForRepaymentEvent() != null) {
+ if (loan.getLoanProduct().getDueDaysForRepaymentEvent() > 0) {
+ numberOfDaysBeforeDueDateToRaiseEvent = loan.getLoanProduct().getDueDaysForRepaymentEvent().longValue();
+ }
+ }
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment repaymentSchedule : loanRepaymentScheduleInstallments) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStep.java
index c370fef36..5c9de4a17 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStep.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStep.java
@@ -42,6 +42,11 @@ public class CheckLoanRepaymentOverdueBusinessStep implements LoanCOBBusinessSte
public Loan execute(Loan loan) {
log.debug("start processing loan repayment overdue business step for loan with Id [{}]", loan.getId());
Long numberOfDaysAfterDueDateToRaiseEvent = configurationDomainService.retrieveRepaymentOverdueDays();
+ if (loan.getLoanProduct().getOverDueDaysForRepaymentEvent() != null) {
+ if (loan.getLoanProduct().getOverDueDaysForRepaymentEvent() > 0) {
+ numberOfDaysAfterDueDateToRaiseEvent = loan.getLoanProduct().getOverDueDaysForRepaymentEvent().longValue();
+ }
+ }
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment repaymentSchedule : loanRepaymentScheduleInstallments) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index e92f8d71c..ce041b03f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -143,4 +143,8 @@ public interface LoanProductConstants {
String OVER_APPLIED_NUMBER = "overAppliedNumber";
String DELINQUENCY_BUCKET_PARAM_NAME = "delinquencyBucketId";
+ // repayment events related
+ String DUE_DAYS_FOR_REPAYMENT_EVENT = "dueDaysForRepaymentEvent";
+ String OVER_DUE_DAYS_FOR_REPAYMENT_EVENT = "overDueDaysForRepaymentEvent";
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
index babae83b8..2a5556601 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -106,7 +106,8 @@ public class LoanProductsApiResource {
"isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate",
"defaultDifferentialLendingRate", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed",
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
- LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
+ LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT,
+ LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions"));
@@ -141,7 +142,7 @@ public class LoanProductsApiResource {
@Operation(summary = "Create a Loan Product", description = "Depending of the Accounting Rule (accountingRule) selected, additional fields with details of the appropriate Ledger Account identifiers would need to be passed in.\n"
+ "\n" + "Refer MifosX Accounting Specs Draft for more details regarding the significance of the selected accounting rule\n\n"
+ "Mandatory Fields: name, shortName, currencyCode, digitsAfterDecimal, inMultiplesOf, principal, numberOfRepayments, repaymentEvery, repaymentFrequencyType, interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, transactionProcessingStrategyCode, accountingRule, isInterestRecalculationEnabled, daysInYearType, daysInMonthType\n\n"
- + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,h [...]
+ + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,h [...]
+ "Additional Mandatory Fields for Cash(2) based accounting: fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields for periodic (3) and upfront (4)accrual accounting: fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields if interest recalculation is enabled(true): interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType\n\n"
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 99bfbdf08..1f2afab08 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -149,6 +149,10 @@ final class LoanProductsApiResourceSwagger {
public Boolean holdGuaranteeFunds;
@Schema(example = "1")
public Long delinquencyBucketId;
+ @Schema(example = "3")
+ public Integer dueDaysForRepaymentEvent;
+ @Schema(example = "3")
+ public Integer overDueDaysForRepaymentEvent;
// Interest Recalculation
@Schema(example = "false")
@@ -1191,6 +1195,10 @@ final class LoanProductsApiResourceSwagger {
public GetDelinquencyBucketsResponse delinquencyBucket;
@Schema(example = "true")
public Boolean disallowExpectedDisbursements;
+ @Schema(example = "3")
+ public Integer dueDaysForRepaymentEvent;
+ @Schema(example = "3")
+ public Integer overDueDaysForRepaymentEvent;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1308,6 +1316,10 @@ final class LoanProductsApiResourceSwagger {
public Boolean holdGuaranteeFunds;
@Schema(example = "1")
public Long delinquencyBucketId;
+ @Schema(example = "3")
+ public Integer dueDaysForRepaymentEvent;
+ @Schema(example = "3")
+ public Integer overDueDaysForRepaymentEvent;
// Interest Recalculation
@Schema(example = "false")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 91c52c59c..805200367 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -190,6 +190,9 @@ public class LoanProductData implements Serializable {
private final Collection<DelinquencyBucketData> delinquencyBucketOptions;
private final DelinquencyBucketData delinquencyBucket;
+ private final Integer dueDaysForRepaymentEvent;
+ private final Integer overDueDaysForRepaymentEvent;
+
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -276,6 +279,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
+ final Integer dueDaysForRepaymentEvent = null;
+ final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -293,7 +298,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
+ fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent);
}
@@ -381,6 +387,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
+ final Integer dueDaysForRepaymentEvent = null;
+ final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -398,7 +406,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
+ fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent);
}
@@ -493,6 +502,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
+ final Integer dueDaysForRepaymentEvent = null;
+ final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -510,7 +521,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
+ fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent);
}
@@ -599,6 +611,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
+ final Integer dueDaysForRepaymentEvent = null;
+ final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -616,7 +630,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
+ fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent);
}
@@ -662,7 +677,8 @@ public class LoanProductData implements Serializable {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
Collection<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled,
final BigDecimal fixedPrincipalPercentagePerInstallment, final Collection<DelinquencyBucketData> delinquencyBucketOptions,
- final DelinquencyBucketData delinquencyBucket) {
+ final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent,
+ final Integer overDueDaysForRepaymentEvent) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -778,6 +794,8 @@ public class LoanProductData implements Serializable {
this.isEqualAmortization = isEqualAmortization;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = delinquencyBucket;
+ this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
+ this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -927,6 +945,8 @@ public class LoanProductData implements Serializable {
this.isRatesEnabled = isRatesEnabled;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = productData.delinquencyBucket;
+ this.dueDaysForRepaymentEvent = productData.dueDaysForRepaymentEvent;
+ this.overDueDaysForRepaymentEvent = productData.overDueDaysForRepaymentEvent;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index a28c66d2f..4ba45d272 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -202,6 +202,12 @@ public class LoanProduct extends AbstractPersistableCustom {
@JoinColumn(name = "delinquency_bucket_id")
private DelinquencyBucket delinquencyBucket;
+ @Column(name = "due_days_for_repayment_event")
+ private Integer dueDaysForRepaymentEvent;
+
+ @Column(name = "overdue_days_for_repayment_event")
+ private Integer overDueDaysForRepaymentEvent;
+
public static LoanProduct assembleFromJson(final Fund fund, final String loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> productRates) {
@@ -373,6 +379,10 @@ public class LoanProduct extends AbstractPersistableCustom {
final Integer overAppliedNumber = command.integerValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER);
+ final Integer dueDaysForRepaymentEvent = command.integerValueOfParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT);
+ final Integer overDueDaysForRepaymentEvent = command
+ .integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT);
+
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery,
@@ -388,7 +398,8 @@ public class LoanProduct extends AbstractPersistableCustom {
defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements,
- allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber);
+ allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent);
}
@@ -599,7 +610,7 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean disallowExpectedDisbursements,
final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType,
- final Integer overAppliedNumber) {
+ final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
this.name = name.trim();
@@ -681,6 +692,10 @@ public class LoanProduct extends AbstractPersistableCustom {
if (rates != null) {
this.rates = rates;
}
+
+ this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
+ this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
+
validateLoanProductPreSave();
}
@@ -1184,6 +1199,21 @@ public class LoanProduct extends AbstractPersistableCustom {
this.overAppliedNumber = newValue;
}
+ if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, this.dueDaysForRepaymentEvent)) {
+ final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT);
+ actualChanges.put(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, newValue);
+ actualChanges.put("locale", localeAsInput);
+ this.dueDaysForRepaymentEvent = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT,
+ this.overDueDaysForRepaymentEvent)) {
+ final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT);
+ actualChanges.put(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, newValue);
+ actualChanges.put("locale", localeAsInput);
+ this.overDueDaysForRepaymentEvent = newValue;
+ }
+
return actualChanges;
}
@@ -1571,4 +1601,12 @@ public class LoanProduct extends AbstractPersistableCustom {
this.delinquencyBucket = delinquencyBucket;
}
+ public Integer getDueDaysForRepaymentEvent() {
+ return this.dueDaysForRepaymentEvent;
+ }
+
+ public Integer getOverDueDaysForRepaymentEvent() {
+ return this.overDueDaysForRepaymentEvent;
+ }
+
}
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 8c44abb38..892d0e8e7 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
@@ -158,7 +158,8 @@ public final class LoanProductDataValidator {
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
- LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME));
+ LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
+ LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -733,6 +734,16 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.CAN_USE_FOR_TOPUP).value(canUseForTopup).validateForBooleanValue();
}
+ final Integer dueDaysForRepaymentEvent = this.fromApiJsonHelper
+ .extractIntegerWithLocaleNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT).value(dueDaysForRepaymentEvent)
+ .integerZeroOrGreater();
+
+ final Integer overDueDaysForRepaymentEvent = this.fromApiJsonHelper
+ .extractIntegerWithLocaleNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent)
+ .integerZeroOrGreater();
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1600,6 +1611,16 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.CAN_USE_FOR_TOPUP).value(canUseForTopup).validateForBooleanValue();
}
+ final Integer dueDaysForRepaymentEvent = this.fromApiJsonHelper
+ .extractIntegerWithLocaleNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT).value(dueDaysForRepaymentEvent)
+ .integerZeroOrGreater();
+
+ final Integer overDueDaysForRepaymentEvent = this.fromApiJsonHelper
+ .extractIntegerWithLocaleNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent)
+ .integerZeroOrGreater();
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index 3f966dc67..7122139c6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -216,6 +216,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.disallow_expected_disbursements as disallowExpectedDisbursements, lp.allow_approved_disbursed_amounts_over_applied as allowApprovedDisbursedAmountsOverApplied, lp.over_applied_calculation_type as overAppliedCalculationType, over_applied_number as overAppliedNumber, "
+ "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
+ "lp.can_define_fixed_emi_amount as canDefineInstallmentAmount, lp.instalment_amount_in_multiples_of as installmentAmountInMultiplesOf, "
+ + "lp.due_days_for_repayment_event as dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as overDueDaysForRepaymentEvent,"
+ "lpr.pre_close_interest_calculation_strategy as preCloseInterestCalculationStrategy, "
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
@@ -345,6 +346,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final boolean useBorrowerCycle = rs.getBoolean("useBorrowerCycle");
final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate");
+ final Integer dueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "dueDaysForRepaymentEvent");
+ final Integer overDueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overDueDaysForRepaymentEvent");
String status = "";
if (closeDate != null && closeDate.isBefore(DateUtils.getBusinessLocalDate())) {
status = "loanProduct.inActive";
@@ -499,7 +502,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
- isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
+ isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket,
+ dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent);
}
}
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 9c6ad412d..8b150c20d 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -123,4 +123,5 @@
<include file="parts/0101_update_transaction_summary_table_report.xml" relativeToChangelogFile="true" />
<include file="parts/0102_add_external_event_for_loan_reschedule.xml" relativeToChangelogFile="true" />
<include file="parts/0103_modify_parameter_json_column_custom_job_parameters.xml" relativeToChangelogFile="true" />
+ <include file="parts/0104_loan_product_add_repayment_overdue_days_config.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0104_loan_product_add_repayment_overdue_days_config.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0104_loan_product_add_repayment_overdue_days_config.xml
new file mode 100644
index 000000000..845e83cb6
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0104_loan_product_add_repayment_overdue_days_config.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <addColumn tableName="m_product_loan">
+ <column name="due_days_for_repayment_event" type="INT">
+ <constraints nullable="true"/>
+ </column>
+ </addColumn>
+ <addColumn tableName="m_product_loan">
+ <column name="overdue_days_for_repayment_event" type="INT">
+ <constraints nullable="true"/>
+ </column>
+ </addColumn>
+ </changeSet>
+</databaseChangeLog>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java
index f21a1754d..0de449f9b 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentDueBusinessStepTest.java
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.repayment.L
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -75,10 +76,13 @@ public class CheckLoanRepaymentDueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().plusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -97,10 +101,13 @@ public class CheckLoanRepaymentDueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDateAfter5Days = DateUtils.getBusinessLocalDate().plusDays(5);
Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays
.asList(new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()),
loanInstallmentRepaymentDueDateAfter5Days, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -110,4 +117,32 @@ public class CheckLoanRepaymentDueBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
}
+
+ @Test
+ public void givenLoanWithInstallmentDueAfterConfiguredDaysInLoanProductWhenStepExecutionThenBusinessEventIsRaised() {
+ ArgumentCaptor<LoanRepaymentDueBusinessEvent> loanRepaymentDueEvent = ArgumentCaptor.forClass(LoanRepaymentDueBusinessEvent.class);
+ // given
+ // Global config settings
+ when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(2L);
+ LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().plusDays(1);
+ Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
+ LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
+ LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
+ BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
+ List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ // Loan Product setting overrides global settings
+ when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(1);
+ when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
+
+ // when
+ Loan processedLoan = underTest.execute(loanForProcessing);
+ // then
+ verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanRepaymentDueEvent.capture());
+ LoanRepaymentScheduleInstallment loanPayloadForEvent = loanRepaymentDueEvent.getValue().get();
+ assertEquals(repaymentInstallment, loanPayloadForEvent);
+ assertEquals(processedLoan, loanForProcessing);
+
+ }
}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java
index 216199b09..41f76b602 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.repayment.L
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -76,10 +77,13 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -97,10 +101,13 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDateBefore5Days = DateUtils.getBusinessLocalDate().minusDays(5);
Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays
.asList(new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()),
loanInstallmentRepaymentDueDateBefore5Days, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
Loan processedLoan = underTest.execute(loanForProcessing);
@@ -116,6 +123,7 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallmentPaidOff = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
@@ -123,6 +131,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
repaymentInstallmentPaidOff.updateObligationMet(true);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallmentPaidOff);
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -132,4 +142,32 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
}
+ @Test
+ public void givenLoanWithInstallmentOverdueAfterConfiguredDaysInLoanProductWhenStepExecutionThenBusinessEventIsRaised() {
+ ArgumentCaptor<LoanRepaymentOverdueBusinessEvent> loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor
+ .forClass(LoanRepaymentOverdueBusinessEvent.class);
+ // given
+ // global configuration
+ when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(2L);
+ LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
+ Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
+ LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
+ LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
+ BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
+ List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
+ when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+ // product configuration overrides global configuration
+ when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(1);
+ when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
+
+ // when
+ Loan processedLoan = underTest.execute(loanForProcessing);
+ // then
+ verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanRepaymentDueBusinessEventArgumentCaptor.capture());
+ LoanRepaymentScheduleInstallment loanPayloadForEvent = loanRepaymentDueBusinessEventArgumentCaptor.getValue().get();
+ assertEquals(repaymentInstallment, loanPayloadForEvent);
+ assertEquals(processedLoan, loanForProcessing);
+ }
+
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithRepaymentDueEventConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithRepaymentDueEventConfigurationTest.java
new file mode 100644
index 000000000..54b3c08ac
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithRepaymentDueEventConfigurationTest.java
@@ -0,0 +1,135 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.UUID;
+import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+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.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class LoanProductWithRepaymentDueEventConfigurationTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private ClientHelper clientHelper;
+ private LoanTransactionHelper loanTransactionHelper;
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+ this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+ this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
+ this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ }
+
+ @Test
+ public void loanProductCreationWithDueDaysConfigurationForRepaymentEventTest() {
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ // Delinquency Bucket
+ final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ // event days configuration
+ Integer dueDaysForRepaymentEvent = 1;
+ Integer overDueDaysForRepaymentEvent = 2;
+
+ // Client and Loan account creation
+
+ final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ Integer loanProductId = createLoanProductWithDueDaysForRepaymentEvent(loanTransactionHelper, delinquencyBucketId,
+ dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent);
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(getLoanProductsProductResponse);
+ assertNotNull(getLoanProductsProductResponse.getDueDaysForRepaymentEvent());
+ assertNotNull(getLoanProductsProductResponse.getOverDueDaysForRepaymentEvent());
+ assertEquals(getLoanProductsProductResponse.getDueDaysForRepaymentEvent(), dueDaysForRepaymentEvent);
+ assertEquals(getLoanProductsProductResponse.getOverDueDaysForRepaymentEvent(), overDueDaysForRepaymentEvent);
+ }
+
+ @Test
+ public void loanProductUpdateWithDueDaysConfigurationForRepaymentEventTest() {
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ // Delinquency Bucket
+ final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ // Client and Loan account creation
+
+ final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
+ delinquencyBucketId);
+ assertNotNull(getLoanProductsProductResponse);
+
+ // Modify Loan Product
+ PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper,
+ getLoanProductsProductResponse.getId());
+ assertNotNull(loanProductModifyResponse);
+
+ }
+
+ private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) {
+ // event days configuration
+ Integer dueDaysForRepaymentEvent = 1;
+ Integer overDueDaysForRepaymentEvent = 2;
+ final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest()
+ .dueDaysForRepaymentEvent(dueDaysForRepaymentEvent).overDueDaysForRepaymentEvent(overDueDaysForRepaymentEvent).locale("en");
+ return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan);
+ }
+
+ private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
+ final Integer delinquencyBucketId) {
+ final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
+ final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+ return loanTransactionHelper.getLoanProduct(loanProductId);
+ }
+
+ private Integer createLoanProductWithDueDaysForRepaymentEvent(final LoanTransactionHelper loanTransactionHelper,
+ final Integer delinquencyBucketId, Integer dueDaysForRepaymentEvent, Integer overDueDaysForRepaymentEvent) {
+ final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().withDueDaysForRepaymentEvent(dueDaysForRepaymentEvent)
+ .withOverDueDaysForRepaymentEvent(overDueDaysForRepaymentEvent).build(null, delinquencyBucketId);
+ final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+ return loanProductId;
+ }
+
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 1c4a38e04..ee14dffda 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -138,6 +138,8 @@ public class LoanProductTestBuilder {
private String installmentAmountInMultiplesOf;
private boolean canDefineInstallmentAmount;
private Integer delinquencyBucketId;
+ private Integer dueDaysForRepaymentEvent = null;
+ private Integer overDueDaysForRepaymentEvent = null;
public String build(final String chargeId) {
final HashMap<String, Object> map = build(chargeId, null);
@@ -267,6 +269,13 @@ public class LoanProductTestBuilder {
map.put("penaltyToIncomeAccountMappings", this.penaltyToIncomeAccountMappings);
}
+ if (this.dueDaysForRepaymentEvent != null) {
+ map.put("dueDaysForRepaymentEvent", this.dueDaysForRepaymentEvent);
+ }
+ if (this.overDueDaysForRepaymentEvent != null) {
+ map.put("overDueDaysForRepaymentEvent", this.overDueDaysForRepaymentEvent);
+ }
+
return map;
}
@@ -656,4 +665,15 @@ public class LoanProductTestBuilder {
this.feeAndPenaltyAssetAccount = account;
return this;
}
+
+ public LoanProductTestBuilder withDueDaysForRepaymentEvent(final Integer dueDaysForRepaymentEvent) {
+ this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
+ return this;
+ }
+
+ public LoanProductTestBuilder withOverDueDaysForRepaymentEvent(final Integer overDueDaysForRepaymentEvent) {
+ this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
+ return this;
+ }
+
}
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 2455ea4a9..0af276d95 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
@@ -70,6 +70,8 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PutChargeTransactionChangesRequest;
import org.apache.fineract.client.models.PutChargeTransactionChangesResponse;
+import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
+import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
@@ -1844,4 +1846,8 @@ public class LoanTransactionHelper extends IntegrationTest {
final String get = Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_URL, null);
return new Gson().fromJson(get, new TypeToken<ArrayList<Integer>>() {}.getType());
}
+
+ public PutLoanProductsProductIdResponse updateLoanProduct(Long id, PutLoanProductsProductIdRequest requestModifyLoan) {
+ return ok(fineract().loanProducts.updateLoanProduct(id, requestModifyLoan));
+ }
}