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
@@ -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);
}
@@ -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) {
@@ -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) {
@@ -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);
}