FINERACT-2042 Handling overpayment of chargeback with credit allocations
This commit is contained in:
committed by
Peter Bagrij
parent
5be1001fd5
commit
4bc6755514
+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);
|
||||
|
||||
Reference in New Issue
Block a user