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 b9e6f21e18 FINERACT-2354: Re-aging-for-interest-bearing-products
b9e6f21e18 is described below

commit b9e6f21e18c327533be5160e530de11da7ef7be6
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Mon Sep 22 07:58:47 2025 -0500

    FINERACT-2354: Re-aging-for-interest-bearing-products
---
 .../core/data/DataValidatorBuilder.java            |  2 +-
 .../common/service/CommonEnumerations.java         |  3 +
 .../loanaccount/api/LoanApiConstants.java          |  3 +
 .../loanaccount/api/LoanReAgingApiConstants.java   |  3 +
 .../api/LoanTransactionsApiResourceSwagger.java    |  5 ++
 .../loanaccount/data/LoanTransactionData.java      |  6 ++
 .../reaging/LoanReAgeInterestHandlingType.java     | 50 ++++++++++++++++
 .../domain/reaging/LoanReAgeParameter.java         | 13 ++++-
 .../loanaccount/mapper/LoanTransactionMapper.java  |  3 +
 .../service/LoanReadPlatformService.java           |  1 +
 .../tenant/module/loan/module-changelog-master.xml |  1 +
 .../loan/parts/1033_add_reage_reasons_for_loan.xml | 47 +++++++++++++++
 .../api/LoanTransactionsApiResource.java           |  2 +
 .../service/LoanReadPlatformServiceImpl.java       | 15 +++++
 .../service/reaging/LoanReAgingServiceImpl.java    | 23 +++++++-
 .../service/reaging/LoanReAgingValidator.java      | 20 +++++++
 .../integrationtests/BaseLoanIntegrationTest.java  |  4 +-
 .../loan/reaging/LoanReAgingIntegrationTest.java   | 67 ++++++++++++++++++++--
 18 files changed, 258 insertions(+), 10 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
index c8745d7a36..1a8f1bedd1 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
@@ -990,7 +990,7 @@ public class DataValidatorBuilder {
             final LocalDate dateVal = (LocalDate) this.value;
             if (DateUtils.isAfter(date, dateVal)) {
                 String validationErrorCode = "validation.msg." + this.resource 
+ "." + this.parameter + ".is.less.than.date";
-                String defaultEnglishMessage = "The parameter `" + 
this.parameter + "` must be greater than the provided date" + date;
+                String defaultEnglishMessage = "The parameter `" + 
this.parameter + "` must be greater than the provided date " + date;
                 final ApiParameterError error = 
ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, 
this.parameter,
                         dateVal, date);
                 this.dataValidationErrors.add(error);
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
index 61a0b9380c..ba27aece22 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/common/service/CommonEnumerations.java
@@ -28,6 +28,9 @@ import 
org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 
 public final class CommonEnumerations {
 
+    public static final List<PeriodFrequencyType> BASIC_PERIOD_FREQUENCY_TYPES 
= List.of(PeriodFrequencyType.DAYS,
+            PeriodFrequencyType.WEEKS, PeriodFrequencyType.MONTHS, 
PeriodFrequencyType.YEARS);
+
     private CommonEnumerations() {
 
     }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index d06a0ee244..2bd8b800d8 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -140,6 +140,8 @@ public interface LoanApiConstants {
     String WRITEOFFREASONS = "WriteOffReasons";
     // loan charge-off
     String CHARGE_OFF_REASONS = "ChargeOffReasons";
+    // loan ReAge
+    String REAGE_REASONS = "ReAgeReasons";
     // fore closure constants
     String transactionDateParamName = "transactionDate";
     String noteParamName = "note";
@@ -183,6 +185,7 @@ public interface LoanApiConstants {
     String UNDO_CONTRACT_TERMINATION_COMMAND = "undoContractTermination";
     String BUY_DOWN_FEE_COMMAND = "buyDownFee";
     String BUY_DOWN_FEE_ADJUSTMENT_COMMAND = "buyDownFeeAdjustment";
+    String REAGE_COMMAND = "reAge";
 
     // Data Validator names
     String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
index 56c18c33a2..701cb65c1f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAgingApiConstants.java
@@ -28,4 +28,7 @@ public interface LoanReAgingApiConstants {
     String frequencyNumber = "frequencyNumber";
     String startDate = "startDate";
     String numberOfInstallments = "numberOfInstallments";
+
+    String reAgeInterestHandlingParamName = "reAgeInterestHandling";
+    String reasonCodeValueIdParamName = "reasonCodeValueId";
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index dd0bb22c1b..324d905e1f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -368,6 +368,11 @@ final class LoanTransactionsApiResourceSwagger {
         public String startDate;
         @Schema(example = "numberOfInstallments")
         public Integer numberOfInstallments;
+        @Schema(example = "DEFAULT")
+        public String reAgeInterestHandling;
+        @Schema(example = "1")
+        public Long reasonCodeValueId;
+
         // command=reAge END
         @Schema(description = "Optional. Controls whether Interest Refund 
transaction should be created for this refund. If not provided, loan product 
config is used.", example = "false")
         public Boolean interestRefundCalculation;
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index c0a4ab510a..715af39a8a 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -30,9 +30,11 @@ import lombok.Builder;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.organisation.monetary.data.CurrencyData;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 
@@ -116,6 +118,10 @@ public class LoanTransactionData implements Serializable {
     private Collection<CodeValueData> classificationOptions = null;
     private CodeValueData classification;
 
+    private Collection<CodeValueData> reAgeReasonOptions = null;
+    private Collection<PeriodFrequencyType> periodFrequencyOptions = null;
+    private Collection<StringEnumOptionData> reAgeInterestHandlingOptions = 
null;
+
     public static LoanTransactionData importInstance(BigDecimal 
repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
             Integer rowIndex, String locale, String dateFormat) {
         return 
LoanTransactionData.builder().transactionAmount(repaymentAmount).transactionDate(lastRepaymentDate)
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeInterestHandlingType.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeInterestHandlingType.java
new file mode 100644
index 0000000000..f61ba080a0
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeInterestHandlingType.java
@@ -0,0 +1,50 @@
+/**
+ * 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.reaging;
+
+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 LoanReAgeInterestHandlingType {
+
+    DEFAULT("loanReAgeInterestHandlingType.default", "Default"), //
+    WAIVE_INTEREST("loanReAgeInterestHandlingType.waiveInterest", "Waive 
Interest"), //
+    
EQUAL_AMORTIZATION_PAYABLE_INTEREST("loanReAgeInterestHandlingType.equalAmortizationPayableInterest",
+            "Equal Amortization of Outstanding payable Interest"), //
+    
EQUAL_AMORTIZATION_FULL_INTEREST("loanReAgeInterestHandlingType.equalAmortizationFullInterest",
+            "Equal Amortization of Outstanding full Interest"), //
+    ;
+
+    private final String code;
+    private final String humanReadableName;
+
+    public static List<StringEnumOptionData> getValuesAsEnumOptionDataList() {
+        return Arrays.stream(values()).map(v -> new 
StringEnumOptionData(v.name(), v.getCode(), v.getHumanReadableName())).toList();
+    }
+
+    public StringEnumOptionData asEnumOptionData() {
+        return new StringEnumOptionData(name(), getCode(), 
getHumanReadableName());
+    }
+
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
index 84b0ee3332..b9dcb87b28 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reaging/LoanReAgeParameter.java
@@ -23,11 +23,13 @@ import jakarta.persistence.Entity;
 import jakarta.persistence.EnumType;
 import jakarta.persistence.Enumerated;
 import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
 import jakarta.persistence.OneToOne;
 import jakarta.persistence.Table;
 import java.time.LocalDate;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -56,10 +58,19 @@ public class LoanReAgeParameter extends 
AbstractAuditableWithUTCDateTimeCustom<L
     @Column(name = "number_of_installments", nullable = false)
     private Integer numberOfInstallments;
 
+    @Enumerated(EnumType.STRING)
+    @Column(name = "interest_handling_type")
+    private LoanReAgeInterestHandlingType interestHandlingType;
+
+    @ManyToOne
+    @JoinColumn(name = "reage_reason_code_value_id", nullable = true)
+    private CodeValue reageReason;
+
     // for JPA, don't use
     protected LoanReAgeParameter() {}
 
     public LoanReAgeParameter getCopy(LoanTransaction loanTransaction) {
-        return new LoanReAgeParameter(loanTransaction, frequencyType, 
frequencyNumber, startDate, numberOfInstallments);
+        return new LoanReAgeParameter(loanTransaction, frequencyType, 
frequencyNumber, startDate, numberOfInstallments,
+                interestHandlingType, reageReason);
     }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
index 57eb2498fc..4aa9a0451f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
@@ -33,6 +33,9 @@ public interface LoanTransactionMapper {
     @Mapping(target = "loanRepaymentScheduleInstallments", ignore = true)
     @Mapping(target = "writeOffReasonOptions", ignore = true)
     @Mapping(target = "chargeOffReasonOptions", ignore = true)
+    @Mapping(target = "reAgeReasonOptions", ignore = true)
+    @Mapping(target = "periodFrequencyOptions", ignore = true)
+    @Mapping(target = "reAgeInterestHandlingOptions", ignore = true)
     @Mapping(target = "classificationOptions", ignore = true)
     @Mapping(target = "paymentTypeOptions", ignore = true)
     @Mapping(target = "overpaymentPortion", ignore = true)
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index 05cb824a21..29773944ab 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -165,4 +165,5 @@ public interface LoanReadPlatformService {
 
     Long getResolvedLoanTransactionId(Long transactionId, ExternalId 
externalTransactionId);
 
+    LoanTransactionData retrieveLoanReAgeTemplate(Long loanId);
 }
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 97127b644c..b319dc40e7 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
@@ -55,4 +55,5 @@
   <include relativeToChangelogFile="true" 
file="parts/1030_add_loan_undo_contract_termination_event.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1031_loan_merchant_buy_down_fee.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1032_add_classification_to_loan_transaction.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/1033_add_reage_reasons_for_loan.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1033_add_reage_reasons_for_loan.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1033_add_reage_reasons_for_loan.xml
new file mode 100644
index 0000000000..1a776ecb54
--- /dev/null
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1033_add_reage_reasons_for_loan.xml
@@ -0,0 +1,47 @@
+<?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="1">
+        <insert tableName="m_code">
+            <column name="code_name" value="ReAgeReasons"/>
+            <column name="is_system_defined" valueBoolean="true"/>
+        </insert>
+    </changeSet>
+    <changeSet author="fineract" id="2" 
objectQuotingStrategy="QUOTE_ALL_OBJECTS">
+        <addColumn tableName="m_loan_reage_parameter">
+            <column defaultValue="DEFAULT" name="interest_handling_type" 
type="VARCHAR(40)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="3">
+        <addColumn tableName="m_loan_reage_parameter">
+            <column defaultValueComputed="NULL" 
name="reage_reason_code_value_id" type="INT"/>
+        </addColumn>
+        <addForeignKeyConstraint baseColumnNames="reage_reason_code_value_id" 
baseTableName="m_loan_reage_parameter"
+                                 
constraintName="FK_reage_reason_code_m_code_value" deferrable="false"
+                                 initiallyDeferred="false" onDelete="RESTRICT" 
onUpdate="RESTRICT"
+                                 referencedColumnNames="id" 
referencedTableName="m_code_value" validate="true"/>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index f8c45fcc53..ac85e4b43c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -697,6 +697,8 @@ public class LoanTransactionsApiResource {
                     LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, 
transactionId);
         } else if (CommandParameterUtil.is(commandParam, 
INTEREST_REFUND_COMMAND_VALUE)) {
             transactionData = 
this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId,
 transactionId);
+        } else if (CommandParameterUtil.is(commandParam, 
LoanApiConstants.REAGE_COMMAND)) {
+            transactionData = 
this.loanReadPlatformService.retrieveLoanReAgeTemplate(resolvedLoanId);
         } else {
             throw new UnrecognizedQueryParamException("command", commandParam);
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 5c31a4d997..28d360460d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -131,6 +131,7 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import 
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
@@ -2102,6 +2103,20 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
         return loanTransactionData;
     }
 
+    @Override
+    public LoanTransactionData retrieveLoanReAgeTemplate(final Long loanId) {
+        final LoanAccountData loan = this.retrieveOne(loanId);
+        final LoanTransactionEnumData transactionType = 
LoanEnumerations.transactionType(LoanTransactionType.REAGE);
+        final BigDecimal totalOutstanding = loan.getSummary() != null ? 
loan.getSummary().getTotalOutstanding() : null;
+        final List<CodeValueData> reAgeReasonOptions = new ArrayList<>(
+                
codeValueReadPlatformService.retrieveCodeValuesByCode(LoanApiConstants.REAGE_REASONS));
+        return 
LoanTransactionData.builder().type(transactionType).currency(loan.getCurrency()).date(DateUtils.getBusinessLocalDate())
+                
.amount(totalOutstanding).netDisbursalAmount(loan.getNetDisbursalAmount()).loanId(loanId)
+                
.externalLoanId(loan.getExternalId()).periodFrequencyOptions(CommonEnumerations.BASIC_PERIOD_FREQUENCY_TYPES)
+                .reAgeReasonOptions(reAgeReasonOptions)
+                
.reAgeInterestHandlingOptions(LoanReAgeInterestHandlingType.getValuesAsEnumOptionDataList()).build();
+    }
+
     @Override
     public LoanTransactionData retrieveLoanChargeOffTemplate(final Long 
loanId) {
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
index ab5b9f0e72..a01487a3c4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
@@ -26,6 +26,8 @@ import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -37,9 +39,9 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.reaging.Loa
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanReAgeTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanUndoReAgeTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
-import 
org.apache.fineract.infrastructure.event.business.service.TransactionHelper;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
 import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -47,6 +49,7 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTra
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import 
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
@@ -78,7 +81,7 @@ public class LoanReAgingServiceImpl {
     private final LoanUtilService loanUtilService;
     private final LoanScheduleService loanScheduleService;
     private final ReprocessLoanTransactionsService 
reprocessLoanTransactionsService;
-    private final TransactionHelper transactionHelper;
+    private final CodeValueRepository codeValueRepository;
 
     public CommandProcessingResult reAge(Long loanId, JsonCommand command) {
         Loan loan = loanAssembler.assembleFrom(loanId);
@@ -196,7 +199,21 @@ public class LoanReAgingServiceImpl {
         LocalDate startDate = 
command.dateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
         Integer numberOfInstallments = 
command.integerValueOfParameterNamed(LoanReAgingApiConstants.numberOfInstallments);
         Integer periodFrequencyNumber = 
command.integerValueOfParameterNamed(LoanReAgingApiConstants.frequencyNumber);
-        return new LoanReAgeParameter(reAgeTransaction, periodFrequencyType, 
periodFrequencyNumber, startDate, numberOfInstallments);
+
+        LoanReAgeInterestHandlingType reAgeInterestHandlingType = command
+                
.enumValueOfParameterNamed(LoanReAgingApiConstants.reAgeInterestHandlingParamName,
 LoanReAgeInterestHandlingType.class);
+        if (reAgeInterestHandlingType == null) {
+            reAgeInterestHandlingType = LoanReAgeInterestHandlingType.DEFAULT;
+        }
+
+        CodeValue reasonCodeValue = null;
+        if 
(command.parameterExists(LoanReAgingApiConstants.reasonCodeValueIdParamName)) {
+            reasonCodeValue = 
codeValueRepository.findByCodeNameAndId(LoanApiConstants.REAGE_REASONS,
+                    
command.longValueOfParameterNamed(LoanReAgingApiConstants.reasonCodeValueIdParamName));
+        }
+
+        return new LoanReAgeParameter(reAgeTransaction, periodFrequencyType, 
periodFrequencyNumber, startDate, numberOfInstallments,
+                reAgeInterestHandlingType, reasonCodeValue);
     }
 
     private void persistNote(Loan loan, JsonCommand command, Map<String, 
Object> changes) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
index dfefabf286..149eb93365 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
@@ -26,17 +26,21 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
 import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
 import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import 
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChangeOperation;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -47,6 +51,7 @@ import org.springframework.stereotype.Component;
 public class LoanReAgingValidator {
 
     private final LoanTransactionRepository loanTransactionRepository;
+    private final CodeValueRepository codeValueRepository;
 
     public void validateReAge(Loan loan, JsonCommand command) {
         validateReAgeRequest(loan, command);
@@ -80,6 +85,21 @@ public class LoanReAgingValidator {
         
baseDataValidator.reset().parameter(LoanReAgingApiConstants.numberOfInstallments).value(numberOfInstallments).notNull()
                 .integerGreaterThanZero();
 
+        final LoanReAgeInterestHandlingType reAgeInterestHandlingType = command
+                
.enumValueOfParameterNamed(LoanReAgingApiConstants.reAgeInterestHandlingParamName,
 LoanReAgeInterestHandlingType.class);
+        
baseDataValidator.reset().parameter(LoanReAgingApiConstants.reAgeInterestHandlingParamName).value(reAgeInterestHandlingType)
+                .ignoreIfNull();
+
+        Long reasonCodeValueId = 
command.longValueOfParameterNamed(LoanReAgingApiConstants.reasonCodeValueIdParamName);
+        
baseDataValidator.reset().parameter(LoanReAgingApiConstants.reasonCodeValueIdParamName).value(reasonCodeValueId).ignoreIfNull();
+        if (reasonCodeValueId != null) {
+            final CodeValue reasonCodeValue = 
codeValueRepository.findByCodeNameAndId(LoanApiConstants.REAGE_REASONS, 
reasonCodeValueId);
+            if (reasonCodeValue == null) {
+                
dataValidationErrors.add(ApiParameterError.parameterError("validation.msg.reage.reason.invalid",
+                        "Reage Reason with ID " + reasonCodeValueId + " does 
not exist", LoanApiConstants.REAGE_REASONS));
+            }
+        }
+
         throwExceptionIfValidationErrorsExist(dataValidationErrors);
     }
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index ba07af2d60..45280c101f 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -1013,7 +1013,8 @@ public abstract class BaseLoanIntegrationTest extends 
IntegrationTest {
         inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
     }
 
-    protected void reAgeLoan(Long loanId, String frequencyType, int 
frequencyNumber, String startDate, Integer numberOfInstallments) {
+    protected void reAgeLoan(Long loanId, String frequencyType, int 
frequencyNumber, String startDate, Integer numberOfInstallments,
+            String reAgeInterestHandling) {
         PostLoansLoanIdTransactionsRequest request = new 
PostLoansLoanIdTransactionsRequest();
         request.setDateFormat(DATETIME_PATTERN);
         request.setLocale("en");
@@ -1021,6 +1022,7 @@ public abstract class BaseLoanIntegrationTest extends 
IntegrationTest {
         request.setFrequencyNumber(frequencyNumber);
         request.setStartDate(startDate);
         request.setNumberOfInstallments(numberOfInstallments);
+        request.setReAgeInterestHandling(reAgeInterestHandling);
         loanTransactionHelper.reAge(loanId, request);
     }
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
index 199c5702f9..bb62309d37 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
@@ -41,6 +41,7 @@ import 
org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import 
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
 import org.junit.jupiter.api.Test;
 
 public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
@@ -135,7 +136,7 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
             long loanId = createdLoanId.get();
 
             // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4);
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4, null);
 
             // verify transactions
             verifyTransactions(loanId, //
@@ -209,7 +210,7 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
             );
 
             // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.DAYS_STRING, 30, "13 
April 2023", 3);
+            reAgeLoan(loanId, RepaymentFrequencyType.DAYS_STRING, 30, "13 
April 2023", 3, null);
 
             // verify transactions
             verifyTransactions(loanId, //
@@ -387,7 +388,7 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
             long loanId = createdLoanId.get();
 
             // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4);
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4, null);
 
             // verify transactions
             verifyTransactions(loanId, //
@@ -481,7 +482,7 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
             long loanId = createdLoanId.get();
 
             // create re-age transaction
-            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "01 
March 2023", 6);
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "01 
March 2023", 6, null);
 
             // verify transactions
             verifyTransactions(loanId, //
@@ -630,4 +631,62 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
             
assertTrue(exception.getMessage().contains("error.msg.loan.transaction.not.found"));
         });
     }
+
+    @Test
+    public void test_LoanReAgeTransactionWithInterestHandling() {
+        AtomicLong createdLoanId = new AtomicLong();
+
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 3;
+            int repaymentEvery = 1;
+
+            // Create Loan Product
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
 //
+                    .numberOfRepayments(numberOfRepayments) //
+                    .repaymentEvery(repaymentEvery) //
+                    .installmentAmountInMultiplesOf(null) //
+                    .enableDownPayment(true) //
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)) //
+                    .enableAutoRepaymentForDownPayment(true) //
+                    
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
+
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            Long loanProductId = loanProductResponse.getResourceId();
+
+            // Apply and Approve Loan
+            double amount = 1250.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductId, "01 January 2023", amount, numberOfRepayments)//
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            PostLoansLoanIdResponse approvedLoanResult = 
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                    approveLoanRequest(amount, "01 January 2023"));
+
+            Long loanId = approvedLoanResult.getLoanId();
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 
2023");
+            createdLoanId.set(loanId);
+        });
+
+        runAt("12 April 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // create re-age transaction with Equal Amortization
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4,
+                    
LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST.name());
+
+            checkMaturityDates(loanId, LocalDate.of(2023, 7, 12), 
LocalDate.of(2023, 7, 12));
+        });
+    }
+
 }


Reply via email to