FINERACT-2398: Re-amortization Interest Handling configuration
This commit is contained in:
committed by
Adam Saghy
parent
8a86759773
commit
a849e9bae3
+1
@@ -120,6 +120,7 @@
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProduct</class>
|
||||
|
||||
+3
@@ -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";
|
||||
|
||||
+3
@@ -23,4 +23,7 @@ public interface LoanReAmortizationApiConstants {
|
||||
String localeParameterName = "locale";
|
||||
String dateFormatParameterName = "dateFormat";
|
||||
String externalIdParameterName = "externalId";
|
||||
|
||||
String reAmortizationInterestHandlingParamName = "reAmortizationInterestHandling";
|
||||
String reasonCodeValueIdParamName = "reasonCodeValueId";
|
||||
}
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
|
||||
+2
@@ -121,6 +121,8 @@ public class LoanTransactionData implements Serializable {
|
||||
private Collection<CodeValueData> reAgeReasonOptions = null;
|
||||
private Collection<PeriodFrequencyType> periodFrequencyOptions = null;
|
||||
private Collection<StringEnumOptionData> reAgeInterestHandlingOptions = null;
|
||||
private Collection<CodeValueData> reAmortizationReasonOptions = null;
|
||||
private Collection<StringEnumOptionData> reAmortizationInterestHandlingOptions = null;
|
||||
|
||||
public static LoanTransactionData importInstance(BigDecimal repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
|
||||
Integer rowIndex, String locale, String dateFormat) {
|
||||
|
||||
+5
@@ -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<Long
|
||||
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "loanTransaction")
|
||||
private LoanReAgeParameter loanReAgeParameter;
|
||||
|
||||
@Setter
|
||||
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "loanTransaction")
|
||||
private LoanReAmortizationParameter loanReAmortizationParameter;
|
||||
|
||||
@Setter
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "classification_cv_id")
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 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 LoanReAmortizationInterestHandlingType {
|
||||
|
||||
DEFAULT("loanReAmortizationInterestHandlingType.default", "Default"), //
|
||||
WAIVE_INTEREST("loanReAmortizationInterestHandlingType.waiveInterest", "Waive Interest"), //
|
||||
EQUAL_AMORTIZATION_INTEREST_SPLIT("loanReAmortizationInterestHandlingType.equalAmortizationInterestSplit",
|
||||
"Equal Amortization Interest Split"), ///
|
||||
;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -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<Long> {
|
||||
|
||||
@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() {}
|
||||
}
|
||||
+2
@@ -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)
|
||||
|
||||
+2
@@ -166,4 +166,6 @@ public interface LoanReadPlatformService {
|
||||
Long getResolvedLoanTransactionId(Long transactionId, ExternalId externalTransactionId);
|
||||
|
||||
LoanTransactionData retrieveLoanReAgeTemplate(Long loanId);
|
||||
|
||||
LoanTransactionData retrieveLoanReAmortizationTemplate(Long loanId);
|
||||
}
|
||||
|
||||
+1
@@ -56,4 +56,5 @@
|
||||
<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"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1034_loan_reamortization_parameters.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?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.1.xsd">
|
||||
<changeSet author="fineract" id="1" objectQuotingStrategy="QUOTE_ALL_OBJECTS">
|
||||
<createTable tableName="m_loan_reamortization_parameter">
|
||||
<column autoIncrement="true" name="id" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="loan_transaction_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValue="DEFAULT" name="interest_handling_type" type="VARCHAR(40)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueComputed="NULL" name="reamortization_reason_code_value_id" type="INT"/>
|
||||
<column name="created_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_modified_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet id="2-mysql" author="fineract" context="mysql">
|
||||
<addColumn tableName="m_loan_reamortization_parameter">
|
||||
<column name="created_on_utc" type="DATETIME(6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_modified_on_utc" type="DATETIME(6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet id="2-postgresql" author="fineract" context="postgresql">
|
||||
<addColumn tableName="m_loan_reamortization_parameter">
|
||||
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="4">
|
||||
<insert tableName="m_code">
|
||||
<column name="code_name" value="ReAmortizationReasons"/>
|
||||
<column name="is_system_defined" valueBoolean="true"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="5">
|
||||
<addForeignKeyConstraint baseColumnNames="reamortization_reason_code_value_id" baseTableName="m_loan_reamortization_parameter"
|
||||
constraintName="FK_reamortization_reason_code_m_code_value" deferrable="false"
|
||||
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
|
||||
referencedColumnNames="id" referencedTableName="m_code_value" validate="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+1
@@ -109,6 +109,7 @@
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProduct</class>
|
||||
|
||||
+1
@@ -108,6 +108,7 @@
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProduct</class>
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
|
||||
+14
@@ -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<CodeValueData> 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) {
|
||||
|
||||
|
||||
+24
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-6
@@ -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",
|
||||
|
||||
+1
@@ -207,6 +207,7 @@
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProduct</class>
|
||||
|
||||
+9
-15
@@ -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
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+14
-13
@@ -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"), //
|
||||
|
||||
Reference in New Issue
Block a user