FINERACT-2281: Interest Activities Not Posting For Loan With Repayment Reversal

This commit is contained in:
Soma Sörös
2025-05-23 09:42:09 +02:00
committed by Adam Saghy
parent ecac6d2153
commit 98055682d3
11 changed files with 549 additions and 113 deletions
@@ -79,6 +79,7 @@ import org.apache.fineract.client.services.GroupsApi;
import org.apache.fineract.client.services.HolidaysApi; import org.apache.fineract.client.services.HolidaysApi;
import org.apache.fineract.client.services.HooksApi; import org.apache.fineract.client.services.HooksApi;
import org.apache.fineract.client.services.ImagesApi; import org.apache.fineract.client.services.ImagesApi;
import org.apache.fineract.client.services.InlineJobApi;
import org.apache.fineract.client.services.InterestRateChartApi; import org.apache.fineract.client.services.InterestRateChartApi;
import org.apache.fineract.client.services.InterestRateSlabAKAInterestBandsApi; import org.apache.fineract.client.services.InterestRateSlabAKAInterestBandsApi;
import org.apache.fineract.client.services.JournalEntriesApi; import org.apache.fineract.client.services.JournalEntriesApi;
@@ -300,6 +301,7 @@ public final class FineractClient {
public final ExternalAssetOwnersApi externalAssetOwners; public final ExternalAssetOwnersApi externalAssetOwners;
public final ExternalAssetOwnerLoanProductAttributesApi externalAssetOwnerLoanProductAttributes; public final ExternalAssetOwnerLoanProductAttributesApi externalAssetOwnerLoanProductAttributes;
public final LoanAccountLockApi loanAccountLockApi; public final LoanAccountLockApi loanAccountLockApi;
public final InlineJobApi inlineJobApi;
private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) {
this.okHttpClient = okHttpClient; this.okHttpClient = okHttpClient;
@@ -424,6 +426,7 @@ public final class FineractClient {
workingDays = retrofit.create(WorkingDaysApi.class); workingDays = retrofit.create(WorkingDaysApi.class);
loanInterestPauseApi = retrofit.create(LoanInterestPauseApi.class); loanInterestPauseApi = retrofit.create(LoanInterestPauseApi.class);
progressiveLoanApi = retrofit.create(ProgressiveLoanApi.class); progressiveLoanApi = retrofit.create(ProgressiveLoanApi.class);
inlineJobApi = retrofit.create(InlineJobApi.class);
} }
public static Builder builder() { public static Builder builder() {
@@ -6037,10 +6037,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
Then Loan Repayment schedule has 1 periods, with the following data for periods: Then Loan Repayment schedule has 1 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2021 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2021 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2021 | | 0.0 | 1085.63 | 5.7 | 0.0 | 0.0 | 1091.33 | 1005.7 | 1005.7 | 0.0 | 85.63 | | 1 | 31 | 01 February 2021 | | 0.0 | 1085.63 | 5.9 | 0.0 | 0.0 | 1091.53 | 1005.7 | 1005.7 | 0.0 | 85.83 |
Then Loan Repayment schedule has the following data in Total row: Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 1085.63 | 5.7 | 0.0 | 0.0 | 1091.33 | 1005.7 | 1005.7 | 0.0 | 85.63 | | 1085.63 | 5.9 | 0.0 | 0.0 | 1091.53 | 1005.7 | 1005.7 | 0.0 | 85.83 |
Then Loan Transactions tab has the following data: Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2021 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | | 01 January 2021 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
@@ -438,8 +438,108 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
processCreditTransaction(loanTransaction, ctx); processCreditTransaction(loanTransaction, ctx);
} }
protected void handleCreditBalanceRefund(LoanTransaction transaction, TransactionCtx ctx) { protected void handleCreditBalanceRefund(LoanTransaction loanTransaction, TransactionCtx ctx) {
super.handleCreditBalanceRefund(transaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder()); MonetaryCurrency currency = ctx.getCurrency();
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
MoneyHolder overpaymentHolder = ctx.getOverpaymentHolder();
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
var model = progressiveTransactionCtx.getModel();
// Copy and paste Logic from super.handleCreditBalanceRefund
loanTransaction.resetDerivedComponents();
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
List<LoanRepaymentScheduleInstallment> installmentToBeProcessed = installments.stream().filter(i -> !i.isDownPayment())
.sorted(byDate).toList();
final Money zeroMoney = Money.zero(currency);
Money transactionAmount = loanTransaction.getAmount(currency);
Money principalPortion = MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
loanTransaction.setOverPayments(repaidAmount);
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
loanTransaction.updateComponents(principalPortion, zeroMoney, zeroMoney, zeroMoney);
if (principalPortion.isGreaterThanZero()) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
boolean loanTransactionMapped = false;
LocalDate pastDueDate = null;
for (final LoanRepaymentScheduleInstallment currentInstallment : installmentToBeProcessed) {
pastDueDate = currentInstallment.getDueDate();
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
if (repaidAmount.isGreaterThanZero()) {
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
}
loanTransactionMapped = true;
break;
// If already exists an additional installment just update the due date and
// principal from the Loan chargeback / CBR transaction
} else if (currentInstallment.isAdditional()) {
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
currentInstallment.updateDueDate(transactionDate);
}
currentInstallment.updateCredits(transactionDate, transactionAmount);
if (repaidAmount.isGreaterThanZero()) {
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
}
loanTransactionMapped = true;
break;
}
}
// New installment will be added (N+1 scenario)
if (!loanTransactionMapped) {
if (transactionDate.equals(pastDueDate)) {
// Transaction is on Maturity date, no additional installment is needed
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed
.get(installmentToBeProcessed.size() - 1);
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
if (repaidAmount.isGreaterThanZero()) {
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
}
} else {
// transaction is after maturity date, create an additional installment
Loan loan = loanTransaction.getLoan();
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
pastDueDate, transactionDate, transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
zeroMoney.getAmount(), false, null);
installment.markAsAdditional();
installment.addToCreditedPrincipal(transactionAmount.getAmount());
loan.addLoanRepaymentScheduleInstallment(installment);
if (repaidAmount.isGreaterThanZero()) {
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
}
}
}
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
}
} else {
super.handleCreditBalanceRefund(loanTransaction, currency, installments, overpaymentHolder);
}
} }
private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) { private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) {
@@ -556,11 +656,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
// handle charge back before or on last installments due date // handle charge back before or on last installments due date
if (chargebackAllocation.get(PRINCIPAL).isGreaterThanZero()) { if (chargebackAllocation.get(PRINCIPAL).isGreaterThanZero()) {
emiCalculator.chargebackPrincipal(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(PRINCIPAL)); emiCalculator.creditPrincipal(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(PRINCIPAL));
} }
if (chargebackAllocation.get(INTEREST).isGreaterThanZero()) { if (chargebackAllocation.get(INTEREST).isGreaterThanZero()) {
emiCalculator.chargebackInterest(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(INTEREST)); emiCalculator.creditInterest(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(INTEREST));
} }
// update repayment periods until maturity date, for principal and interest portions // update repayment periods until maturity date, for principal and interest portions
@@ -2171,8 +2271,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
if (installment != null) { if (installment != null) {
installment.updatePrincipal(rm.getDuePrincipal().getAmount()); installment.updatePrincipal(rm.getDuePrincipal().getAmount());
installment.updateInterestCharged(rm.getDueInterest().getAmount()); installment.updateInterestCharged(rm.getDueInterest().getAmount());
installment.setCreditedInterest(rm.getChargebackInterest().getAmount()); installment.setCreditedInterest(rm.getCreditedInterest().getAmount());
installment.setCreditedPrincipal(rm.getChargebackPrincipal().getAmount()); installment.setCreditedPrincipal(rm.getCreditedPrincipal().getAmount());
installment.updateObligationsMet(ctx.getCurrency(), loanTransaction.getTransactionDate()); installment.updateObligationsMet(ctx.getCurrency(), loanTransaction.getTransactionDate());
} }
}); });
@@ -93,17 +93,16 @@ public interface EMICalculator {
Money principalAmount); Money principalAmount);
/** /**
* This method used for charge back principal portion. This method increases the outstanding balance. This method * This method used for credit principal portion. This method increases the outstanding balance. This method creates
* creates a calculated "virtual" EMI for the applied period. * a calculated "virtual" EMI for the applied period.
*/ */
void chargebackPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, void creditPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedPrincipalAmount);
Money chargebackPrincipalAmount);
/** /**
* This method used for charge back interest portion. This method adds extra interest due. This method creates a * This method used for credit interest portion. This method adds extra interest due. This method creates a
* calculated "virtual" EMI for the applied period. * calculated "virtual" EMI for the applied period.
*/ */
void chargebackInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money chargebackInterestAmount); void creditInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedInterestAmount);
/** /**
* This method gives back the maximum of the due principal and maximum of the due interest for a requested day. * This method gives back the maximum of the due principal and maximum of the due interest for a requested day.
@@ -193,31 +193,30 @@ public final class ProgressiveEMICalculator implements EMICalculator {
repaymentPeriod.ifPresent(rp -> { repaymentPeriod.ifPresent(rp -> {
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as // If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
// fully paid // fully paid
if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusChargeback())) { if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmounts())) {
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount())); rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
} else if (transactionDateIsBefore } else if (transactionDateIsBefore
&& rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalChargebackAmount()))) { && rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalCreditedAmount()))) {
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount())); rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
} }
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
}); });
} }
} }
private void addChargebackAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, private void addCreditedAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
Money chargebackPrincipalAmount, Money chargeBackInterestAmount) { Money creditedPrincipalAmount, Money creditedInterestAmount) {
scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInChargebackRange(scheduleModel, transactionDate)) scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInCreditRange(scheduleModel, transactionDate)).findFirst()
.findFirst()
.flatMap(repaymentPeriod -> repaymentPeriod.getInterestPeriods().stream() .flatMap(repaymentPeriod -> repaymentPeriod.getInterestPeriods().stream()
.filter(interestPeriod -> interestPeriod.getFromDate().equals(transactionDate)).reduce((v1, v2) -> v2)) .filter(interestPeriod -> interestPeriod.getFromDate().equals(transactionDate)).reduce((v1, v2) -> v2))
.ifPresent(interestPeriod -> { .ifPresent(interestPeriod -> {
interestPeriod.addChargebackPrincipalAmount(chargebackPrincipalAmount); interestPeriod.addCreditedPrincipalAmount(creditedPrincipalAmount);
interestPeriod.addChargebackInterestAmount(chargeBackInterestAmount); interestPeriod.addCreditedInterestAmount(creditedInterestAmount);
}); });
} }
@Nonnull @Nonnull
private static Predicate<RepaymentPeriod> checkRepaymentPeriodIsInChargebackRange(ProgressiveLoanInterestScheduleModel scheduleModel, private static Predicate<RepaymentPeriod> checkRepaymentPeriodIsInCreditRange(ProgressiveLoanInterestScheduleModel scheduleModel,
LocalDate transactionDate) { LocalDate transactionDate) {
return repaymentPeriod -> scheduleModel.isLastRepaymentPeriod(repaymentPeriod) return repaymentPeriod -> scheduleModel.isLastRepaymentPeriod(repaymentPeriod)
? !transactionDate.isBefore(repaymentPeriod.getFromDate()) && !transactionDate.isAfter(repaymentPeriod.getDueDate()) ? !transactionDate.isBefore(repaymentPeriod.getFromDate()) && !transactionDate.isAfter(repaymentPeriod.getDueDate())
@@ -225,23 +224,22 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
@Override @Override
public void chargebackPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, public void creditPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
Money chargebackPrincipalAmount) { Money creditedPrincipalAmount) {
addChargeback(scheduleModel, transactionDate, chargebackPrincipalAmount, scheduleModel.zero()); addCredit(scheduleModel, transactionDate, creditedPrincipalAmount, scheduleModel.zero());
} }
@Override @Override
public void chargebackInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, public void creditInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
Money chargebackInterestAmount) { Money creditedInterestAmount) {
addChargeback(scheduleModel, transactionDate, scheduleModel.zero(), chargebackInterestAmount); addCredit(scheduleModel, transactionDate, scheduleModel.zero(), creditedInterestAmount);
} }
private void addChargeback(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, private void addCredit(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedPrincipalAmount,
Money chargebackPrincipalAmount, Money chargebackInterestAmount) { Money creditedInterestAmount) {
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), chargebackPrincipalAmount) scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), creditedPrincipalAmount)
.ifPresent(repaymentPeriod -> { .ifPresent(repaymentPeriod -> {
addChargebackAmountsToInterestPeriod(scheduleModel, transactionDate, chargebackPrincipalAmount, addCreditedAmountsToInterestPeriod(scheduleModel, transactionDate, creditedPrincipalAmount, creditedInterestAmount);
chargebackInterestAmount);
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel); calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
calculateOutstandingBalance(scheduleModel); calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
@@ -288,12 +286,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
@NotNull @NotNull
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest) { @NotNull LocalDate targetDate, boolean includeCreditedInterest) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel, ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
targetDate); targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow(); RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest() return includeCreditedInterest ? repaymentPeriod.getCalculatedDueInterest()
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(), : repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getCreditedInterest(),
recalculatedScheduleModelTillDate.mc()); recalculatedScheduleModelTillDate.mc());
} }
@@ -379,18 +377,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
.size() > nextIdx; .size() > nextIdx;
if (thereIsInterestPeriodFromDateOnTargetDate) { if (thereIsInterestPeriodFromDateOnTargetDate) {
// NOTE: If there is a next interest period with fromDate on the target date // NOTE: If there is a next interest period with fromDate on the target date
// then the related chargeback amounts comes from the next interest period too. // then the related credited amount comes from the next interest period too.
InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx); InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal()); ip.addCreditedPrincipalAmount(nextInterestPeriod.getCreditedPrincipal());
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest()); ip.addCreditedInterestAmount(nextInterestPeriod.getCreditedInterest());
} }
ip.getRepaymentPeriod().getInterestPeriods() ip.getRepaymentPeriod().getInterestPeriods()
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear(); .subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
}); });
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod) } else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
&& (rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero() && (rp.getInterestPeriods().get(0).getCreditedInterest().isGreaterThanZero()
|| rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) { || rp.getInterestPeriods().get(0).getCreditedPrincipal().isGreaterThanZero())) {
// NOTE: we need to check whether there is chargeback on the 1st interest period of the next // NOTE: we need to check whether there is credited on the 1st interest period of the next
// period // period
// if so, we need to retain that interest period, but need to update due date to match with from // if so, we need to retain that interest period, but need to update due date to match with from
// date -> 0 interest // date -> 0 interest
@@ -438,12 +436,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
MathContext mc = scheduleModel.mc(); MathContext mc = scheduleModel.mc();
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(), Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(),
(m1, m2) -> m1.plus(m2, mc)); // 1.46 (m1, m2) -> m1.plus(m2, mc)); // 1.46
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusChargeback).reduce(scheduleModel.zero(), Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusCreditedAmounts)
(m1, m2) -> m1.plus(m2, mc)); // 101.48 .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 101.48
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream() Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)) .flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)) // 100 .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)) // 100
.plus(scheduleModel.getTotalChargebackPrincipal(), mc); // .plus(scheduleModel.getTotalCreditedPrincipal(), mc); //
Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc); Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
@@ -451,8 +449,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> { findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)); repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc));
if (repaymentPeriod.getEmi() if (repaymentPeriod.getEmi()
.isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalChargebackAmount(), mc))) { .isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc))) {
repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalChargebackAmount(), mc)); repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc));
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
} }
}); });
@@ -53,8 +53,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
@Setter @Setter
private BigDecimal rateFactorTillPeriodDueDate; private BigDecimal rateFactorTillPeriodDueDate;
private Money chargebackPrincipal; /** Stores credited principals. Related transactions: Chargeback or Credit Balance Refound */
private Money chargebackInterest; private Money creditedPrincipal;
/** Stores credited principals. Related transaction: Chargeback */
private Money creditedInterest;
private Money disbursementAmount; private Money disbursementAmount;
private Money balanceCorrectionAmount; private Money balanceCorrectionAmount;
@@ -66,8 +68,8 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) { public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) {
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getChargebackPrincipal(), interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
interestPeriod.getChargebackInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
interestPeriod.getOutstandingLoanBalance(), mc, interestPeriod.isPaused()); interestPeriod.getOutstandingLoanBalance(), mc, interestPeriod.isPaused());
} }
@@ -77,8 +79,8 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod) { public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod) {
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getChargebackPrincipal(), interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
interestPeriod.getChargebackInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), interestPeriod.isPaused()); interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), interestPeriod.isPaused());
} }
@@ -116,17 +118,17 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc); this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc);
} }
public void addChargebackPrincipalAmount(final Money chargebackPrincipal) { public void addCreditedPrincipalAmount(final Money creditedPrincipal) {
this.chargebackPrincipal = MathUtil.plus(this.chargebackPrincipal, chargebackPrincipal, mc); this.creditedPrincipal = MathUtil.plus(this.creditedPrincipal, creditedPrincipal, mc);
} }
public void addChargebackInterestAmount(final Money chargebackInterest) { public void addCreditedInterestAmount(final Money creditedInterest) {
this.chargebackInterest = MathUtil.plus(this.chargebackInterest, chargebackInterest, mc); this.creditedInterest = MathUtil.plus(this.creditedInterest, creditedInterest, mc);
} }
public BigDecimal getCalculatedDueInterest() { public BigDecimal getCalculatedDueInterest() {
if (isPaused) { if (isPaused) {
return chargebackInterest.getAmount(); return creditedInterest.getAmount();
} }
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate(); long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
@@ -136,7 +138,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
.multiply(getRateFactorTillPeriodDueDate(), mc) // .multiply(getRateFactorTillPeriodDueDate(), mc) //
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) // .divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
.multiply(BigDecimal.valueOf(getLength()), mc); // .multiply(BigDecimal.valueOf(getLength()), mc); //
return MathUtil.negativeToZero(MathUtil.add(chargebackInterest.getAmount(), interestDueTillRepaymentDueDate, mc)); return MathUtil.negativeToZero(MathUtil.add(creditedInterest.getAmount(), interestDueTillRepaymentDueDate, mc));
} }
public long getLength() { public long getLength() {
@@ -168,10 +170,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
} }
/** /**
* Include principal like amounts (all disbursement amount + chargeback principal) * Include principal like amounts (all disbursement amount + credited principal)
*/ */
public Money getCreditedAmounts() { public Money getCreditedAmounts() {
return getDisbursementAmount().plus(getChargebackPrincipal(), mc); return getDisbursementAmount().plus(getCreditedPrincipal(), mc);
} }
public boolean isFirstInterestPeriod() { public boolean isFirstInterestPeriod() {
@@ -304,8 +304,7 @@ public class ProgressiveLoanInterestScheduleModel {
} }
/** /**
* Gives back the total due interest amount in the whole repayment schedule. Also includes chargeback interest * Gives back the total due interest amount in the whole repayment schedule. Also includes credited interest amount.
* amount.
* *
* @return * @return
*/ */
@@ -315,7 +314,7 @@ public class ProgressiveLoanInterestScheduleModel {
/** /**
* Gives back the total due principal amount in the whole repayment schedule based on disbursements. Do not contain * Gives back the total due principal amount in the whole repayment schedule based on disbursements. Do not contain
* chargeback principal amount. * credited principal amount.
* *
* @return * @return
*/ */
@@ -342,12 +341,12 @@ public class ProgressiveLoanInterestScheduleModel {
} }
/** /**
* Gives back the total chargeback principal amount in the whole repayment schedule. * Gives back the total credited principal amount in the whole repayment schedule.
* *
* @return * @return
*/ */
public Money getTotalChargebackPrincipal() { public Money getTotalCreditedPrincipal() {
return MathUtil.negativeToZero(repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus), return MathUtil.negativeToZero(repaymentPeriods().stream().map(RepaymentPeriod::getCreditedPrincipal).reduce(zero, Money::plus),
mc); mc);
} }
@@ -146,7 +146,7 @@ public final class RepaymentPeriod {
} }
/** /**
* Gives back calculated due interest + chargeback interest * Gives back calculated due interest + credited interest
* *
* @return * @return
*/ */
@@ -169,28 +169,29 @@ public final class RepaymentPeriod {
} }
/** /**
* Gives back due interest + chargeback interest OR paid interest * Gives back due interest + credited interest OR paid interest
* *
* @return * @return
*/ */
public Money getDueInterest() { public Money getDueInterest() {
if (dueInterestCalculation == null) { if (dueInterestCalculation == null) {
// Due interest might be the maximum paid if there is pay-off or early repayment // Due interest might be the maximum paid if there is pay-off or early repayment
dueInterestCalculation = Memo.of( dueInterestCalculation = Memo
() -> MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest() .of(() -> MathUtil.max(
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusChargeback(), false), getPaidInterest(), false), getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods }); : MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmounts(), false),
getPaidInterest(), false), () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
} }
return dueInterestCalculation.get(); return dueInterestCalculation.get();
} }
/** /**
* Gives back an EMI amount which includes chargeback amounts as well * Gives back an EMI amount which includes credited amounts as well
* *
* @return * @return
*/ */
public Money getEmiPlusChargeback() { public Money getEmiPlusCreditedAmounts() {
return getEmi().plus(getTotalChargebackAmount(), mc); // return getEmi().plus(getTotalCreditedAmount(), mc); //
} }
/** /**
@@ -199,48 +200,49 @@ public final class RepaymentPeriod {
* @return * @return
*/ */
public Money getCalculatedDuePrincipal() { public Money getCalculatedDuePrincipal() {
return MathUtil.negativeToZero(getEmiPlusChargeback().minus(getCalculatedDueInterest(), mc), mc); return MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getCalculatedDueInterest(), mc), mc);
} }
/** /**
* Sum of chargeback principals * Sum of credited principals
* *
* @return * @return
*/ */
public Money getChargebackPrincipal() { public Money getCreditedPrincipal() {
return MathUtil.negativeToZero(interestPeriods.stream() // return MathUtil.negativeToZero(interestPeriods.stream() //
.map(InterestPeriod::getChargebackPrincipal) // .map(InterestPeriod::getCreditedPrincipal) //
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); // .reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
} }
/** /**
* Sum of chargeback interests * Sum of credited interests
* *
* @return * @return
*/ */
public Money getChargebackInterest() { public Money getCreditedInterest() {
return MathUtil.negativeToZero(interestPeriods.stream() // return MathUtil.negativeToZero(interestPeriods.stream() //
.map(InterestPeriod::getChargebackInterest) // .map(InterestPeriod::getCreditedInterest) //
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); // .reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
} }
/** /**
* Gives back due principal + chargeback principal or paid principal * Gives back due principal + credited principal or paid principal
* *
* @return * @return
*/ */
public Money getDuePrincipal() { public Money getDuePrincipal() {
// Due principal might be the maximum paid if there is pay-off or early repayment // Due principal might be the maximum paid if there is pay-off or early repayment
return MathUtil.max(MathUtil.negativeToZero(getEmiPlusChargeback().minus(getDueInterest(), mc), mc), getPaidPrincipal(), false); return MathUtil.max(MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getDueInterest(), mc), mc), getPaidPrincipal(),
false);
} }
/** /**
* Gives back sum of all chargeback principal + chargeback interest * Gives back sum of all credited principal + credited interest
* *
* @return * @return
*/ */
public Money getTotalChargebackAmount() { public Money getTotalCreditedAmount() {
return getChargebackPrincipal().plus(getChargebackInterest(), mc); return getCreditedPrincipal().plus(getCreditedInterest(), mc);
} }
/** /**
@@ -253,7 +255,7 @@ public final class RepaymentPeriod {
} }
public boolean isFullyPaid() { public boolean isFullyPaid() {
return getEmiPlusChargeback().isEqualTo(getTotalPaidAmount()); return getEmiPlusCreditedAmounts().isEqualTo(getTotalPaidAmount());
} }
/** /**
@@ -1305,7 +1305,7 @@ class ProgressiveEMICalculatorTest {
final LocalDate startDay = LocalDate.of(2024, 1, 1); final LocalDate startDay = LocalDate.of(2024, 1, 1);
emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56)); emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0)); emiCalculator.creditInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0)); emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19); checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
@@ -2403,7 +2403,7 @@ class ProgressiveEMICalculatorTest {
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49)); emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
// full chargeback on duedate // full chargeback on duedate
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01)); emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2456,7 +2456,7 @@ class ProgressiveEMICalculatorTest {
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49)); emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
// partial chargeback // partial chargeback
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(15.0)); emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(15.0));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2467,7 +2467,7 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 5, 0, 17.09, 0.005833333333, 0.0991083333276, 0.1, 16.99, 0.0); checkPeriod(interestSchedule, 5, 0, 17.09, 0.005833333333, 0.0991083333276, 0.1, 16.99, 0.0);
// full chargeback // full chargeback
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01)); emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2520,7 +2520,7 @@ class ProgressiveEMICalculatorTest {
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49)); emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
// full chargeback on duedate // full chargeback on duedate
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 15), toMoney(17.01)); emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 15), toMoney(17.01));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2579,8 +2579,8 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 3, 1); txnDate = LocalDate.of(2024, 3, 1);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2637,8 +2637,8 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 7, 1); txnDate = LocalDate.of(2024, 7, 1);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
checkPeriod(interestSchedule, 5, 0, 17.00, 0.005833333333, 0.0985833333276, 0.59, 33.42, 0.0); checkPeriod(interestSchedule, 5, 0, 17.00, 0.005833333333, 0.0985833333276, 0.59, 33.42, 0.0);
} }
@@ -2689,8 +2689,8 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 3, 1); txnDate = LocalDate.of(2024, 3, 1);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(14.51)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(14.51));
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2747,7 +2747,7 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 3, 1); txnDate = LocalDate.of(2024, 3, 1);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(15.0)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(15.0));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -2804,10 +2804,10 @@ class ProgressiveEMICalculatorTest {
// chargeback 1st // chargeback 1st
txnDate = LocalDate.of(2024, 3, 1); txnDate = LocalDate.of(2024, 3, 1);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(15.0)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(15.0));
// chargeback 2nd // chargeback 2nd
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
@@ -3098,8 +3098,8 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 3, 15); txnDate = LocalDate.of(2024, 3, 15);
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(interestSchedule, LocalDate.of(2024, 4, 1), txnDate); PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(interestSchedule, LocalDate.of(2024, 4, 1), txnDate);
Assertions.assertEquals(33.09, toDouble(interestSchedule.repaymentPeriods().get(2).getDuePrincipal())); Assertions.assertEquals(33.09, toDouble(interestSchedule.repaymentPeriods().get(2).getDuePrincipal()));
@@ -3172,10 +3172,10 @@ class ProgressiveEMICalculatorTest {
// chargeback // chargeback
txnDate = LocalDate.of(2024, 3, 15); txnDate = LocalDate.of(2024, 3, 15);
emiCalculator.chargebackPrincipal(interestScheduleExpected, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestScheduleExpected, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestScheduleExpected, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestScheduleExpected, txnDate, toMoney(0.49));
emiCalculator.chargebackPrincipal(interestScheduleActual, txnDate, toMoney(16.52)); emiCalculator.creditPrincipal(interestScheduleActual, txnDate, toMoney(16.52));
emiCalculator.chargebackInterest(interestScheduleActual, txnDate, toMoney(0.49)); emiCalculator.creditInterest(interestScheduleActual, txnDate, toMoney(0.49));
verifyAllPeriods(interestScheduleExpected, interestScheduleActual); verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
interestScheduleActual = copyJson(interestScheduleExpected); interestScheduleActual = copyJson(interestScheduleExpected);
@@ -0,0 +1,324 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.integrationtests;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.junit.jupiter.api.Test;
public class ProgressiveLoanCreditBalanceRefundTest extends BaseLoanIntegrationTest {
Long clientId = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
Long loanProductId = loanProductHelper.createLoanProduct(create4IProgressive()).getResourceId();
@Test
public void testAccrualCreationAfterCBRThenReverseRepayment() {
AtomicReference<Long> loanIdRef = new AtomicReference<>();
AtomicReference<Long> reverseRepaymentIdRef = new AtomicReference<>();
runAt("13 February 2021", () -> {
loanIdRef.set(applyAndApproveProgressiveLoan(clientId, loanProductId, "13 February 2021", 300.0, 37.56, 12, null));
disburseLoan(loanIdRef.get(), BigDecimal.valueOf(300.0), "13 February 2021");
verifyRepaymentSchedule(loanIdRef.get(), //
installment(300.0, null, "13 February 2021"), //
installment(20.98, 9.39, 30.37, false, "13 March 2021"), //
installment(21.64, 8.73, 30.37, false, "13 April 2021"), //
installment(22.31, 8.06, 30.37, false, "13 May 2021"), //
installment(23.01, 7.36, 30.37, false, "13 June 2021"), //
installment(23.73, 6.64, 30.37, false, "13 July 2021"), //
installment(24.48, 5.89, 30.37, false, "13 August 2021"), //
installment(25.24, 5.13, 30.37, false, "13 September 2021"), //
installment(26.03, 4.34, 30.37, false, "13 October 2021"), //
installment(26.85, 3.52, 30.37, false, "13 November 2021"), //
installment(27.69, 2.68, 30.37, false, "13 December 2021"), //
installment(28.55, 1.82, 30.37, false, "13 January 2022"), //
installment(29.49, 0.92, 30.41, false, "13 February 2022") //
);
loanTransactionHelper.makeLoanRepayment(loanIdRef.get(), "Repayment", "13 February 2021", 60.0);
Long repaymentId = loanTransactionHelper.makeLoanRepayment(loanIdRef.get(), "Repayment", "13 February 2021", 40.0)
.getResourceId();
reverseRepaymentIdRef.set(repaymentId);
loanTransactionHelper.makeLoanRepayment(loanIdRef.get(), "MerchantIssuedRefund", "13 February 2021", 300.0);
verifyRepaymentSchedule(loanIdRef.get(), //
installment(300.0, null, "13 February 2021"), //
installment(30.37, 0.0, 0.0, true, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanIdRef.get(), //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(60.0, "Repayment", "13 February 2021", 240.0, 60.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(40.0, "Repayment", "13 February 2021", 200.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 100.0, false) //
);
});
runAt("19 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
loanTransactionHelper.makeLoanRepayment(loanId, "CreditBalanceRefund", "19 February 2021", 100.0);
// 0 overpaid
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(30.37, 0.0, 0.0, true, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(60.0, "Repayment", "13 February 2021", 240.0, 60.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(40.0, "Repayment", "13 February 2021", 200.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 100.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, false) //
);
});
runAt("23 February 2021", () -> {
final Long loanId = loanIdRef.get();
final Long reverseRepaymentId = reverseRepaymentIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
loanTransactionHelper.reverseLoanTransaction(loanId, reverseRepaymentId, "23 February 2021");
// 40 outstanding
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(130.37, 0.98, 40.98, false, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(60.0, "Repayment", "13 February 2021", 240.0, 60.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(40.0, "Repayment", "13 February 2021", 200.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 240.0, 0.0, 0.0, 0.0, 0.0, 60.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 40.0, 40.0, 0.0, 0.0, 0.0, 0.0, 60.0, false) //
);
});
runAt("24 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(60.0, "Repayment", "13 February 2021", 240.0, 60.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(40.0, "Repayment", "13 February 2021", 200.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 240.0, 0.0, 0.0, 0.0, 0.0, 60.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 40.0, 40.0, 0.0, 0.0, 0.0, 0.0, 60.0, false), //
transaction(0.18, "Accrual", "23 February 2021", 0.0, 0.0, 0.18, 0.0, 0.0, 0.0, 0.0, false) //
);
});
runAt("28 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(60.0, "Repayment", "13 February 2021", 240.0, 60.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(40.0, "Repayment", "13 February 2021", 200.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 240.0, 0.0, 0.0, 0.0, 0.0, 60.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 40.0, 40.0, 0.0, 0.0, 0.0, 0.0, 60.0, false), //
transaction(0.18, "Accrual", "23 February 2021", 0.0, 0.0, 0.18, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.04, "Accrual", "24 February 2021", 0.0, 0.0, 0.04, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.05, "Accrual", "25 February 2021", 0.0, 0.0, 0.05, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.04, "Accrual", "26 February 2021", 0.0, 0.0, 0.04, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.05, "Accrual", "27 February 2021", 0.0, 0.0, 0.05, 0.0, 0.0, 0.0, 0.0, false) //
);
});
}
@Test
public void testAccrualCreationAfterCBRThenReverseRepaymentThenRepayment() {
AtomicReference<Long> loanIdRef = new AtomicReference<>();
AtomicReference<Long> reverseRepaymentIdRef = new AtomicReference<>();
runAt("13 February 2021", () -> {
Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "13 February 2021", 300.0, 37.56, 12, null);
loanIdRef.set(loanId);
disburseLoan(loanId, BigDecimal.valueOf(300.0), "13 February 2021");
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(20.98, 9.39, 30.37, false, "13 March 2021"), //
installment(21.64, 8.73, 30.37, false, "13 April 2021"), //
installment(22.31, 8.06, 30.37, false, "13 May 2021"), //
installment(23.01, 7.36, 30.37, false, "13 June 2021"), //
installment(23.73, 6.64, 30.37, false, "13 July 2021"), //
installment(24.48, 5.89, 30.37, false, "13 August 2021"), //
installment(25.24, 5.13, 30.37, false, "13 September 2021"), //
installment(26.03, 4.34, 30.37, false, "13 October 2021"), //
installment(26.85, 3.52, 30.37, false, "13 November 2021"), //
installment(27.69, 2.68, 30.37, false, "13 December 2021"), //
installment(28.55, 1.82, 30.37, false, "13 January 2022"), //
installment(29.49, 0.92, 30.41, false, "13 February 2022") //
);
Long resourceId = loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "13 February 2021", 100.0).getResourceId();
reverseRepaymentIdRef.set(resourceId);
loanTransactionHelper.makeLoanRepayment(loanId, "MerchantIssuedRefund", "13 February 2021", 300.0);
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(30.37, 0.0, 0.0, true, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Repayment", "13 February 2021", 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 100.0, false) //
);
});
runAt("19 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
loanTransactionHelper.makeLoanRepayment(loanId, "CreditBalanceRefund", "19 February 2021", 100.0);
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(30.37, 0.0, 0.0, true, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Repayment", "13 February 2021", 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 100.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, false) //
);
});
runAt("23 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
final Long reverseRepaymentId = reverseRepaymentIdRef.get();
loanTransactionHelper.reverseLoanTransaction(loanId, reverseRepaymentId, "23 February 2021");
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(130.37, 2.46, 102.46, false, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Repayment", "13 February 2021", 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, false) //
);
});
runAt("24 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Repayment", "13 February 2021", 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.45, "Accrual", "23 February 2021", 0.0, 0.0, 0.45, 0.0, 0.0, 0.0, 0.0, false) //
);
});
runAt("28 February 2021", () -> {
final Long loanId = loanIdRef.get();
inlineLoanCOBHelper.executeInlineCOB(loanId);
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "28 February 2021", 101.01);
verifyRepaymentSchedule(loanId, //
installment(300.0, null, "13 February 2021"), //
installment(130.37, 1.01, 0.0, true, "13 March 2021"), //
installment(30.37, 0.0, 0.0, true, "13 April 2021"), //
installment(30.37, 0.0, 0.0, true, "13 May 2021"), //
installment(30.37, 0.0, 0.0, true, "13 June 2021"), //
installment(30.37, 0.0, 0.0, true, "13 July 2021"), //
installment(30.37, 0.0, 0.0, true, "13 August 2021"), //
installment(30.37, 0.0, 0.0, true, "13 September 2021"), //
installment(30.37, 0.0, 0.0, true, "13 October 2021"), //
installment(30.37, 0.0, 0.0, true, "13 November 2021"), //
installment(26.67, 0.0, 0.0, true, "13 December 2021"), //
installment(0.0, 0.0, 0.0, true, "13 January 2022"), //
installment(0.0, 0.0, 0.0, true, "13 February 2022") //
);
verifyTransactions(loanId, //
transaction(300.0, "Disbursement", "13 February 2021", 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Repayment", "13 February 2021", 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
transaction(300.0, "Merchant Issued Refund", "13 February 2021", 0.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(100.0, "Credit Balance Refund", "19 February 2021", 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.45, "Accrual", "23 February 2021", 0.0, 0.0, 0.45, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.11, "Accrual", "24 February 2021", 0.0, 0.0, 0.11, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.11, "Accrual", "25 February 2021", 0.0, 0.0, 0.11, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.11, "Accrual", "26 February 2021", 0.0, 0.0, 0.11, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.11, "Accrual", "27 February 2021", 0.0, 0.0, 0.11, 0.0, 0.0, 0.0, 0.0, false), //
transaction(101.01, "Repayment", "28 February 2021", 0.0, 100.0, 1.01, 0.0, 0.0, 0.0, 0.0, false), //
transaction(0.12, "Accrual", "28 February 2021", 0.0, 0.0, 0.12, 0.0, 0.0, 0.0, 0.0, false) //
);
});
}
}
@@ -24,6 +24,10 @@ import io.restassured.specification.ResponseSpecification;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.InlineJobRequest;
import org.apache.fineract.client.models.InlineJobResponse;
import org.apache.fineract.client.util.Calls;
import org.apache.fineract.integrationtests.common.FineractClientHelper;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
@Slf4j @Slf4j
@@ -52,6 +56,11 @@ public class InlineLoanCOBHelper {
return Utils.performServerPost(requestSpec, responseSpec, EXECUTE_INLINE_COB_API, buildInlineCOBRequest(loanIds)); return Utils.performServerPost(requestSpec, responseSpec, EXECUTE_INLINE_COB_API, buildInlineCOBRequest(loanIds));
} }
public InlineJobResponse executeInlineCOB(Long loanId) {
return Calls.ok(FineractClientHelper.getFineractClient().inlineJobApi.executeInlineJob("LOAN_COB",
new InlineJobRequest().addLoanIdsItem(loanId)));
}
// TODO: Rewrite to use fineract-client instead! // TODO: Rewrite to use fineract-client instead!
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
// org.apache.fineract.client.models.PostLoansLoanIdRequest) // org.apache.fineract.client.models.PostLoansLoanIdRequest)