FINERACT-2042 Handling overpayment of chargeback with credit allocations

This commit is contained in:
Peter Bagrij
2024-02-28 10:09:11 +01:00
committed by Peter Bagrij
parent 5be1001fd5
commit 4bc6755514
17 changed files with 1337 additions and 415 deletions
@@ -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")
@@ -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;
}
}
@@ -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,
@@ -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);
@@ -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;
@@ -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);
}
}
@@ -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);