FINERACT-1968: Enhanced charge handling
This commit is contained in:
+19
-8
@@ -779,14 +779,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||||
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
|
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
|
||||||
|
.fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
|
||||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
boolean isDue = installment.isFirstPeriod()
|
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
|
||||||
? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate())
|
? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate())
|
||||||
: charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate());
|
: charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate());
|
||||||
if (installmentNumber == null && isDue) {
|
if (installmentNumber == null && isFirstNormalInstallment) {
|
||||||
chargePaymentInstallments.add(installment);
|
chargePaymentInstallments.add(installment);
|
||||||
break;
|
break;
|
||||||
} else if (installmentNumber != null && installment.getInstallmentNumber().equals(installmentNumber)) {
|
} else if (installment.getInstallmentNumber().equals(installmentNumber)) {
|
||||||
chargePaymentInstallments.add(installment);
|
chargePaymentInstallments.add(installment);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1347,7 +1349,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
LocalDate transactionDateForRange = isBasedOnSubmittedOnDate
|
LocalDate transactionDateForRange = isBasedOnSubmittedOnDate
|
||||||
? loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getDueDate()
|
? loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getDueDate()
|
||||||
: loanTransaction.getTransactionDate();
|
: loanTransaction.getTransactionDate();
|
||||||
if (installment.isInPeriod(transactionDateForRange)) {
|
boolean isInPeriod = LoanRepaymentScheduleProcessingWrapper.isInPeriod(transactionDateForRange, installment, installments);
|
||||||
|
if (isInPeriod) {
|
||||||
interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
|
interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
|
||||||
fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
|
fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
|
||||||
penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
|
penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
|
||||||
@@ -6641,13 +6644,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
Money paidFromFutureInstallments = Money.zero(currency);
|
Money paidFromFutureInstallments = Money.zero(currency);
|
||||||
Money fee = Money.zero(currency);
|
Money fee = Money.zero(currency);
|
||||||
Money penalty = Money.zero(currency);
|
Money penalty = Money.zero(currency);
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
|
||||||
|
.fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
|
||||||
|
|
||||||
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||||
if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) {
|
if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) {
|
||||||
interest = interest.plus(installment.getInterestOutstanding(currency));
|
interest = interest.plus(installment.getInterestOutstanding(currency));
|
||||||
penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
|
penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
|
||||||
fee = fee.plus(installment.getFeeChargesOutstanding(currency));
|
fee = fee.plus(installment.getFeeChargesOutstanding(currency));
|
||||||
} else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) {
|
} else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) {
|
||||||
Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment);
|
Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment,
|
||||||
|
isFirstNormalInstallment);
|
||||||
if (balancesForCurrentPeroid[0].isGreaterThan(balancesForCurrentPeroid[5])) {
|
if (balancesForCurrentPeroid[0].isGreaterThan(balancesForCurrentPeroid[5])) {
|
||||||
interest = interest.plus(balancesForCurrentPeroid[0]).minus(balancesForCurrentPeroid[5]);
|
interest = interest.plus(balancesForCurrentPeroid[0]).minus(balancesForCurrentPeroid[5]);
|
||||||
} else {
|
} else {
|
||||||
@@ -6680,7 +6688,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, final MonetaryCurrency currency,
|
private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, final MonetaryCurrency currency,
|
||||||
final LoanRepaymentScheduleInstallment installment) {
|
final LoanRepaymentScheduleInstallment installment, boolean isFirstNormalInstallment) {
|
||||||
Money penaltyForCurrentPeriod = Money.zero(getCurrency());
|
Money penaltyForCurrentPeriod = Money.zero(getCurrency());
|
||||||
Money penaltyAccoutedForCurrentPeriod = Money.zero(getCurrency());
|
Money penaltyAccoutedForCurrentPeriod = Money.zero(getCurrency());
|
||||||
Money feeForCurrentPeriod = Money.zero(getCurrency());
|
Money feeForCurrentPeriod = Money.zero(getCurrency());
|
||||||
@@ -6694,7 +6702,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency()));
|
interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency()));
|
||||||
for (LoanCharge loanCharge : this.charges) {
|
for (LoanCharge loanCharge : this.charges) {
|
||||||
if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
|
if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
|
||||||
boolean isDue = installment.isFirstPeriod()
|
boolean isDue = isFirstNormalInstallment
|
||||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate)
|
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate)
|
||||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate);
|
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate);
|
||||||
if (isDue) {
|
if (isDue) {
|
||||||
@@ -6734,7 +6742,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
Money[] balances = new Money[3];
|
Money[] balances = new Money[3];
|
||||||
final MonetaryCurrency currency = getCurrency();
|
final MonetaryCurrency currency = getCurrency();
|
||||||
balances[0] = balances[1] = balances[2] = Money.zero(currency);
|
balances[0] = balances[1] = balances[2] = Money.zero(currency);
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
|
||||||
|
.fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
|
||||||
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||||
if (DateUtils.isEqual(paymentDate, installment.getDueDate())) {
|
if (DateUtils.isEqual(paymentDate, installment.getDueDate())) {
|
||||||
Money interest = installment.getInterestCharged(currency);
|
Money interest = installment.getInterestCharged(currency);
|
||||||
Money fee = installment.getFeeChargesCharged(currency);
|
Money fee = installment.getFeeChargesCharged(currency);
|
||||||
@@ -6745,7 +6756,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
break;
|
break;
|
||||||
} else if (DateUtils.isAfter(paymentDate, installment.getFromDate())
|
} else if (DateUtils.isAfter(paymentDate, installment.getFromDate())
|
||||||
&& DateUtils.isBefore(paymentDate, installment.getDueDate())) {
|
&& DateUtils.isBefore(paymentDate, installment.getDueDate())) {
|
||||||
balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment);
|
balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-9
@@ -960,15 +960,6 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
this.additional = true;
|
this.additional = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFirstPeriod() {
|
|
||||||
return (this.installmentNumber == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInPeriod(LocalDate date) {
|
|
||||||
return (isFirstPeriod() ? !DateUtils.isBefore(date, getFromDate()) : DateUtils.isAfter(date, getFromDate()))
|
|
||||||
&& !DateUtils.isAfter(date, getDueDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
|
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
|
||||||
return this.loanTransactionToRepaymentScheduleMappings;
|
return this.loanTransactionToRepaymentScheduleMappings;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-3
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.domain;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -43,13 +44,14 @@ public class LoanRepaymentScheduleProcessingWrapper {
|
|||||||
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
||||||
}
|
}
|
||||||
LocalDate startDate = disbursementDate;
|
LocalDate startDate = disbursementDate;
|
||||||
|
LoanRepaymentScheduleInstallment firstNormalPeriod = repaymentPeriods.stream()
|
||||||
|
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
|
||||||
|
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow();
|
||||||
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
|
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
|
||||||
|
|
||||||
if (!period.isDownPayment()) {
|
if (!period.isDownPayment()) {
|
||||||
|
|
||||||
boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
|
boolean isFirstNonDownPaymentPeriod = period.equals(firstNormalPeriod);
|
||||||
.filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
|
|
||||||
.allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
|
|
||||||
|
|
||||||
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
|
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
|
||||||
currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(),
|
currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(),
|
||||||
@@ -235,4 +237,18 @@ public class LoanRepaymentScheduleProcessingWrapper {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int fetchFirstNormalInstallmentNumber(List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
return installments.stream().sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
|
||||||
|
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
int firstPeriod = fetchFirstNormalInstallmentNumber(installments);
|
||||||
|
return (targetInstallment.getInstallmentNumber().equals(firstPeriod)
|
||||||
|
? !DateUtils.isBefore(transactionDate, targetInstallment.getFromDate())
|
||||||
|
: DateUtils.isAfter(transactionDate, targetInstallment.getFromDate()))
|
||||||
|
&& !DateUtils.isAfter(transactionDate, targetInstallment.getDueDate());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -42,13 +42,11 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
|||||||
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
||||||
}
|
}
|
||||||
LocalDate startDate = disbursementDate;
|
LocalDate startDate = disbursementDate;
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(repaymentPeriods);
|
||||||
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
|
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
|
||||||
|
|
||||||
if (!period.isDownPayment()) {
|
if (!period.isDownPayment()) {
|
||||||
|
boolean isFirstNonDownPaymentPeriod = period.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||||
boolean isFirstNonDownPaymentPeriod = repaymentPeriods.stream()
|
|
||||||
.filter(repaymentPeriod -> repaymentPeriod.getInstallmentNumber() < period.getInstallmentNumber())
|
|
||||||
.allMatch(LoanRepaymentScheduleInstallment::isDownPayment);
|
|
||||||
|
|
||||||
final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, period.getDueDate(), loanCharge, currency,
|
final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, period.getDueDate(), loanCharge, currency,
|
||||||
period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
|
period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), isFirstNonDownPaymentPeriod);
|
||||||
|
|||||||
+24
-10
@@ -102,9 +102,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LocalDate startDate = disbursementDate;
|
LocalDate startDate = disbursementDate;
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
|
boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||||
for (final LoanCharge loanCharge : transferCharges) {
|
for (final LoanCharge loanCharge : transferCharges) {
|
||||||
boolean isDue = installment.isFirstPeriod()
|
boolean isDue = isFirstPeriod
|
||||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
|
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
|
||||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
|
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
|
||||||
if (isDue) {
|
if (isDue) {
|
||||||
@@ -635,15 +637,15 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
return penaltyCharges;
|
return penaltyCharges;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
|
protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money chargeAmount, final Set<LoanCharge> charges,
|
||||||
final Integer installmentNumber) {
|
final Integer installmentNumber) {
|
||||||
|
|
||||||
Money amountRemaining = feeCharges;
|
Money amountRemaining = chargeAmount;
|
||||||
while (amountRemaining.isGreaterThanZero()) {
|
while (amountRemaining.isGreaterThanZero()) {
|
||||||
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
|
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges, chargeAmount.getCurrency());
|
||||||
Money feeAmount = feeCharges.zero();
|
Money feeAmount = chargeAmount.zero();
|
||||||
if (loanTransaction.isChargePayment()) {
|
if (loanTransaction.isChargePayment()) {
|
||||||
feeAmount = feeCharges;
|
feeAmount = chargeAmount;
|
||||||
}
|
}
|
||||||
if (unpaidCharge == null) {
|
if (unpaidCharge == null) {
|
||||||
break; // All are trache charges
|
break; // All are trache charges
|
||||||
@@ -669,6 +671,18 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ChargesPaidByFunction {
|
||||||
|
|
||||||
|
void accept(LoanTransaction loanTransaction, Money feeCharges, Set<LoanCharge> charges, Integer installmentNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChargesPaidByFunction getChargesPaymentFunction(LoanRepaymentScheduleInstallment.PaymentAction action) {
|
||||||
|
return switch (action) {
|
||||||
|
case PAY -> this::updateChargesPaidAmountBy;
|
||||||
|
case UNPAY -> this::undoChargesPaidAmountBy;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
|
protected LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
|
||||||
LoanCharge earliestUnpaidCharge = null;
|
LoanCharge earliestUnpaidCharge = null;
|
||||||
LoanCharge installemntCharge = null;
|
LoanCharge installemntCharge = null;
|
||||||
@@ -770,15 +784,15 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
|
protected void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money chargeAmount, final Set<LoanCharge> charges,
|
||||||
final Integer installmentNumber) {
|
final Integer installmentNumber) {
|
||||||
|
|
||||||
Money amountRemaining = feeCharges;
|
Money amountRemaining = chargeAmount;
|
||||||
while (amountRemaining.isGreaterThanZero()) {
|
while (amountRemaining.isGreaterThanZero()) {
|
||||||
final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, feeCharges.getCurrency());
|
final LoanCharge paidCharge = findLatestPaidChargeFromUnOrderedSet(charges, chargeAmount.getCurrency());
|
||||||
|
|
||||||
if (paidCharge != null) {
|
if (paidCharge != null) {
|
||||||
Money feeAmount = feeCharges.zero();
|
Money feeAmount = chargeAmount.zero();
|
||||||
|
|
||||||
final Money amountDeductedTowardsCharge = paidCharge.undoPaidOrPartiallyAmountBy(amountRemaining, installmentNumber,
|
final Money amountDeductedTowardsCharge = paidCharge.undoPaidOrPartiallyAmountBy(amountRemaining, installmentNumber,
|
||||||
feeAmount);
|
feeAmount);
|
||||||
|
|||||||
+89
-106
@@ -49,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||||
@@ -196,7 +197,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
||||||
transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, currency, installments,
|
transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, currency, installments,
|
||||||
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule,
|
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule,
|
||||||
transactionMappings, balances);
|
transactionMappings, charges, balances);
|
||||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -205,7 +206,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||||
transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, currency, installments, zero,
|
transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, currency, installments, zero,
|
||||||
transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, balances,
|
transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, charges, balances,
|
||||||
paymentAllocationType);
|
paymentAllocationType);
|
||||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||||
break;
|
break;
|
||||||
@@ -216,20 +217,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
|
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
|
||||||
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
|
|
||||||
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
|
||||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
|
||||||
Integer installmentNumber = null;
|
|
||||||
|
|
||||||
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
|
||||||
if (feeCharges.isGreaterThanZero()) {
|
|
||||||
undoChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
|
||||||
if (penaltyCharges.isGreaterThanZero()) {
|
|
||||||
undoChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
@@ -350,44 +337,39 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
private Money processPaymentAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment,
|
private Money processPaymentAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment,
|
||||||
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
|
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Balances balances,
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Set<LoanCharge> chargesOfInstallment,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction action) {
|
Balances balances, LoanRepaymentScheduleInstallment.PaymentAction action) {
|
||||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||||
Money zero = transactionAmountUnprocessed.zero();
|
Money zero = transactionAmountUnprocessed.zero();
|
||||||
return switch (paymentAllocationType.getAllocationType()) {
|
|
||||||
|
LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment
|
||||||
|
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
||||||
|
ChargesPaidByFunction chargesPaidByFunction = getChargesPaymentFunction(action);
|
||||||
|
Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
|
||||||
|
|
||||||
|
switch (paymentAllocationType.getAllocationType()) {
|
||||||
case PENALTY -> {
|
case PENALTY -> {
|
||||||
LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment
|
|
||||||
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
|
||||||
Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
|
|
||||||
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(portion));
|
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(portion));
|
||||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, portion);
|
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, portion);
|
||||||
yield portion;
|
Set<LoanCharge> penalties = chargesOfInstallment.stream().filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet());
|
||||||
|
chargesPaidByFunction.accept(loanTransaction, portion, penalties, currentInstallment.getInstallmentNumber());
|
||||||
}
|
}
|
||||||
case FEE -> {
|
case FEE -> {
|
||||||
LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
|
|
||||||
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
|
||||||
Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
|
|
||||||
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(portion));
|
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(portion));
|
||||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, portion, zero);
|
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, portion, zero);
|
||||||
yield portion;
|
Set<LoanCharge> fees = chargesOfInstallment.stream().filter(LoanCharge::isFeeCharge).collect(Collectors.toSet());
|
||||||
|
chargesPaidByFunction.accept(loanTransaction, portion, fees, currentInstallment.getInstallmentNumber());
|
||||||
}
|
}
|
||||||
case INTEREST -> {
|
case INTEREST -> {
|
||||||
LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
|
|
||||||
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
|
||||||
Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
|
|
||||||
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(portion));
|
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(portion));
|
||||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, portion, zero, zero);
|
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, portion, zero, zero);
|
||||||
yield portion;
|
|
||||||
}
|
}
|
||||||
case PRINCIPAL -> {
|
case PRINCIPAL -> {
|
||||||
LoanRepaymentScheduleInstallment.PaymentFunction functional = currentInstallment
|
|
||||||
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
|
||||||
Money portion = functional.accept(transactionDate, transactionAmountUnprocessed);
|
|
||||||
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(portion));
|
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(portion));
|
||||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, portion, zero, zero, zero);
|
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, portion, zero, zero, zero);
|
||||||
yield portion;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
return portion;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping,
|
private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping,
|
||||||
@@ -427,7 +409,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
|
principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
|
||||||
interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
|
interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
|
||||||
feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
|
feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
|
||||||
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
|
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,8 +432,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
|
LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
|
||||||
|
|
||||||
Money unprocessed = loanTransaction.getAmount(currency);
|
Money unprocessed = loanTransaction.getAmount(currency);
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
boolean isDue = installment.isFirstPeriod()
|
boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
|
||||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
|
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
|
||||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
|
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
|
||||||
if (isDue) {
|
if (isDue) {
|
||||||
@@ -487,7 +470,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private Money refundTransactionHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money refundTransactionHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
||||||
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
||||||
Money zero = Money.zero(currency);
|
Money zero = Money.zero(currency);
|
||||||
Money refundedPortion;
|
Money refundedPortion;
|
||||||
outerLoop: do {
|
outerLoop: do {
|
||||||
@@ -498,15 +481,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
List<LoanRepaymentScheduleInstallment> inAdvanceInstallments = getFutureInstallmentsForRefund(loanTransaction, currency,
|
List<LoanRepaymentScheduleInstallment> inAdvanceInstallments = getFutureInstallmentsForRefund(loanTransaction, currency,
|
||||||
installments, futureInstallmentAllocationRule, zero);
|
installments, futureInstallmentAllocationRule, zero);
|
||||||
|
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||||
switch (paymentAllocationType.getDueType()) {
|
switch (paymentAllocationType.getDueType()) {
|
||||||
case PAST_DUE -> {
|
case PAST_DUE -> {
|
||||||
if (latestPastDueInstallment != null) {
|
if (latestPastDueInstallment != null) {
|
||||||
|
Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, latestPastDueInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, latestPastDueInstallment, currency);
|
transactionMappings, loanTransaction, latestPastDueInstallment, currency);
|
||||||
refundedPortion = processPaymentAllocation(paymentAllocationType, latestPastDueInstallment, loanTransaction,
|
refundedPortion = processPaymentAllocation(paymentAllocationType, latestPastDueInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||||
} else {
|
} else {
|
||||||
break outerLoop;
|
break outerLoop;
|
||||||
@@ -514,11 +500,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
case DUE -> {
|
case DUE -> {
|
||||||
if (dueInstallment != null) {
|
if (dueInstallment != null) {
|
||||||
|
Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, dueInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, dueInstallment, currency);
|
transactionMappings, loanTransaction, dueInstallment, currency);
|
||||||
refundedPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
|
refundedPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||||
} else {
|
} else {
|
||||||
break outerLoop;
|
break outerLoop;
|
||||||
@@ -530,13 +518,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode());
|
Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode());
|
||||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||||
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
||||||
|
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
||||||
evenPortion = evenPortion.add(balanceAdjustment);
|
evenPortion = evenPortion.add(balanceAdjustment);
|
||||||
}
|
}
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
||||||
refundedPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
refundedPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
||||||
evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
|
evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||||
}
|
}
|
||||||
@@ -554,30 +544,35 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private Money refundTransactionVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money refundTransactionVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money zero,
|
List<LoanRepaymentScheduleInstallment> installments, Money zero,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
|
||||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Balances balances,
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Set<LoanCharge> charges, Balances balances,
|
||||||
PaymentAllocationType paymentAllocationType) {
|
PaymentAllocationType paymentAllocationType) {
|
||||||
LoanRepaymentScheduleInstallment currentInstallment = null;
|
LoanRepaymentScheduleInstallment currentInstallment = null;
|
||||||
Money refundedPortion = zero;
|
Money refundedPortion = zero;
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
do {
|
do {
|
||||||
switch (paymentAllocationType.getDueType()) {
|
switch (paymentAllocationType.getDueType()) {
|
||||||
case PAST_DUE -> {
|
case PAST_DUE -> {
|
||||||
currentInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
currentInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
||||||
if (currentInstallment != null) {
|
if (currentInstallment != null) {
|
||||||
|
Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case DUE -> {
|
case DUE -> {
|
||||||
currentInstallment = getDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
currentInstallment = getDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
||||||
if (currentInstallment != null) {
|
if (currentInstallment != null) {
|
||||||
|
Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
refundedPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges, balances,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||||
}
|
}
|
||||||
@@ -592,14 +587,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||||
for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
|
for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
|
||||||
currentInstallment = internalCurrentInstallment;
|
currentInstallment = internalCurrentInstallment;
|
||||||
|
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
|
if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
|
||||||
evenPortion = evenPortion.add(balanceAdjustment);
|
evenPortion = evenPortion.add(balanceAdjustment);
|
||||||
}
|
}
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
Money internalUnpaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
|
Money internalUnpaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
|
||||||
loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
|
loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
balances, LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||||
if (internalUnpaidPortion.isGreaterThanZero()) {
|
if (internalUnpaidPortion.isGreaterThanZero()) {
|
||||||
refundedPortion = internalUnpaidPortion;
|
refundedPortion = internalUnpaidPortion;
|
||||||
}
|
}
|
||||||
@@ -666,25 +663,23 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
if (LoanScheduleProcessingType.HORIZONTAL
|
if (LoanScheduleProcessingType.HORIZONTAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments, transactionAmountUnprocessed,
|
transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments, transactionAmountUnprocessed,
|
||||||
paymentAllocationRule, transactionMappings, balances);
|
paymentAllocationRule, transactionMappings, charges, balances);
|
||||||
} else if (LoanScheduleProcessingType.VERTICAL
|
} else if (LoanScheduleProcessingType.VERTICAL
|
||||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||||
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, transactionAmountUnprocessed,
|
transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, transactionAmountUnprocessed,
|
||||||
paymentAllocationRule, transactionMappings, balances);
|
paymentAllocationRule, transactionMappings, charges, balances);
|
||||||
}
|
}
|
||||||
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
|
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
|
||||||
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
|
|
||||||
payAdditionalCharges(loanTransaction, currency, charges);
|
|
||||||
|
|
||||||
handleOverpayment(transactionAmountUnprocessed, loanTransaction);
|
handleOverpayment(transactionAmountUnprocessed, loanTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money processPeriodsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processPeriodsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
||||||
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
||||||
Balances balances) {
|
Set<LoanCharge> charges, Balances balances) {
|
||||||
LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationRule.getAllocationTypes().stream()
|
LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationRule.getAllocationTypes().stream()
|
||||||
.collect(Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new,
|
.collect(Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new,
|
||||||
mapping(Function.identity(), toList())));
|
mapping(Function.identity(), toList())));
|
||||||
@@ -692,7 +687,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
||||||
transactionAmountUnprocessed = processAllocationsHorizontally(loanTransaction, currency, installments,
|
transactionAmountUnprocessed = processAllocationsHorizontally(loanTransaction, currency, installments,
|
||||||
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(),
|
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(),
|
||||||
paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, balances);
|
paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, charges, balances);
|
||||||
}
|
}
|
||||||
return transactionAmountUnprocessed;
|
return transactionAmountUnprocessed;
|
||||||
}
|
}
|
||||||
@@ -700,7 +695,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private Money processAllocationsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processAllocationsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
||||||
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
||||||
Money paidPortion;
|
Money paidPortion;
|
||||||
boolean exit = false;
|
boolean exit = false;
|
||||||
do {
|
do {
|
||||||
@@ -727,15 +722,19 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
|
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||||
switch (paymentAllocationType.getDueType()) {
|
switch (paymentAllocationType.getDueType()) {
|
||||||
case PAST_DUE -> {
|
case PAST_DUE -> {
|
||||||
if (oldestPastDueInstallment != null) {
|
if (oldestPastDueInstallment != null) {
|
||||||
|
Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, oldestPastDueInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, oldestPastDueInstallment, currency);
|
transactionMappings, loanTransaction, oldestPastDueInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, oldestPastDueInstallment, loanTransaction,
|
paidPortion = processPaymentAllocation(paymentAllocationType, oldestPastDueInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
} else {
|
} else {
|
||||||
exit = true;
|
exit = true;
|
||||||
@@ -743,11 +742,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
case DUE -> {
|
case DUE -> {
|
||||||
if (dueInstallment != null) {
|
if (dueInstallment != null) {
|
||||||
|
Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, dueInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, dueInstallment, currency);
|
transactionMappings, loanTransaction, dueInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
|
paidPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
} else {
|
} else {
|
||||||
exit = true;
|
exit = true;
|
||||||
@@ -762,6 +763,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
// Adjustment might be needed due to the divide operation and the rounding mode
|
// Adjustment might be needed due to the divide operation and the rounding mode
|
||||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||||
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
||||||
|
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
// Adjust the portion for the last installment
|
// Adjust the portion for the last installment
|
||||||
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
||||||
evenPortion = evenPortion.add(balanceAdjustment);
|
evenPortion = evenPortion.add(balanceAdjustment);
|
||||||
@@ -769,7 +772,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
||||||
evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
|
evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
}
|
}
|
||||||
@@ -787,10 +790,21 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return transactionAmountUnprocessed;
|
return transactionAmountUnprocessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static Set<LoanCharge> getLoanChargesOfInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment currentInstallment,
|
||||||
|
int firstNormalInstallmentNumber) {
|
||||||
|
return charges.stream().filter(loanCharge -> currentInstallment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
|
||||||
|
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(currentInstallment.getFromDate(),
|
||||||
|
currentInstallment.getDueDate())
|
||||||
|
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(currentInstallment.getFromDate(), currentInstallment.getDueDate()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
private Money processPeriodsVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processPeriodsVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
||||||
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
||||||
Balances balances) {
|
Set<LoanCharge> charges, Balances balances) {
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
|
||||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
|
||||||
LoanRepaymentScheduleInstallment currentInstallment = null;
|
LoanRepaymentScheduleInstallment currentInstallment = null;
|
||||||
@@ -802,11 +816,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isAfter(e.getDueDate()))
|
currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isAfter(e.getDueDate()))
|
||||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||||
if (currentInstallment != null) {
|
if (currentInstallment != null) {
|
||||||
|
Set<LoanCharge> oldestPastDueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
oldestPastDueInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -814,11 +830,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isOn(e.getDueDate()))
|
currentInstallment = installments.stream().filter(predicate).filter(e -> loanTransaction.isOn(e.getDueDate()))
|
||||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||||
if (currentInstallment != null) {
|
if (currentInstallment != null) {
|
||||||
|
Set<LoanCharge> dueInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
paidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,6 +866,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||||
for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
|
for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
|
||||||
currentInstallment = internalCurrentInstallment;
|
currentInstallment = internalCurrentInstallment;
|
||||||
|
Set<LoanCharge> inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, currentInstallment,
|
||||||
|
firstNormalInstallmentNumber);
|
||||||
// Adjust the portion for the last installment
|
// Adjust the portion for the last installment
|
||||||
if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
|
if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
|
||||||
evenPortion = evenPortion.add(balanceAdjustment);
|
evenPortion = evenPortion.add(balanceAdjustment);
|
||||||
@@ -855,8 +875,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||||
Money internalPaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
|
Money internalPaidPortion = processPaymentAllocation(paymentAllocationType, currentInstallment,
|
||||||
loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
|
loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping,
|
||||||
LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
inAdvanceInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||||
// Some extra logic to allocate as much as possible across the installments if the
|
// Some extra logic to allocate as much as possible across the installments if the
|
||||||
// outstanding balances are different
|
// outstanding balances are different
|
||||||
if (internalPaidPortion.isGreaterThanZero()) {
|
if (internalPaidPortion.isGreaterThanZero()) {
|
||||||
@@ -887,43 +907,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void payAdditionalCharges(LoanTransaction loanTransaction, MonetaryCurrency currency, Set<LoanCharge> charges) {
|
|
||||||
final Set<LoanCharge> paidFeeCharges = loanTransaction.getLoanChargesPaid().stream() //
|
|
||||||
.map(LoanChargePaidBy::getLoanCharge) //
|
|
||||||
.filter(LoanCharge::isFeeCharge).collect(Collectors.toSet());
|
|
||||||
final Set<LoanCharge> paidPenaltyCharges = loanTransaction.getLoanChargesPaid().stream() //
|
|
||||||
.map(LoanChargePaidBy::getLoanCharge) //
|
|
||||||
.filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet());
|
|
||||||
// TODO: rewrite to provide sorted list
|
|
||||||
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
|
||||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
|
||||||
loanFees.removeAll(paidFeeCharges);
|
|
||||||
loanPenalties.removeAll(paidPenaltyCharges);
|
|
||||||
|
|
||||||
BigDecimal sumFeePaidAmount = paidFeeCharges.stream() //
|
|
||||||
.map(paidCharge -> paidCharge.getAmountPaid(currency)) //
|
|
||||||
.map(Money::getAmount) //
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
|
|
||||||
BigDecimal sumPenaltyPaidAmount = paidPenaltyCharges.stream() //
|
|
||||||
.map(paidCharge -> paidCharge.getAmountPaid(currency)) //
|
|
||||||
.map(Money::getAmount) //
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
|
|
||||||
if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) {
|
|
||||||
Money feeCharges = loanTransaction.getFeeChargesPortion(currency).minus(sumFeePaidAmount);
|
|
||||||
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency).minus(sumPenaltyPaidAmount);
|
|
||||||
|
|
||||||
if (feeCharges.isGreaterThanZero()) {
|
|
||||||
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (penaltyCharges.isGreaterThanZero()) {
|
|
||||||
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
|||||||
+9
-1
@@ -44,7 +44,11 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
|
|
||||||
private LocalDate getEffectiveDate() {
|
private LocalDate getEffectiveDate() {
|
||||||
if (loanCharge.isPresent()) {
|
if (loanCharge.isPresent()) {
|
||||||
return loanCharge.get().getDueDate();
|
if (isBackdatedCharge()) {
|
||||||
|
return loanCharge.get().getDueDate();
|
||||||
|
} else {
|
||||||
|
return loanCharge.get().getSubmittedOnDate();
|
||||||
|
}
|
||||||
} else if (loanTransaction.isPresent()) {
|
} else if (loanTransaction.isPresent()) {
|
||||||
return loanTransaction.get().getTransactionDate();
|
return loanTransaction.get().getTransactionDate();
|
||||||
} else {
|
} else {
|
||||||
@@ -52,6 +56,10 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBackdatedCharge() {
|
||||||
|
return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate());
|
||||||
|
}
|
||||||
|
|
||||||
private LocalDate getSubmittedOnDate() {
|
private LocalDate getSubmittedOnDate() {
|
||||||
if (loanCharge.isPresent()) {
|
if (loanCharge.isPresent()) {
|
||||||
return loanCharge.get().getSubmittedOnDate();
|
return loanCharge.get().getSubmittedOnDate();
|
||||||
|
|||||||
+6
-3
@@ -612,15 +612,17 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||||
CurrencyData currencyData = applicationCurrency.toData();
|
CurrencyData currencyData = applicationCurrency.toData();
|
||||||
Set<LoanCharge> loanCharges = loan.getActiveCharges();
|
Set<LoanCharge> loanCharges = loan.getActiveCharges();
|
||||||
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
|
|
||||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
if (DateUtils.isAfter(installment.getDueDate(), loan.getMaturityDate())) {
|
if (DateUtils.isAfter(installment.getDueDate(), loan.getMaturityDate())) {
|
||||||
accruedTill = DateUtils.getBusinessLocalDate();
|
accruedTill = DateUtils.getBusinessLocalDate();
|
||||||
}
|
}
|
||||||
if (!isOrganisationDateEnabled || DateUtils.isBefore(organisationStartDate, installment.getDueDate())) {
|
if (!isOrganisationDateEnabled || DateUtils.isBefore(organisationStartDate, installment.getDueDate())) {
|
||||||
|
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||||
generateLoanScheduleAccrualData(accruedTill, loanScheduleAccrualList, loanId, officeId, accrualStartDate,
|
generateLoanScheduleAccrualData(accruedTill, loanScheduleAccrualList, loanId, officeId, accrualStartDate,
|
||||||
repaymentFrequency, repayEvery, interestCalculatedFrom, loanProductId, currency, currencyData, loanCharges,
|
repaymentFrequency, repayEvery, interestCalculatedFrom, loanProductId, currency, currencyData, loanCharges,
|
||||||
installment);
|
installment, isFirstNormalInstallment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +641,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas, final Long loanId, Long officeId,
|
final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas, final Long loanId, Long officeId,
|
||||||
final LocalDate accrualStartDate, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery,
|
final LocalDate accrualStartDate, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery,
|
||||||
final LocalDate interestCalculatedFrom, final Long loanProductId, final MonetaryCurrency currency,
|
final LocalDate interestCalculatedFrom, final Long loanProductId, final MonetaryCurrency currency,
|
||||||
final CurrencyData currencyData, final Set<LoanCharge> loanCharges, final LoanRepaymentScheduleInstallment installment) {
|
final CurrencyData currencyData, final Set<LoanCharge> loanCharges, final LoanRepaymentScheduleInstallment installment,
|
||||||
|
boolean isFirstNormalInstallment) {
|
||||||
|
|
||||||
if (!DateUtils.isBefore(accruedTill, installment.getDueDate()) || (DateUtils.isAfter(accruedTill, installment.getFromDate())
|
if (!DateUtils.isBefore(accruedTill, installment.getDueDate()) || (DateUtils.isAfter(accruedTill, installment.getFromDate())
|
||||||
&& !DateUtils.isAfter(accruedTill, installment.getDueDate()))) {
|
&& !DateUtils.isAfter(accruedTill, installment.getDueDate()))) {
|
||||||
@@ -651,7 +654,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final LoanCharge loanCharge : loanCharges) {
|
for (final LoanCharge loanCharge : loanCharges) {
|
||||||
boolean isDue = installment.isFirstPeriod()
|
boolean isDue = isFirstNormalInstallment
|
||||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)
|
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)
|
||||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate);
|
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate);
|
||||||
if (isDue) {
|
if (isDue) {
|
||||||
|
|||||||
-3
@@ -109,7 +109,6 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||||
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
||||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||||
Mockito.when(installment.isFirstPeriod()).thenReturn(true);
|
|
||||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||||
@@ -154,7 +153,6 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||||
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
||||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||||
Mockito.when(installment.isFirstPeriod()).thenReturn(true);
|
|
||||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||||
@@ -204,7 +202,6 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
Mockito.when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
Mockito.when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||||
Mockito.when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
Mockito.when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||||
Mockito.when(installment.isFirstPeriod()).thenReturn(true);
|
|
||||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||||
|
|||||||
+21
-1
@@ -40,6 +40,14 @@ public class ChargeOrTransactionTest {
|
|||||||
Assertions.assertTrue(transaction.compareTo(charge) == 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
|
@Test
|
||||||
public void testCompareToCreatedDateTime() {
|
public void testCompareToCreatedDateTime() {
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
|
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
|
||||||
@@ -77,7 +85,7 @@ public class ChargeOrTransactionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testComparatorOnSameDay() {
|
public void testComparatorOnSameDayBackdatedCharge() {
|
||||||
ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
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 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");
|
ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
||||||
@@ -88,6 +96,18 @@ public class ChargeOrTransactionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
private ChargeOrTransaction createCharge(String effectiveDate, String submittedDate, String creationDateTime) {
|
||||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||||
Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
|
Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
|
||||||
|
|||||||
+449
@@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||||
import org.apache.fineract.client.models.BusinessDateRequest;
|
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||||
@@ -2522,6 +2523,445 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UC112: Advanced payment allocation, horizontal repayment processing
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Disburse the loan (1000)
|
||||||
|
// 2. Add charge after maturity date
|
||||||
|
// 3. Pay 1st installment
|
||||||
|
// 4. Pay 2nd installment
|
||||||
|
// 5. Add charge to 3rd installment
|
||||||
|
// 6. Add charge to 4th installment
|
||||||
|
// 7. Do goodwill credit (in advance payment)
|
||||||
|
@Test
|
||||||
|
public void uc112() {
|
||||||
|
try {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||||
|
Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
|
||||||
|
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
|
||||||
|
BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("01 September 2023").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
// Add Charge Penalty
|
||||||
|
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeGoodwillCredit(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(50.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 510.0, 550.0, 490.0, 510.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 10.0, 240.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 30.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 20.0, 0.0);
|
||||||
|
|
||||||
|
validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 0.0, 20.0);
|
||||||
|
validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0);
|
||||||
|
validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
} finally {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UC113: Advanced payment allocation, vertical repayment processing
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Disburse the loan (1000)
|
||||||
|
// 2. Add charge after maturity date
|
||||||
|
// 3. Pay 1st installment
|
||||||
|
// 4. Pay 2nd installment
|
||||||
|
// 5. Add charge to 3rd installment
|
||||||
|
// 6. Add charge to 4th installment
|
||||||
|
// 7. Do goodwill credit (in advance payment)
|
||||||
|
@Test
|
||||||
|
public void uc113() {
|
||||||
|
try {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||||
|
Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
|
||||||
|
LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
|
||||||
|
BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("01 September 2023").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
// Add Charge Penalty
|
||||||
|
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeGoodwillCredit(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(50.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 510.0, 550.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
|
||||||
|
0.0, 10.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 20.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 20.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 20.0, 0.0);
|
||||||
|
validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
|
||||||
|
validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0);
|
||||||
|
validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
} finally {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UC114: Advanced payment allocation, horizontal repayment processing
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Disburse the loan (1000)
|
||||||
|
// 2. Add charge after maturity date
|
||||||
|
// 3. Pay 1st installment
|
||||||
|
// 4. Pay 2nd installment
|
||||||
|
// 5. Add charge to 3rd installment
|
||||||
|
// 6. Add charge to 4th installment
|
||||||
|
// 7. Do merchant issued refund (in advance payment)
|
||||||
|
@Test
|
||||||
|
public void uc114() {
|
||||||
|
try {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||||
|
Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
|
||||||
|
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
|
||||||
|
BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("01 September 2023").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
// Add Charge Penalty
|
||||||
|
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeMerchantIssuedRefund(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(30.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 530.0, 530.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
|
||||||
|
0.0, 10.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0,
|
||||||
|
0.0, 0.0, 10.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
|
||||||
|
0.0, 10.0, 0.0);
|
||||||
|
validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
|
||||||
|
validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0);
|
||||||
|
validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
} finally {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UC115: Advanced payment allocation, vertical repayment processing
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Disburse the loan (1000)
|
||||||
|
// 2. Add charge after maturity date
|
||||||
|
// 3. Pay 1st installment
|
||||||
|
// 4. Pay 2nd installment
|
||||||
|
// 5. Add charge to 3rd installment
|
||||||
|
// 6. Add charge to 4th installment
|
||||||
|
// 7. Do merchant issued refund (in advance payment)
|
||||||
|
@Test
|
||||||
|
public void uc115() {
|
||||||
|
try {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||||
|
Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
|
||||||
|
LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
|
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId,
|
||||||
|
BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023");
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("01 September 2023").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("01 September 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
// Add Charge Penalty
|
||||||
|
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", true));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 October 2023", "20"));
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 1020.0, 0.0, 1000.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("01 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 770.0, 250.0, 750.0, 250.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||||
|
.date("2023.09.16").dateFormat("yyyy.MM.dd").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(250.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 520.0, 500.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 20.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "17 September 2023", "20"));
|
||||||
|
loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(),
|
||||||
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "16 October 2023", "20"));
|
||||||
|
loanTransactionHelper.makeMerchantIssuedRefund(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("16 September 2023").locale("en").transactionAmount(30.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 530.0, 530.0, 500.0, 500.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 9, 1), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 9, 16), 250.0, 250.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 10, 1), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
|
||||||
|
0.0, 10.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 10, 16), 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0,
|
||||||
|
0.0, 0.0, 10.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 10, 17), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 10.0, 10.0, 0.0, 0.0,
|
||||||
|
0.0, 10.0, 0.0);
|
||||||
|
validateLoanCharge(loanDetails, 0, LocalDate.of(2023, 9, 17), 20.0, 10.0, 10.0);
|
||||||
|
validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0);
|
||||||
|
validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
} finally {
|
||||||
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
|
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
|
||||||
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
|
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
|
||||||
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
|
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
|
||||||
@@ -2723,4 +3163,13 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest {
|
|||||||
assertEquals(overPaidPortion, loanDetails.getTransactions().get(index).getOverpaymentPortion());
|
assertEquals(overPaidPortion, loanDetails.getTransactions().get(index).getOverpaymentPortion());
|
||||||
assertEquals(loanBalance, loanDetails.getTransactions().get(index).getOutstandingLoanBalance());
|
assertEquals(loanBalance, loanDetails.getTransactions().get(index).getOutstandingLoanBalance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateLoanCharge(GetLoansLoanIdResponse loanDetails, int index, LocalDate dueDate, double charged, double paid,
|
||||||
|
double outstanding) {
|
||||||
|
GetLoansLoanIdLoanChargeData chargeData = loanDetails.getCharges().get(index);
|
||||||
|
assertEquals(dueDate, chargeData.getDueDate());
|
||||||
|
assertEquals(charged, chargeData.getAmount());
|
||||||
|
assertEquals(paid, chargeData.getAmountPaid());
|
||||||
|
assertEquals(outstanding, chargeData.getAmountOutstanding());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user