diff --git a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml index 656e3f3ea..3daf8ed78 100644 --- a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml +++ b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml @@ -120,6 +120,7 @@ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter + org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest org.apache.fineract.portfolio.loanproduct.domain.LoanProduct 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 2bd8b800d..fa651a64c 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 @@ -142,6 +142,8 @@ public interface LoanApiConstants { String CHARGE_OFF_REASONS = "ChargeOffReasons"; // loan ReAge String REAGE_REASONS = "ReAgeReasons"; + // loan ReAmortization + String REAMORTIZATION_REASONS = "ReAmortizationReasons"; // fore closure constants String transactionDateParamName = "transactionDate"; String noteParamName = "note"; @@ -186,6 +188,7 @@ public interface LoanApiConstants { String BUY_DOWN_FEE_COMMAND = "buyDownFee"; String BUY_DOWN_FEE_ADJUSTMENT_COMMAND = "buyDownFeeAdjustment"; String REAGE_COMMAND = "reAge"; + String REAMORTIZATION_COMMAND = "reAmortization"; // Data Validator names String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud"; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java index 5ef37442e..256513268 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanReAmortizationApiConstants.java @@ -23,4 +23,7 @@ public interface LoanReAmortizationApiConstants { String localeParameterName = "locale"; String dateFormatParameterName = "dateFormat"; String externalIdParameterName = "externalId"; + + String reAmortizationInterestHandlingParamName = "reAmortizationInterestHandling"; + 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 324d905e1..205d1b833 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 @@ -370,6 +370,8 @@ final class LoanTransactionsApiResourceSwagger { public Integer numberOfInstallments; @Schema(example = "DEFAULT") public String reAgeInterestHandling; + @Schema(example = "DEFAULT") + public String reAmortizationInterestHandling; @Schema(example = "1") public Long reasonCodeValueId; 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 715af39a8..04d6a25ee 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 @@ -121,6 +121,8 @@ public class LoanTransactionData implements Serializable { private Collection reAgeReasonOptions = null; private Collection periodFrequencyOptions = null; private Collection reAgeInterestHandlingOptions = null; + private Collection reAmortizationReasonOptions = null; + private Collection reAmortizationInterestHandlingOptions = null; public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId, Integer rowIndex, String locale, String dateFormat) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index f77a490ae..1490462f7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -48,6 +48,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; +import org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; @@ -145,6 +146,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom 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/reamortization/LoanReAmortizationParameter.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reamortization/LoanReAmortizationParameter.java new file mode 100644 index 000000000..bac04341a --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/reamortization/LoanReAmortizationParameter.java @@ -0,0 +1,55 @@ +/** + * 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.reamortization; + +import jakarta.persistence.Column; +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 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.loanaccount.domain.LoanTransaction; + +@Entity +@Table(name = "m_loan_reamortization_parameter") +@AllArgsConstructor +@Getter +public class LoanReAmortizationParameter extends AbstractAuditableWithUTCDateTimeCustom { + + @OneToOne + @JoinColumn(name = "loan_transaction_id", nullable = false) + private LoanTransaction loanTransaction; + + @Enumerated(EnumType.STRING) + @Column(name = "interest_handling_type") + private LoanReAmortizationInterestHandlingType interestHandlingType; + + @ManyToOne + @JoinColumn(name = "reamortization_reason_code_value_id", nullable = true) + private CodeValue reamortizationReason; + + // for JPA, don't use + protected LoanReAmortizationParameter() {} +} 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 4aa9a0451..9682a4df3 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 @@ -34,8 +34,10 @@ public interface LoanTransactionMapper { @Mapping(target = "writeOffReasonOptions", ignore = true) @Mapping(target = "chargeOffReasonOptions", ignore = true) @Mapping(target = "reAgeReasonOptions", ignore = true) + @Mapping(target = "reAmortizationReasonOptions", ignore = true) @Mapping(target = "periodFrequencyOptions", ignore = true) @Mapping(target = "reAgeInterestHandlingOptions", ignore = true) + @Mapping(target = "reAmortizationInterestHandlingOptions", 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 29773944a..f8c5e0294 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 @@ -166,4 +166,6 @@ public interface LoanReadPlatformService { Long getResolvedLoanTransactionId(Long transactionId, ExternalId externalTransactionId); LoanTransactionData retrieveLoanReAgeTemplate(Long loanId); + + LoanTransactionData retrieveLoanReAmortizationTemplate(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 b319dc40e..e086e3124 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 @@ -56,4 +56,5 @@ + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1034_loan_reamortization_parameters.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1034_loan_reamortization_parameters.xml new file mode 100644 index 000000000..b1f3ad9ce --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1034_loan_reamortization_parameters.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml index d749153be..edc5b8c27 100644 --- a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml +++ b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml @@ -109,6 +109,7 @@ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter + org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest org.apache.fineract.portfolio.loanproduct.domain.LoanProduct diff --git a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml index 72a418ca9..33d37d489 100644 --- a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml +++ b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml @@ -108,6 +108,7 @@ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter + org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest org.apache.fineract.portfolio.loanproduct.domain.LoanProduct 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 ac85e4b43..a3627c9ac 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 @@ -699,6 +699,8 @@ public class LoanTransactionsApiResource { transactionData = this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId, transactionId); } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.REAGE_COMMAND)) { transactionData = this.loanReadPlatformService.retrieveLoanReAgeTemplate(resolvedLoanId); + } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.REAMORTIZATION_COMMAND)) { + transactionData = this.loanReadPlatformService.retrieveLoanReAmortizationTemplate(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 28d360460..94e1cc8e7 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 @@ -132,6 +132,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepayment 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.reamortization.LoanReAmortizationInterestHandlingType; 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; @@ -2117,6 +2118,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .reAgeInterestHandlingOptions(LoanReAgeInterestHandlingType.getValuesAsEnumOptionDataList()).build(); } + @Override + public LoanTransactionData retrieveLoanReAmortizationTemplate(final Long loanId) { + final LoanAccountData loan = this.retrieveOne(loanId); + final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REAMORTIZE); + final BigDecimal totalOutstanding = loan.getSummary() != null ? loan.getSummary().getTotalOutstanding() : null; + final List reAmortizationReasonOptions = new ArrayList<>( + codeValueReadPlatformService.retrieveCodeValuesByCode(LoanApiConstants.REAMORTIZATION_REASONS)); + return LoanTransactionData.builder().type(transactionType).currency(loan.getCurrency()).date(DateUtils.getBusinessLocalDate()) + .amount(totalOutstanding).netDisbursalAmount(loan.getNetDisbursalAmount()).loanId(loanId) + .externalLoanId(loan.getExternalId()).reAmortizationReasonOptions(reAmortizationReasonOptions) + .reAmortizationInterestHandlingOptions(LoanReAmortizationInterestHandlingType.getValuesAsEnumOptionDataList()).build(); + } + @Override public LoanTransactionData retrieveLoanChargeOffTemplate(final Long loanId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java index 223dc4fdd..a64e324a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.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; @@ -38,12 +40,15 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reamortization.LoanUndoReAmortizeTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanReAmortizationApiConstants; 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.reamortization.LoanReAmortizationInterestHandlingType; +import org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; @@ -66,6 +71,7 @@ public class LoanReAmortizationServiceImpl { private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanChargeValidator loanChargeValidator; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final CodeValueRepository codeValueRepository; public CommandProcessingResult reAmortize(Long loanId, JsonCommand command) { Loan loan = loanAssembler.assembleFrom(loanId); @@ -82,6 +88,8 @@ public class LoanReAmortizationServiceImpl { loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAmortizeTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + reAmortizeTransaction.setLoanReAmortizationParameter(createReAmortizationParameter(reAmortizeTransaction, command)); + loanTransactionRepository.saveAndFlush(reAmortizeTransaction); // delinquency recalculation will be triggered by the event in a decoupled way via a listener @@ -159,4 +167,20 @@ public class LoanReAmortizationServiceImpl { return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.REAMORTIZE, transactionDate, txPrincipalAmount, txPrincipalAmount, ZERO, ZERO, ZERO, null, false, null, txExternalId); } + + private LoanReAmortizationParameter createReAmortizationParameter(LoanTransaction reAmortizationTransaction, JsonCommand command) { + LoanReAmortizationInterestHandlingType reAmortizationInterestHandlingType = command.enumValueOfParameterNamed( + LoanReAmortizationApiConstants.reAmortizationInterestHandlingParamName, LoanReAmortizationInterestHandlingType.class); + if (reAmortizationInterestHandlingType == null) { + reAmortizationInterestHandlingType = LoanReAmortizationInterestHandlingType.DEFAULT; + } + + CodeValue reasonCodeValue = null; + if (command.parameterExists(LoanReAmortizationApiConstants.reasonCodeValueIdParamName)) { + reasonCodeValue = codeValueRepository.findByCodeNameAndId(LoanApiConstants.REAMORTIZATION_REASONS, + command.longValueOfParameterNamed(LoanReAmortizationApiConstants.reasonCodeValueIdParamName)); + } + + return new LoanReAmortizationParameter(reAmortizationTransaction, reAmortizationInterestHandlingType, reasonCodeValue); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java index 58ad2fb7c..3e4888908 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java @@ -24,23 +24,31 @@ import java.util.ArrayList; 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.LoanReAmortizationApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationInterestHandlingType; 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; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class LoanReAmortizationValidator { + private final CodeValueRepository codeValueRepository; + public void validateReAmortize(Loan loan, JsonCommand command) { validateReAmortizeRequest(command); validateReAmortizeBusinessRules(loan); @@ -54,6 +62,23 @@ public class LoanReAmortizationValidator { baseDataValidator.reset().parameter(LoanReAmortizationApiConstants.externalIdParameterName).ignoreIfNull().value(externalId) .notExceedingLengthOf(100); + final LoanReAmortizationInterestHandlingType reAmortizationInterestHandlingType = command.enumValueOfParameterNamed( + LoanReAmortizationApiConstants.reAmortizationInterestHandlingParamName, LoanReAmortizationInterestHandlingType.class); + baseDataValidator.reset().parameter(LoanReAmortizationApiConstants.reAmortizationInterestHandlingParamName) + .value(reAmortizationInterestHandlingType).ignoreIfNull(); + + Long reasonCodeValueId = command.longValueOfParameterNamed(LoanReAmortizationApiConstants.reasonCodeValueIdParamName); + baseDataValidator.reset().parameter(LoanReAmortizationApiConstants.reasonCodeValueIdParamName).value(reasonCodeValueId) + .ignoreIfNull(); + if (reasonCodeValueId != null) { + final CodeValue reasonCodeValue = codeValueRepository.findByCodeNameAndId(LoanApiConstants.REAMORTIZATION_REASONS, + reasonCodeValueId); + if (reasonCodeValue == null) { + dataValidationErrors.add(ApiParameterError.parameterError("validation.msg.reamortization.reason.invalid", + "Reamortization Reason with ID " + reasonCodeValueId + " does not exist", LoanApiConstants.REAMORTIZATION_REASONS)); + } + } + throwExceptionIfValidationErrorsExist(dataValidationErrors); } @@ -78,12 +103,6 @@ public class LoanReAmortizationValidator { loan.getId()); } - // validate reamortization is only available for non-interest bearing loans - if (loan.isInterestBearing()) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.supported.only.for.non.interest.loans", - "Loan reamortization is only available for non-interest bearing loans", loan.getId()); - } - // validate reamortization is only done on an active loan if (!loan.getStatus().isActive()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.supported.only.for.active.loans", diff --git a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml index 7c7b5dd9c..f3998cc5a 100644 --- a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml +++ b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml @@ -207,6 +207,7 @@ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter + org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest org.apache.fineract.portfolio.loanproduct.domain.LoanProduct diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java index baa24ee4a..d84219a47 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java @@ -53,13 +53,21 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; @SuppressFBWarnings({ "VA_FORMAT_STRING_USES_NEWLINE" }) +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) class LoanReAmortizationValidatorTest { private final LocalDate actualDate = LocalDate.now(Clock.systemUTC()); - private LoanReAmortizationValidator underTest = new LoanReAmortizationValidator(); + @InjectMocks + private LoanReAmortizationValidator underTest; @BeforeEach public void setUp() { @@ -140,20 +148,6 @@ class LoanReAmortizationValidatorTest { .isEqualTo("error.msg.loan.reamortize.supported.only.for.progressive.loan.schedule.type"); } - @Test - public void testValidateReAmortize_ShouldThrowException_WhenLoanIsInterestBearing() { - // given - Loan loan = loan(); - given(loan.isInterestBearing()).willReturn(true); - JsonCommand command = jsonCommand(); - // when - GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class, - () -> underTest.validateReAmortize(loan, command)); - // then - assertThat(result).isNotNull(); - assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.supported.only.for.non.interest.loans"); - } - @Test public void testValidateReAmortize_ShouldThrowException_WhenLoanIsNotActive() { // given 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 e8daf2906..4bf242ae1 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 @@ -1027,8 +1027,9 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest { loanTransactionHelper.reAge(loanId, request); } - protected void reAmortizeLoan(Long loanId) { + protected void reAmortizeLoan(Long loanId, String reAmortizationInterestHandling) { PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest(); + request.setReAmortizationInterestHandling(reAmortizationInterestHandling); request.setDateFormat(DATETIME_PATTERN); request.setLocale("en"); loanTransactionHelper.reAmortize(loanId, request); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java index 2df5de656..b6ac4b30a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java @@ -30,6 +30,7 @@ import org.apache.fineract.client.models.PostLoansResponse; 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.reamortization.LoanReAmortizationInterestHandlingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.junit.jupiter.api.Test; @@ -90,7 +91,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { runAt("02 February 2023", () -> { // create re-amortize transaction - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); // verify transactions verifyTransactions(loanId.get(), // @@ -157,7 +158,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { runAt("02 February 2023", () -> { // create re-amortize transaction - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); // verify transactions verifyTransactions(loanId.get(), // @@ -220,7 +221,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("25 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -265,7 +266,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { }); runAt("01 February 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -319,7 +320,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("30 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -375,7 +376,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("31 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -421,7 +422,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { runAt("01 February 2023", () -> { addCharge(loanId.get(), false, 10.0, "27 February 2023"); - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -466,7 +467,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("01 February 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -549,7 +550,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("01 February 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -613,7 +614,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { }); runAt("01 February 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -677,7 +678,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); }); runAt("17 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -726,7 +727,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { }); runAt("25 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), // @@ -821,7 +822,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { }); runAt("25 January 2023", () -> { - reAmortizeLoan(loanId.get()); + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.DEFAULT.name()); verifyRepaymentSchedule(loanId.get(), // installment(500, null, "01 January 2023"), //