This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 479e71cda FINERACT-2148: loan charge off behaviour configuration
479e71cda is described below

commit 479e71cdad237dff4248dad56c1fb8441f3aa8b2
Author: adam.magyari <[email protected]>
AuthorDate: Wed Nov 27 09:22:09 2024 +0100

    FINERACT-2148: loan charge off behaviour configuration
---
 .../loanaccount/domain/LoanChargeOffBehaviour.java | 45 ++++++++++++++++++++++
 .../loanschedule/domain/LoanApplicationTerms.java  | 15 +++++---
 .../loanproduct/LoanProductConstants.java          |  1 +
 .../api/LoanProductsApiResourceSwagger.java        |  8 ++++
 .../loanproduct/data/LoanProductData.java          | 25 +++++++++---
 .../portfolio/loanproduct/domain/LoanProduct.java  | 17 ++++++--
 .../domain/LoanProductRelatedDetail.java           | 15 ++++++--
 .../tenant/module/loan/module-changelog-master.xml |  1 +
 .../loan/parts/1023_add_charge_off_behaviour.xml   | 36 +++++++++++++++++
 .../service/LoanScheduleAssembler.java             |  3 +-
 .../loanproduct/api/LoanProductsApiResource.java   |  4 +-
 .../serialization/LoanProductDataValidator.java    | 15 +++++++-
 .../LoanProductReadPlatformServiceImpl.java        | 11 ++++--
 ...oductWritePlatformServiceJpaRepositoryImpl.java |  6 +++
 .../domain/DefaultScheduledDateGeneratorTest.java  |  4 +-
 ...hAdvancedPaymentAllocationIntegrationTests.java | 45 ++++++++++++++++++++++
 .../common/loans/LoanProductTestBuilder.java       | 11 ++++++
 17 files changed, 237 insertions(+), 25 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeOffBehaviour.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeOffBehaviour.java
new file mode 100644
index 000000000..dc5754910
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeOffBehaviour.java
@@ -0,0 +1,45 @@
+/**
+ * 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.portfolio.loanaccount.domain;
+
+import java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
+
+@Getter
+@RequiredArgsConstructor
+public enum LoanChargeOffBehaviour {
+
+    REGULAR("chargeOffBehaviour.regular", "Regular"), //
+    ZERO_INTEREST("chargeOffBehaviour.zeroInterest", "Zero interest after 
charge-off"), //
+    ;
+
+    private final String code;
+    private final String humanReadableName;
+
+    public static List<StringEnumOptionData> 
getValuesAsStringEnumOptionDataList() {
+        return Arrays.stream(values()).map(v -> new 
StringEnumOptionData(v.name(), v.getCode(), v.getHumanReadableName())).toList();
+    }
+
+    public StringEnumOptionData getValueAsStringEnumOptionData() {
+        return new StringEnumOptionData(name(), getCode(), 
getHumanReadableName());
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index da8560127..661076c89 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -47,6 +47,7 @@ import 
org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
 import 
org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
 import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -228,6 +229,7 @@ public final class LoanApplicationTerms {
     private LoanScheduleProcessingType loanScheduleProcessingType;
     private boolean enableAccrualActivityPosting;
     private List<LoanSupportedInterestRefundTypes> 
supportedInterestRefundTypes;
+    private LoanChargeOffBehaviour chargeOffBehaviour;
 
     private LoanApplicationTerms(Builder builder) {
         this.currency = builder.currency;
@@ -458,7 +460,8 @@ public final class LoanApplicationTerms {
             final Boolean isAutoRepaymentForDownPaymentEnabled, final 
RepaymentStartDateType repaymentStartDateType,
             final LocalDate submittedOnDate, final LoanScheduleType 
loanScheduleType,
             final LoanScheduleProcessingType loanScheduleProcessingType, final 
Integer fixedLength,
-            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes) {
+            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
+            final LoanChargeOffBehaviour chargeOffBehaviour) {
 
         final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
         final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -477,7 +480,7 @@ public final class LoanApplicationTerms {
                 isInterestToBeRecoveredFirstWhenGreaterThanEMI, 
fixedPrincipalPercentagePerInstallment,
                 isPrincipalCompoundingDisabledForOverdueLoans, 
enableDownPayment, disbursedAmountPercentageForDownPayment,
                 isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, 
submittedOnDate, loanScheduleType, loanScheduleProcessingType,
-                fixedLength, enableAccrualActivityPosting, 
supportedInterestRefundTypes);
+                fixedLength, enableAccrualActivityPosting, 
supportedInterestRefundTypes, chargeOffBehaviour);
 
     }
 
@@ -551,7 +554,7 @@ public final class LoanApplicationTerms {
                 isPrincipalCompoundingDisabledForOverdueLoans, 
isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
                 isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, 
submittedOnDate, loanScheduleType, loanScheduleProcessingType,
                 fixedLength, 
loanProductRelatedDetail.isEnableAccrualActivityPosting(),
-                loanProductRelatedDetail.getSupportedInterestRefundTypes());
+                loanProductRelatedDetail.getSupportedInterestRefundTypes(), 
loanProductRelatedDetail.getChargeOffBehaviour());
     }
 
     private LoanApplicationTerms(final ApplicationCurrency currency, final 
Integer loanTermFrequency,
@@ -581,7 +584,7 @@ public final class LoanApplicationTerms {
             final BigDecimal disbursedAmountPercentageForDownPayment, final 
boolean isAutoRepaymentForDownPaymentEnabled,
             final RepaymentStartDateType repaymentStartDateType, final 
LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
             final LoanScheduleProcessingType loanScheduleProcessingType, final 
Integer fixedLength, boolean enableAccrualActivityPosting,
-            final List<LoanSupportedInterestRefundTypes> 
supportedInterestRefundTypes) {
+            final List<LoanSupportedInterestRefundTypes> 
supportedInterestRefundTypes, final LoanChargeOffBehaviour chargeOffBehaviour) {
 
         this.currency = currency;
         this.loanTermFrequency = loanTermFrequency;
@@ -680,6 +683,7 @@ public final class LoanApplicationTerms {
         this.loanScheduleProcessingType = loanScheduleProcessingType;
         this.fixedLength = fixedLength;
         this.supportedInterestRefundTypes = supportedInterestRefundTypes;
+        this.chargeOffBehaviour = chargeOffBehaviour;
     }
 
     public Money adjustPrincipalIfLastRepaymentPeriod(final Money 
principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1540,7 +1544,8 @@ public final class LoanApplicationTerms {
                 this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), 
this.daysInYearType.getValue(),
                 this.interestRecalculationEnabled, this.isEqualAmortization, 
this.isDownPaymentEnabled,
                 this.disbursedAmountPercentageForDownPayment, 
this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType,
-                this.loanScheduleProcessingType, this.fixedLength, 
this.enableAccrualActivityPosting, this.supportedInterestRefundTypes);
+                this.loanScheduleProcessingType, this.fixedLength, 
this.enableAccrualActivityPosting, this.supportedInterestRefundTypes,
+                this.chargeOffBehaviour);
     }
 
     public Integer getLoanTermFrequency() {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index 90308e5b3..55ab6a1ff 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -166,5 +166,6 @@ public interface LoanProductConstants {
 
     String ENABLE_ACCRUAL_ACTIVITY_POSTING = "enableAccrualActivityPosting";
     String SUPPORTED_INTEREST_REFUND_TYPES = "supportedInterestRefundTypes";
+    String CHARGE_OFF_BEHAVIOUR = "chargeOffBehaviour";
 
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index e83786baa..a97d1d9c9 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -321,6 +321,8 @@ final class LoanProductsApiResourceSwagger {
         }
 
         public List<String> supportedInterestRefundTypes;
+        @Schema(example = "REGULAR")
+        public String chargeOffBehaviour;
     }
 
     @Schema(description = "PostLoanProductsResponse")
@@ -604,6 +606,7 @@ final class LoanProductsApiResourceSwagger {
         public Integer principalThresholdForLastInstalment;
         public GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType 
repaymentStartDateType;
         public List<StringEnumOptionData> supportedInterestRefundTypes;
+        public StringEnumOptionData chargeOffBehaviour;
     }
 
     @Schema(description = "GetLoanProductsTemplateResponse")
@@ -1088,6 +1091,8 @@ final class LoanProductsApiResourceSwagger {
         public List<StringEnumOptionData> supportedInterestRefundTypes;
         public List<StringEnumOptionData> supportedInterestRefundTypesOptions;
         public List<GetLoanProductsChargeOffReasonOptions> 
chargeOffReasonOptions;
+        public StringEnumOptionData chargeOffBehaviour;
+        public List<StringEnumOptionData> chargeOffBehaviourOptions;
     }
 
     @Schema(description = "GetLoanProductsProductIdResponse")
@@ -1374,6 +1379,7 @@ final class LoanProductsApiResourceSwagger {
         public Boolean enableAccrualActivityPosting;
         public List<StringEnumOptionData> supportedInterestRefundTypes;
         public 
List<GetLoanProductsTemplateResponse.GetLoanProductsChargeOffReasonOptions> 
chargeOffReasonOptions;
+        public StringEnumOptionData chargeOffBehaviour;
     }
 
     @Schema(description = "PutLoanProductsProductIdRequest")
@@ -1667,6 +1673,8 @@ final class LoanProductsApiResourceSwagger {
         }
 
         public List<String> supportedInterestRefundTypes;
+        @Schema(example = "REGULAR")
+        public String chargeOffBehaviour;
     }
 
     public static final class AdvancedPaymentData {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index dd1273338..8ede47ac5 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -46,6 +46,7 @@ import 
org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
 import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
 import org.apache.fineract.portfolio.fund.data.FundData;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
@@ -137,6 +138,7 @@ public class LoanProductData implements Serializable {
     private final Integer installmentAmountInMultiplesOf;
     private final EnumOptionData repaymentStartDateType;
     private final List<StringEnumOptionData> supportedInterestRefundTypes;
+    private final StringEnumOptionData chargeOffBehaviour;
 
     // charges
     private final Collection<ChargeData> charges;
@@ -195,6 +197,7 @@ public class LoanProductData implements Serializable {
     private final List<FloatingRateData> floatingRateOptions;
     private final List<EnumOptionData> repaymentStartDateTypeOptions;
     private final List<StringEnumOptionData> 
supportedInterestRefundTypesOptions;
+    private final List<StringEnumOptionData> chargeOffBehaviourOptions;
 
     private final Boolean multiDisburseLoan;
     private final Integer maxTrancheCount;
@@ -333,6 +336,7 @@ public class LoanProductData implements Serializable {
         final EnumOptionData loanScheduleProcessingTypeOptions = null;
         final boolean enableAccrualActivityPosting = false;
         final List<StringEnumOptionData> supportedInterestRefundTypes = null;
+        final StringEnumOptionData chargeOffBehaviour = null;
 
         return new LoanProductData(id, name, shortName, description, currency, 
principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, 
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -353,7 +357,7 @@ public class LoanProductData implements Serializable {
                 fixedPrincipalPercentagePerInstallment, 
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
                 overDueDaysForRepaymentEvent, enableDownPayment, 
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
                 paymentAllocation, creditAllocation, repaymentStartDateType, 
enableInstallmentLevelDelinquency, loanScheduleType,
-                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes);
+                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour);
 
     }
 
@@ -455,6 +459,7 @@ public class LoanProductData implements Serializable {
         final EnumOptionData loanScheduleProcessingType = null;
         final boolean enableAccrualActivityPosting = false;
         final List<StringEnumOptionData> supportedInterestRefundTypes = null;
+        final StringEnumOptionData chargeOffBehaviour = null;
 
         return new LoanProductData(id, name, shortName, description, currency, 
principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, 
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -475,7 +480,7 @@ public class LoanProductData implements Serializable {
                 fixedPrincipalPercentagePerInstallment, 
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
                 overDueDaysForRepaymentEvent, enableDownPayment, 
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
                 paymentAllocation, creditAllocation, repaymentStartDateType, 
enableInstallmentLevelDelinquency, loanScheduleType,
-                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes);
+                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour);
 
     }
 
@@ -584,6 +589,7 @@ public class LoanProductData implements Serializable {
         final EnumOptionData loanScheduleProcessingType = 
LoanScheduleProcessingType.HORIZONTAL.asEnumOptionData();
         final boolean enableAccrualActivityPosting = false;
         final List<StringEnumOptionData> supportedInterestRefundTypes = null;
+        final StringEnumOptionData chargeOffBehaviour = 
LoanChargeOffBehaviour.REGULAR.getValueAsStringEnumOptionData();
 
         return new LoanProductData(id, name, shortName, description, currency, 
principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, 
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -604,7 +610,7 @@ public class LoanProductData implements Serializable {
                 fixedPrincipalPercentagePerInstallment, 
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
                 overDueDaysForRepaymentEvent, enableDownPayment, 
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
                 paymentAllocation, creditAllocation, repaymentStartDateType, 
enableInstallmentLevelDelinquency, loanScheduleType,
-                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes);
+                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour);
 
     }
 
@@ -707,6 +713,7 @@ public class LoanProductData implements Serializable {
         final EnumOptionData loanScheduleProcessingType = null;
         final boolean enableAccrualActivityPosting = false;
         final List<StringEnumOptionData> supportedInterestRefundTypes = null;
+        final StringEnumOptionData chargeOffBehaviour = null;
 
         return new LoanProductData(id, name, shortName, description, currency, 
principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, 
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -727,7 +734,7 @@ public class LoanProductData implements Serializable {
                 fixedPrincipalPercentagePerInstallment, 
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
                 overDueDaysForRepaymentEvent, enableDownPayment, 
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
                 paymentAllocation, creditAllocationData, 
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
-                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes);
+                loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour);
     }
 
     public static LoanProductData withAccountingDetails(final LoanProductData 
productData, final Map<String, Object> accountingMappings,
@@ -778,7 +785,8 @@ public class LoanProductData implements Serializable {
             final Collection<AdvancedPaymentData> paymentAllocation, final 
Collection<CreditAllocationData> creditAllocation,
             final EnumOptionData repaymentStartDateType, final boolean 
enableInstallmentLevelDelinquency,
             final EnumOptionData loanScheduleType, final EnumOptionData 
loanScheduleProcessingType, final Integer fixedLength,
-            final boolean enableAccrualActivityPosting, final 
List<StringEnumOptionData> supportedInterestRefundTypes) {
+            final boolean enableAccrualActivityPosting, final 
List<StringEnumOptionData> supportedInterestRefundTypes,
+            StringEnumOptionData chargeOffBehaviour) {
         this.id = id;
         this.name = name;
         this.shortName = shortName;
@@ -918,6 +926,8 @@ public class LoanProductData implements Serializable {
         this.enableAccrualActivityPosting = enableAccrualActivityPosting;
         this.supportedInterestRefundTypes = supportedInterestRefundTypes;
         this.supportedInterestRefundTypesOptions = null;
+        this.chargeOffBehaviour = chargeOffBehaviour;
+        this.chargeOffBehaviourOptions = null;
         this.chargeOffReasonOptions = null;
     }
 
@@ -941,7 +951,8 @@ public class LoanProductData implements Serializable {
             final List<EnumOptionData> advancedPaymentAllocationTypes, final 
List<EnumOptionData> loanScheduleTypeOptions,
             final List<EnumOptionData> loanScheduleProcessingTypeOptions, 
final List<EnumOptionData> creditAllocationTransactionTypes,
             final List<EnumOptionData> creditAllocationAllocationTypes,
-            final List<StringEnumOptionData> 
supportedInterestRefundTypesOptions, final List<CodeValueData> 
chargeOffReasonOptions) {
+            final List<StringEnumOptionData> 
supportedInterestRefundTypesOptions,
+            final List<StringEnumOptionData> chargeOffBehaviourOptions, final 
List<CodeValueData> chargeOffReasonOptions) {
 
         this.id = productData.id;
         this.name = productData.name;
@@ -1099,6 +1110,8 @@ public class LoanProductData implements Serializable {
         this.enableAccrualActivityPosting = 
productData.enableAccrualActivityPosting;
         this.supportedInterestRefundTypesOptions = 
supportedInterestRefundTypesOptions;
         this.supportedInterestRefundTypes = 
productData.supportedInterestRefundTypes;
+        this.chargeOffBehaviour = productData.chargeOffBehaviour;
+        this.chargeOffBehaviourOptions = chargeOffBehaviourOptions;
         this.chargeOffReasonOptions = chargeOffReasonOptions;
     }
 
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index 9d1920e45..cbc6d2b07 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -63,6 +63,7 @@ import 
org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
 import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
 import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
 import org.apache.fineract.portfolio.fund.domain.Fund;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -451,6 +452,15 @@ public class LoanProduct extends 
AbstractPersistableCustom<Long> {
             });
         }
 
+        final LoanChargeOffBehaviour chargeOffBehaviour;
+        if 
(command.parameterExists(LoanProductConstants.CHARGE_OFF_BEHAVIOUR)) {
+            chargeOffBehaviour = LoanChargeOffBehaviour
+                    
.valueOf(command.stringValueOfParameterNamed(LoanProductConstants.CHARGE_OFF_BEHAVIOUR));
+        } else {
+            // For backward compatibility
+            chargeOffBehaviour = LoanChargeOffBehaviour.REGULAR;
+        }
+
         return new LoanProduct(fund, loanTransactionProcessingStrategy, 
loanProductPaymentAllocationRules, loanProductCreditAllocationRules,
                 name, shortName, description, currency, principal, 
minPrincipal, maxPrincipal, interestRatePerPeriod,
                 minInterestRatePerPeriod, maxInterestRatePerPeriod, 
interestFrequencyType, annualInterestRate, interestMethod,
@@ -470,7 +480,7 @@ public class LoanProduct extends 
AbstractPersistableCustom<Long> {
                 allowApprovedDisbursedAmountsOverApplied, 
overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent,
                 overDueDaysForRepaymentEvent, enableDownPayment, 
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
                 repaymentStartDateType, enableInstallmentLevelDelinquency, 
loanScheduleType, loanScheduleProcessingType, fixedLength,
-                enableAccrualActivityPosting, supportedInterestRefundTypes);
+                enableAccrualActivityPosting, supportedInterestRefundTypes, 
chargeOffBehaviour);
 
     }
 
@@ -688,7 +698,8 @@ public class LoanProduct extends 
AbstractPersistableCustom<Long> {
             final boolean enableAutoRepaymentForDownPayment, final 
RepaymentStartDateType repaymentStartDateType,
             final boolean enableInstallmentLevelDelinquency, final 
LoanScheduleType loanScheduleType,
             final LoanScheduleProcessingType loanScheduleProcessingType, final 
Integer fixedLength,
-            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes) {
+            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
+            final LoanChargeOffBehaviour chargeOffBehaviour) {
         this.fund = fund;
         this.transactionProcessingStrategyCode = 
transactionProcessingStrategyCode;
 
@@ -738,7 +749,7 @@ public class LoanProduct extends 
AbstractPersistableCustom<Long> {
                 inArrearsTolerance, graceOnArrearsAgeing, 
daysInMonthType.getValue(), daysInYearType.getValue(),
                 isInterestRecalculationEnabled, isEqualAmortization, 
enableDownPayment, disbursedAmountPercentageForDownPayment,
                 enableAutoRepaymentForDownPayment, loanScheduleType, 
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
-                supportedInterestRefundTypes);
+                supportedInterestRefundTypes, chargeOffBehaviour);
 
         this.loanProductMinMaxConstraints = new 
LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
                 defaultMinNominalInterestRatePerPeriod, 
defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
index d6790db78..e65a78fca 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
@@ -37,6 +37,7 @@ import 
org.apache.fineract.portfolio.common.domain.DaysInMonthType;
 import org.apache.fineract.portfolio.common.domain.DaysInYearType;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -159,6 +160,10 @@ public class LoanProductRelatedDetail implements 
LoanProductMinimumRepaymentSche
     @Column(name = "supported_interest_refund_types")
     private List<LoanSupportedInterestRefundTypes> 
supportedInterestRefundTypes = List.of();
 
+    @Column(name = "charge_off_behaviour")
+    @Enumerated(EnumType.STRING)
+    private LoanChargeOffBehaviour chargeOffBehaviour;
+
     public static LoanProductRelatedDetail createFrom(final MonetaryCurrency 
currency, final BigDecimal principal,
             final BigDecimal nominalInterestRatePerPeriod, final 
PeriodFrequencyType interestRatePeriodFrequencyType,
             final BigDecimal nominalAnnualInterestRate, final InterestMethod 
interestMethod,
@@ -171,7 +176,8 @@ public class LoanProductRelatedDetail implements 
LoanProductMinimumRepaymentSche
             final boolean enableDownPayment, final BigDecimal 
disbursedAmountPercentageForDownPayment,
             final boolean enableAutoRepaymentForDownPayment, final 
LoanScheduleType loanScheduleType,
             final LoanScheduleProcessingType loanScheduleProcessingType, final 
Integer fixedLength,
-            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes) {
+            final boolean enableAccrualActivityPosting, final 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
+            final LoanChargeOffBehaviour chargeOffBehaviour) {
 
         return new LoanProductRelatedDetail(currency, principal, 
nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
                 nominalAnnualInterestRate, interestMethod, 
interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
@@ -179,7 +185,8 @@ public class LoanProductRelatedDetail implements 
LoanProductMinimumRepaymentSche
                 recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, 
graceOnInterestCharged, amortizationMethod,
                 inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, 
daysInYearType, isInterestRecalculationEnabled,
                 isEqualAmortization, enableDownPayment, 
disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
-                loanScheduleType, loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes);
+                loanScheduleType, loanScheduleProcessingType, fixedLength, 
enableAccrualActivityPosting, supportedInterestRefundTypes,
+                chargeOffBehaviour);
     }
 
     protected LoanProductRelatedDetail() {
@@ -198,7 +205,8 @@ public class LoanProductRelatedDetail implements 
LoanProductMinimumRepaymentSche
             final boolean enableDownPayment, final BigDecimal 
disbursedAmountPercentageForDownPayment,
             final boolean enableAutoRepaymentForDownPayment, final 
LoanScheduleType loanScheduleType,
             final LoanScheduleProcessingType loanScheduleProcessingType, final 
Integer fixedLength,
-            final boolean enableAccrualActivityPosting, 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes) {
+            final boolean enableAccrualActivityPosting, 
List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
+            final LoanChargeOffBehaviour chargeOffBehaviour) {
         this.currency = currency;
         this.principal = defaultPrincipal;
         this.nominalInterestRatePerPeriod = 
defaultNominalInterestRatePerPeriod;
@@ -233,6 +241,7 @@ public class LoanProductRelatedDetail implements 
LoanProductMinimumRepaymentSche
         this.loanScheduleProcessingType = loanScheduleProcessingType;
         this.enableAccrualActivityPosting = enableAccrualActivityPosting;
         this.supportedInterestRefundTypes = supportedInterestRefundTypes;
+        this.chargeOffBehaviour = chargeOffBehaviour;
     }
 
     private Integer defaultToNullIfZero(final Integer value) {
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index e3b76c417..098e59db5 100644
--- 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -45,4 +45,5 @@
   <include relativeToChangelogFile="true" 
file="parts/1020_add_re_aged_flag_to_loan_installment.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1021_add_loan_status_change_history.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1022_add_interest_refund_support.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/1023_add_charge_off_behaviour.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1023_add_charge_off_behaviour.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1023_add_charge_off_behaviour.xml
new file mode 100644
index 000000000..239d3dd5e
--- /dev/null
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1023_add_charge_off_behaviour.xml
@@ -0,0 +1,36 @@
+<?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.3.xsd";>
+
+    <changeSet author="fineract" id="1023-1">
+        <addColumn tableName="m_product_loan">
+            <column name="charge_off_behaviour" type="varchar(20)" 
defaultValue="REGULAR"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="1023-2">
+        <addColumn tableName="m_loan">
+            <column name="charge_off_behaviour" type="varchar(20)" 
defaultValue="REGULAR"/>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index bf6a554f0..eb02ba9bf 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -528,7 +528,8 @@ public class LoanScheduleAssembler {
                 disbursedAmountPercentageForDownPayment, 
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
                 loanScheduleType, loanScheduleProcessingType, fixedLength,
                 
loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting(),
-                
loanProduct.getLoanProductRelatedDetail().getSupportedInterestRefundTypes());
+                
loanProduct.getLoanProductRelatedDetail().getSupportedInterestRefundTypes(),
+                
loanProduct.getLoanProductRelatedDetail().getChargeOffBehaviour());
     }
 
     private CalendarInstance createCalendarForSameAsRepayment(final Integer 
repaymentEvery,
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 b2ac25942..3c9351cc6 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
@@ -79,6 +79,7 @@ import 
org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlat
 import org.apache.fineract.portfolio.fund.data.FundData;
 import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
@@ -429,6 +430,7 @@ public class LoanProductsApiResource {
         final List<EnumOptionData> creditAllocationAllocationTypes = 
AllocationType.getValuesAsEnumOptionDataList();
         final List<StringEnumOptionData> supportedInterestRefundTypesOptions = 
LoanSupportedInterestRefundTypes
                 .getValuesAsStringEnumOptionDataList();
+        final List<StringEnumOptionData> chargeOffBehaviourOptions = 
LoanChargeOffBehaviour.getValuesAsStringEnumOptionDataList();
         final List<CodeValueData> chargeOffReasonOptions = 
codeValueReadPlatformService
                 .retrieveCodeValuesByCode(LoanApiConstants.CHARGE_OFF_REASONS);
 
@@ -442,7 +444,7 @@ public class LoanProductsApiResource {
                 advancedPaymentAllocationTransactionTypes, 
advancedPaymentAllocationFutureInstallmentAllocationRules,
                 advancedPaymentAllocationTypes, 
LoanScheduleType.getValuesAsEnumOptionDataList(),
                 LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), 
creditAllocationTransactionTypes,
-                creditAllocationAllocationTypes, 
supportedInterestRefundTypesOptions, chargeOffReasonOptions);
+                creditAllocationAllocationTypes, 
supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, 
chargeOffReasonOptions);
     }
 
 }
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 e0d45318e..7ecc81e6f 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
@@ -49,6 +49,7 @@ import 
org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
@@ -182,7 +183,8 @@ public final class LoanProductDataValidator {
             LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, 
LoanProductConstants.REPAYMENT_START_DATE_TYPE,
             LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, 
LoanProductConstants.LOAN_SCHEDULE_TYPE,
             LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, 
LoanProductConstants.FIXED_LENGTH,
-            LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, 
LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES));
+            LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, 
LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES,
+            LoanProductConstants.CHARGE_OFF_BEHAVIOUR));
 
     private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { 
LoanProductConstants.amortizationTypeParamName,
             LoanProductConstants.interestTypeParamName, 
LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -862,6 +864,17 @@ public final class LoanProductDataValidator {
                     "Automatic interest refund functionality is only supported 
for Progressive loans");
         }
 
+        if 
(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategyCode)
+                && 
this.fromApiJsonHelper.parameterExists(LoanProductConstants.CHARGE_OFF_BEHAVIOUR,
 element)) {
+            String chargeOffBehaviour = 
this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.CHARGE_OFF_BEHAVIOUR,
 element);
+            
baseDataValidator.reset().parameter(LoanProductConstants.CHARGE_OFF_BEHAVIOUR).value(chargeOffBehaviour)
+                    .isOneOfEnumValues(LoanChargeOffBehaviour.class);
+        } else if 
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.CHARGE_OFF_BEHAVIOUR,
 element)) {
+            
baseDataValidator.reset().parameter(LoanProductConstants.CHARGE_OFF_BEHAVIOUR).failWithCode(
+                    "supported.only.for.progressive.loan.charge.off.behaviour",
+                    "Charge off behaviour is only supported for Progressive 
loans");
+        }
+
         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 b63935129..feddcf502 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
@@ -44,6 +44,7 @@ import 
org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
 import org.apache.fineract.portfolio.common.service.CommonEnumerations;
 import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
 import 
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData;
@@ -278,8 +279,9 @@ public class LoanProductReadPlatformServiceImpl implements 
LoanProductReadPlatfo
                     + "lfr.is_floating_interest_rate_calculation_allowed as 
isFloatingInterestRateCalculationAllowed, "
                     + "lp.allow_variabe_installments as 
isVariableIntallmentsAllowed, " + "lvi.minimum_gap as minimumGap, "
                     + "lvi.maximum_gap as maximumGap, dbuc.id as 
delinquencyBucketId, dbuc.name as delinquencyBucketName, "
-                    + "lp.can_use_for_topup as canUseForTopup, 
lp.is_equal_amortization as isEqualAmortization, lp.loan_schedule_type as 
loanScheduleType, lp.loan_schedule_processing_type as 
loanScheduleProcessingType, lp.supported_interest_refund_types as 
supportedInterestRefundTypes  "
-                    + " from m_product_loan lp " + " left join m_fund f on 
f.id = lp.fund_id "
+                    + "lp.can_use_for_topup as canUseForTopup, 
lp.is_equal_amortization as isEqualAmortization, lp.loan_schedule_type as 
loanScheduleType, lp.loan_schedule_processing_type as 
loanScheduleProcessingType, lp.supported_interest_refund_types as 
supportedInterestRefundTypes, "
+                    + "lp.charge_off_behaviour as chargeOffBehaviour" + " from 
m_product_loan lp "
+                    + " left join m_fund f on f.id = lp.fund_id "
                     + " left join m_product_loan_recalculation_details lpr on 
lpr.product_id=lp.id "
                     + " left join m_product_loan_guarantee_details lpg on 
lpg.loan_product_id=lp.id "
                     + " left join m_product_loan_configurable_attributes lca 
on lca.loan_product_id = lp.id "
@@ -535,6 +537,8 @@ public class LoanProductReadPlatformServiceImpl implements 
LoanProductReadPlatfo
                         .map(LoanSupportedInterestRefundTypes::valueOf)
                         
.map(LoanSupportedInterestRefundTypes::getValueAsStringEnumOptionData).toList();
             }
+            final String chargeOffBehaviourStr = 
rs.getString("chargeOffBehaviour");
+            final LoanChargeOffBehaviour loanChargeOffBehaviour = 
LoanChargeOffBehaviour.valueOf(chargeOffBehaviourStr);
 
             return new LoanProductData(id, name, shortName, description, 
currency, principal, minPrincipal, maxPrincipal, tolerance,
                     numberOfRepayments, minNumberOfRepayments, 
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -557,7 +561,8 @@ public class LoanProductReadPlatformServiceImpl implements 
LoanProductReadPlatfo
                     dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, 
enableDownPayment, disbursedAmountPercentageForDownPayment,
                     enableAutoRepaymentForDownPayment, advancedPaymentData, 
creditAllocationData, repaymentStartDateType,
                     enableInstallmentLevelDelinquency, 
loanScheduleType.asEnumOptionData(), 
loanScheduleProcessingType.asEnumOptionData(),
-                    fixedLength, enableAccrualActivityPosting, 
supportedInterestRefundTypes);
+                    fixedLength, enableAccrualActivityPosting, 
supportedInterestRefundTypes,
+                    loanChargeOffBehaviour.getValueAsStringEnumOptionData());
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
index 67b543149..2f25dfb74 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
@@ -51,6 +51,7 @@ import 
org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepository
 import org.apache.fineract.portfolio.fund.domain.Fund;
 import org.apache.fineract.portfolio.fund.domain.FundRepository;
 import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
@@ -277,6 +278,11 @@ public class 
LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
                 
product.getLoanProductRelatedDetail().setSupportedInterestRefundTypes(supportedInterestRefundTypes);
             }
 
+            if 
(command.parameterExists(LoanProductConstants.CHARGE_OFF_BEHAVIOUR)) {
+                product.getLoanProductRelatedDetail().setChargeOffBehaviour(
+                        
command.enumValueOfParameterNamed(LoanProductConstants.CHARGE_OFF_BEHAVIOUR, 
LoanChargeOffBehaviour.class));
+            }
+
             if (!changes.isEmpty()) {
                 product.validateLoanProductPreSave();
                 this.loanProductRepository.saveAndFlush(product);
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
index 4a212eee5..61cd464f9 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
@@ -98,7 +98,7 @@ public class DefaultScheduledDateGeneratorTest {
                 Money.of(fromApplicationCurrency(dollarCurrency), ZERO), 
false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null,
                 DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, 
null, null, null, null, ZERO, null, NONE, null, ZERO,
                 EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, 
false, null, false, false, null, false, DISBURSEMENT_DATE,
-                submittedOnDate, CUMULATIVE, 
LoanScheduleProcessingType.HORIZONTAL, null, false, null);
+                submittedOnDate, CUMULATIVE, 
LoanScheduleProcessingType.HORIZONTAL, null, false, null, null);
 
         // when
         List<? extends LoanScheduleModelPeriod> result = 
underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate,
@@ -169,7 +169,7 @@ public class DefaultScheduledDateGeneratorTest {
                 null, null, null, null, null, 
Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, 
EMPTY_LIST,
                 BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, 
DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO,
                 null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, 
holidayDetailDTO, false, false, false, null, false, false, null, false,
-                DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, 
LoanScheduleProcessingType.HORIZONTAL, null, false, null);
+                DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, 
LoanScheduleProcessingType.HORIZONTAL, null, false, null, null);
     }
 
     private HolidayDetailDTO createHolidayDTO() {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
index 40edf8ac3..5b83691d0 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
@@ -45,6 +45,7 @@ import 
org.apache.fineract.integrationtests.common.accounting.FinancialActivityA
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
@@ -408,6 +409,50 @@ public class 
LoanProductWithAdvancedPaymentAllocationIntegrationTests {
                 loanProductError.get(0).get("defaultUserMessage").replace('`', 
' '));
     }
 
+    @Test
+    public void 
testCreateAndReadProgressiveLoanProductWithChargeOffBehaviour() {
+        // given
+        AdvancedPaymentData defaultAllocation = 
createDefaultPaymentAllocation();
+        AdvancedPaymentData repaymentPaymentAllocation = 
createRepaymentPaymentAllocation();
+
+        // when
+        String loanProductRequest = loanProductTestBuilder(
+                customization -> 
customization.addAdvancedPaymentAllocation(defaultAllocation, 
repaymentPaymentAllocation)
+                        
.withChargeOffBehaviour(LoanChargeOffBehaviour.ZERO_INTEREST));
+
+        Integer loanProductId = 
LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductRequest);
+        Assertions.assertNotNull(loanProductId);
+        GetLoanProductsProductIdResponse loanProduct = 
LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
+
+        // then
+        Assertions.assertNotNull(loanProduct.getChargeOffBehaviour());
+        Assertions.assertEquals(LoanChargeOffBehaviour.ZERO_INTEREST.name(), 
loanProduct.getChargeOffBehaviour().getId());
+
+        // when a new interest refund transaction was added
+        LOAN_TRANSACTION_HELPER.updateLoanProduct(loanProductId.longValue(),
+                new 
PutLoanProductsProductIdRequest().chargeOffBehaviour(LoanChargeOffBehaviour.REGULAR.name()));
+
+        loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
+
+        // then
+        Assertions.assertNotNull(loanProduct.getChargeOffBehaviour());
+        Assertions.assertEquals(LoanChargeOffBehaviour.REGULAR.name(), 
loanProduct.getChargeOffBehaviour().getId());
+    }
+
+    @Test
+    public void testCreateCumulativeLoanProductWithChargeOff() {
+        // given
+        // when
+        String loanProductRequest = loanProductTestBuilder(
+                customization -> 
customization.withChargeOffBehaviour(LoanChargeOffBehaviour.ZERO_INTEREST)
+                        
.withLoanScheduleType(LoanScheduleType.CUMULATIVE).withRepaymentStrategy("mifos-standard-strategy"));
+        LoanTransactionHelper loanTransactionHelperBadRequest = new 
LoanTransactionHelper(REQUEST_SPEC,
+                new ResponseSpecBuilder().expectStatusCode(400).build());
+        List<Map<String, String>> loanProductError = 
loanTransactionHelperBadRequest.getLoanProductError(loanProductRequest, 
"errors");
+        
Assertions.assertEquals("validation.msg.loanproduct.chargeOffBehaviour.supported.only.for.progressive.loan.charge.off.behaviour",
+                loanProductError.get(0).get("userMessageGlobalisationCode"));
+    }
+
     private String loanProductTestBuilder(Consumer<LoanProductTestBuilder> 
customization) {
         LoanProductTestBuilder builder = new 
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
                 
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
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 75624a8c5..eded00f83 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
@@ -31,6 +31,7 @@ import org.apache.fineract.client.models.AdvancedPaymentData;
 import org.apache.fineract.client.models.CreditAllocationData;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 
@@ -162,6 +163,7 @@ public class LoanProductTestBuilder {
     private String loanScheduleProcessingType = 
LoanScheduleProcessingType.HORIZONTAL.name();
     private FullAccountingConfig fullAccountingConfig;
     private List<String> supportedInterestRefundTypes = null;
+    private String chargeOffBehaviour;
 
     public String build() {
         final HashMap<String, Object> map = build(null, null);
@@ -330,6 +332,10 @@ public class LoanProductTestBuilder {
             map.put("supportedInterestRefundTypes", 
supportedInterestRefundTypes);
         }
 
+        if (this.chargeOffBehaviour != null) {
+            map.put("chargeOffBehaviour", chargeOffBehaviour);
+        }
+
         return map;
     }
 
@@ -801,6 +807,11 @@ public class LoanProductTestBuilder {
         return this;
     }
 
+    public LoanProductTestBuilder 
withChargeOffBehaviour(LoanChargeOffBehaviour chargeOffBehaviour) {
+        this.chargeOffBehaviour = chargeOffBehaviour.name();
+        return this;
+    }
+
     public LoanProductTestBuilder withChargeOffReasonsToExpenseMappings(final 
Long reasonId, final Long accountId) {
         if (this.chargeOffReasonsToExpenseMappings == null) {
             this.chargeOffReasonsToExpenseMappings = new ArrayList<>();

Reply via email to