FINERACT-2181: Installment amount in multiples of should be inherited from loan product to loan level

This commit is contained in:
Jose Alberto Hernandez
2025-06-06 10:54:29 +02:00
committed by Adam Saghy
parent 95a8231b18
commit 39a8d5c63e
16 changed files with 94 additions and 36 deletions
@@ -1616,7 +1616,7 @@ public final class LoanApplicationTerms {
this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting, this.supportedInterestRefundTypes,
this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy,
this.enableIncomeCapitalization, this.capitalizedIncomeCalculationType, this.capitalizedIncomeStrategy,
this.capitalizedIncomeType);
this.capitalizedIncomeType, this.installmentAmountInMultiplesOf);
}
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() {
@@ -112,8 +112,9 @@ public class LoanTermVariationsMapper {
scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), loan.getInArrearsTolerance(),
loan.getLoanRepaymentScheduleDetail(), loan.getLoanProduct().isMultiDisburseLoan(), loan.getFixedEmiAmount(),
disbursementData, loan.getMaxOutstandingLoanBalance(), interestChargedFromDate,
loan.getLoanProduct().getPrincipalThresholdForLastInstallment(), loan.getLoanProduct().getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loan.getLoanProduct().getPrincipalThresholdForLastInstallment(),
loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance,
compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loan.getLoanProduct().preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, calendar,
loan.getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper,
scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO,
@@ -197,8 +197,9 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
}
Money downPaymentMoney = Money.of(loan.getCurrency(),
MathUtil.percentageOf(disbursementTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19));
if (loan.getLoanProduct().getInstallmentAmountInMultiplesOf() != null) {
downPaymentMoney = Money.roundToMultiplesOf(downPaymentMoney, loan.getLoanProduct().getInstallmentAmountInMultiplesOf());
if (loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf() != null) {
downPaymentMoney = Money.roundToMultiplesOf(downPaymentMoney,
loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf());
}
final Money adjustedDownPaymentMoney = switch (loan.getLoanProductRelatedDetail().getLoanScheduleType()) {
// For Cumulative loan: To check whether the loan was overpaid when the disbursement happened and to get the
@@ -179,9 +179,6 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
@Column(name = "can_define_fixed_emi_amount")
private boolean canDefineInstallmentAmount;
@Column(name = "instalment_amount_in_multiples_of")
private Integer installmentAmountInMultiplesOf;
@Column(name = "is_linked_to_floating_interest_rates", nullable = false)
private boolean isLinkedToFloatingInterestRate;
@@ -336,7 +333,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy,
enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType,
installmentAmountInMultiplesOf);
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
@@ -368,7 +366,6 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
this.principalThresholdForLastInstallment = principalThresholdForLastInstallment;
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
this.canDefineInstallmentAmount = canDefineEmiAmount;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate;
this.canUseForTopup = canUseForTopup;
this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment;
@@ -186,6 +186,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "capitalized_income_type")
private LoanCapitalizedIncomeType capitalizedIncomeType;
@Column(name = "installment_amount_in_multiples_of")
private Integer installmentAmountInMultiplesOf;
public static LoanProductRelatedDetail createFrom(final CurrencyData currencyData, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -202,7 +205,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType) {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final Integer installmentAmountInMultiplesOf) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData);
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
@@ -213,7 +217,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, installmentAmountInMultiplesOf);
}
protected LoanProductRelatedDetail() {
@@ -236,7 +240,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType) {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final Integer installmentAmountInMultiplesOf) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -278,6 +283,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
this.capitalizedIncomeType = capitalizedIncomeType;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -51,4 +51,5 @@
<include relativeToChangelogFile="true" file="parts/1026_add_interest_recognition_flag.xml"/>
<include relativeToChangelogFile="true" file="parts/1027_add_capitalized_income_transaction_type.xml"/>
<include relativeToChangelogFile="true" file="parts/1028_add_missing_indexes.xml"/>
<include relativeToChangelogFile="true" file="parts/1029_add_installment_amount_in_multiples_of_to_loan.xml"/>
</databaseChangeLog>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1029-1">
<addColumn tableName="m_loan">
<column defaultValueComputed="NULL" name="installment_amount_in_multiples_of" type="DECIMAL(19, 6)"/>
</addColumn>
</changeSet>
<changeSet author="fineract" id="1029-2" objectQuotingStrategy="QUOTE_ALL_OBJECTS">
<renameColumn tableName="m_product_loan" oldColumnName="instalment_amount_in_multiples_of" newColumnName="installment_amount_in_multiples_of" columnDataType="DECIMAL(19, 6)"/>
</changeSet>
<changeSet author="fineract" id="1029-3-postgresql" context="postgresql">
<sql>
UPDATE m_loan SET installment_amount_in_multiples_of = lp.installment_amount_in_multiples_of FROM (
SELECT id, installment_amount_in_multiples_of FROM m_product_loan
) lp WHERE lp.id = m_loan.product_id
</sql>
</changeSet>
<changeSet author="fineract" id="1029-3-mysql" context="mysql">
<sql>
UPDATE m_loan l inner join m_product_loan lp on lp.id = l.product_id set l.installment_amount_in_multiples_of = lp.installment_amount_in_multiples_of
</sql>
</changeSet>
</databaseChangeLog>