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 c8745d7a365..1a8f1bedd18 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 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); 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 61a0b9380c2..ba27aece22d 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 @@ public final class CommonEnumerations { + public static final List 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 d06a0ee244c..2bd8b800d80 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 56c18c33a27..701cb65c1fb 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 dd0bb22c1b8..324d905e1fd 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 @@ 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; 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 c0a4ab510a3..715af39a8ac 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.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 classificationOptions = null; private CodeValueData classification; + private Collection reAgeReasonOptions = null; + private Collection periodFrequencyOptions = null; + private Collection 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 00000000000..f61ba080a08 --- /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 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 84b0ee33325..b9dcb87b280 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.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 retrieveLoanTransactio 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 97127b644c8..b319dc40e7e 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 @@ + 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 00000000000..1a776ecb544 --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 f8c45fcc53c..ac85e4b43c8 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 @@ 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); } 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 5c31a4d997c..28d360460d0 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.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 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 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 ab5b9f0e72c..a01487a3c4e 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.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.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.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 @@ 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 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 dfefabf2865..149eb933656 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.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 @@ 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 @@ private void validateReAgeRequest(Loan loan, JsonCommand command) { 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 ba07af2d609..45280c101f7 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 @@ protected void executeInlineCOB(Long loanId) { 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 @@ protected void reAgeLoan(Long loanId, String frequencyType, int frequencyNumber, 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 199c5702f9b..bb62309d373 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.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 void test_LoanReAgeTransaction_Works() { 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 void test_LoanReAgeTransaction_Works() { ); // 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 void test_LoanReAgeTransaction_WithChargeback_Works() { 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 void test_LoanReAgeReverseReplay_Works() { 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 void test_LoanReAgeReverseReplay_Works() { 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)); + }); + } + }