FINERACT-2354: Re-aging-for-interest-bearing-products

This commit is contained in:
Jose Alberto Hernandez
2025-10-15 11:38:10 +02:00
committed by Adam Saghy
parent c50113a868
commit b9e6f21e18
18 changed files with 258 additions and 10 deletions
@@ -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";
@@ -28,4 +28,7 @@ public interface LoanReAgingApiConstants {
String frequencyNumber = "frequencyNumber";
String startDate = "startDate";
String numberOfInstallments = "numberOfInstallments";
String reAgeInterestHandlingParamName = "reAgeInterestHandling";
String reasonCodeValueIdParamName = "reasonCodeValueId";
}
@@ -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;
@@ -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)
@@ -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());
}
}
@@ -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);
}
}
@@ -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)
@@ -165,4 +165,5 @@ public interface LoanReadPlatformService {
Long getResolvedLoanTransactionId(Long transactionId, ExternalId externalTransactionId);
LoanTransactionData retrieveLoanReAgeTemplate(Long loanId);
}
@@ -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>
@@ -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>