FINERACT-1905: Distribution logic fix

This commit is contained in:
Adam Saghy
2023-07-06 15:09:55 +02:00
parent b691ec4b1d
commit 43639bf0a3
6 changed files with 2736 additions and 198 deletions
@@ -410,6 +410,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money penaltyPortionOfTransaction = Money.zero(currency); Money penaltyPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return penaltyPortionOfTransaction;
}
final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency); final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(penaltyChargesDue).getAmount(); this.penaltyChargesPaid = getPenaltyChargesPaid(currency).plus(penaltyChargesDue).getAmount();
@@ -432,7 +436,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money feePortionOfTransaction = Money.zero(currency); Money feePortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return feePortionOfTransaction;
}
final Money feeChargesDue = getFeeChargesOutstanding(currency); final Money feeChargesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) {
this.feeChargesPaid = getFeeChargesPaid(currency).plus(feeChargesDue).getAmount(); this.feeChargesPaid = getFeeChargesPaid(currency).plus(feeChargesDue).getAmount();
@@ -455,7 +461,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money interestPortionOfTransaction = Money.zero(currency); Money interestPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return interestPortionOfTransaction;
}
final Money interestDue = getInterestOutstanding(currency); final Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestPaid = getInterestPaid(currency).plus(interestDue).getAmount(); this.interestPaid = getInterestPaid(currency).plus(interestDue).getAmount();
@@ -478,7 +486,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money principalPortionOfTransaction = Money.zero(currency); Money principalPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return principalPortionOfTransaction;
}
final Money principalDue = getPrincipalOutstanding(currency); final Money principalDue = getPrincipalOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount(); this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
@@ -500,7 +510,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
public Money waiveInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { public Money waiveInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedInterestPortionOfTransaction = Money.zero(currency); Money waivedInterestPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return waivedInterestPortionOfTransaction;
}
final Money interestDue = getInterestOutstanding(currency); final Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount(); this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
@@ -520,7 +532,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
public Money waivePenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { public Money waivePenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency); Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return waivedPenaltyChargesPortionOfTransaction;
}
final Money penanltiesDue = getPenaltyChargesOutstanding(currency); final Money penanltiesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount(); this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount();
@@ -540,7 +554,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
public Money waiveFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { public Money waiveFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency); Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
return waivedFeeChargesPortionOfTransaction;
}
final Money feesDue = getFeeChargesOutstanding(currency); final Money feesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount(); this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount();
@@ -56,15 +56,6 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
return STRATEGY_NAME; return STRATEGY_NAME;
} }
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/** /**
* For early/'in advance' repayments * For early/'in advance' repayments
*/ */
@@ -73,8 +64,106 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) { List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, final LocalDate transactionDate = loanTransaction.getTransactionDate();
charges);
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
Money transactionAmountRemaining = paymentInAdvance;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isChargesWaiver()) {
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate,
loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if (loanTransaction.isChargePayment()) {
if (loanTransaction.isPenaltyPayment()) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
boolean ignoreDueDateCheck = false;
boolean rerun = false;
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid)
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate))
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
Money calculatedPenaltyCharge = Money.zero(currency);
Money calculatedFeeCharge = Money.zero(currency);
// Calculate the amount of due charges
for (LoanCharge charge : orderedLoanChargesByDueDate) {
if (charge.isPenaltyCharge()) {
calculatedPenaltyCharge = calculatedPenaltyCharge.add(charge.getAmount(currency));
} else {
calculatedFeeCharge = calculatedFeeCharge.add(charge.getAmount(currency));
}
}
do {
Money subPenaltyPortion;
if (!ignoreDueDateCheck) {
if (calculatedPenaltyCharge.isGreaterThan(transactionAmountRemaining)) {
calculatedPenaltyCharge = transactionAmountRemaining;
}
} else {
calculatedPenaltyCharge = transactionAmountRemaining;
}
subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, calculatedPenaltyCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subFeePortion;
if (!ignoreDueDateCheck) {
if (calculatedFeeCharge.isGreaterThan(transactionAmountRemaining)) {
calculatedFeeCharge = transactionAmountRemaining;
}
} else {
calculatedFeeCharge = transactionAmountRemaining;
}
subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, calculatedFeeCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
Money subInterestPortion;
if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) {
subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
}
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
// If the transactionAmountRemaining is greater than zero, rerun the allocation without due date check
// to distribute the in advance portions
if (transactionAmountRemaining.isGreaterThanZero()) {
ignoreDueDateCheck = true;
}
rerun = !rerun;
} while (ignoreDueDateCheck && rerun);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
} }
/** /**
@@ -131,66 +220,22 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
} }
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else { } else {
boolean ignoreDueDateCheck = false; Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
boolean rerun = false; transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid) Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) feeChargesPortion = feeChargesPortion.add(subFeePortion);
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
Money calculatedPenaltyCharge = Money.zero(currency);
Money calculatedFeeCharge = Money.zero(currency);
// Calculate the amount of due charges
for (LoanCharge charge : orderedLoanChargesByDueDate) {
if (charge.isPenaltyCharge()) {
calculatedPenaltyCharge = calculatedPenaltyCharge.add(charge.getAmount(currency));
} else {
calculatedFeeCharge = calculatedFeeCharge.add(charge.getAmount(currency));
}
}
do { Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
Money subPenaltyPortion; transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
if (!ignoreDueDateCheck) { interestPortion = interestPortion.add(subInterestPortion);
if (calculatedPenaltyCharge.isGreaterThan(transactionAmountRemaining)) {
calculatedPenaltyCharge = transactionAmountRemaining;
}
} else {
calculatedPenaltyCharge = transactionAmountUnprocessed;
}
subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, calculatedPenaltyCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subFeePortion; Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
if (!ignoreDueDateCheck) { transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
if (calculatedFeeCharge.isGreaterThan(transactionAmountRemaining)) { principalPortion = principalPortion.add(subPrincipalPortion);
calculatedFeeCharge = transactionAmountRemaining;
}
} else {
calculatedFeeCharge = transactionAmountUnprocessed;
}
subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, calculatedFeeCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
Money subInterestPortion;
if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) {
subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
}
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
// If the transactionAmountRemaining is greater than zero, rerun the allocation without due date check
// to distribute the in advance portions
if (transactionAmountRemaining.isGreaterThanZero()) {
ignoreDueDateCheck = true;
}
rerun = !rerun;
} while (ignoreDueDateCheck && rerun);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} }
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
@@ -56,109 +56,17 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
return STRATEGY_NAME; return STRATEGY_NAME;
} }
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/** /**
* For early/'in advance' repayments * For early/'in advance' repayments
*/ */
@Override @Override
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money inAdvancePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) { List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate(); final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
Money transactionAmountRemaining = paymentInAdvance;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isChargesWaiver()) { final MonetaryCurrency currency = inAdvancePayment.getCurrency();
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, Money transactionAmountRemaining = inAdvancePayment;
loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate,
loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if (loanTransaction.isChargePayment()) {
if (loanTransaction.isPenaltyPayment()) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
// In advance penalty, interest, principal, fee
Money subPenaltyPortion;
subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subInterestPortion;
subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
Money subFeePortion;
subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
Set<LoanCharge> charges) {
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed,
transactionMappings, charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(currency); Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency); Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency); Money feeChargesPortion = Money.zero(currency);
@@ -214,7 +122,7 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
calculatedPenaltyCharge = transactionAmountRemaining; calculatedPenaltyCharge = transactionAmountRemaining;
} }
} else { } else {
calculatedPenaltyCharge = transactionAmountUnprocessed; calculatedPenaltyCharge = transactionAmountRemaining;
} }
subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, calculatedPenaltyCharge); subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, calculatedPenaltyCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion); transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
@@ -227,9 +135,11 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
interestPortion = interestPortion.add(subInterestPortion); interestPortion = interestPortion.add(subInterestPortion);
} }
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining); if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) {
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion); Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
principalPortion = principalPortion.add(subPrincipalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
}
Money subFeePortion; Money subFeePortion;
if (!ignoreDueDateCheck) { if (!ignoreDueDateCheck) {
@@ -237,7 +147,7 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
calculatedFeeCharge = transactionAmountRemaining; calculatedFeeCharge = transactionAmountRemaining;
} }
} else { } else {
calculatedFeeCharge = transactionAmountUnprocessed; calculatedFeeCharge = transactionAmountRemaining;
} }
subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, calculatedFeeCharge); subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, calculatedFeeCharge);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion); transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
@@ -259,6 +169,84 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
return transactionAmountRemaining; return transactionAmountRemaining;
} }
/**
* For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction, final Money latePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, latePayment, transactionMappings,
charges);
}
/**
* For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money onTimePayment,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = onTimePayment.getCurrency();
Money transactionAmountRemaining = onTimePayment;
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isChargesWaiver()) {
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate,
loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else if (loanTransaction.isChargePayment()) {
if (loanTransaction.isPenaltyPayment()) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion);
penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion);
Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion);
interestPortion = interestPortion.add(subInterestPortion);
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion);
principalPortion = principalPortion.add(subPrincipalPortion);
Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion);
feeChargesPortion = feeChargesPortion.add(subFeePortion);
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
}
if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) {
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion));
}
return transactionAmountRemaining;
}
@Override @Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now // TODO - KW - dont do anything with loan over-payment for now
@@ -19,6 +19,7 @@
package org.apache.fineract.integrationtests; package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,8 +31,12 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import org.apache.fineract.client.models.BusinessDateRequest; import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -49,6 +54,7 @@ import org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTe
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker; import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -64,7 +70,7 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
private BusinessDateHelper businessDateHelper; private BusinessDateHelper businessDateHelper;
private LoanTransactionHelper loanTransactionHelper; private LoanTransactionHelper loanTransactionHelper;
private LoanRescheduleRequestHelper loanRescheduleRequestHelper; private LoanRescheduleRequestHelper loanRescheduleRequestHelper;
private InlineLoanCOBHelper inlineLoanCOBHelper;
private AccountHelper accountHelper; private AccountHelper accountHelper;
@BeforeEach @BeforeEach
@@ -78,6 +84,7 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec); this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec);
this.businessDateHelper = new BusinessDateHelper(); this.businessDateHelper = new BusinessDateHelper();
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec);
} }
// Scenario1: // Scenario1:
@@ -104,12 +111,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", true)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", true));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -216,12 +223,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -324,12 +331,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -441,12 +448,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "3", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "3", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "3", "1", "3", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "90", "30", "3", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -530,12 +537,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "3", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "3", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "3", "1", "3", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "90", "30", "3", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -678,12 +685,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -753,12 +760,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -890,12 +897,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false));
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -909,7 +916,7 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000"); loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("1 February 2023") final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("31 January 2023")
.updateAdjustedDueDate("01 March 2023").updateSubmittedOnDate("25 January 2023").updateGraceOnPrincipal(null) .updateAdjustedDueDate("01 March 2023").updateSubmittedOnDate("25 January 2023").updateGraceOnPrincipal(null)
.updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString()); .updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString());
final HashMap<String, String> map = new HashMap<>(); final HashMap<String, String> map = new HashMap<>();
@@ -1114,12 +1121,12 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "20", false));
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true)); ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "15", true));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount); assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY, LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
"01 January 2023", "01 January 2023"); "01 January 2023", "01 January 2023");
@@ -1133,7 +1140,7 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000"); loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("1 February 2023") final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("31 January 2023")
.updateAdjustedDueDate("01 March 2023").updateSubmittedOnDate("25 January 2023").updateGraceOnPrincipal(null) .updateAdjustedDueDate("01 March 2023").updateSubmittedOnDate("25 January 2023").updateGraceOnPrincipal(null)
.updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString()); .updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString());
final HashMap<String, String> map = new HashMap<>(); final HashMap<String, String> map = new HashMap<>();
@@ -1397,13 +1404,401 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
} }
} }
// Scenario10:
// DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY
// 1. Disburse the loan
// 2. Snooze fee
// 3. Merchant issued refund (partial)
// 4. Charge adjustment (same day)
// 5. Merchant issued refund (rest)
@Test
public void scenario10() {
try {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.05.14").dateFormat("yyyy.MM.dd").locale("en"));
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(requestSpec, responseSpec, "submitted-date");
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "3.65", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY,
"14 May 2023", "14 May 2023");
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = loanTransactionHelper.approveLoan("14 May 2023", loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("14 May 2023", loanID, "127.95");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.11").dateFormat("yyyy.MM.dd").locale("en"));
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("13 June 2023")
.updateAdjustedDueDate("13 July 2023").updateSubmittedOnDate("11 June 2023").updateGraceOnPrincipal(null)
.updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString());
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy");
map.put("approvedOnDate", "11 June 2023");
final String aproveRequestJSON = new Gson().toJson(map);
Integer loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequestId, aproveRequestJSON);
Integer penalty1LoanChargeId = loanTransactionHelper.addChargesForLoan(loanID,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "13 July 2023", "3.65"));
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.12").dateFormat("yyyy.MM.dd").locale("en"));
inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(131.6, loanDetails.getSummary().getTotalOutstanding());
assertEquals(131.6, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(0).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(0).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement());
assertEquals(127.95, loanDetails.getTransactions().get(0).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(0).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getInterestPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getFeeChargesPortion());
assertEquals(127.95, loanDetails.getTransactions().get(0).getOutstandingLoanBalance());
assertNull(loanDetails.getTransactions().get(1).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(1).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual());
assertEquals(3.65, loanDetails.getTransactions().get(1).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getInterestPortion());
assertEquals(3.65, loanDetails.getTransactions().get(1).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.17").dateFormat("yyyy.MM.dd").locale("en"));
PostLoansLoanIdTransactionsResponse merchantIssuedRefund1 = loanTransactionHelper.makeMerchantIssuedRefund(Long.valueOf(loanID),
new PostLoansLoanIdTransactionsRequest().locale("en").dateFormat("dd MMMM yyyy").transactionDate("17 June 2023")
.transactionAmount(125.0));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(6.6, loanDetails.getSummary().getTotalOutstanding());
assertEquals(6.6, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(125.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(2).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(2).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(2).getType().getMerchantIssuedRefund());
assertEquals(125.0, loanDetails.getTransactions().get(2).getAmount());
assertEquals(125.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getInterestPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getFeeChargesPortion());
assertEquals(2.95, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID,
(long) penalty1LoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().amount(3.65));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(2.95, loanDetails.getSummary().getTotalOutstanding());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.70, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(3).getReversedOnDate());
assertFalse(loanDetails.getTransactions().get(3).getTransactionRelations().isEmpty());
assertEquals((long) penalty1LoanChargeId,
loanDetails.getTransactions().get(3).getTransactionRelations().iterator().next().getToLoanCharge());
assertTrue(loanDetails.getTransactions().get(3).getType().getChargeAdjustment());
assertEquals(3.65, loanDetails.getTransactions().get(3).getAmount());
assertEquals(2.95, loanDetails.getTransactions().get(3).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getInterestPortion());
assertEquals(0.7, loanDetails.getTransactions().get(3).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
PostLoansLoanIdTransactionsResponse merchantIssuedRefund2 = loanTransactionHelper.makeMerchantIssuedRefund(Long.valueOf(loanID),
new PostLoansLoanIdTransactionsRequest().locale("en").dateFormat("dd MMMM yyyy").transactionDate("17 June 2023")
.transactionAmount(2.95));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
assertNull(loanDetails.getTransactions().get(4).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(4).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(4).getType().getMerchantIssuedRefund());
assertEquals(2.95, loanDetails.getTransactions().get(4).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(4).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getInterestPortion());
assertEquals(2.95, loanDetails.getTransactions().get(4).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(requestSpec, responseSpec, "due-date");
}
}
// Scenario11:
// DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY
// 1. Disburse the loan
// 2. Snooze fee
// 3. Merchant issued refund (partial)
// 4. Charge adjustment (same day)
// 5. Merchant issued refund (rest)
@Test
public void scenario11() {
try {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.05.14").dateFormat("yyyy.MM.dd").locale("en"));
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(requestSpec, responseSpec, "submitted-date");
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "3.65", false));
final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "30", "1", "0",
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023");
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "30", "30", "1", "0",
LoanApplicationTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY,
"14 May 2023", "14 May 2023");
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = loanTransactionHelper.approveLoan("14 May 2023", loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("14 May 2023", loanID, "127.95");
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.11").dateFormat("yyyy.MM.dd").locale("en"));
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateRescheduleFromDate("13 June 2023")
.updateAdjustedDueDate("13 July 2023").updateSubmittedOnDate("11 June 2023").updateGraceOnPrincipal(null)
.updateGraceOnInterest(null).updateExtraTerms(null).build(loanID.toString());
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy");
map.put("approvedOnDate", "11 June 2023");
final String aproveRequestJSON = new Gson().toJson(map);
Integer loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequestId, aproveRequestJSON);
Integer penalty1LoanChargeId = loanTransactionHelper.addChargesForLoan(loanID,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "13 July 2023", "3.65"));
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.12").dateFormat("yyyy.MM.dd").locale("en"));
inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(131.6, loanDetails.getSummary().getTotalOutstanding());
assertEquals(131.6, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(0).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(0).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement());
assertEquals(127.95, loanDetails.getTransactions().get(0).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(0).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getInterestPortion());
assertEquals(0.0, loanDetails.getTransactions().get(0).getFeeChargesPortion());
assertEquals(127.95, loanDetails.getTransactions().get(0).getOutstandingLoanBalance());
assertNull(loanDetails.getTransactions().get(1).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(1).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual());
assertEquals(3.65, loanDetails.getTransactions().get(1).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getInterestPortion());
assertEquals(3.65, loanDetails.getTransactions().get(1).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
.date("2023.06.17").dateFormat("yyyy.MM.dd").locale("en"));
PostLoansLoanIdTransactionsResponse merchantIssuedRefund1 = loanTransactionHelper.makeMerchantIssuedRefund(Long.valueOf(loanID),
new PostLoansLoanIdTransactionsRequest().locale("en").dateFormat("dd MMMM yyyy").transactionDate("17 June 2023")
.transactionAmount(125.0));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(6.6, loanDetails.getSummary().getTotalOutstanding());
assertEquals(6.6, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(125.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(2).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(2).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(2).getType().getMerchantIssuedRefund());
assertEquals(125.0, loanDetails.getTransactions().get(2).getAmount());
assertEquals(125.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getInterestPortion());
assertEquals(0.0, loanDetails.getTransactions().get(2).getFeeChargesPortion());
assertEquals(2.95, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = this.loanTransactionHelper.chargeAdjustment((long) loanID,
(long) penalty1LoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().amount(3.65));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(2.95, loanDetails.getSummary().getTotalOutstanding());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(0.70, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(2.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getActive());
assertNull(loanDetails.getTransactions().get(3).getReversedOnDate());
assertFalse(loanDetails.getTransactions().get(3).getTransactionRelations().isEmpty());
assertEquals((long) penalty1LoanChargeId,
loanDetails.getTransactions().get(3).getTransactionRelations().iterator().next().getToLoanCharge());
assertTrue(loanDetails.getTransactions().get(3).getType().getChargeAdjustment());
assertEquals(3.65, loanDetails.getTransactions().get(3).getAmount());
assertEquals(2.95, loanDetails.getTransactions().get(3).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getInterestPortion());
assertEquals(0.7, loanDetails.getTransactions().get(3).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
PostLoansLoanIdTransactionsResponse merchantIssuedRefund2 = loanTransactionHelper.makeMerchantIssuedRefund(Long.valueOf(loanID),
new PostLoansLoanIdTransactionsRequest().locale("en").dateFormat("dd MMMM yyyy").transactionDate("17 June 2023")
.transactionAmount(2.95));
loanDetails = loanTransactionHelper.getLoanDetails((long) loanID);
assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getTotalOutstanding());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue());
assertEquals(3.65, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
assertEquals(127.95, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
assertEquals(0.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
assertNull(loanDetails.getTransactions().get(4).getReversedOnDate());
assertTrue(loanDetails.getTransactions().get(4).getTransactionRelations().isEmpty());
assertTrue(loanDetails.getTransactions().get(4).getType().getMerchantIssuedRefund());
assertEquals(2.95, loanDetails.getTransactions().get(4).getAmount());
assertEquals(0.0, loanDetails.getTransactions().get(4).getPrincipalPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getPenaltyChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getOverpaymentPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getInterestPortion());
assertEquals(2.95, loanDetails.getTransactions().get(4).getFeeChargesPortion());
assertEquals(0.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(requestSpec, responseSpec, "due-date");
}
}
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String principal, private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String principal,
final String loanTermFrequency, final String repaymentAfterEvery, final String numberOfRepayments, final String interestRate, final String loanTermFrequency, final String repaymentAfterEvery, final String numberOfRepayments, final String interestRate,
final String repaymentStrategy, final String expectedDisbursementDate, final String submittedOnDate) { final String repaymentStrategy, final String expectedDisbursementDate, final String submittedOnDate) {
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principal) final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principal)
.withLoanTermFrequency(loanTermFrequency).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments) .withLoanTermFrequency(loanTermFrequency).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments)
.withRepaymentEveryAfter(repaymentAfterEvery).withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod(interestRate) .withRepaymentEveryAfter(repaymentAfterEvery).withRepaymentFrequencyTypeAsDays().withInterestRatePerPeriod(interestRate)
.withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments().withRepaymentStrategy(repaymentStrategy) .withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments().withRepaymentStrategy(repaymentStrategy)
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(expectedDisbursementDate) .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(expectedDisbursementDate)
.withSubmittedOnDate(submittedOnDate).withLoanType("individual").build(clientID.toString(), loanProductID.toString(), null); .withSubmittedOnDate(submittedOnDate).withLoanType("individual").build(clientID.toString(), loanProductID.toString(), null);
@@ -1413,8 +1808,8 @@ public class DueDateRespectiveLoanRepaymentScheduleTest {
private Integer createLoanProductWithNoAccountingNoInterest(final String principal, final String repaymentAfterEvery, private Integer createLoanProductWithNoAccountingNoInterest(final String principal, final String repaymentAfterEvery,
final String numberOfRepayments, final String interestRate, final String repaymentStrategy, final Account... accounts) { final String numberOfRepayments, final String interestRate, final String repaymentStrategy, final Account... accounts) {
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(principal).withRepaymentTypeAsMonth() final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(principal).withRepaymentTypeAsDays()
.withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments).withRepaymentTypeAsMonth() .withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
.withinterestRatePerPeriod(interestRate).withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy) .withinterestRatePerPeriod(interestRate).withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy)
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts) .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0").build(null); .withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0").build(null);