Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ public DataValidatorBuilder validateDateAfter(final LocalDate date) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

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() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ public interface LoanReAgingApiConstants {
String frequencyNumber = "frequencyNumber";
String startDate = "startDate";
String numberOfInstallments = "numberOfInstallments";

String reAgeInterestHandlingParamName = "reAgeInterestHandling";
String reasonCodeValueIdParamName = "reasonCodeValueId";
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ private PostLoansLoanIdTransactionsRequest() {}
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
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;

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ org.springframework.data.domain.Page<LoanTransactionData> retrieveLoanTransactio

Long getResolvedLoanTransactionId(Long transactionId, ExternalId externalTransactionId);

LoanTransactionData retrieveLoanReAgeTemplate(Long loanId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ private String retrieveTransactionTemplate(Long loanId, String loanExternalIdStr
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
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;
Expand Down Expand Up @@ -2102,6 +2103,20 @@ public LoanTransactionData retrieveLoanWriteoffTemplate(final Long loanId) {
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) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
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;
Expand All @@ -37,16 +39,17 @@
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;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -196,7 +199,21 @@ private LoanReAgeParameter createReAgeParameter(LoanTransaction reAgeTransaction
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) {
Expand Down
Loading
Loading