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 <[email protected]>
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));
+    }
 }

Reply via email to