FINERACT-2042 Handling overpayment of chargeback with credit allocations
This commit is contained in:
committed by
Peter Bagrij
parent
5be1001fd5
commit
4bc6755514
+19
-3
@@ -140,6 +140,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCom
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||
@@ -5959,10 +5960,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
||||
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
|
||||
Money transactionOutstanding = loanTransaction.getAmount(getCurrency());
|
||||
Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
|
||||
if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
|
||||
transactionOutstanding = loanTransaction.getAmount(getCurrency())
|
||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||
// in case of advanced payment strategy and creditAllocations the full amount is recognized first
|
||||
if (this.getCreditAllocationRules() != null && this.getCreditAllocationRules().size() > 0) {
|
||||
Money payedPrincipal = loanTransaction.getLoanTransactionToRepaymentScheduleMappings().stream()
|
||||
.map(mapping -> mapping.getPrincipalPortion(getCurrency())).reduce(Money.zero(getCurrency()), Money::plus);
|
||||
transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency()).minus(payedPrincipal);
|
||||
} else {
|
||||
// in case legacy payment strategy
|
||||
transactionOutstanding = loanTransaction.getAmount(getCurrency())
|
||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||
}
|
||||
if (transactionOutstanding.isLessThanZero()) {
|
||||
transactionOutstanding = Money.zero(getCurrency());
|
||||
}
|
||||
@@ -7198,6 +7207,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return paymentAllocationRules;
|
||||
}
|
||||
|
||||
public LoanPaymentAllocationRule getPaymentAllocationRuleOrDefault(PaymentAllocationTransactionType transactionType) {
|
||||
Optional<LoanPaymentAllocationRule> paymentAllocationRule = this.getPaymentAllocationRules().stream()
|
||||
.filter(rule -> rule.getTransactionType().equals(transactionType)).findFirst();
|
||||
return paymentAllocationRule.orElse(this.getPaymentAllocationRules().stream()
|
||||
.filter(rule -> rule.getTransactionType().equals(PaymentAllocationTransactionType.DEFAULT)).findFirst().get());
|
||||
}
|
||||
|
||||
public void setPaymentAllocationRules(List<LoanPaymentAllocationRule> loanPaymentAllocationRules) {
|
||||
this.paymentAllocationRules = loanPaymentAllocationRules;
|
||||
}
|
||||
|
||||
+50
-12
@@ -130,8 +130,14 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
@Column(name = "is_additional", nullable = false)
|
||||
private boolean additional;
|
||||
|
||||
@Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal credits;
|
||||
@Column(name = "credited_principal", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal creditedPrincipal;
|
||||
|
||||
@Column(name = "credited_fee", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal creditedFee;
|
||||
|
||||
@Column(name = "credited_penalty", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal creditedPenalty;
|
||||
|
||||
@Column(name = "is_down_payment", nullable = false)
|
||||
private boolean isDownPayment;
|
||||
@@ -246,8 +252,16 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
public Money getCredits(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.credits);
|
||||
public Money getCreditedPrincipal(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.creditedPrincipal);
|
||||
}
|
||||
|
||||
public Money getCreditedFee(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.creditedFee);
|
||||
}
|
||||
|
||||
public Money getCreditedPenalty(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.creditedPenalty);
|
||||
}
|
||||
|
||||
public Money getPrincipal(final MonetaryCurrency currency) {
|
||||
@@ -408,9 +422,17 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
|
||||
this.obligationsMet = false;
|
||||
this.obligationsMetOnDate = null;
|
||||
if (this.credits != null) {
|
||||
this.principal = this.principal.subtract(this.credits);
|
||||
this.credits = null;
|
||||
if (this.creditedPrincipal != null) {
|
||||
this.principal = this.principal.subtract(this.creditedPrincipal);
|
||||
this.creditedPrincipal = null;
|
||||
}
|
||||
if (this.creditedFee != null) {
|
||||
this.feeChargesCharged = this.feeChargesCharged.subtract(this.creditedFee);
|
||||
this.creditedFee = null;
|
||||
}
|
||||
if (this.creditedPenalty != null) {
|
||||
this.penaltyCharges = this.penaltyCharges.subtract(this.creditedPenalty);
|
||||
this.creditedPenalty = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,11 +802,27 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
|
||||
}
|
||||
|
||||
public void addToCredits(final BigDecimal amount) {
|
||||
if (this.credits == null) {
|
||||
this.credits = amount;
|
||||
public void addToCreditedPrincipal(final BigDecimal amount) {
|
||||
if (this.creditedPrincipal == null) {
|
||||
this.creditedPrincipal = amount;
|
||||
} else {
|
||||
this.credits = this.credits.add(amount);
|
||||
this.creditedPrincipal = this.creditedPrincipal.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void addToCreditedFee(final BigDecimal amount) {
|
||||
if (this.creditedFee == null) {
|
||||
this.creditedFee = amount;
|
||||
} else {
|
||||
this.creditedFee = this.creditedFee.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void addToCreditedPenalty(final BigDecimal amount) {
|
||||
if (this.creditedPenalty == null) {
|
||||
this.creditedPenalty = amount;
|
||||
} else {
|
||||
this.creditedPenalty = this.creditedPenalty.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,7 +946,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
}
|
||||
|
||||
public void updateCredits(final LocalDate transactionDate, final Money transactionAmount) {
|
||||
addToCredits(transactionAmount.getAmount());
|
||||
addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
addToPrincipal(transactionDate, transactionAmount);
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -74,6 +74,9 @@ public class LoanSummary {
|
||||
@Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalFeeChargesDueAtDisbursement;
|
||||
|
||||
@Column(name = "fee_adjustments_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalFeeAdjustments;
|
||||
|
||||
@Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalFeeChargesRepaid;
|
||||
|
||||
@@ -89,6 +92,9 @@ public class LoanSummary {
|
||||
@Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPenaltyChargesCharged;
|
||||
|
||||
@Column(name = "penalty_adjustments_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPenaltyAdjustments;
|
||||
|
||||
@Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPenaltyChargesRepaid;
|
||||
|
||||
@@ -204,6 +210,8 @@ public class LoanSummary {
|
||||
public void zeroFields() {
|
||||
this.totalPrincipalDisbursed = BigDecimal.ZERO;
|
||||
this.totalPrincipalAdjustments = BigDecimal.ZERO;
|
||||
this.totalFeeAdjustments = BigDecimal.ZERO;
|
||||
this.totalPenaltyAdjustments = BigDecimal.ZERO;
|
||||
this.totalPrincipalRepaid = BigDecimal.ZERO;
|
||||
this.totalPrincipalWrittenOff = BigDecimal.ZERO;
|
||||
this.totalPrincipalOutstanding = BigDecimal.ZERO;
|
||||
@@ -238,6 +246,8 @@ public class LoanSummary {
|
||||
this.totalPrincipalDisbursed = principal.getAmount();
|
||||
this.totalPrincipalAdjustments = summaryWrapper.calculateTotalPrincipalAdjusted(repaymentScheduleInstallments, currency)
|
||||
.getAmount();
|
||||
this.totalFeeAdjustments = summaryWrapper.calculateTotalFeeAdjusted(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPenaltyAdjustments = summaryWrapper.calculateTotalPenaltyAdjusted(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency)
|
||||
.getAmount();
|
||||
@@ -259,7 +269,9 @@ public class LoanSummary {
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount();
|
||||
|
||||
Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges, currency);
|
||||
this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount();
|
||||
Money totalFeeChargesRepaidAfterDisbursement = summaryWrapper.calculateTotalFeeChargesRepaid(repaymentScheduleInstallments,
|
||||
currency);
|
||||
this.totalFeeChargesRepaid = totalFeeChargesRepaidAfterDisbursement.plus(totalFeeChargesRepaidAtDisbursement).getAmount();
|
||||
|
||||
if (charges != null) {
|
||||
this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount();
|
||||
|
||||
+21
-2
@@ -45,7 +45,25 @@ public final class LoanSummaryWrapper {
|
||||
final MonetaryCurrency currency) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
|
||||
total = total.plus(installment.getCredits(currency));
|
||||
total = total.plus(installment.getCreditedPrincipal(currency));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public Money calculateTotalFeeAdjusted(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
|
||||
final MonetaryCurrency currency) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
|
||||
total = total.plus(installment.getCreditedFee(currency));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public Money calculateTotalPenaltyAdjusted(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
|
||||
final MonetaryCurrency currency) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
|
||||
total = total.plus(installment.getCreditedPenalty(currency));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
@@ -247,7 +265,8 @@ public final class LoanSummaryWrapper {
|
||||
return total;
|
||||
}
|
||||
for (final LoanCharge loanCharge : charges) {
|
||||
if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) {
|
||||
if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()
|
||||
&& loanCharge.isDisbursementCharge()) {
|
||||
total = total.plus(loanCharge.getAmountPaid(currency));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -494,7 +494,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
@@ -526,7 +526,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
if (!loanTransactionMapped) {
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
@@ -539,7 +539,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
pastDueDate, transactionDate, transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), false, null);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCredits(transactionAmount.getAmount());
|
||||
installment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
|
||||
+110
-69
@@ -25,6 +25,7 @@ import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FE
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType.DEFAULT;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
@@ -76,7 +77,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransact
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.DueType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -235,27 +235,31 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
if (hasNoCustomCreditAllocationRule(loanTransaction)) {
|
||||
super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
|
||||
} else {
|
||||
log.debug("Processing credit transaction with custom credit allocation rules");
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
ctx.getInstallments().sort(byDate);
|
||||
final Money zeroMoney = Money.zero(ctx.getCurrency());
|
||||
Money transactionAmount = loanTransaction.getAmount(ctx.getCurrency());
|
||||
Money amountToDistribute = MathUtil
|
||||
.negativeToZero(loanTransaction.getAmount(ctx.getCurrency()).minus(ctx.getOverpaymentHolder().getMoneyObject()));
|
||||
Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
|
||||
loanTransaction.setOverPayments(repaidAmount);
|
||||
ctx.getOverpaymentHolder().setMoneyObject(ctx.getOverpaymentHolder().getMoneyObject().minus(repaidAmount));
|
||||
Money totalOverpaid = ctx.getOverpaymentHolder().getMoneyObject();
|
||||
Money amountToDistribute = MathUtil.negativeToZero(loanTransaction.getAmount(ctx.getCurrency()).minus(totalOverpaid));
|
||||
Money overpaymentAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
|
||||
loanTransaction.setOverPayments(overpaymentAmount);
|
||||
|
||||
if (amountToDistribute.isGreaterThanZero()) {
|
||||
if (transactionAmount.isGreaterThanZero()) {
|
||||
if (loanTransaction.isChargeback()) {
|
||||
LoanTransaction originalTransaction = findOriginalTransaction(loanTransaction, ctx);
|
||||
Map<AllocationType, BigDecimal> originalAllocation = getOriginalAllocation(originalTransaction);
|
||||
// get the original allocation from the opriginal transaction
|
||||
Map<AllocationType, Money> originalAllocationNotAdjusted = getOriginalAllocation(originalTransaction,
|
||||
ctx.getCurrency());
|
||||
LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules(loanTransaction);
|
||||
|
||||
// if there were earlier chargebacks then let's calculate the remaining amounts for each portion
|
||||
Map<AllocationType, Money> originalAllocation = adjustOriginalAllocationWithFormerChargebacks(originalTransaction,
|
||||
originalAllocationNotAdjusted, loanTransaction, ctx, chargeBackAllocationRule);
|
||||
|
||||
// calculate the current chargeback allocation
|
||||
Map<AllocationType, Money> chargebackAllocation = calculateChargebackAllocationMap(originalAllocation,
|
||||
amountToDistribute.getAmount(), chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
|
||||
transactionAmount.getAmount(), chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
|
||||
|
||||
loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), chargebackAllocation.get(INTEREST),
|
||||
chargebackAllocation.get(FEE), chargebackAllocation.get(PENALTY));
|
||||
@@ -266,18 +270,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : ctx.getInstallments()) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
|
||||
@@ -287,16 +280,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
|
||||
currentInstallment.updateDueDate(transactionDate);
|
||||
}
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
}
|
||||
@@ -307,42 +291,99 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments()
|
||||
.get(ctx.getInstallments().size() - 1);
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(ctx.getCurrency());
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
||||
} else {
|
||||
Loan loan = loanTransaction.getLoan();
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
|
||||
(ctx.getInstallments().size() + 1), pastDueDate, transactionDate, zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(), false, null);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCredits(transactionAmount.getAmount());
|
||||
installment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = installment.getInterestCharged(ctx.getCurrency());
|
||||
installment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, installment, chargebackAllocation);
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
allocateOverpayment(loanTransaction, ctx.getCurrency(), loanTransaction.getLoan().getRepaymentScheduleInstallments(),
|
||||
ctx.getOverpaymentHolder());
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported transaction " + loanTransaction.getTypeOf().name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<AllocationType, Money> adjustOriginalAllocationWithFormerChargebacks(LoanTransaction originalTransaction,
|
||||
Map<AllocationType, Money> originalAllocation, LoanTransaction chargeBackTransaction, TransactionCtx ctx,
|
||||
LoanCreditAllocationRule chargeBackAllocationRule) {
|
||||
// these are the list of existing transactions
|
||||
List<LoanTransaction> allTransactions = new ArrayList<>(chargeBackTransaction.getLoan().getLoanTransactions());
|
||||
|
||||
// Remove the current chargeback from the list
|
||||
if (chargeBackTransaction.getId() != null) {
|
||||
allTransactions.remove(chargeBackTransaction);
|
||||
} else {
|
||||
Long oldId = ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(chargeBackTransaction);
|
||||
allTransactions.remove(allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), oldId)).findFirst().get());
|
||||
}
|
||||
|
||||
// Add the replayed transactions and remove their old version before the replay
|
||||
if (ctx.getChangedTransactionDetail() != null && ctx.getChangedTransactionDetail().getNewTransactionMappings() != null) {
|
||||
for (Long id : ctx.getChangedTransactionDetail().getNewTransactionMappings().keySet()) {
|
||||
allTransactions.remove(allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), id)).findFirst().get());
|
||||
allTransactions.add(ctx.getChangedTransactionDetail().getNewTransactionMappings().get(id));
|
||||
}
|
||||
}
|
||||
|
||||
// keep only the chargeback transactions
|
||||
List<LoanTransaction> chargebacks = allTransactions.stream().filter(LoanTransaction::isChargeback).toList();
|
||||
|
||||
// let's figure out the original transaction for these chargebacks, and order them by ascending order
|
||||
List<LoanTransaction> chargebacksForTheSameOriginal = chargebacks.stream()
|
||||
.filter(tr -> findOriginalTransaction(tr, ctx) == originalTransaction).sorted(loanTransactionDateComparator()).toList();
|
||||
|
||||
Map<AllocationType, Money> allocation = new HashMap<>(originalAllocation);
|
||||
for (LoanTransaction loanTransaction : chargebacksForTheSameOriginal) {
|
||||
Map<AllocationType, Money> temp = calculateChargebackAllocationMap(allocation, loanTransaction.getAmount(),
|
||||
chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
|
||||
allocation.keySet().forEach(k -> allocation.put(k, allocation.get(k).minus(temp.get(k))));
|
||||
}
|
||||
return allocation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Comparator<LoanTransaction> loanTransactionDateComparator() {
|
||||
return (tr1, tr2) -> {
|
||||
if (tr1.getTransactionDate().compareTo(tr2.getTransactionDate()) != 0) {
|
||||
return tr1.getTransactionDate().compareTo(tr2.getTransactionDate());
|
||||
} else if (tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate()) != 0) {
|
||||
return tr1.getSubmittedOnDate().compareTo(tr2.getSubmittedOnDate());
|
||||
} else {
|
||||
return tr1.getCreatedDateTime().compareTo(tr2.getCreatedDateTime());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void recognizeAmountsAfterChargeback(MonetaryCurrency currency, LocalDate localDate,
|
||||
LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
|
||||
Money principal = chargebackAllocation.get(PRINCIPAL);
|
||||
if (principal.isGreaterThanZero()) {
|
||||
installment.addToCreditedPrincipal(principal.getAmount());
|
||||
installment.addToPrincipal(localDate, principal);
|
||||
}
|
||||
|
||||
Money fee = chargebackAllocation.get(FEE);
|
||||
if (fee.isGreaterThanZero()) {
|
||||
installment.addToCreditedFee(fee.getAmount());
|
||||
installment.addToChargePortion(fee, Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
|
||||
Money.zero(currency));
|
||||
}
|
||||
|
||||
Money penalty = chargebackAllocation.get(PENALTY);
|
||||
if (penalty.isGreaterThanZero()) {
|
||||
installment.addToCreditedPenalty(penalty.getAmount());
|
||||
installment.addToChargePortion(Money.zero(currency), Money.zero(currency), Money.zero(currency), penalty, Money.zero(currency),
|
||||
Money.zero(currency));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LoanCreditAllocationRule getChargebackAllocationRules(LoanTransaction loanTransaction) {
|
||||
LoanCreditAllocationRule chargeBackAllocationRule = loanTransaction.getLoan().getCreditAllocationRules().stream()
|
||||
@@ -351,23 +392,23 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<AllocationType, BigDecimal> getOriginalAllocation(LoanTransaction originalLoanTransaction) {
|
||||
Map<AllocationType, BigDecimal> originalAllocation = new HashMap<>();
|
||||
originalAllocation.put(PRINCIPAL, originalLoanTransaction.getPrincipalPortion());
|
||||
originalAllocation.put(INTEREST, originalLoanTransaction.getInterestPortion());
|
||||
originalAllocation.put(PENALTY, originalLoanTransaction.getPenaltyChargesPortion());
|
||||
originalAllocation.put(FEE, originalLoanTransaction.getFeeChargesPortion());
|
||||
private Map<AllocationType, Money> getOriginalAllocation(LoanTransaction originalLoanTransaction, MonetaryCurrency currency) {
|
||||
Map<AllocationType, Money> originalAllocation = new HashMap<>();
|
||||
originalAllocation.put(PRINCIPAL, Money.of(currency, originalLoanTransaction.getPrincipalPortion()));
|
||||
originalAllocation.put(INTEREST, Money.of(currency, originalLoanTransaction.getInterestPortion()));
|
||||
originalAllocation.put(PENALTY, Money.of(currency, originalLoanTransaction.getPenaltyChargesPortion()));
|
||||
originalAllocation.put(FEE, Money.of(currency, originalLoanTransaction.getFeeChargesPortion()));
|
||||
return originalAllocation;
|
||||
}
|
||||
|
||||
protected Map<AllocationType, Money> calculateChargebackAllocationMap(Map<AllocationType, BigDecimal> originalAllocation,
|
||||
protected Map<AllocationType, Money> calculateChargebackAllocationMap(Map<AllocationType, Money> originalAllocation,
|
||||
BigDecimal amountToDistribute, List<AllocationType> allocationTypes, MonetaryCurrency currency) {
|
||||
BigDecimal remainingAmount = amountToDistribute;
|
||||
Map<AllocationType, Money> result = new HashMap<>();
|
||||
Arrays.stream(AllocationType.values()).forEach(allocationType -> result.put(allocationType, Money.of(currency, BigDecimal.ZERO)));
|
||||
for (AllocationType allocationType : allocationTypes) {
|
||||
if (remainingAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal originalAmount = originalAllocation.get(allocationType);
|
||||
BigDecimal originalAmount = originalAllocation.get(allocationType).getAmount();
|
||||
if (originalAmount != null && remainingAmount.compareTo(originalAmount) > 0
|
||||
&& originalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
result.put(allocationType, Money.of(currency, originalAmount));
|
||||
@@ -395,14 +436,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
.filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
LoanPaymentAllocationRule paymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst()
|
||||
.orElse(defaultPaymentAllocationRule);
|
||||
Balances balances = new Balances(zero, zero, zero, zero);
|
||||
List<PaymentAllocationType> paymentAllocationTypes;
|
||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule;
|
||||
if (PaymentAllocationTransactionType.DEFAULT.equals(paymentAllocationRule.getTransactionType())) {
|
||||
if (DEFAULT.equals(paymentAllocationRule.getTransactionType())) {
|
||||
// if the allocation rule is not defined then the reverse order of the default allocation rule will be used
|
||||
paymentAllocationTypes = new ArrayList<>(paymentAllocationRule.getAllocationTypes());
|
||||
Collections.reverse(paymentAllocationTypes);
|
||||
@@ -560,7 +601,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
.filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
|
||||
Money transactionAmountUnprocessed = null;
|
||||
Money zero = Money.zero(currency);
|
||||
@@ -926,7 +967,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
.filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
LoanPaymentAllocationRule paymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst()
|
||||
.orElse(defaultPaymentAllocationRule);
|
||||
|
||||
+1
@@ -39,4 +39,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/1014_add_loan_account_custom_snapshot_event.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1015_remove_disable_schedule_extension_column.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1016_add_credit_allocation_rule.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1017_add_fee_and_penalty_adjustments_to_loan.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_loan">
|
||||
<column name="fee_adjustments_derived" type="DECIMAL(19, 6)" defaultValueNumeric="0.000000">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="penalty_adjustments_derived" type="DECIMAL(19, 6)" defaultValueNumeric="0.000000">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="2">
|
||||
<addColumn tableName="m_loan_repayment_schedule">
|
||||
<column defaultValueComputed="NULL" name="credited_fee" type="DECIMAL(19, 6)"/>
|
||||
<column defaultValueComputed="NULL" name="credited_penalty" type="DECIMAL(19, 6)"/>
|
||||
</addColumn>
|
||||
<renameColumn tableName="m_loan_repayment_schedule" oldColumnName="credits_amount" newColumnName="credited_principal" columnDataType="DECIMAL(19, 6)"/>
|
||||
</changeSet>
|
||||
|
||||
|
||||
</databaseChangeLog>
|
||||
+4
@@ -524,6 +524,8 @@ final class LoansApiResourceSwagger {
|
||||
public Double interestOutstanding;
|
||||
@Schema(example = "200000.000000")
|
||||
public Double interestOverdue;
|
||||
@Schema(example = "0.00")
|
||||
public Double feeAdjustments;
|
||||
@Schema(example = "18000.000000")
|
||||
public Double feeChargesCharged;
|
||||
@Schema(example = "0.000000")
|
||||
@@ -538,6 +540,8 @@ final class LoansApiResourceSwagger {
|
||||
public Double feeChargesOutstanding;
|
||||
@Schema(example = "15000.000000")
|
||||
public Double feeChargesOverdue;
|
||||
@Schema(example = "0.00")
|
||||
public Double penaltyAdjustments;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesCharged;
|
||||
@Schema(example = "0.000000")
|
||||
|
||||
+14
-1
@@ -51,12 +51,15 @@ public class LoanScheduleAccrualData {
|
||||
private BigDecimal dueDatePenaltyIncome;
|
||||
private BigDecimal accruableIncome;
|
||||
|
||||
private BigDecimal creditedFee;
|
||||
private BigDecimal creditedPenalty;
|
||||
|
||||
public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
|
||||
final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate,
|
||||
final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome,
|
||||
final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome,
|
||||
final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom,
|
||||
final BigDecimal waivedInterestIncome) {
|
||||
final BigDecimal waivedInterestIncome, BigDecimal creditedFee, BigDecimal creditedPenalty) {
|
||||
this.loanId = loanId;
|
||||
this.installmentNumber = installmentNumber;
|
||||
this.officeId = officeId;
|
||||
@@ -76,6 +79,8 @@ public class LoanScheduleAccrualData {
|
||||
this.repayEvery = repayEvery;
|
||||
this.interestCalculatedFrom = interestCalculatedFrom;
|
||||
this.waivedInterestIncome = waivedInterestIncome;
|
||||
this.creditedFee = creditedFee;
|
||||
this.creditedPenalty = creditedPenalty;
|
||||
}
|
||||
|
||||
public Long getLoanId() {
|
||||
@@ -185,4 +190,12 @@ public class LoanScheduleAccrualData {
|
||||
this.accruableIncome = accruableIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getCreditedFee() {
|
||||
return this.creditedFee;
|
||||
}
|
||||
|
||||
public BigDecimal getCreditedPenalty() {
|
||||
return this.creditedPenalty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+36
-80
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
@@ -31,6 +32,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* Immutable data object representing loan summary information.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Accessors(chain = true)
|
||||
public class LoanSummaryData {
|
||||
|
||||
@@ -48,6 +50,7 @@ public class LoanSummaryData {
|
||||
private final BigDecimal interestOutstanding;
|
||||
private final BigDecimal interestOverdue;
|
||||
private final BigDecimal feeChargesCharged;
|
||||
private final BigDecimal feeAdjustments;
|
||||
private final BigDecimal feeChargesDueAtDisbursementCharged;
|
||||
private final BigDecimal feeChargesPaid;
|
||||
private final BigDecimal feeChargesWaived;
|
||||
@@ -55,6 +58,7 @@ public class LoanSummaryData {
|
||||
private final BigDecimal feeChargesOutstanding;
|
||||
private final BigDecimal feeChargesOverdue;
|
||||
private final BigDecimal penaltyChargesCharged;
|
||||
private final BigDecimal penaltyAdjustments;
|
||||
private final BigDecimal penaltyChargesPaid;
|
||||
private final BigDecimal penaltyChargesWaived;
|
||||
private final BigDecimal penaltyChargesWrittenOff;
|
||||
@@ -90,61 +94,6 @@ public class LoanSummaryData {
|
||||
private final Long chargeOffReasonId;
|
||||
private final String chargeOffReason;
|
||||
|
||||
public LoanSummaryData(final CurrencyData currency, final BigDecimal principalDisbursed, final BigDecimal principalAdjustments,
|
||||
final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
|
||||
final BigDecimal principalOverdue, final BigDecimal interestCharged, final BigDecimal interestPaid,
|
||||
final BigDecimal interestWaived, final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding,
|
||||
final BigDecimal interestOverdue, final BigDecimal feeChargesCharged, final BigDecimal feeChargesDueAtDisbursementCharged,
|
||||
final BigDecimal feeChargesPaid, final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff,
|
||||
final BigDecimal feeChargesOutstanding, final BigDecimal feeChargesOverdue, final BigDecimal penaltyChargesCharged,
|
||||
final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived, final BigDecimal penaltyChargesWrittenOff,
|
||||
final BigDecimal penaltyChargesOutstanding, final BigDecimal penaltyChargesOverdue, final BigDecimal totalExpectedRepayment,
|
||||
final BigDecimal totalRepayment, final BigDecimal totalExpectedCostOfLoan, final BigDecimal totalCostOfLoan,
|
||||
final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
|
||||
final BigDecimal totalOverdue, final LocalDate overdueSinceDate, final Long writeoffReasonId, final String writeoffReason,
|
||||
final BigDecimal totalRecovered, final Long chargeOffReasonId, final String chargeOffReason) {
|
||||
this.currency = currency;
|
||||
this.principalDisbursed = principalDisbursed;
|
||||
this.principalAdjustments = principalAdjustments;
|
||||
this.principalPaid = principalPaid;
|
||||
this.principalWrittenOff = principalWrittenOff;
|
||||
this.principalOutstanding = principalOutstanding;
|
||||
this.principalOverdue = principalOverdue;
|
||||
this.interestCharged = interestCharged;
|
||||
this.interestPaid = interestPaid;
|
||||
this.interestWaived = interestWaived;
|
||||
this.interestWrittenOff = interestWrittenOff;
|
||||
this.interestOutstanding = interestOutstanding;
|
||||
this.interestOverdue = interestOverdue;
|
||||
this.feeChargesCharged = feeChargesCharged;
|
||||
this.feeChargesDueAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
|
||||
this.feeChargesPaid = feeChargesPaid;
|
||||
this.feeChargesWaived = feeChargesWaived;
|
||||
this.feeChargesWrittenOff = feeChargesWrittenOff;
|
||||
this.feeChargesOutstanding = feeChargesOutstanding;
|
||||
this.feeChargesOverdue = feeChargesOverdue;
|
||||
this.penaltyChargesCharged = penaltyChargesCharged;
|
||||
this.penaltyChargesPaid = penaltyChargesPaid;
|
||||
this.penaltyChargesWaived = penaltyChargesWaived;
|
||||
this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
|
||||
this.penaltyChargesOutstanding = penaltyChargesOutstanding;
|
||||
this.penaltyChargesOverdue = penaltyChargesOverdue;
|
||||
this.totalExpectedRepayment = totalExpectedRepayment;
|
||||
this.totalRepayment = totalRepayment;
|
||||
this.totalExpectedCostOfLoan = totalExpectedCostOfLoan;
|
||||
this.totalCostOfLoan = totalCostOfLoan;
|
||||
this.totalWaived = totalWaived;
|
||||
this.totalWrittenOff = totalWrittenOff;
|
||||
this.totalOutstanding = totalOutstanding;
|
||||
this.totalOverdue = totalOverdue;
|
||||
this.overdueSinceDate = overdueSinceDate;
|
||||
this.writeoffReasonId = writeoffReasonId;
|
||||
this.writeoffReason = writeoffReason;
|
||||
this.totalRecovered = totalRecovered;
|
||||
this.chargeOffReasonId = chargeOffReasonId;
|
||||
this.chargeOffReason = chargeOffReason;
|
||||
}
|
||||
|
||||
public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
|
||||
final Collection<LoanTransactionData> loanTransactions) {
|
||||
|
||||
@@ -184,34 +133,41 @@ public class LoanSummaryData {
|
||||
totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions);
|
||||
}
|
||||
|
||||
return new LoanSummaryData(defaultSummaryData.currency, defaultSummaryData.principalDisbursed,
|
||||
defaultSummaryData.principalAdjustments, defaultSummaryData.principalPaid, defaultSummaryData.principalWrittenOff,
|
||||
defaultSummaryData.principalOutstanding, defaultSummaryData.principalOverdue, defaultSummaryData.interestCharged,
|
||||
defaultSummaryData.interestPaid, defaultSummaryData.interestWaived, defaultSummaryData.interestWrittenOff,
|
||||
defaultSummaryData.interestOutstanding, defaultSummaryData.interestOverdue, defaultSummaryData.feeChargesCharged,
|
||||
defaultSummaryData.feeChargesDueAtDisbursementCharged, defaultSummaryData.feeChargesPaid,
|
||||
defaultSummaryData.feeChargesWaived, defaultSummaryData.feeChargesWrittenOff, defaultSummaryData.feeChargesOutstanding,
|
||||
defaultSummaryData.feeChargesOverdue, defaultSummaryData.penaltyChargesCharged, defaultSummaryData.penaltyChargesPaid,
|
||||
defaultSummaryData.penaltyChargesWaived, defaultSummaryData.penaltyChargesWrittenOff,
|
||||
defaultSummaryData.penaltyChargesOutstanding, defaultSummaryData.penaltyChargesOverdue,
|
||||
defaultSummaryData.totalExpectedRepayment, defaultSummaryData.totalRepayment, defaultSummaryData.totalExpectedCostOfLoan,
|
||||
defaultSummaryData.totalCostOfLoan, defaultSummaryData.totalWaived, defaultSummaryData.totalWrittenOff,
|
||||
defaultSummaryData.totalOutstanding, defaultSummaryData.totalOverdue, defaultSummaryData.overdueSinceDate,
|
||||
defaultSummaryData.writeoffReasonId, defaultSummaryData.writeoffReason, defaultSummaryData.totalRecovered,
|
||||
defaultSummaryData.chargeOffReasonId, defaultSummaryData.chargeOffReason).setTotalMerchantRefund(totalMerchantRefund)
|
||||
.setTotalMerchantRefundReversed(totalMerchantRefundReversed).setTotalPayoutRefund(totalPayoutRefund)
|
||||
.setTotalPayoutRefundReversed(totalPayoutRefundReversed).setTotalGoodwillCredit(totalGoodwillCredit)
|
||||
.setTotalGoodwillCreditReversed(totalGoodwillCreditReversed).setTotalChargeAdjustment(totalChargeAdjustment)
|
||||
.setTotalChargeAdjustmentReversed(totalChargeAdjustmentReversed).setTotalChargeback(totalChargeback)
|
||||
.setTotalCreditBalanceRefund(totalCreditBalanceRefund).setTotalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed)
|
||||
.setTotalRepaymentTransaction(totalRepaymentTransaction)
|
||||
.setTotalRepaymentTransactionReversed(totalRepaymentTransactionReversed);
|
||||
return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed)
|
||||
.principalAdjustments(defaultSummaryData.principalAdjustments).principalPaid(defaultSummaryData.principalPaid)
|
||||
.principalWrittenOff(defaultSummaryData.principalWrittenOff).principalOutstanding(defaultSummaryData.principalOutstanding)
|
||||
.principalOverdue(defaultSummaryData.principalOverdue).interestCharged(defaultSummaryData.interestCharged)
|
||||
.interestPaid(defaultSummaryData.interestPaid).interestWaived(defaultSummaryData.interestWaived)
|
||||
.interestWrittenOff(defaultSummaryData.interestWrittenOff).interestOutstanding(defaultSummaryData.interestOutstanding)
|
||||
.interestOverdue(defaultSummaryData.interestOverdue).feeChargesCharged(defaultSummaryData.feeChargesCharged)
|
||||
.feeAdjustments(defaultSummaryData.feeAdjustments)
|
||||
.feeChargesDueAtDisbursementCharged(defaultSummaryData.feeChargesDueAtDisbursementCharged)
|
||||
.feeChargesPaid(defaultSummaryData.feeChargesPaid).feeChargesWaived(defaultSummaryData.feeChargesWaived)
|
||||
.feeChargesWrittenOff(defaultSummaryData.feeChargesWrittenOff)
|
||||
.feeChargesOutstanding(defaultSummaryData.feeChargesOutstanding).feeChargesOverdue(defaultSummaryData.feeChargesOverdue)
|
||||
.penaltyChargesCharged(defaultSummaryData.penaltyChargesCharged).penaltyAdjustments(defaultSummaryData.penaltyAdjustments)
|
||||
.penaltyChargesPaid(defaultSummaryData.penaltyChargesPaid).penaltyChargesWaived(defaultSummaryData.penaltyChargesWaived)
|
||||
.penaltyChargesWrittenOff(defaultSummaryData.penaltyChargesWrittenOff)
|
||||
.penaltyChargesOutstanding(defaultSummaryData.penaltyChargesOutstanding)
|
||||
.penaltyChargesOverdue(defaultSummaryData.penaltyChargesOverdue)
|
||||
.totalExpectedRepayment(defaultSummaryData.totalExpectedRepayment).totalRepayment(defaultSummaryData.totalRepayment)
|
||||
.totalExpectedCostOfLoan(defaultSummaryData.totalExpectedCostOfLoan).totalCostOfLoan(defaultSummaryData.totalCostOfLoan)
|
||||
.totalWaived(defaultSummaryData.totalWaived).totalWrittenOff(defaultSummaryData.totalWrittenOff)
|
||||
.totalOutstanding(defaultSummaryData.totalOutstanding).totalOverdue(defaultSummaryData.totalOverdue)
|
||||
.overdueSinceDate(defaultSummaryData.overdueSinceDate).writeoffReasonId(defaultSummaryData.writeoffReasonId)
|
||||
.writeoffReason(defaultSummaryData.writeoffReason).totalRecovered(defaultSummaryData.totalRecovered)
|
||||
.chargeOffReasonId(defaultSummaryData.chargeOffReasonId).chargeOffReason(defaultSummaryData.chargeOffReason)
|
||||
.totalMerchantRefund(totalMerchantRefund).totalMerchantRefundReversed(totalMerchantRefundReversed)
|
||||
.totalPayoutRefund(totalPayoutRefund).totalPayoutRefundReversed(totalPayoutRefundReversed)
|
||||
.totalGoodwillCredit(totalGoodwillCredit).totalGoodwillCreditReversed(totalGoodwillCreditReversed)
|
||||
.totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
|
||||
.totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund)
|
||||
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
|
||||
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).build();
|
||||
}
|
||||
|
||||
public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
|
||||
return new LoanSummaryData(currencyData, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null);
|
||||
return LoanSummaryData.builder().currency(currencyData).build();
|
||||
}
|
||||
|
||||
private static BigDecimal computeTotalAmountForReversedTransactions(LoanTransactionType transactionType,
|
||||
|
||||
+6
-3
@@ -695,7 +695,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
|
||||
installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(),
|
||||
installment.getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom,
|
||||
installment.getInterestWaived(currency).getAmount());
|
||||
installment.getInterestWaived(currency).getAmount(), installment.getCreditedFee(currency).getAmount(),
|
||||
installment.getCreditedPenalty(currency).getAmount());
|
||||
loanScheduleAccrualDatas.add(accrualData);
|
||||
|
||||
}
|
||||
@@ -940,10 +941,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
.minus(loanRepaymentScheduleInstallment.getInterestWaived(currency));
|
||||
feePortion = feePortion.add(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getFeeAccrued(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency));
|
||||
.minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getCreditedFee(currency));
|
||||
penaltyPortion = penaltyPortion.add(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getPenaltyAccrued(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency));
|
||||
.minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getCreditedPenalty(currency));
|
||||
}
|
||||
Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
|
||||
|
||||
|
||||
+16
-2
@@ -168,7 +168,9 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
|
||||
BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
|
||||
BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
|
||||
BigDecimal totalCreditedPenalty = accrualData.getCreditedPenalty();
|
||||
BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
|
||||
BigDecimal totalCreditedFee = accrualData.getCreditedFee();
|
||||
|
||||
if (totalAccInterest == null) {
|
||||
totalAccInterest = BigDecimal.ZERO;
|
||||
@@ -183,7 +185,10 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
if (totalAccFee == null) {
|
||||
totalAccFee = BigDecimal.ZERO;
|
||||
}
|
||||
feePortion = feePortion.subtract(totalAccFee);
|
||||
if (totalCreditedFee == null) {
|
||||
totalCreditedFee = BigDecimal.ZERO;
|
||||
}
|
||||
feePortion = feePortion.subtract(totalAccFee).subtract(totalCreditedFee);
|
||||
amount = amount.add(feePortion);
|
||||
totalAccFee = totalAccFee.add(feePortion);
|
||||
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
@@ -195,7 +200,10 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
if (totalAccPenalty == null) {
|
||||
totalAccPenalty = BigDecimal.ZERO;
|
||||
}
|
||||
penaltyPortion = penaltyPortion.subtract(totalAccPenalty);
|
||||
if (totalCreditedPenalty == null) {
|
||||
totalCreditedPenalty = BigDecimal.ZERO;
|
||||
}
|
||||
penaltyPortion = penaltyPortion.subtract(totalAccPenalty).subtract(totalCreditedPenalty);
|
||||
amount = amount.add(penaltyPortion);
|
||||
totalAccPenalty = totalAccPenalty.add(penaltyPortion);
|
||||
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
@@ -234,6 +242,9 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
if (scheduleAccrualData.getAccruedFeeIncome() != null) {
|
||||
feePortion = feePortion.subtract(scheduleAccrualData.getAccruedFeeIncome());
|
||||
}
|
||||
if (scheduleAccrualData.getCreditedFee() != null) {
|
||||
feePortion = feePortion.subtract(scheduleAccrualData.getCreditedFee());
|
||||
}
|
||||
amount = amount.add(feePortion);
|
||||
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
feePortion = null;
|
||||
@@ -248,6 +259,9 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
if (scheduleAccrualData.getAccruedPenaltyIncome() != null) {
|
||||
penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
|
||||
}
|
||||
if (scheduleAccrualData.getCreditedPenalty() != null) {
|
||||
penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getCreditedPenalty());
|
||||
}
|
||||
amount = amount.add(penaltyPortion);
|
||||
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
penaltyPortion = null;
|
||||
|
||||
+31
-11
@@ -649,6 +649,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
+ " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, "
|
||||
+ " l.principal_disbursed_derived as principalDisbursed, l.principal_repaid_derived as principalPaid,"
|
||||
+ " l.principal_adjustments_derived as principalAdjustments, l.principal_writtenoff_derived as principalWrittenOff,"
|
||||
+ " l.fee_adjustments_derived as feeAdjustments, l.penalty_adjustments_derived as penaltyAdjustments,"
|
||||
+ " l.principal_outstanding_derived as principalOutstanding, l.interest_charged_derived as interestCharged,"
|
||||
+ " l.interest_repaid_derived as interestPaid, l.interest_waived_derived as interestWaived,"
|
||||
+ " l.interest_writtenoff_derived as interestWrittenOff, l.interest_outstanding_derived as interestOutstanding,"
|
||||
@@ -912,6 +913,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final BigDecimal interestOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestOverdue");
|
||||
|
||||
final BigDecimal feeChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesCharged");
|
||||
final BigDecimal feeAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAdjustments");
|
||||
final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
|
||||
final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
|
||||
final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
|
||||
@@ -919,6 +921,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final BigDecimal feeChargesOverdue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesOverdue");
|
||||
|
||||
final BigDecimal penaltyChargesCharged = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesCharged");
|
||||
final BigDecimal penaltyAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyAdjustments");
|
||||
final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesPaid");
|
||||
final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived");
|
||||
final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff");
|
||||
@@ -938,14 +941,24 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
|
||||
inArrears = (overdueSinceDate != null);
|
||||
|
||||
loanSummary = new LoanSummaryData(currencyData, principalDisbursed, principalAdjustments, principalPaid,
|
||||
principalWrittenOff, principalOutstanding, principalOverdue, interestCharged, interestPaid, interestWaived,
|
||||
interestWrittenOff, interestOutstanding, interestOverdue, feeChargesCharged, feeChargesDueAtDisbursementCharged,
|
||||
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, feeChargesOverdue,
|
||||
penaltyChargesCharged, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff,
|
||||
penaltyChargesOutstanding, penaltyChargesOverdue, totalExpectedRepayment, totalRepayment, totalExpectedCostOfLoan,
|
||||
totalCostOfLoan, totalWaived, totalWrittenOff, totalOutstanding, totalOverdue, overdueSinceDate, writeoffReasonId,
|
||||
writeoffReason, totalRecovered, chargeOffReasonId, chargeOffReason);
|
||||
loanSummary = LoanSummaryData.builder().currency(currencyData).principalDisbursed(principalDisbursed)
|
||||
.principalAdjustments(principalAdjustments).principalPaid(principalPaid).principalWrittenOff(principalWrittenOff)
|
||||
.principalOutstanding(principalOutstanding).principalOverdue(principalOverdue).interestCharged(interestCharged)
|
||||
.interestPaid(interestPaid).interestWaived(interestWaived).interestWrittenOff(interestWrittenOff)
|
||||
.interestOutstanding(interestOutstanding).interestOverdue(interestOverdue).feeChargesCharged(feeChargesCharged)
|
||||
.feeAdjustments(feeAdjustments).feeChargesDueAtDisbursementCharged(feeChargesDueAtDisbursementCharged)
|
||||
.feeChargesPaid(feeChargesPaid).feeChargesWaived(feeChargesWaived).feeChargesWrittenOff(feeChargesWrittenOff)
|
||||
.feeChargesOutstanding(feeChargesOutstanding).feeChargesOverdue(feeChargesOverdue)
|
||||
.penaltyChargesCharged(penaltyChargesCharged).penaltyAdjustments(penaltyAdjustments)
|
||||
.penaltyChargesPaid(penaltyChargesPaid).penaltyChargesWaived(penaltyChargesWaived)
|
||||
.penaltyChargesWrittenOff(penaltyChargesWrittenOff).penaltyChargesOutstanding(penaltyChargesOutstanding)
|
||||
.penaltyChargesOverdue(penaltyChargesOverdue).totalExpectedRepayment(totalExpectedRepayment)
|
||||
.totalRepayment(totalRepayment).totalExpectedCostOfLoan(totalExpectedCostOfLoan).totalCostOfLoan(totalCostOfLoan)
|
||||
.totalWaived(totalWaived).totalWrittenOff(totalWrittenOff).totalOutstanding(totalOutstanding)
|
||||
.totalOverdue(totalOverdue).overdueSinceDate(overdueSinceDate).writeoffReasonId(writeoffReasonId)
|
||||
.writeoffReason(writeoffReason).totalRecovered(totalRecovered).chargeOffReasonId(chargeOffReasonId)
|
||||
.chargeOffReason(chargeOffReason).build();
|
||||
|
||||
}
|
||||
|
||||
GroupGeneralData groupData = null;
|
||||
@@ -1144,7 +1157,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
+ " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
|
||||
+ " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, "
|
||||
+ " ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, "
|
||||
+ " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as totalCredits, ls.is_down_payment isDownPayment "
|
||||
+ " ls.total_paid_late_derived as totalPaidLateForPeriod, (coalesce(ls.credited_principal,0) + coalesce(ls.credited_fee,0) + coalesce(ls.credited_penalty,0)) as totalCredits, ls.is_down_payment isDownPayment "
|
||||
+ " from m_loan_repayment_schedule ls ";
|
||||
}
|
||||
|
||||
@@ -1912,6 +1925,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
.append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,")
|
||||
.append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
|
||||
.append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
|
||||
.append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
|
||||
.append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
|
||||
.append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
|
||||
.append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
|
||||
@@ -1946,6 +1960,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
|
||||
final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
|
||||
final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
|
||||
final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
|
||||
final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
@@ -1958,7 +1974,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
|
||||
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate,
|
||||
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
|
||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
|
||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1972,6 +1988,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
.append("ls.installment as installmentNumber, ")
|
||||
.append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
|
||||
.append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
|
||||
.append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
|
||||
.append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
|
||||
.append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
|
||||
.append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
|
||||
@@ -1996,6 +2013,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
|
||||
final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
|
||||
final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
|
||||
final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
|
||||
final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
|
||||
|
||||
final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
|
||||
final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
|
||||
final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
|
||||
@@ -2015,7 +2035,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final LocalDate interestCalculatedFrom = null;
|
||||
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromdate,
|
||||
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
|
||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived);
|
||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+227
-186
@@ -24,11 +24,15 @@ import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.IN
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.refEq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -83,7 +87,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
|
||||
private final LocalDate transactionDate = LocalDate.of(2023, 7, 11);
|
||||
private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = Mockito.mockStatic(MoneyHelper.class);
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
||||
|
||||
@BeforeAll
|
||||
@@ -123,34 +127,33 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = mock(Loan.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
LoanRepaymentScheduleInstallment installment = Mockito
|
||||
.spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
LoanRepaymentScheduleInstallment installment = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
Mockito.when(loanTransaction.getAmount()).thenReturn(chargeAmount);
|
||||
Mockito.when(loanTransaction.getAmount(currency)).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||
.thenReturn(true);
|
||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(chargeAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount());
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, Mockito.times(0)).getPaymentAllocationRules();
|
||||
Mockito.verify(loan, times(0)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,35 +171,33 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(20.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
LoanRepaymentScheduleInstallment installment = Mockito
|
||||
.spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
LoanRepaymentScheduleInstallment installment = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
Mockito.when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
Mockito.when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||
.thenReturn(true);
|
||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
Mockito.when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
|
||||
Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
|
||||
Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney),
|
||||
refEq(zero));
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
|
||||
assertEquals(zero.getAmount(), loanTransaction.getAmount(currency).minus(transactionAmountMoney).getAmount());
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.valueOf(80.00).compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, Mockito.times(0)).getPaymentAllocationRules();
|
||||
Mockito.verify(loan, times(0)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -216,94 +217,50 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(120.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
LoanPaymentAllocationRule loanPaymentAllocationRule = mock(LoanPaymentAllocationRule.class);
|
||||
LoanRepaymentScheduleInstallment installment = Mockito
|
||||
.spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate, BigDecimal.valueOf(100L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
LoanRepaymentScheduleInstallment installment = spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate,
|
||||
BigDecimal.valueOf(100L), BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
Mockito.when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
Mockito.when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
Mockito.when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
Mockito.when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
Mockito.when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
Mockito.when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
Mockito.when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
Mockito.when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
Mockito.when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||
Mockito.when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
Mockito.when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate()))
|
||||
.thenReturn(true);
|
||||
Mockito.when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
Mockito.when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
||||
Mockito.when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
|
||||
Mockito.when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
|
||||
Mockito.when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(chargePaidBy.getLoanCharge()).thenReturn(charge);
|
||||
when(loanTransaction.getLoanChargesPaid()).thenReturn(Set.of(chargePaidBy));
|
||||
when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||
when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true);
|
||||
when(installment.getInstallmentNumber()).thenReturn(1);
|
||||
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||
when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
||||
when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
|
||||
when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
|
||||
when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
|
||||
|
||||
underTest.processLatestTransaction(loanTransaction,
|
||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
|
||||
|
||||
Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||
assertEquals(0, BigDecimal.valueOf(20).compareTo(loanTransaction.getAmount(currency).minus(chargeAmountMoney).getAmount()));
|
||||
assertEquals(0, chargeAmount.compareTo(installment.getFeeChargesCharged(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(installment.getFeeChargesOutstanding(currency).getAmount()));
|
||||
assertEquals(0, BigDecimal.valueOf(80).compareTo(installment.getPrincipalOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, Mockito.times(1)).getPaymentAllocationRules();
|
||||
Mockito.verify(loan, times(1)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRuleInterestAndPrincipal() {
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalPenaltyFeeInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(INTEREST, PRINCIPAL, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("20.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
@@ -315,15 +272,31 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
|
||||
// verify principal
|
||||
Mockito.verify(installment, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Mockito.verify(installment, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("10.00"));
|
||||
Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
@@ -331,24 +304,74 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePenaltyFeePrincipalInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PENALTY, FEE, PRINCIPAL, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment, times(1)).addToCreditedFee(new BigDecimal("20.00"));
|
||||
Mockito.verify(installment, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWithAdditionalInstallment() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan, 25.0);
|
||||
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction);
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, PENALTY, FEE, INTEREST);
|
||||
when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan, chargeBackTransaction, 10, 0, 20, 5);
|
||||
lenient().when(loan.getLoanTransactions()).thenReturn(List.of(repayment));
|
||||
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
|
||||
LoanRepaymentScheduleInstallment installment1 = createMockInstallment(LocalDate.of(2022, 12, 20), false);
|
||||
LoanRepaymentScheduleInstallment installment2 = createMockInstallment(LocalDate.of(2022, 12, 27), true);
|
||||
installments.add(installment1);
|
||||
@@ -358,15 +381,31 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
|
||||
underTest.processCreditTransaction(chargeBackTransaction, ctx);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment2, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment2, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
|
||||
// verify principal
|
||||
Mockito.verify(installment2, times(1)).addToCreditedPrincipal(new BigDecimal("10.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment2, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Mockito.verify(installment2, times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
// verify charges on installment
|
||||
Mockito.verify(installment2, times(1)).addToCreditedFee(new BigDecimal("10.00"));
|
||||
Mockito.verify(installment2, times(1)).addToCreditedPenalty(new BigDecimal("5.00"));
|
||||
|
||||
ArgumentCaptor<Money> feeCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penaltyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment2, times(2)).addToChargePortion(feeCaptor.capture(), any(), any(), penaltyCaptor.capture(), any(), any());
|
||||
assertEquals(2, feeCaptor.getAllValues().size());
|
||||
assertEquals(2, penaltyCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(0, feeCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, feeCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.ZERO));
|
||||
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(0).getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penaltyCaptor.getAllValues().get(1).getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
// verify transaction
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
@@ -374,9 +413,9 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate localDate, boolean isAdditional) {
|
||||
@@ -396,15 +435,16 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
return mockCreditAllocationRule;
|
||||
}
|
||||
|
||||
private LoanTransaction createRepayment(Loan loan, LoanTransaction toTransaction) {
|
||||
private LoanTransaction createRepayment(Loan loan, LoanTransaction toTransaction, double principalPortion, double interestPortion,
|
||||
double feePortion, double penaltyPortion) {
|
||||
LoanTransaction repayment = mock(LoanTransaction.class);
|
||||
lenient().when(repayment.getLoan()).thenReturn(loan);
|
||||
lenient().when(repayment.isRepayment()).thenReturn(true);
|
||||
lenient().when(repayment.getTypeOf()).thenReturn(REPAYMENT);
|
||||
lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(10));
|
||||
lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(20));
|
||||
lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.ZERO);
|
||||
lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.ZERO);
|
||||
lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(principalPortion));
|
||||
lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(interestPortion));
|
||||
lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.valueOf(feePortion));
|
||||
lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.valueOf(penaltyPortion));
|
||||
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
lenient().when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
@@ -414,14 +454,14 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
return repayment;
|
||||
}
|
||||
|
||||
private LoanTransaction createChargebackTransaction(Loan loan) {
|
||||
private LoanTransaction createChargebackTransaction(Loan loan, double transactionAmount) {
|
||||
LoanTransaction chargeback = mock(LoanTransaction.class);
|
||||
lenient().when(chargeback.isChargeback()).thenReturn(true);
|
||||
lenient().when(chargeback.getTypeOf()).thenReturn(LoanTransactionType.CHARGEBACK);
|
||||
lenient().when(chargeback.getLoan()).thenReturn(loan);
|
||||
lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(25));
|
||||
Money amount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(25));
|
||||
lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(amount);
|
||||
lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(transactionAmount));
|
||||
Money money = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(transactionAmount));
|
||||
lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(money);
|
||||
lenient().when(chargeback.getTransactionDate()).thenReturn(LocalDate.of(2023, 1, 1));
|
||||
return chargeback;
|
||||
}
|
||||
@@ -431,40 +471,41 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
Map<AllocationType, Money> result;
|
||||
MonetaryCurrency currency = mock(MonetaryCurrency.class);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(50.0, 0, 0, 0), result);
|
||||
verify(allocationMap(50.0, 0, 0, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(40.0, 10, 0, 0), result);
|
||||
verify(allocationMap(40.0, 10, 0, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 0, 10, 0), result);
|
||||
verify(allocationMap(40.0, 0, 10, 0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(340.0),
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(340.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 88.0, 200.0, 12.0), result);
|
||||
verify(allocationMap(40.0, 88.0, 200.0, 12.0, currency), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(352.0),
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(352.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 100.0, 200.0, 12.0), result);
|
||||
verify(allocationMap(40.0, 100.0, 200.0, 12.0, currency), result);
|
||||
}
|
||||
|
||||
private void verify(Map<AllocationType, BigDecimal> expected, Map<AllocationType, Money> actual) {
|
||||
private void verify(Map<AllocationType, Money> expected, Map<AllocationType, Money> actual) {
|
||||
Assertions.assertEquals(expected.size(), actual.size());
|
||||
expected.forEach((k, v) -> {
|
||||
Assertions.assertEquals(0, v.compareTo(actual.get(k).getAmount()), "Not matching for " + k);
|
||||
Assertions.assertEquals(0, v.getAmount().compareTo(actual.get(k).getAmount()), "Not matching for " + k);
|
||||
});
|
||||
}
|
||||
|
||||
private Map<AllocationType, BigDecimal> allocationMap(double principal, double interest, double fee, double penalty) {
|
||||
Map<AllocationType, BigDecimal> allocationMap = new HashMap<>();
|
||||
allocationMap.put(AllocationType.PRINCIPAL, BigDecimal.valueOf(principal));
|
||||
allocationMap.put(AllocationType.INTEREST, BigDecimal.valueOf(interest));
|
||||
allocationMap.put(AllocationType.FEE, BigDecimal.valueOf(fee));
|
||||
allocationMap.put(AllocationType.PENALTY, BigDecimal.valueOf(penalty));
|
||||
private Map<AllocationType, Money> allocationMap(double principal, double interest, double fee, double penalty,
|
||||
MonetaryCurrency currency) {
|
||||
Map<AllocationType, Money> allocationMap = new HashMap<>();
|
||||
allocationMap.put(AllocationType.PRINCIPAL, Money.of(currency, BigDecimal.valueOf(principal)));
|
||||
allocationMap.put(AllocationType.INTEREST, Money.of(currency, BigDecimal.valueOf(interest)));
|
||||
allocationMap.put(AllocationType.FEE, Money.of(currency, BigDecimal.valueOf(fee)));
|
||||
allocationMap.put(AllocationType.PENALTY, Money.of(currency, BigDecimal.valueOf(penalty)));
|
||||
return allocationMap;
|
||||
}
|
||||
|
||||
@@ -472,17 +513,17 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionWhenIdProvided() {
|
||||
// given
|
||||
LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
|
||||
Mockito.when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
Loan loan = mock(Loan.class);
|
||||
Mockito.when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
Mockito.when(relation.getToTransaction()).thenReturn(chargebackTransaction);
|
||||
Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
when(relation.getToTransaction()).thenReturn(chargebackTransaction);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
|
||||
// when
|
||||
@@ -496,14 +537,14 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
public void testFindOriginalTransactionThrowsRuntimeExceptionWhenIdProvidedAndRelationsAreMissing() {
|
||||
// given
|
||||
LoanTransaction chargebackTransaction = mock(LoanTransaction.class);
|
||||
Mockito.when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
when(chargebackTransaction.getId()).thenReturn(123L);
|
||||
Loan loan = mock(Loan.class);
|
||||
Mockito.when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
when(chargebackTransaction.getLoan()).thenReturn(loan);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(chargebackTransaction, repayment1, repayment2));
|
||||
|
||||
Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of());
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of());
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
|
||||
@@ -517,22 +558,22 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvided() {
|
||||
// given
|
||||
LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
|
||||
Mockito.when(chargebackReplayed.getId()).thenReturn(null);
|
||||
when(chargebackReplayed.getId()).thenReturn(null);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
|
||||
LoanTransaction originalChargeback = mock(LoanTransaction.class);
|
||||
Mockito.when(originalChargeback.getId()).thenReturn(123L);
|
||||
when(originalChargeback.getId()).thenReturn(123L);
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
Mockito.when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||
Mockito.when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
Mockito.when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
Mockito.when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
|
||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
|
||||
|
||||
// when
|
||||
LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
|
||||
@@ -545,25 +586,25 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
public void testFindOriginalTransactionShouldFindOriginalInLoansTransactionFromTransactionCtxWhenIdIsNotProvidedFallbackToPersistedTransactions() {
|
||||
// given
|
||||
LoanTransaction chargebackReplayed = mock(LoanTransaction.class);
|
||||
Mockito.when(chargebackReplayed.getId()).thenReturn(null);
|
||||
when(chargebackReplayed.getId()).thenReturn(null);
|
||||
LoanTransaction repayment1 = mock(LoanTransaction.class);
|
||||
LoanTransaction repayment2 = mock(LoanTransaction.class);
|
||||
Loan loan = mock(Loan.class);
|
||||
Mockito.when(chargebackReplayed.getLoan()).thenReturn(loan);
|
||||
Mockito.when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
|
||||
when(chargebackReplayed.getLoan()).thenReturn(loan);
|
||||
when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
|
||||
|
||||
LoanTransaction originalChargeback = mock(LoanTransaction.class);
|
||||
Mockito.when(originalChargeback.getId()).thenReturn(123L);
|
||||
when(originalChargeback.getId()).thenReturn(123L);
|
||||
LoanTransactionRelation relation = mock(LoanTransactionRelation.class);
|
||||
Mockito.when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
Mockito.when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
Mockito.when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
when(relation.getToTransaction()).thenReturn(originalChargeback);
|
||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||
|
||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||
Mockito.when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
Mockito.when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
Mockito.when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
|
||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
|
||||
|
||||
// when
|
||||
LoanTransaction originalTransaction = underTest.findOriginalTransaction(chargebackReplayed, ctx);
|
||||
|
||||
+64
-4
@@ -476,6 +476,46 @@ public abstract class BaseLoanIntegrationTest {
|
||||
totalOutstanding));
|
||||
}
|
||||
|
||||
Double outstandingPrincipalExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.principalOutstanding
|
||||
: null;
|
||||
Double outstandingPrincipal = period.getPrincipalOutstanding();
|
||||
if (outstandingPrincipalExpected != null) {
|
||||
Assertions.assertEquals(outstandingPrincipalExpected, outstandingPrincipal,
|
||||
"%d. installment's outstanding principal is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
outstandingPrincipalExpected, outstandingPrincipal));
|
||||
}
|
||||
|
||||
Double outstandingFeeExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.feeOutstanding
|
||||
: null;
|
||||
Double outstandingFee = period.getFeeChargesOutstanding();
|
||||
if (outstandingFeeExpected != null) {
|
||||
Assertions.assertEquals(outstandingFeeExpected, outstandingFee,
|
||||
"%d. installment's outstanding fee is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
outstandingFeeExpected, outstandingFee));
|
||||
}
|
||||
|
||||
Double outstandingPenaltyExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.penaltyOutstanding
|
||||
: null;
|
||||
Double outstandingPenalty = period.getPenaltyChargesOutstanding();
|
||||
if (outstandingPenaltyExpected != null) {
|
||||
Assertions.assertEquals(outstandingPenaltyExpected, outstandingPenalty,
|
||||
"%d. installment's outstanding penalty is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
outstandingPenaltyExpected, outstandingPenalty));
|
||||
}
|
||||
|
||||
Double outstandingTotalExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.totalOutstanding
|
||||
: null;
|
||||
Double outstandingTotal = period.getTotalOutstandingForPeriod();
|
||||
if (outstandingTotalExpected != null) {
|
||||
Assertions.assertEquals(outstandingTotalExpected, outstandingTotal,
|
||||
"%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
outstandingTotalExpected, outstandingTotal));
|
||||
}
|
||||
|
||||
}
|
||||
Assertions.assertEquals(installments[i].completed, period.getComplete());
|
||||
Assertions.assertEquals(LocalDate.parse(installments[i].dueDate, dateTimeFormatter), period.getDueDate());
|
||||
@@ -603,22 +643,31 @@ public abstract class BaseLoanIntegrationTest {
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, null, null, null, null, completed, dueDate);
|
||||
return new Installment(principalAmount, null, null, null, null, completed, dueDate, null);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, Boolean completed,
|
||||
String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate);
|
||||
return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate, null);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
|
||||
Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate);
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, null);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
|
||||
double totalOutstandingAmount, Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate);
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate, null);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
|
||||
OutstandingAmounts outstandingAmounts, Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, null, completed, dueDate, outstandingAmounts);
|
||||
}
|
||||
|
||||
protected OutstandingAmounts outstanding(double principal, double fee, double penalty, double total) {
|
||||
return new OutstandingAmounts(principal, fee, penalty, total);
|
||||
}
|
||||
|
||||
protected BatchRequestBuilder batchRequest() {
|
||||
@@ -735,6 +784,17 @@ public abstract class BaseLoanIntegrationTest {
|
||||
Double totalOutstandingAmount;
|
||||
Boolean completed;
|
||||
String dueDate;
|
||||
OutstandingAmounts outstandingAmounts;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public static class OutstandingAmounts {
|
||||
|
||||
Double principalOutstanding;
|
||||
Double feeOutstanding;
|
||||
Double penaltyOutstanding;
|
||||
Double totalOutstanding;
|
||||
}
|
||||
|
||||
public static class AmortizationType {
|
||||
|
||||
+679
-38
@@ -26,6 +26,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||
import org.apache.fineract.client.models.CreditAllocationData;
|
||||
import org.apache.fineract.client.models.CreditAllocationOrder;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
@@ -38,6 +40,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -46,13 +49,15 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoanIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipal() {
|
||||
public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
@@ -92,15 +97,13 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
@@ -109,13 +112,145 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment() {
|
||||
public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnTheLastDayOfTheInstallment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("01 February 2023");
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "01 February 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0, true, "01 February 2023"), //
|
||||
installment(343.0, 0, 50, 20, 413.0, false, "01 March 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnTheLastDayOfTheLoan() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("01 May 2023");
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "01 May 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(341.0, 0, 50, 20, 411.0, false, "01 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
@@ -169,7 +304,7 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
transaction(313.0, "Repayment", "20 March 2023", 311.0, 313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(381.0, "Repayment", "20 April 2023", 0.0, 311.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(70.0, "Accrual", "20 April 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "02 May 2023", 100.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
transaction(100.0, "Chargeback", "02 May 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
@@ -178,20 +313,21 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 50, 20, 0.0, true, "01 May 2023"), //
|
||||
installment(30.0, 0, 0, 0, 30.0, false, "02 May 2023") // TODO: fee and penalty must be added here
|
||||
// after chargeback
|
||||
installment(30.0, 0, 50, 20, 100.0, false, "02 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackReverseReplayWithBackdatedPayment() {
|
||||
public void chargebackWithCreditAllocationAndReverseReplayWithBackdatedPayment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
@@ -232,15 +368,13 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
@@ -260,13 +394,15 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndOnlyTheChargebackReverseReplayedWithBackdatedPayment() {
|
||||
public void chargebackWithCreditAllocationReverseReplayedWithBackdatedPayment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
@@ -307,15 +443,13 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "22 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
transaction(100.0, "Chargeback", "22 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
@@ -330,18 +464,24 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(200.0, "Repayment", "21 January 2023", 737.0, 200.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "22 January 2023", 837.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
transaction(100.0, "Chargeback", "22 January 2023", 767.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 837.0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPrincipalInterestFeePenalty() {
|
||||
public void chargebackWithCreditAllocationPrincipalInterestFeePenalty() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY"));
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY")//
|
||||
);
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
@@ -387,16 +527,501 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(413.0, 0, 50, 20, 100.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(413.0, 0, 50, 20, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
verifyLoanSummaryAmounts(loanId, 100.0, 0.0, 0.0, 1037);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargebackWithCreditAllocationPrincipalInterestFeePenaltyWhenOverpaid() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
|
||||
// overpayment
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("02 May 2023");
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "02 May 2023", 50.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
// DEFAULT payment allocation is ..., DUE_PENALTY, DUE_FEE, DUE_PRINCIPAL, ...
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0, true, "01 May 2023"), //
|
||||
installment(100.0, 0, 0, 0, outstanding(50.0, 0d, 0d, 50.0), false, "02 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpaid() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("FEE", "PENALTY", "PRINCIPAL", "INTEREST")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
|
||||
// overpayment
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule,
|
||||
// DEFAULT payment allocation is ..., DUE_PENALTY, DUE_FEE, DUE_PRINCIPAL, ...
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 100, 40, outstanding(30.0, 20.0, 0.0, 50.0), false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 50.0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpaidDefaultPaymentPrincipalFirst() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocationPrincipalFirst(), //
|
||||
chargebackAllocation("FEE", "PENALTY", "PRINCIPAL", "INTEREST")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 1370.0, "20 January 2023"); // 1250 + 70 = 1320; 50
|
||||
// overpayment
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 0.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule,
|
||||
// DEFAULT payment allocation is ..., DUE_PRINCIPAL, DUE_FEE, DUE_PENALTY ...
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 100, 40, outstanding(0.0, 30.0, 20.0, 50.0), false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
verifyLoanSummaryAmounts(loanId, 30.0, 50.0, 20.0, 50.0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleChargebackWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("21 January 2023");
|
||||
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleChargebackReverseReplayedBothFeeAndPenaltyPayedWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("21 January 2023");
|
||||
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Let's add repayment to trigger reverse replay for both chargebacks
|
||||
addRepaymentForLoan(loanId, 200.0, "19 January 2023");
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(200.0, "Repayment", "19 January 2023", 1120.0, 130.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 737.0, 383.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 837.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 937.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleChargebackReverseReplayedOnlyPenaltyPayedWithCreditAllocationPenaltyFeeInterestAndPrincipal() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(//
|
||||
createDefaultPaymentAllocation(), //
|
||||
chargebackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL")//
|
||||
);
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "15 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 100, 40, 100.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("21 January 2023");
|
||||
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 to principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(443.0, 0, 100, 40, 200.0, false, "01 February 2023"),
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Let's add repayment to trigger reverse replay for both chargebacks
|
||||
addRepaymentForLoan(loanId, 20.0, "19 January 2023");
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(20.0, "Repayment", "19 January 2023", 1250.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 917.0, 333.0, 0.0, 50.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 967.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1067.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyLoanSummaryAmounts(Long loanId, double creditedPrincipal, double creditedFee, double creditedPenalty,
|
||||
double totalOutstanding) {
|
||||
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
GetLoansLoanIdSummary summary = loanResponse.getSummary();
|
||||
Assertions.assertNotNull(summary);
|
||||
Assertions.assertEquals(creditedPrincipal, summary.getPrincipalAdjustments());
|
||||
Assertions.assertEquals(creditedFee, summary.getFeeAdjustments());
|
||||
Assertions.assertEquals(creditedPenalty, summary.getPenaltyAdjustments());
|
||||
Assertions.assertEquals(totalOutstanding, summary.getTotalOutstanding());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long applyAndApproveLoan(Long clientId, Long loanProductId) {
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", 1250.0, 4)//
|
||||
@@ -415,22 +1040,23 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
return loanId;
|
||||
}
|
||||
|
||||
public Long createLoanProduct(CreditAllocationData... creditAllocationData) {
|
||||
PostLoanProductsRequest postLoanProductsRequest = loanProductWithAdvancedPaymentAllocationWith4Installments(creditAllocationData);
|
||||
public Long createLoanProduct(AdvancedPaymentData defaultAllocation, CreditAllocationData creditAllocationData) {
|
||||
PostLoanProductsRequest postLoanProductsRequest = loanProductWithAdvancedPaymentAllocationWith4Installments(defaultAllocation,
|
||||
creditAllocationData);
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(postLoanProductsRequest);
|
||||
return loanProductResponse.getResourceId();
|
||||
}
|
||||
|
||||
private PostLoanProductsRequest loanProductWithAdvancedPaymentAllocationWith4Installments(
|
||||
CreditAllocationData... creditAllocationData) {
|
||||
private PostLoanProductsRequest loanProductWithAdvancedPaymentAllocationWith4Installments(AdvancedPaymentData defaultAllocation,
|
||||
CreditAllocationData creditAllocationData) {
|
||||
return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().numberOfRepayments(4)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
|
||||
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
|
||||
.loanScheduleProcessingType(LoanScheduleProcessingType.VERTICAL.toString()) //
|
||||
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")
|
||||
.paymentAllocation(List.of(createDefaultPaymentAllocation(), createRepaymentPaymentAllocation()))
|
||||
.creditAllocation(Arrays.asList(creditAllocationData));
|
||||
.paymentAllocation(List.of(defaultAllocation, createRepaymentPaymentAllocation()))
|
||||
.creditAllocation(List.of(creditAllocationData));
|
||||
}
|
||||
|
||||
private AdvancedPaymentData createDefaultPaymentAllocation() {
|
||||
@@ -448,6 +1074,21 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
return advancedPaymentData;
|
||||
}
|
||||
|
||||
private AdvancedPaymentData createDefaultPaymentAllocationPrincipalFirst() {
|
||||
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
|
||||
advancedPaymentData.setTransactionType("DEFAULT");
|
||||
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
|
||||
|
||||
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
|
||||
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
|
||||
PaymentAllocationType.DUE_PRINCIPAL, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PENALTY,
|
||||
PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_FEE,
|
||||
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_INTEREST);
|
||||
|
||||
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
|
||||
return advancedPaymentData;
|
||||
}
|
||||
|
||||
private AdvancedPaymentData createRepaymentPaymentAllocation() {
|
||||
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
|
||||
advancedPaymentData.setTransactionType("REPAYMENT");
|
||||
|
||||
Reference in New Issue
Block a user