FINERACT-1932: Fineract modularization Fineract progressive loan module
This commit is contained in:
committed by
Adam Saghy
parent
f59ae64222
commit
53f4864980
-148
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
|
||||
public final class LoanScheduleModelDownPaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
private final LocalDate periodDate;
|
||||
private Money principalDue;
|
||||
private final Money outstandingLoanBalance;
|
||||
|
||||
public static LoanScheduleModelDownPaymentPeriod downPayment(final int periodNumber, final LocalDate periodDate,
|
||||
final Money principalDue, final Money outstandingLoanBalance) {
|
||||
|
||||
return new LoanScheduleModelDownPaymentPeriod(periodNumber, periodDate, principalDue, outstandingLoanBalance);
|
||||
}
|
||||
|
||||
private LoanScheduleModelDownPaymentPeriod(int periodNumber, LocalDate periodDate, Money principalDue, Money outstandingLoanBalance) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.periodDate = periodDate;
|
||||
this.principalDue = principalDue;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.downPaymentOnlyPeriod(this.periodNumber, this.periodDate, this.principalDue.getAmount(),
|
||||
this.outstandingLoanBalance.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return this.periodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return this.periodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
BigDecimal value = BigDecimal.ZERO;
|
||||
if (this.principalDue != null) {
|
||||
value = this.principalDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecalculatedInterestComponent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(Money principalDue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(Money interestDue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEMIFixedSpecificToInstallmentTrue() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEMIFixedSpecificToInstallment() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal rescheduleInterestPortion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
|
||||
|
||||
}
|
||||
}
|
||||
-532
@@ -1,532 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
|
||||
|
||||
public final class LoanScheduleParams {
|
||||
|
||||
// Actual period Number as per the schedule
|
||||
private int periodNumber;
|
||||
// Actual period Number plus interest only repayments
|
||||
private int instalmentNumber;
|
||||
|
||||
private LocalDate periodStartDate;
|
||||
private LocalDate actualRepaymentDate;
|
||||
|
||||
// variables for cumulative totals
|
||||
private Money totalCumulativePrincipal;
|
||||
private Money totalCumulativeInterest;
|
||||
private Money totalFeeChargesCharged;
|
||||
private Money totalPenaltyChargesCharged;
|
||||
private Money totalRepaymentExpected;
|
||||
private Money totalOutstandingInterestPaymentDueToGrace;
|
||||
|
||||
// early payments will be added here and as per the selected strategy
|
||||
// action will be performed on this value
|
||||
private Money reducePrincipal;
|
||||
|
||||
// principal changes will be added along with date(after applying rest)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
private final Map<LocalDate, Money> principalPortionMap;
|
||||
|
||||
// compounding(principal) amounts will be added along with
|
||||
// date(after applying compounding frequency)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
private final Map<LocalDate, Money> latePaymentMap;
|
||||
|
||||
// compounding(interest/Fee) amounts will be added along with
|
||||
// date(after applying compounding frequency)
|
||||
// from when these amounts will effect the outstanding balance for
|
||||
// interest calculation
|
||||
private final Map<LocalDate, Money> compoundingMap;
|
||||
private final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
|
||||
private Money unCompoundedAmount;
|
||||
private Money compoundedInLastInstallment;
|
||||
|
||||
public Money getCompoundedInLastInstallment() {
|
||||
return this.compoundedInLastInstallment;
|
||||
}
|
||||
|
||||
public void setCompoundedInLastInstallment(Money compoundedInLastInstallment) {
|
||||
this.compoundedInLastInstallment = compoundedInLastInstallment;
|
||||
}
|
||||
|
||||
// disbursement map for tranche details(will added to outstanding
|
||||
// balance as per the start date)
|
||||
private final Map<LocalDate, Money> disburseDetailMap;
|
||||
private Money principalToBeScheduled;
|
||||
private Money outstandingBalance;
|
||||
|
||||
// total outstanding balance as per rest for interest calculation.
|
||||
private Money outstandingBalanceAsPerRest;
|
||||
|
||||
private final List<LoanRepaymentScheduleInstallment> installments;
|
||||
private final Collection<RecalculationDetail> recalculationDetails;
|
||||
private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
|
||||
private final LocalDate scheduleTillDate;
|
||||
private final boolean partialUpdate;
|
||||
private int loanTermInDays;
|
||||
private final MonetaryCurrency currency;
|
||||
private final boolean applyInterestRecalculation;
|
||||
|
||||
private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
|
||||
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
|
||||
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
|
||||
Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
|
||||
final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate, Money> compoundingMap, final Money unCompoundedAmount,
|
||||
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
|
||||
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.instalmentNumber = instalmentNumber;
|
||||
this.loanTermInDays = loanTermInDays;
|
||||
this.periodStartDate = periodStartDate;
|
||||
this.actualRepaymentDate = actualRepaymentDate;
|
||||
this.totalCumulativePrincipal = totalCumulativePrincipal;
|
||||
this.totalCumulativeInterest = totalCumulativeInterest;
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged;
|
||||
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
|
||||
this.totalRepaymentExpected = totalRepaymentExpected;
|
||||
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
|
||||
this.reducePrincipal = reducePrincipal;
|
||||
this.principalPortionMap = principalPortionMap;
|
||||
this.latePaymentMap = latePaymentMap;
|
||||
this.compoundingMap = compoundingMap;
|
||||
this.unCompoundedAmount = unCompoundedAmount;
|
||||
this.disburseDetailMap = disburseDetailMap;
|
||||
this.principalToBeScheduled = principalToBeScheduled;
|
||||
this.outstandingBalance = outstandingBalance;
|
||||
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
|
||||
this.installments = installments;
|
||||
this.recalculationDetails = recalculationDetails;
|
||||
this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
|
||||
this.scheduleTillDate = scheduleTillDate;
|
||||
this.partialUpdate = partialUpdate;
|
||||
this.currency = currency;
|
||||
this.applyInterestRecalculation = applyInterestRecalculation;
|
||||
if (this.currency != null) {
|
||||
this.compoundedInLastInstallment = Money.zero(this.currency);
|
||||
}
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber,
|
||||
int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
|
||||
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
|
||||
final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
|
||||
final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
|
||||
final Map<LocalDate, Money> compoundingMap, Money unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
|
||||
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
|
||||
final boolean partialUpdate = true;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final boolean applyInterestRecalculation) {
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final LocalDate periodStartDate = null;
|
||||
final LocalDate actualRepaymentDate = null;
|
||||
final Money totalCumulativePrincipal = null;
|
||||
final Money totalCumulativeInterest = null;
|
||||
final Money totalFeeChargesCharged = null;
|
||||
final Money totalPenaltyChargesCharged = null;
|
||||
final Money totalRepaymentExpected = null;
|
||||
final Money reducePrincipal = null;
|
||||
final Map<LocalDate, Money> principalPortionMap = null;
|
||||
final Map<LocalDate, Money> latePaymentMap = null;
|
||||
final Map<LocalDate, Money> compoundingMap = null;
|
||||
final Map<LocalDate, Money> disburseDetailMap = null;
|
||||
final Money principalToBeScheduled = null;
|
||||
final Money outstandingBalance = null;
|
||||
final Money outstandingBalanceAsPerRest = null;
|
||||
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
final boolean partialUpdate = false;
|
||||
final int loanTermInDays = 0;
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = null;
|
||||
final MonetaryCurrency currency = null;
|
||||
final Money unCompoundedAmount = null;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled) {
|
||||
final int loanTermInDays = 0;
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final Money totalCumulativePrincipal = Money.zero(currency);
|
||||
final Money totalCumulativeInterest = Money.zero(currency);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
|
||||
final LocalDate actualRepaymentDate = periodStartDate;
|
||||
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency);
|
||||
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
final Money reducePrincipal = Money.zero(currency);
|
||||
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
|
||||
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
|
||||
final Money outstandingBalance = principalToBeScheduled;
|
||||
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
|
||||
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
final boolean partialUpdate = false;
|
||||
final Collection<RecalculationDetail> recalculationDetails = null;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
||||
final LocalDate scheduleTillDate = null;
|
||||
final Money unCompoundedAmount = Money.zero(currency);
|
||||
final boolean applyInterestRecalculation = false;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) {
|
||||
final int loanTermInDays = 0;
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final Money totalCumulativePrincipal = Money.zero(currency);
|
||||
final Money totalCumulativeInterest = Money.zero(currency);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
|
||||
final LocalDate actualRepaymentDate = periodStartDate;
|
||||
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency);
|
||||
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
final Money reducePrincipal = Money.zero(currency);
|
||||
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
|
||||
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
|
||||
final Money outstandingBalance = principalToBeScheduled;
|
||||
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
|
||||
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
final boolean partialUpdate = false;
|
||||
final Collection<RecalculationDetail> recalculationDetails = loanScheduleParams.recalculationDetails;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
|
||||
final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
|
||||
final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
|
||||
final Money unCompoundedAmount = Money.zero(currency);
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
}
|
||||
|
||||
public int getPeriodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
public void setPeriodNumber(int periodNumber) {
|
||||
this.periodNumber = periodNumber;
|
||||
}
|
||||
|
||||
public int getInstalmentNumber() {
|
||||
return this.instalmentNumber;
|
||||
}
|
||||
|
||||
public LocalDate getActualRepaymentDate() {
|
||||
return this.actualRepaymentDate;
|
||||
}
|
||||
|
||||
public void setActualRepaymentDate(LocalDate actualRepaymentDate) {
|
||||
this.actualRepaymentDate = actualRepaymentDate;
|
||||
}
|
||||
|
||||
public Money getTotalCumulativePrincipal() {
|
||||
return this.totalCumulativePrincipal;
|
||||
}
|
||||
|
||||
public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
|
||||
if (this.totalCumulativePrincipal != null) {
|
||||
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal);
|
||||
} else {
|
||||
this.totalCumulativePrincipal = totalCumulativePrincipal;
|
||||
}
|
||||
}
|
||||
|
||||
public Money getTotalCumulativeInterest() {
|
||||
return this.totalCumulativeInterest;
|
||||
}
|
||||
|
||||
public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
|
||||
if (this.totalCumulativeInterest != null) {
|
||||
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest);
|
||||
} else {
|
||||
this.totalCumulativeInterest = totalCumulativeInterest;
|
||||
}
|
||||
}
|
||||
|
||||
public Money getTotalFeeChargesCharged() {
|
||||
return this.totalFeeChargesCharged;
|
||||
}
|
||||
|
||||
public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
|
||||
if (this.totalFeeChargesCharged != null) {
|
||||
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged);
|
||||
} else {
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged;
|
||||
}
|
||||
}
|
||||
|
||||
public Money getTotalPenaltyChargesCharged() {
|
||||
return this.totalPenaltyChargesCharged;
|
||||
}
|
||||
|
||||
public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
|
||||
if (this.totalPenaltyChargesCharged != null) {
|
||||
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged);
|
||||
} else {
|
||||
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
|
||||
}
|
||||
}
|
||||
|
||||
public Money getTotalRepaymentExpected() {
|
||||
return this.totalRepaymentExpected;
|
||||
}
|
||||
|
||||
public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
|
||||
if (this.totalRepaymentExpected != null) {
|
||||
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected);
|
||||
} else {
|
||||
this.totalRepaymentExpected = totalRepaymentExpected;
|
||||
}
|
||||
}
|
||||
|
||||
public Money getReducePrincipal() {
|
||||
return this.reducePrincipal;
|
||||
}
|
||||
|
||||
public void setReducePrincipal(Money reducePrincipal) {
|
||||
this.reducePrincipal = reducePrincipal;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getPrincipalPortionMap() {
|
||||
return this.principalPortionMap;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getLatePaymentMap() {
|
||||
return this.latePaymentMap;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getCompoundingMap() {
|
||||
return this.compoundingMap;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Money> getDisburseDetailMap() {
|
||||
return this.disburseDetailMap;
|
||||
}
|
||||
|
||||
public Money getOutstandingBalance() {
|
||||
return this.outstandingBalance;
|
||||
}
|
||||
|
||||
public void setOutstandingBalance(Money outstandingBalance) {
|
||||
this.outstandingBalance = outstandingBalance;
|
||||
}
|
||||
|
||||
public Money getOutstandingBalanceAsPerRest() {
|
||||
return this.outstandingBalanceAsPerRest;
|
||||
}
|
||||
|
||||
public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
|
||||
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
|
||||
}
|
||||
|
||||
public List<LoanRepaymentScheduleInstallment> getInstallments() {
|
||||
return this.installments;
|
||||
}
|
||||
|
||||
public Collection<RecalculationDetail> getRecalculationDetails() {
|
||||
return this.recalculationDetails;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
|
||||
return this.loanRepaymentScheduleTransactionProcessor;
|
||||
}
|
||||
|
||||
public LocalDate getScheduleTillDate() {
|
||||
return this.scheduleTillDate;
|
||||
}
|
||||
|
||||
public boolean isPartialUpdate() {
|
||||
return this.partialUpdate;
|
||||
}
|
||||
|
||||
public LocalDate getPeriodStartDate() {
|
||||
return this.periodStartDate;
|
||||
}
|
||||
|
||||
public void setPeriodStartDate(LocalDate periodStartDate) {
|
||||
this.periodStartDate = periodStartDate;
|
||||
}
|
||||
|
||||
public Money getPrincipalToBeScheduled() {
|
||||
return this.principalToBeScheduled;
|
||||
}
|
||||
|
||||
public void setPrincipalToBeScheduled(Money principalToBeScheduled) {
|
||||
this.principalToBeScheduled = principalToBeScheduled;
|
||||
}
|
||||
|
||||
public void incrementPeriodNumber() {
|
||||
this.periodNumber++;
|
||||
}
|
||||
|
||||
public void incrementInstalmentNumber() {
|
||||
this.instalmentNumber++;
|
||||
}
|
||||
|
||||
public void addReducePrincipal(Money reducePrincipal) {
|
||||
if (this.reducePrincipal != null) {
|
||||
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal);
|
||||
} else {
|
||||
this.reducePrincipal = reducePrincipal;
|
||||
}
|
||||
}
|
||||
|
||||
public void reduceReducePrincipal(Money reducePrincipal) {
|
||||
if (this.reducePrincipal != null) {
|
||||
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
|
||||
if (this.principalToBeScheduled != null) {
|
||||
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled);
|
||||
} else {
|
||||
this.principalToBeScheduled = principalToBeScheduled;
|
||||
}
|
||||
}
|
||||
|
||||
public void addOutstandingBalance(Money outstandingBalance) {
|
||||
if (this.outstandingBalance != null) {
|
||||
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance);
|
||||
} else {
|
||||
this.outstandingBalance = outstandingBalance;
|
||||
}
|
||||
}
|
||||
|
||||
public void reduceOutstandingBalance(Money outstandingBalance) {
|
||||
if (this.outstandingBalance != null) {
|
||||
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
|
||||
if (this.outstandingBalanceAsPerRest != null) {
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest);
|
||||
} else {
|
||||
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
|
||||
}
|
||||
}
|
||||
|
||||
public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
|
||||
if (this.outstandingBalanceAsPerRest != null) {
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest);
|
||||
}
|
||||
}
|
||||
|
||||
public int getLoanTermInDays() {
|
||||
return this.loanTermInDays;
|
||||
}
|
||||
|
||||
public void addLoanTermInDays(int loanTermInDays) {
|
||||
this.loanTermInDays += loanTermInDays;
|
||||
}
|
||||
|
||||
public Money getTotalOutstandingInterestPaymentDueToGrace() {
|
||||
return this.totalOutstandingInterestPaymentDueToGrace;
|
||||
}
|
||||
|
||||
public void setTotalOutstandingInterestPaymentDueToGrace(Money totalOutstandingInterestPaymentDueToGrace) {
|
||||
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
|
||||
}
|
||||
|
||||
public Map<LocalDate, Map<LocalDate, Money>> getCompoundingDateVariations() {
|
||||
return this.compoundingDateVariations;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
return this.currency;
|
||||
}
|
||||
|
||||
public boolean applyInterestRecalculation() {
|
||||
return this.applyInterestRecalculation;
|
||||
}
|
||||
|
||||
public Money getUnCompoundedAmount() {
|
||||
return this.unCompoundedAmount;
|
||||
}
|
||||
|
||||
public void setUnCompoundedAmount(Money unCompoundedAmount) {
|
||||
this.unCompoundedAmount = unCompoundedAmount;
|
||||
}
|
||||
|
||||
public void addUnCompoundedAmount(Money unCompoundedAmount) {
|
||||
if (this.unCompoundedAmount != null) {
|
||||
this.unCompoundedAmount = this.unCompoundedAmount.plus(unCompoundedAmount);
|
||||
} else {
|
||||
this.unCompoundedAmount = unCompoundedAmount;
|
||||
}
|
||||
}
|
||||
|
||||
public void minusUnCompoundedAmount(Money unCompoundedAmount) {
|
||||
if (this.unCompoundedAmount != null) {
|
||||
this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFirstPeriod() {
|
||||
return 1 == instalmentNumber;
|
||||
}
|
||||
}
|
||||
-604
@@ -1,604 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
|
||||
public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||
// TODO: handle interest calculation
|
||||
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
||||
// generate list of proposed schedule due dates
|
||||
LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
|
||||
LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
|
||||
.fetchLoanTermDueDateVariationsData(loanEndDate);
|
||||
if (lastDueDateVariation != null) {
|
||||
loanEndDate = lastDueDateVariation.getDateValue();
|
||||
}
|
||||
loanApplicationTerms.updateLoanEndDate(loanEndDate);
|
||||
|
||||
// determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
// setup variables for tracking important facts required for loan
|
||||
// schedule generation.
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
|
||||
? loanApplicationTerms.getExpectedDisbursementDate()
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
|
||||
LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency));
|
||||
|
||||
List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
|
||||
boolean isFirstRepayment = true;
|
||||
|
||||
// charges which depends on total loan interest will be added to this
|
||||
// set and handled separately after all installments generated
|
||||
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
||||
boolean isNextRepaymentAvailable = true;
|
||||
|
||||
boolean thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
||||
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
||||
while (!scheduleParams.getOutstandingBalance().isZero() || thereIsDisbursementBeforeOrOnLoanEndDate) {
|
||||
scheduleParams.setActualRepaymentDate(getScheduledDateGenerator().generateNextRepaymentDate(
|
||||
scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, scheduleParams.getPeriodNumber()));
|
||||
AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator()
|
||||
.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
|
||||
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
|
||||
isFirstRepayment = false;
|
||||
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
|
||||
|
||||
// Loan Schedule Exceptions that need to be applied for Loan Account
|
||||
LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, scheduledDueDate);
|
||||
scheduledDueDate = termVariationParams.scheduledDueDate();
|
||||
|
||||
// Updates total days in term
|
||||
scheduleParams
|
||||
.addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate)));
|
||||
|
||||
ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
|
||||
|
||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate);
|
||||
|
||||
// 5 determine principal,interest of repayment period
|
||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
|
||||
scheduleParams, mc);
|
||||
|
||||
// update cumulative fields for principal
|
||||
currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
|
||||
updateOutstandingBalance(scheduleParams, currentPeriodParams);
|
||||
|
||||
if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) {
|
||||
currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance());
|
||||
scheduleParams.setOutstandingBalance(Money.zero(currency));
|
||||
}
|
||||
|
||||
if (!isNextRepaymentAvailable) {
|
||||
scheduleParams.getDisburseDetailMap().clear();
|
||||
}
|
||||
|
||||
// applies charges for the period
|
||||
applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc);
|
||||
|
||||
// sum up real totalInstallmentDue from components
|
||||
final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
|
||||
|
||||
// if previous installment is last then add interest to same
|
||||
// installment
|
||||
if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) {
|
||||
currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod());
|
||||
continue;
|
||||
}
|
||||
|
||||
// create repayment period from parts
|
||||
LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
|
||||
scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
|
||||
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
|
||||
totalInstallmentDue, false);
|
||||
if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
|
||||
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
|
||||
}
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
|
||||
installment.setEMIFixedSpecificToInstallmentTrue();
|
||||
}
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// Updates principal paid map with effective date for reducing
|
||||
// the amount from outstanding balance(interest calculation)
|
||||
updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
|
||||
installment);
|
||||
|
||||
// handle cumulative fields
|
||||
|
||||
scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
|
||||
scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
|
||||
scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod());
|
||||
scheduleParams.setPeriodStartDate(scheduledDueDate);
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.incrementPeriodNumber();
|
||||
// if (termVariationParams.isRecalculateAmounts()) {
|
||||
// loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
|
||||
// loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
|
||||
// adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(),
|
||||
// scheduleParams.getPeriodNumber(), mc);
|
||||
// }
|
||||
thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
||||
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
||||
}
|
||||
|
||||
// If the disbursement happened after maturity date
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods,
|
||||
DateUtils.getBusinessLocalDate().plusDays(1));
|
||||
}
|
||||
|
||||
// determine fees and penalties for charges which depends on total
|
||||
// loan interest
|
||||
updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
|
||||
|
||||
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
final BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
|
||||
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
|
||||
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
|
||||
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
|
||||
scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
|
||||
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
LocalDate rescheduleFrom) {
|
||||
|
||||
LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
|
||||
return LoanScheduleDTO.from(null, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Abstract methods
|
||||
public abstract ScheduledDateGenerator getScheduledDateGenerator();
|
||||
|
||||
public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams loanScheduleParams, MathContext mc);
|
||||
|
||||
// Private, internal methods
|
||||
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
return chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
private List<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
List<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
|
||||
// In case of `disallowExpectedDisbursementDetails = true`, anyway at least 1 disbursement details must exist
|
||||
if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
|
||||
loanApplicationTerms.getDisbursementDatas()
|
||||
.add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null,
|
||||
null, null));
|
||||
}
|
||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||
Money principalMoney = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal());
|
||||
if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) {
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
disbursementData.disbursementDate(), Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()),
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
loanScheduleParams.addOutstandingBalance(principalMoney);
|
||||
loanScheduleParams.addOutstandingBalanceAsPerRest(principalMoney);
|
||||
loanScheduleParams.addPrincipalToBeScheduled(principalMoney);
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(principalMoney));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal());
|
||||
periods.add(downPaymentPeriod);
|
||||
}
|
||||
} else {
|
||||
Money disbursedAmount = loanScheduleParams.getDisburseDetailMap().getOrDefault(disbursementData.disbursementDate(),
|
||||
Money.zero(loanApplicationTerms.getCurrency()));
|
||||
loanScheduleParams.getDisburseDetailMap().put(disbursementData.disbursementDate(), disbursedAmount.add(principalMoney));
|
||||
}
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) {
|
||||
Money downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(),
|
||||
MathUtil.percentageOf(periodBaseAmount, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment(
|
||||
scheduleParams.getInstalmentNumber(), date, downPaymentAmount,
|
||||
scheduleParams.getOutstandingBalance().minus(downPaymentAmount));
|
||||
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.addTotalRepaymentExpected(downPaymentAmount);
|
||||
scheduleParams.reduceOutstandingBalance(downPaymentAmount);
|
||||
scheduleParams.reduceOutstandingBalanceAsPerRest(downPaymentAmount);
|
||||
scheduleParams.setPrincipalToBeScheduled(scheduleParams.getPrincipalToBeScheduled().minus(downPaymentAmount));
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().minus(downPaymentAmount));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
return installment;
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method add extra disbursement periods (if applicable) and update the schedule params
|
||||
*/
|
||||
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||
LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
|
||||
Iterator<Map.Entry<LocalDate, Money>> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<LocalDate, Money> disburseDetail = iter.next();
|
||||
if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
|
||||
|| disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()))
|
||||
&& disburseDetail.getKey().isBefore(scheduledDueDate)) {
|
||||
// validation check for amount not exceeds specified max
|
||||
// amount as per the configuration
|
||||
if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getMaxOutstandingBalance() != null) {
|
||||
Money maxOutstandingBalance = loanApplicationTerms.getMaxOutstandingBalanceMoney();
|
||||
if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue()).isGreaterThan(maxOutstandingBalance)) {
|
||||
String errorMsg = "Outstanding balance must not exceed the amount: " + maxOutstandingBalance;
|
||||
throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance(),
|
||||
disburseDetail.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// creates and add disbursement detail to the repayments
|
||||
// period
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
BigDecimal downPaymentAmt = BigDecimal.ZERO;
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
|
||||
scheduleParams.addOutstandingBalanceAsPerRest(disburseDetail.getValue());
|
||||
scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue());
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
// get list of disbursements done on same day and create down payment periods
|
||||
List<DisbursementData> disbursementsOnSameDate = loanApplicationTerms.getDisbursementDatas().stream()
|
||||
.filter(disbursementData -> DateUtils.isEqual(disbursementData.disbursementDate(), disburseDetail.getKey()))
|
||||
.toList();
|
||||
for (DisbursementData disbursementData : disbursementsOnSameDate) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
scheduleParams, disbursementData.disbursementDate(), disbursementData.getPrincipal());
|
||||
periods.add(downPaymentPeriod);
|
||||
downPaymentAmt = downPaymentAmt.add(downPaymentPeriod.principalDue());
|
||||
}
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
final MathContext mc) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
currentPeriodParams.getInterestForThisPeriod(), null);
|
||||
currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate,
|
||||
loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
|
||||
scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment());
|
||||
scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment());
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
|
||||
Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
|
||||
boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
|
||||
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
|
||||
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
|
||||
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
|
||||
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = cumulative.plus(loanCharge.chargeAmount());
|
||||
} else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative,
|
||||
loanCharge, mc);
|
||||
} else if (isDue) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
|
||||
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
|
||||
final LoanCharge loanCharge, final MathContext mc) {
|
||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
|
||||
.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
} else {
|
||||
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalDisbursed.getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
LoanScheduleModelPeriod installment) {
|
||||
LocalDate amountApplicableDate = installment.periodDueDate();
|
||||
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
|
||||
amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
}
|
||||
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
|
||||
currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
|
||||
|
||||
// update outstanding balance for interest calculation
|
||||
updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
|
||||
}
|
||||
|
||||
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
|
||||
final HolidayDetailDTO holidayDetailDTO) {
|
||||
LocalDate nextScheduleDate;
|
||||
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
|
||||
nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate,
|
||||
loanApplicationTerms, holidayDetailDTO);
|
||||
} else {
|
||||
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
|
||||
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
|
||||
}
|
||||
|
||||
return nextScheduleDate;
|
||||
}
|
||||
|
||||
private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount, final LocalDate amountApplicableDate) {
|
||||
Money principalPaid = amount;
|
||||
if (map.containsKey(amountApplicableDate)) {
|
||||
principalPaid = map.get(amountApplicableDate).plus(principalPaid);
|
||||
}
|
||||
map.put(amountApplicableDate, principalPaid);
|
||||
|
||||
}
|
||||
|
||||
private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
|
||||
scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(),
|
||||
scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies all the past date principal changes and apply them on outstanding balance for future calculations
|
||||
*/
|
||||
private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap, final LocalDate scheduledDueDate,
|
||||
final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
|
||||
List<LocalDate> removeFromPrincipalPortionMap = new ArrayList<>();
|
||||
Money outstandingBalance = outstandingBalanceAsPerRest;
|
||||
for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
|
||||
if (!principal.getKey().isAfter(scheduledDueDate)) {
|
||||
if (addMapDetails) {
|
||||
outstandingBalance = outstandingBalance.plus(principal.getValue());
|
||||
} else {
|
||||
outstandingBalance = outstandingBalance.minus(principal.getValue());
|
||||
}
|
||||
removeFromPrincipalPortionMap.add(principal.getKey());
|
||||
}
|
||||
}
|
||||
for (LocalDate date : removeFromPrincipalPortionMap) {
|
||||
principalPortionMap.remove(date);
|
||||
}
|
||||
return outstandingBalance;
|
||||
}
|
||||
|
||||
private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
|
||||
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
|
||||
Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
|
||||
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
|
||||
scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
|
||||
scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
|
||||
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<LoanCharge> separateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
|
||||
Set<LoanCharge> interestCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
|
||||
|| loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
|
||||
interestCharges.add(loanCharge);
|
||||
}
|
||||
}
|
||||
loanCharges.removeAll(interestCharges);
|
||||
return interestCharges;
|
||||
}
|
||||
|
||||
private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
|
||||
// update outstandingLoanBlance using current period
|
||||
// 'principalDue'
|
||||
scheduleParams
|
||||
.reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loanApplicationTerms
|
||||
* @param scheduleParams
|
||||
* @param scheduledDueDate
|
||||
* @return
|
||||
*/
|
||||
private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
|
||||
boolean skipPeriod = false;
|
||||
boolean recalculateAmounts = false;
|
||||
LocalDate modifiedScheduledDueDate = scheduledDueDate;
|
||||
ArrayList<LoanTermVariationsData> variationsData = null;
|
||||
|
||||
// due date changes should be applied only for that dueDate
|
||||
if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
|
||||
LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
|
||||
if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
|
||||
modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
|
||||
loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
|
||||
if (!loanTermVariationsData.isSpecificToInstallment()) {
|
||||
scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate);
|
||||
loanApplicationTerms.setNewScheduledDueDateStart(modifiedScheduledDueDate);
|
||||
}
|
||||
loanTermVariationsData.setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
return new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, variationsData);
|
||||
}
|
||||
}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule Disbursement Period (not used for persistence)
|
||||
*/
|
||||
public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Integer periodNumber;
|
||||
private final LocalDate disbursementDate;
|
||||
private final Money principalDisbursed;
|
||||
private final BigDecimal chargesDueAtTimeOfDisbursement;
|
||||
private boolean isEMIFixedSpecificToInstallment = false;
|
||||
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
|
||||
}
|
||||
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
return new LoanScheduleModelDisbursementPeriod(null, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement);
|
||||
}
|
||||
|
||||
private LoanScheduleModelDisbursementPeriod(final Integer periodNumber, final LocalDate disbursementDate,
|
||||
final Money principalDisbursed, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.disbursementDate = disbursementDate;
|
||||
this.principalDisbursed = principalDisbursed;
|
||||
this.chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.disbursementOnlyPeriod(this.disbursementDate, this.principalDisbursed.getAmount(),
|
||||
this.chargesDueAtTimeOfDisbursement, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecalculatedInterestComponent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(@SuppressWarnings("unused") Money principalDue) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(@SuppressWarnings("unused") Money principalDue) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEMIFixedSpecificToInstallmentTrue() {
|
||||
this.isEMIFixedSpecificToInstallment = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEMIFixedSpecificToInstallment() {
|
||||
return isEMIFixedSpecificToInstallment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal rescheduleInterestPortion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule Repayment Period (not used for persistence)
|
||||
*/
|
||||
@Getter
|
||||
public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
private final LocalDate fromDate;
|
||||
private final LocalDate dueDate;
|
||||
private Money principalDue;
|
||||
private final Money outstandingLoanBalance;
|
||||
private Money interestDue;
|
||||
private Money feeChargesDue;
|
||||
private Money penaltyChargesDue;
|
||||
private Money totalDue;
|
||||
private final boolean recalculatedInterestComponent;
|
||||
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
|
||||
private boolean isEMIFixedSpecificToInstallment = false;
|
||||
BigDecimal rescheduleInterestPortion;
|
||||
|
||||
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
|
||||
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
|
||||
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) {
|
||||
|
||||
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
|
||||
interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent);
|
||||
}
|
||||
|
||||
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
|
||||
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
|
||||
final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.principalDue = principalDue;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
this.interestDue = interestDue;
|
||||
this.feeChargesDue = feeChargesDue;
|
||||
this.penaltyChargesDue = penaltyChargesDue;
|
||||
this.totalDue = totalDue;
|
||||
this.recalculatedInterestComponent = recalculatedInterestComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.repaymentOnlyPeriod(this.periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
|
||||
this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
|
||||
this.penaltyChargesDue.getAmount(), this.totalDue.getAmount(), this.principalDue.plus(this.interestDue).getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return this.fromDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.principalDue != null) {
|
||||
value = this.principalDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.interestDue != null) {
|
||||
value = this.interestDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.feeChargesDue != null) {
|
||||
value = this.feeChargesDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.penaltyChargesDue != null) {
|
||||
value = this.penaltyChargesDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
|
||||
this.feeChargesDue = this.feeChargesDue.plus(feeCharge);
|
||||
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge);
|
||||
this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(final Money principalDue) {
|
||||
this.principalDue = this.principalDue.plus(principalDue);
|
||||
this.totalDue = this.totalDue.plus(principalDue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecalculatedInterestComponent() {
|
||||
return this.recalculatedInterestComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(Money interestDue) {
|
||||
this.interestDue = this.interestDue.plus(interestDue);
|
||||
this.totalDue = this.totalDue.plus(interestDue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return this.loanCompoundingDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEMIFixedSpecificToInstallment() {
|
||||
return this.isEMIFixedSpecificToInstallment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEMIFixedSpecificToInstallmentTrue() {
|
||||
this.isEMIFixedSpecificToInstallment = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
|
||||
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal rescheduleInterestPortion() {
|
||||
return this.rescheduleInterestPortion;
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
|
||||
public record LoanTermVariationParams(boolean skipPeriod, boolean recalculateAmounts, LocalDate scheduledDueDate,
|
||||
List<LoanTermVariationsData> variationsData) {
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
@Override
|
||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||
return scheduledDateGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
|
||||
return paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams loanScheduleParams, MathContext mc) {
|
||||
// TODO: handle interest calculation
|
||||
int periodNumber = loanScheduleParams.getPeriodNumber();
|
||||
BigDecimal fixedEMIAmount = loanApplicationTerms.getFixedEmiAmount();
|
||||
Money calculatedPrincipal;
|
||||
if (fixedEMIAmount == null) {
|
||||
int noRemainingPeriod = loanApplicationTerms.getActualNoOfRepaymnets() - periodNumber + 1;
|
||||
calculatedPrincipal = loanScheduleParams.getOutstandingBalance().dividedBy(noRemainingPeriod, mc.getRoundingMode());
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal,
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
loanApplicationTerms.setFixedEmiAmount(calculatedPrincipal.getAmount());
|
||||
} else {
|
||||
calculatedPrincipal = Money.of(loanApplicationTerms.getCurrency(), fixedEMIAmount);
|
||||
}
|
||||
// adjust if needed
|
||||
if (periodNumber == loanApplicationTerms.getActualNoOfRepaymnets()) {
|
||||
Money remainingAmount = loanScheduleParams.getOutstandingBalance().minus(calculatedPrincipal);
|
||||
calculatedPrincipal = calculatedPrincipal.plus(remainingAmount);
|
||||
}
|
||||
|
||||
return new PrincipalInterest(calculatedPrincipal, Money.zero(loanApplicationTerms.getCurrency()),
|
||||
Money.zero(loanApplicationTerms.getCurrency()));
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public class RecalculationDetail {
|
||||
|
||||
private LocalDate transactionDate;
|
||||
private boolean isProcessed;
|
||||
private LoanTransaction transaction;
|
||||
|
||||
public RecalculationDetail(final LocalDate transactionDate, final LoanTransaction transaction) {
|
||||
this.transactionDate = transactionDate;
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
public LocalDate getTransactionDate() {
|
||||
return this.transactionDate;
|
||||
}
|
||||
|
||||
public LoanTransaction getTransaction() {
|
||||
return this.transaction;
|
||||
}
|
||||
|
||||
public boolean isProcessed() {
|
||||
return this.isProcessed;
|
||||
}
|
||||
|
||||
public void setProcessed(boolean isProcessed) {
|
||||
this.isProcessed = isProcessed;
|
||||
}
|
||||
}
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
|
||||
public class ScheduleCurrentPeriodParams {
|
||||
|
||||
Money earlyPaidAmount;
|
||||
LoanScheduleModelPeriod lastInstallment;
|
||||
boolean skipCurrentLoop;
|
||||
Money interestForThisPeriod;
|
||||
Money principalForThisPeriod;
|
||||
Money feeChargesForInstallment;
|
||||
Money penaltyChargesForInstallment;
|
||||
// for adjusting outstandingBalances
|
||||
Money reducedBalance;
|
||||
boolean isEmiAmountChanged;
|
||||
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
|
||||
ScheduleCurrentPeriodParams(final MonetaryCurrency currency) {
|
||||
this(currency, BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||
this.earlyPaidAmount = Money.zero(currency);
|
||||
this.lastInstallment = null;
|
||||
this.skipCurrentLoop = false;
|
||||
this.interestForThisPeriod = Money.zero(currency);
|
||||
this.principalForThisPeriod = Money.zero(currency);
|
||||
this.reducedBalance = Money.zero(currency);
|
||||
this.feeChargesForInstallment = Money.zero(currency);
|
||||
this.penaltyChargesForInstallment = Money.zero(currency);
|
||||
this.isEmiAmountChanged = false;
|
||||
this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
}
|
||||
|
||||
public Money getEarlyPaidAmount() {
|
||||
return this.earlyPaidAmount;
|
||||
}
|
||||
|
||||
public void plusEarlyPaidAmount(Money earlyPaidAmount) {
|
||||
this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount);
|
||||
}
|
||||
|
||||
public void minusEarlyPaidAmount(Money earlyPaidAmount) {
|
||||
this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount);
|
||||
}
|
||||
|
||||
public LoanScheduleModelPeriod getLastInstallment() {
|
||||
return this.lastInstallment;
|
||||
}
|
||||
|
||||
public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) {
|
||||
this.lastInstallment = lastInstallment;
|
||||
}
|
||||
|
||||
public boolean isSkipCurrentLoop() {
|
||||
return this.skipCurrentLoop;
|
||||
}
|
||||
|
||||
public void setSkipCurrentLoop(boolean skipCurrentLoop) {
|
||||
this.skipCurrentLoop = skipCurrentLoop;
|
||||
}
|
||||
|
||||
public Money getInterestForThisPeriod() {
|
||||
return this.interestForThisPeriod;
|
||||
}
|
||||
|
||||
public void setInterestForThisPeriod(Money interestForThisPeriod) {
|
||||
this.interestForThisPeriod = interestForThisPeriod;
|
||||
}
|
||||
|
||||
public void minusInterestForThisPeriod(Money interestForThisPeriod) {
|
||||
this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod);
|
||||
}
|
||||
|
||||
public Money getPrincipalForThisPeriod() {
|
||||
return this.principalForThisPeriod;
|
||||
}
|
||||
|
||||
public void setPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = principalForThisPeriod;
|
||||
}
|
||||
|
||||
public void plusPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod);
|
||||
}
|
||||
|
||||
public void minusPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod);
|
||||
}
|
||||
|
||||
public Money getReducedBalance() {
|
||||
return this.reducedBalance;
|
||||
}
|
||||
|
||||
public void setReducedBalance(Money reducedBalance) {
|
||||
this.reducedBalance = reducedBalance;
|
||||
}
|
||||
|
||||
public Money getFeeChargesForInstallment() {
|
||||
return this.feeChargesForInstallment;
|
||||
}
|
||||
|
||||
public void setFeeChargesForInstallment(Money feeChargesForInstallment) {
|
||||
this.feeChargesForInstallment = feeChargesForInstallment;
|
||||
}
|
||||
|
||||
public void minusFeeChargesForInstallment(Money feeChargesForInstallment) {
|
||||
this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment);
|
||||
}
|
||||
|
||||
public Money getPenaltyChargesForInstallment() {
|
||||
return this.penaltyChargesForInstallment;
|
||||
}
|
||||
|
||||
public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
|
||||
this.penaltyChargesForInstallment = penaltyChargesForInstallment;
|
||||
}
|
||||
|
||||
public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
|
||||
this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment);
|
||||
}
|
||||
|
||||
public Money fetchTotalAmountForPeriod() {
|
||||
return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);
|
||||
}
|
||||
|
||||
public boolean isEmiAmountChanged() {
|
||||
return this.isEmiAmountChanged;
|
||||
}
|
||||
|
||||
public void setEmiAmountChanged(boolean isEmiAmountChanged) {
|
||||
this.isEmiAmountChanged = isEmiAmountChanged;
|
||||
}
|
||||
|
||||
public BigDecimal getInterestCalculationGraceOnRepaymentPeriodFraction() {
|
||||
return this.interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
}
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
|
||||
LocalDate firstRepaymentDate, Calendar loanCalendar, HolidayDetailDTO holidayDetailDTO,
|
||||
LoanApplicationTerms loanApplicationTerms);
|
||||
|
||||
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
|
||||
|
||||
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
|
||||
Integer periodNumber);
|
||||
|
||||
AdjustedDateDetailsDTO adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
|
||||
HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
|
||||
|
||||
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
|
||||
|
||||
LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
|
||||
HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LocalDate generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate lastRepaymentDate,
|
||||
LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class MultiDisbursementOutstandingAmoutException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public MultiDisbursementOutstandingAmoutException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
|
||||
super("error.msg.loanschedule.exceeding.max.outstanding.balance", defaultUserMessage, defaultUserMessageArgs);
|
||||
}
|
||||
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
|
||||
public class AdvancedPaymentScheduleTransactionProcessorCondition extends PropertiesCondition {
|
||||
|
||||
@Override
|
||||
protected boolean matches(FineractProperties properties) {
|
||||
return properties.getLoan().getTransactionProcessor().getAdvancedPaymentStrategy().isEnabled();
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AdvancedPaymentData implements Serializable {
|
||||
|
||||
private final String transactionType;
|
||||
private final String futureInstallmentAllocationRule;
|
||||
private final List<PaymentAllocationOrder> paymentAllocationOrder;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class PaymentAllocationOrder implements Serializable {
|
||||
|
||||
private final String paymentAllocationRule;
|
||||
private final Integer order;
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class CreditAllocationData implements Serializable {
|
||||
|
||||
private final String transactionType;
|
||||
private final List<CreditAllocationOrder> creditAllocationOrder;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class CreditAllocationOrder implements Serializable {
|
||||
|
||||
private final String creditAllocationRule;
|
||||
private final Integer order;
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
@Mapper(config = MapstructMapperConfig.class)
|
||||
public interface AdvancedPaymentDataMapper {
|
||||
|
||||
List<AdvancedPaymentData> mapLoanProductPaymentAllocationRule(List<LoanProductPaymentAllocationRule> paymentAllocationRule);
|
||||
|
||||
List<AdvancedPaymentData> mapLoanPaymentAllocationRule(List<LoanPaymentAllocationRule> paymentAllocationRule);
|
||||
|
||||
@Mapping(target = "paymentAllocationOrder", source = "allocationTypes")
|
||||
AdvancedPaymentData mapLoanProductPaymentAllocationRule(LoanProductPaymentAllocationRule paymentAllocationRule);
|
||||
|
||||
@Mapping(target = "paymentAllocationOrder", source = "allocationTypes")
|
||||
AdvancedPaymentData mapLoanPaymentAllocationRule(LoanPaymentAllocationRule paymentAllocationRule);
|
||||
|
||||
default List<AdvancedPaymentData.PaymentAllocationOrder> mapAllocationTypes(List<PaymentAllocationType> allocationTypes) {
|
||||
AtomicInteger counter = new AtomicInteger(1);
|
||||
return allocationTypes.stream().map(a -> new AdvancedPaymentData.PaymentAllocationOrder(a.name(), counter.getAndIncrement()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
|
||||
|
||||
public class LoanProductCreditAllocationRuleMerger {
|
||||
|
||||
public boolean updateCreditAllocationRules(LoanProduct loanProduct,
|
||||
final List<LoanProductCreditAllocationRule> newLoanProductCreditAllocationRules) {
|
||||
if (newLoanProductCreditAllocationRules == null) {
|
||||
return false;
|
||||
}
|
||||
boolean updated = false;
|
||||
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> originalItems = loanProduct.getCreditAllocationRules()
|
||||
.stream().collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
|
||||
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> newItems = newLoanProductCreditAllocationRules.stream()
|
||||
.collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
|
||||
|
||||
// elements to be deleted
|
||||
Set<CreditAllocationTransactionType> existing = new HashSet<>(originalItems.keySet());
|
||||
Set<CreditAllocationTransactionType> newSet = new HashSet<>(newItems.keySet());
|
||||
existing.removeAll(newSet);
|
||||
if (existing.size() > 0) {
|
||||
updated = true;
|
||||
existing.forEach(type -> {
|
||||
loanProduct.getCreditAllocationRules().remove(originalItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be added
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
newSet.removeAll(existing);
|
||||
if (newSet.size() > 0) {
|
||||
updated = true;
|
||||
newSet.forEach(type -> {
|
||||
loanProduct.getCreditAllocationRules().add(newItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be merged
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
existing.retainAll(newSet);
|
||||
|
||||
for (CreditAllocationTransactionType type : existing) {
|
||||
boolean result = mergeLoanProductCreditAllocationRule(originalItems.get(type), newItems.get(type));
|
||||
if (result) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean mergeLoanProductCreditAllocationRule(LoanProductCreditAllocationRule into, LoanProductCreditAllocationRule newElement) {
|
||||
boolean changed = false;
|
||||
|
||||
if (!Objects.equals(into.getAllocationTypes(), newElement.getAllocationTypes())) {
|
||||
into.setAllocationTypes(newElement.getAllocationTypes());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
|
||||
public class LoanProductPaymentAllocationRuleMerger {
|
||||
|
||||
public boolean updateProductPaymentAllocationRules(LoanProduct loanProduct,
|
||||
final List<LoanProductPaymentAllocationRule> newLoanProductPaymentAllocationRules) {
|
||||
if (newLoanProductPaymentAllocationRules == null) {
|
||||
return false;
|
||||
}
|
||||
boolean updated = false;
|
||||
Map<PaymentAllocationTransactionType, LoanProductPaymentAllocationRule> originalItems = loanProduct.getPaymentAllocationRules()
|
||||
.stream().collect(Collectors.toMap(LoanProductPaymentAllocationRule::getTransactionType, Function.identity()));
|
||||
Map<PaymentAllocationTransactionType, LoanProductPaymentAllocationRule> newItems = newLoanProductPaymentAllocationRules.stream()
|
||||
.collect(Collectors.toMap(LoanProductPaymentAllocationRule::getTransactionType, Function.identity()));
|
||||
|
||||
// elements to be deleted
|
||||
Set<PaymentAllocationTransactionType> existing = new HashSet<>(originalItems.keySet());
|
||||
Set<PaymentAllocationTransactionType> newSet = new HashSet<>(newItems.keySet());
|
||||
existing.removeAll(newSet);
|
||||
if (existing.size() > 0) {
|
||||
updated = true;
|
||||
existing.forEach(type -> {
|
||||
loanProduct.getPaymentAllocationRules().remove(originalItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be added
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
newSet.removeAll(existing);
|
||||
if (newSet.size() > 0) {
|
||||
updated = true;
|
||||
newSet.forEach(type -> {
|
||||
loanProduct.getPaymentAllocationRules().add(newItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be merged
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
existing.retainAll(newSet);
|
||||
|
||||
for (PaymentAllocationTransactionType type : existing) {
|
||||
boolean result = mergeLoanProductPaymentAllocationRule(originalItems.get(type), newItems.get(type));
|
||||
if (result) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean mergeLoanProductPaymentAllocationRule(LoanProductPaymentAllocationRule into,
|
||||
LoanProductPaymentAllocationRule newElement) {
|
||||
boolean changed = false;
|
||||
|
||||
if (!Objects.equals(into.getFutureInstallmentAllocationRule(), newElement.getFutureInstallmentAllocationRule())) {
|
||||
into.setFutureInstallmentAllocationRule(newElement.getFutureInstallmentAllocationRule());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!Objects.equals(into.getAllocationTypes(), newElement.getAllocationTypes())) {
|
||||
into.setAllocationTypes(newElement.getAllocationTypes());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
-618
@@ -1,618 +0,0 @@
|
||||
/**
|
||||
* 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.transactionprocessor.impl;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.refEq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
|
||||
private final LocalDate transactionDate = LocalDate.of(2023, 7, 11);
|
||||
private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
||||
private LoanReAgingParameterRepository reAgingParameterRepository = Mockito.mock(LoanReAgingParameterRepository.class);
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(reAgingParameterRepository);
|
||||
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
ThreadLocalContextUtil.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargePaymentTransactionTestWithExactAmount() {
|
||||
final BigDecimal chargeAmount = BigDecimal.valueOf(100);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = LocalDate.of(2023, 1, 5);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = mock(Loan.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
LoanRepaymentScheduleInstallment installment = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(chargeAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount());
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, times(0)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargePaymentTransactionTestWithLessTransactionAmount() {
|
||||
BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = LocalDate.of(2023, 1, 5);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = mock(Loan.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(20.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
LoanRepaymentScheduleInstallment installment = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
|
||||
assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(transactionAmountMoney).getAmount());
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.valueOf(80.00).compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, times(0)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargePaymentTransactionTestWithMoreTransactionAmount() {
|
||||
BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = disbursementDate.plusMonths(1);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = mock(LoanProductRelatedDetail.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(120.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
LoanPaymentAllocationRule loanPaymentAllocationRule = mock(LoanPaymentAllocationRule.class);
|
||||
LoanRepaymentScheduleInstallment installment = spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate,
|
||||
BigDecimal.valueOf(100L), BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
||||
when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
|
||||
when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
|
||||
when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
assertEquals(0, BigDecimal.valueOf(20).compareTo(loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount()));
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.valueOf(80).compareTo(installment.getPrincipalOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, times(1)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalPenaltyFeeInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// verify principal
|
||||
Mockito.verify(installment, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("10.00"));
|
||||
Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePenaltyFeePrincipalInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PENALTY, FEE, PRINCIPAL, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("20.00"));
|
||||
Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWithAdditionalInstallment() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
|
||||
LoanRepaymentScheduleInstallment installment1 = createMockInstallment(LocalDate.of(2022, 12, 20), false);
|
||||
LoanRepaymentScheduleInstallment installment2 = createMockInstallment(LocalDate.of(2022, 12, 27), true);
|
||||
installments.add(installment1);
|
||||
installments.add(installment2);
|
||||
|
||||
// when
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// verify principal
|
||||
Mockito.verify(installment2, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment2, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment2, times(1)).addToCreditedFee(new BigDecimal("10.00"));
|
||||
Mockito.verify(installment2, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment2, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate localDate, boolean isAdditional) {
|
||||
LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
|
||||
lenient().when(installment.isAdditional()).thenReturn(isAdditional);
|
||||
lenient().when(installment.getDueDate()).thenReturn(localDate);
|
||||
Money interestCharged = Money.of(MONETARY_CURRENCY, BigDecimal.ZERO);
|
||||
lenient().when(installment.getInterestCharged(MONETARY_CURRENCY)).thenReturn(interestCharged);
|
||||
return installment;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LoanCreditAllocationRule createMockCreditAllocationRule(AllocationType... allocationTypes) {
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = mock(LoanCreditAllocationRule.class);
|
||||
lenient().when(mockCreditAllocationRule.getTransactionType()).thenReturn(CreditAllocationTransactionType.CHARGEBACK);
|
||||
lenient().when(mockCreditAllocationRule.getAllocationTypes()).thenReturn(Arrays.asList(allocationTypes));
|
||||
return mockCreditAllocationRule;
|
||||
}
|
||||
|
||||
private LoanTransaction createRepayment(Loan loan, LoanTransaction toTransaction, double principalPortion, double interestPortion,
|
||||
double feePortion, double penaltyPortion) {
|
||||
LoanTransaction repayment = mock(LoanTransaction.class);
|
||||
lenient().when(repayment.getLoan()).thenReturn(loan);
|
||||
lenient().when(repayment.isRepayment()).thenReturn(true);
|
||||
lenient().when(repayment.getTypeOf()).thenReturn(REPAYMENT);
|
||||
lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(principalPortion));
|
||||
lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(interestPortion));
|
||||
lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.valueOf(feePortion));
|
||||
lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.valueOf(penaltyPortion));
|
||||
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
lenient().when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
lenient().when(relation.getToTransaction()).thenReturn(toTransaction);
|
||||
|
||||
lenient().when(repayment.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
return repayment;
|
||||
}
|
||||
|
||||
private LoanTransaction createChargebackTransaction(Loan loan, double transactionAmount) {
|
||||
LoanTransaction chargeback = mock(LoanTransaction.class);
|
||||
lenient().when(chargeback.isChargeback()).thenReturn(true);
|
||||
lenient().when(chargeback.getTypeOf()).thenReturn(LoanTransactionType.CHARGEBACK);
|
||||
lenient().when(chargeback.getLoan()).thenReturn(loan);
|
||||
lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(transactionAmount));
|
||||
Money money = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(transactionAmount));
|
||||
lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(money);
|
||||
lenient().when(chargeback.getTransactionDate()).thenReturn(LocalDate.of(2023, 1, 1));
|
||||
return chargeback;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateChargebackAllocationMap() {
|
||||
Map<AllocationType, Money> result;
|
||||
MonetaryCurrency currency = mock(MonetaryCurrency.class);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(50.0, 0, 0, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(40.0, 10, 0, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 0, 10, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(340.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 88.0, 200.0, 12.0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(352.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 100.0, 200.0, 12.0, currency), result);
|
||||
}
|
||||
|
||||
private void verify(Map<AllocationType, Money> expected, Map<AllocationType, Money> actual) {
|
||||
Assertions.assertEquals(expected.size(), actual.size());
|
||||
expected.forEach((k, v) -> {
|
||||
Assertions.assertEquals(0, v.getAmount().compareTo(actual.get(k).getAmount()), "Not matching for " + k);
|
||||
});
|
||||
}
|
||||
|
||||
private Map<AllocationType, Money> allocationMap(double principal, double interest, double fee, double penalty,
|
||||
MonetaryCurrency currency) {
|
||||
Map<AllocationType, Money> allocationMap = new HashMap<>();
|
||||
allocationMap.put(AllocationType.PRINCIPAL, Money.of(currency, BigDecimal.valueOf(principal)));
|
||||
allocationMap.put(AllocationType.INTEREST, Money.of(currency, BigDecimal.valueOf(interest)));
|
||||
allocationMap.put(AllocationType.FEE, Money.of(currency, BigDecimal.valueOf(fee)));
|
||||
allocationMap.put(AllocationType.PENALTY, Money.of(currency, BigDecimal.valueOf(penalty)));
|
||||
return allocationMap;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionWhenIdProvided() {
|
||||
// given
|
||||
LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
|
||||
when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
Loan loan = mock(Loan.class);
|
||||
when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
when(relation.getToTransaction()).thenReturn(chargebackTransaction);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
|
||||
// when
|
||||
LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackTransaction, ctx);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(originalTransaction, repayment2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindOriginalTransactionThrowsRuntimeExceptionWhenIdProvidedAndRelationsAreMissing() {
|
||||
// given
|
||||
LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
|
||||
when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
Loan loan = mock(Loan.class);
|
||||
when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of());
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
|
||||
// when + then
|
||||
RuntimeException runtimeException = Assertions.assertThrows(RuntimeException.class,
|
||||
() -> underTest.findOriginalTransaction(chargebackTransaction, ctx));
|
||||
Assertions.assertEquals("Chargeback transaction must have an original transaction", runtimeException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvided() {
|
||||
// given
|
||||
LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
|
||||
when(chargebackReplayed.getId()).thenReturn(null);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
|
||||
LoanTransaction originalChargeback = mock(LoanTransaction.class);
|
||||
when(originalChargeback.getId()).thenReturn(123L);
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
|
||||
|
||||
// when
|
||||
LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(originalTransaction, repayment2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvidedFallbackToPersistedTransactions() {
|
||||
// given
|
||||
LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
|
||||
when(chargebackReplayed.getId()).thenReturn(null);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
Loan loan = mock(Loan.class);
|
||||
when(chargebackReplayed.getLoan()).thenReturn(loan);
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
|
||||
|
||||
LoanTransaction originalChargeback = mock(LoanTransaction.class);
|
||||
when(originalChargeback.getId()).thenReturn(123L);
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
|
||||
|
||||
// when
|
||||
LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(originalTransaction, repayment2);
|
||||
}
|
||||
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 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.transactionprocessor.impl;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class ChargeOrTransactionTest {
|
||||
|
||||
@Test
|
||||
public void testCompareToEqual() {
|
||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
||||
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToEqualBackdatedCharge() {
|
||||
ChargeOrTransaction charge = createCharge("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
ChargeOrTransaction transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
||||
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToCreatedDateTime() {
|
||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
|
||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareToSubmittedOnDate() {
|
||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-16", "2023-10-17T10:15:30+01:00");
|
||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorEffectiveDate() {
|
||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
ChargeOrTransaction transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorOnDifferentSubmittedDay() {
|
||||
ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||
ChargeOrTransaction cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:16:30+01:00");
|
||||
ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-18", "2023-10-18T10:14:30+01:00");
|
||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorOnSameDayBackdatedCharge() {
|
||||
ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
||||
ChargeOrTransaction cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
||||
ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorOnSameDay() {
|
||||
ChargeOrTransaction cot1 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
||||
ChargeOrTransaction cot2 = createTransaction("2023-10-19", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
||||
ChargeOrTransaction cot3 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||
}
|
||||
}
|
||||
|
||||
private ChargeOrTransaction createCharge(String effectiveDate, String submittedDate, String creationDateTime) {
|
||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
|
||||
Mockito.when(charge.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
||||
Mockito.when(charge.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.parse(creationDateTime)));
|
||||
return new ChargeOrTransaction(charge);
|
||||
}
|
||||
|
||||
private ChargeOrTransaction createTransaction(String transactionDate, String submittedDate, String creationDateTime) {
|
||||
LoanTransaction transaction = Mockito.mock(LoanTransaction.class);
|
||||
Mockito.when(transaction.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
||||
Mockito.when(transaction.getTransactionDate()).thenReturn(LocalDate.parse(transactionDate));
|
||||
Mockito.when(transaction.getCreatedDateTime()).thenReturn(OffsetDateTime.parse(creationDateTime));
|
||||
return new ChargeOrTransaction(transaction);
|
||||
}
|
||||
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanProductCreditAllocationRuleMergerTest {
|
||||
|
||||
private LoanProductCreditAllocationRuleMerger underTest = new LoanProductCreditAllocationRuleMerger();
|
||||
|
||||
@Test
|
||||
public void testMergerOneNewAdded() {
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
|
||||
|
||||
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule1));
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
|
||||
Assertions.assertEquals(rule1, loanProduct.getCreditAllocationRules().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergerExistingUpdated() {
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
|
||||
LoanProductCreditAllocationRule rule2 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(FEE, INTEREST, PRINCIPAL, PENALTY));
|
||||
|
||||
loanProduct.getCreditAllocationRules().add(rule1);
|
||||
|
||||
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule2));
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
|
||||
Assertions.assertEquals(rule2.getTransactionType(), loanProduct.getCreditAllocationRules().get(0).getTransactionType());
|
||||
Assertions.assertEquals(rule2.getAllocationTypes(), loanProduct.getCreditAllocationRules().get(0).getAllocationTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergerNothingIsChanged() {
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
|
||||
LoanProductCreditAllocationRule rule2 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
|
||||
|
||||
loanProduct.getCreditAllocationRules().add(rule1);
|
||||
|
||||
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of(rule2));
|
||||
Assertions.assertFalse(result);
|
||||
Assertions.assertEquals(1, loanProduct.getCreditAllocationRules().size());
|
||||
Assertions.assertEquals(rule1.getTransactionType(), loanProduct.getCreditAllocationRules().get(0).getTransactionType());
|
||||
Assertions.assertEquals(rule1.getAllocationTypes(), loanProduct.getCreditAllocationRules().get(0).getAllocationTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergerExistingDeleted() {
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductCreditAllocationRule rule1 = createRule(CreditAllocationTransactionType.CHARGEBACK,
|
||||
List.of(PENALTY, FEE, INTEREST, PRINCIPAL));
|
||||
|
||||
loanProduct.getCreditAllocationRules().add(rule1);
|
||||
|
||||
boolean result = underTest.updateCreditAllocationRules(loanProduct, List.of());
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(0, loanProduct.getCreditAllocationRules().size());
|
||||
}
|
||||
|
||||
public LoanProductCreditAllocationRule createRule(CreditAllocationTransactionType transactionType,
|
||||
List<AllocationType> allocationTypeList) {
|
||||
return new LoanProductCreditAllocationRule(null, transactionType, allocationTypeList);
|
||||
}
|
||||
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule.LAST_INSTALLMENT;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType.DEFAULT;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType.REPAYMENT;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.DUE_INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType.PAST_DUE_FEE;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanProductPaymentAllocationRuleMergerTest {
|
||||
|
||||
@Test
|
||||
public void testMergerOneNewAdded() {
|
||||
// given
|
||||
LoanProductPaymentAllocationRuleMerger underTest = new LoanProductPaymentAllocationRuleMerger();
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductPaymentAllocationRule rule1 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
|
||||
// when
|
||||
boolean result = underTest.updateProductPaymentAllocationRules(loanProduct, List.of(rule1));
|
||||
|
||||
// then
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(1, loanProduct.getPaymentAllocationRules().size());
|
||||
Assertions.assertEquals(rule1, loanProduct.getPaymentAllocationRules().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeExistingUpdated() {
|
||||
// given
|
||||
LoanProductPaymentAllocationRuleMerger underTest = new LoanProductPaymentAllocationRuleMerger();
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductPaymentAllocationRule rule1 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
LoanProductPaymentAllocationRule rule2 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(PAST_DUE_FEE));
|
||||
|
||||
loanProduct.getPaymentAllocationRules().add(rule1);
|
||||
|
||||
// when
|
||||
boolean result = underTest.updateProductPaymentAllocationRules(loanProduct, List.of(rule2));
|
||||
|
||||
// then
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(1, loanProduct.getPaymentAllocationRules().size());
|
||||
Assertions.assertEquals(PAST_DUE_FEE, loanProduct.getPaymentAllocationRules().get(0).getAllocationTypes().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNothingChanged() {
|
||||
// given
|
||||
LoanProductPaymentAllocationRuleMerger underTest = new LoanProductPaymentAllocationRuleMerger();
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
|
||||
LoanProductPaymentAllocationRule rule1 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
LoanProductPaymentAllocationRule rule2 = createRule(REPAYMENT, LAST_INSTALLMENT, List.of(PAST_DUE_FEE));
|
||||
|
||||
loanProduct.getPaymentAllocationRules().addAll(List.of(rule1, rule2));
|
||||
|
||||
// when
|
||||
boolean result = underTest.updateProductPaymentAllocationRules(loanProduct, List.of(rule2, rule1));
|
||||
|
||||
// then
|
||||
Assertions.assertFalse(result);
|
||||
Assertions.assertEquals(2, loanProduct.getPaymentAllocationRules().size());
|
||||
Assertions.assertEquals(rule1, loanProduct.getPaymentAllocationRules().get(0));
|
||||
Assertions.assertEquals(rule2, loanProduct.getPaymentAllocationRules().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergerExistingDeleted() {
|
||||
// given
|
||||
LoanProductPaymentAllocationRuleMerger underTest = new LoanProductPaymentAllocationRuleMerger();
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
LoanProductPaymentAllocationRule rule1 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
loanProduct.getPaymentAllocationRules().add(rule1);
|
||||
|
||||
// when
|
||||
boolean result = underTest.updateProductPaymentAllocationRules(loanProduct, List.of());
|
||||
|
||||
// then
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(0, loanProduct.getPaymentAllocationRules().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeOneOriginalOneAdded() {
|
||||
// given
|
||||
LoanProductPaymentAllocationRuleMerger underTest = new LoanProductPaymentAllocationRuleMerger();
|
||||
LoanProduct loanProduct = new LoanProduct();
|
||||
LoanProductPaymentAllocationRule rule1 = createRule(DEFAULT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
loanProduct.getPaymentAllocationRules().add(rule1);
|
||||
LoanProductPaymentAllocationRule rule2 = createRule(REPAYMENT, LAST_INSTALLMENT, List.of(DUE_INTEREST));
|
||||
|
||||
// when
|
||||
boolean result = underTest.updateProductPaymentAllocationRules(loanProduct, List.of(rule1, rule2));
|
||||
|
||||
// then
|
||||
Assertions.assertTrue(result);
|
||||
Assertions.assertEquals(2, loanProduct.getPaymentAllocationRules().size());
|
||||
Assertions.assertEquals(rule1, loanProduct.getPaymentAllocationRules().get(0));
|
||||
Assertions.assertEquals(rule2, loanProduct.getPaymentAllocationRules().get(1));
|
||||
}
|
||||
|
||||
public LoanProductPaymentAllocationRule createRule(PaymentAllocationTransactionType transactionType,
|
||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule, List<PaymentAllocationType> allocationTypeList) {
|
||||
return new LoanProductPaymentAllocationRule(null, transactionType, allocationTypeList, futureInstallmentAllocationRule);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user