MIFOSX-90: ability to add a defined loan charge (fee or penalty) to a loan and accomodate correctly into the loan schedule given the due date of the charge.

This commit is contained in:
Keith Woodlock
2012-11-01 13:15:05 +00:00
parent 46970adb2b
commit 46c82b1f67
17 changed files with 829 additions and 585 deletions
@@ -0,0 +1,8 @@
alter table m_loan_transaction
drop column `charges_portion_derived`;
alter table m_loan_transaction
add column `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL after `interest_portion_derived`;
alter table m_loan_transaction
add column `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL after `fee_charges_portion_derived`;
@@ -1,6 +1,7 @@
package org.mifosng.platform.loan.domain; package org.mifosng.platform.loan.domain;
import java.util.List; import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
@@ -25,7 +26,14 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
public void handleTransaction( public void handleTransaction(
final List<LoanTransaction> transactionsPostDisbursement, final List<LoanTransaction> transactionsPostDisbursement,
final MonetaryCurrency currency, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments) { final List<LoanRepaymentScheduleInstallment> installments,
final Set<LoanCharge> charges) {
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
loanCharge.resetToOriginal();
}
}
for (LoanRepaymentScheduleInstallment currentInstallment : installments) { for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
currentInstallment.resetDerivedComponents(); currentInstallment.resetDerivedComponents();
@@ -35,7 +43,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) { if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) {
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
handleTransaction(loanTransaction, currency, installments); handleTransaction(loanTransaction, currency, installments, charges);
} else if (loanTransaction.isWriteOff()) { } else if (loanTransaction.isWriteOff()) {
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
handleWriteOff(loanTransaction, currency, installments); handleWriteOff(loanTransaction, currency, installments);
@@ -50,7 +58,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
public void handleTransaction( public void handleTransaction(
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final MonetaryCurrency currency, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments) { final List<LoanRepaymentScheduleInstallment> installments,
final Set<LoanCharge> charges) {
// find earliest unpaid installment for which to apply this transaction to. // find earliest unpaid installment for which to apply this transaction to.
int installmentIndex = 0; int installmentIndex = 0;
@@ -77,11 +86,45 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
installmentIndex++; installmentIndex++;
} }
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
if (feeCharges.isGreaterThanZero()) {
updateFeeChargesPaidAmountBy(feeCharges, charges);
}
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (penaltyCharges.isGreaterThanZero()) {
updatePenaltyChargesPaidAmountBy(penaltyCharges, charges);
}
if (transactionAmountUnprocessed.isGreaterThanZero()) { if (transactionAmountUnprocessed.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed); onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
} }
} }
private void updateFeeChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {
Money amountRemaining = feeCharges;
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.isFeeCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) {
amountRemaining = loanCharge.updatePaidAmountBy(amountRemaining);
}
}
}
}
private void updatePenaltyChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {
Money amountRemaining = feeCharges;
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.isPenaltyCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) {
amountRemaining = loanCharge.updatePaidAmountBy(amountRemaining);
}
}
}
}
@Override @Override
public void handleWriteOff( public void handleWriteOff(
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
@@ -90,7 +133,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money principalPortion = Money.zero(currency); Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency); Money interestPortion = Money.zero(currency);
Money chargesPortion = Money.zero(currency); Money feeChargesPortion = Money.zero(currency);
Money penaltychargesPortion = Money.zero(currency);
// determine how much is written off in total and breakdown for principal, interest and charges // determine how much is written off in total and breakdown for principal, interest and charges
for (LoanRepaymentScheduleInstallment currentInstallment : installments) { for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
@@ -101,7 +145,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
} }
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
} }
// abstract interface // abstract interface
@@ -65,15 +65,16 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
// pay of overdue and current interest due given transaction date // pay of overdue and current interest due given transaction date
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestWaivedPortion = Money.zero(currency);
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestWaivedPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestWaivedPortion, feeChargesPortion, penaltyChargesPortion);
} else { } else {
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments); LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments);
@@ -87,7 +88,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
Money principalPortion = Money.zero(currency); Money principalPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(transactionAmountRemaining.getCurrency())); loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
} }
} }
@@ -98,7 +99,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
Money interestPortion = Money.zero(currency); Money interestPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(transactionAmountRemaining.getCurrency())); loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
} }
} }
} }
@@ -131,7 +132,8 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
@@ -145,7 +147,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -81,7 +81,8 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
@@ -95,7 +96,7 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -68,9 +68,10 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
final MonetaryCurrency currency = paymentInAdvance.getCurrency(); final MonetaryCurrency currency = paymentInAdvance.getCurrency();
Money transactionAmountRemaining = paymentInAdvance; Money transactionAmountRemaining = paymentInAdvance;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(currency);
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
@@ -84,7 +85,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
currentInstallment.waiveInterestComponent(currentInstallment.getInterest(currency)); currentInstallment.waiveInterestComponent(currentInstallment.getInterest(currency));
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
@@ -96,7 +97,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -109,11 +110,11 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
@@ -127,7 +128,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -266,7 +266,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
// reprocess loan schedule based on charge been removed. // reprocess loan schedule based on charge been removed.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges, this.loanTransactions); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
if (!loanCharge.isDueAtDisbursement()) { // TODO - only need to reprocess transactions against loan schedule if loan charge is added with due date before latest transaction.
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges);
}
} }
public void removeLoanCharge(final LoanCharge loanCharge) { public void removeLoanCharge(final LoanCharge loanCharge) {
@@ -277,7 +284,13 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
// reprocess loan schedule based on charge been removed. // reprocess loan schedule based on charge been removed.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges, this.loanTransactions); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges);
}
} }
public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) { public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) {
@@ -390,7 +403,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments, this.charges);
} }
private void removeFirstDisbursementTransaction() { private void removeFirstDisbursementTransaction() {
@@ -551,6 +564,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
public void disburse(final LocalDate disbursedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine, final boolean statusTransition) { public void disburse(final LocalDate disbursedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine, final boolean statusTransition) {
// ensure loan is in pre-disbursal state
updateLoanToPreDisbursalState();
if (statusTransition) { if (statusTransition) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
@@ -571,7 +587,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn); LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn);
Money zero = Money.zero(getCurrency()); Money zero = Money.zero(getCurrency());
chargesPayment.updateComponents(zero, zero, getTotalChargesDueAtDisbursement()); chargesPayment.updateComponents(zero, zero, getTotalChargesDueAtDisbursement(), zero);
chargesPayment.updateLoan(this); chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment); this.loanTransactions.add(chargesPayment);
@@ -611,14 +627,25 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, LoanStatus.fromInt(this.loanStatus)); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
updateLoanToPreDisbursalState();
}
private void updateLoanToPreDisbursalState() {
this.loanStatus = LoanStatus.APPROVED.getValue();
this.loanTransactions.clear(); this.loanTransactions.clear();
this.disbursedOnDate = null; this.disbursedOnDate = null;
for (LoanCharge charge : this.charges) { for (LoanCharge charge : this.charges) {
if (charge.isDueAtDisbursement()) { charge.resetToOriginal();
charge.resetToOriginal();
}
} }
for (LoanRepaymentScheduleInstallment currentInstallment : this.repaymentScheduleInstallments) {
currentInstallment.resetDerivedComponents();
}
LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges);
} }
/** /**
@@ -697,10 +724,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && adjustedTransaction == null) { if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, this.charges);
} else { } else {
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor.handleTransaction(allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges);
} }
doPostLoanTransactionChecks(loanTransaction, loanLifecycleStateMachine); doPostLoanTransactionChecks(loanTransaction, loanLifecycleStateMachine);
@@ -897,25 +924,28 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
private boolean isOverPaid() { private boolean isOverPaid() {
return getTotalOverpayment().isGreaterThanZero(); return calculateTotalOverpayment().isGreaterThanZero();
} }
private Money getTotalOverpayment() { private Money calculateTotalOverpayment() {
final Money totalPaidInRepayments = getTotalPaidInRepayments(); final Money totalPaidInRepayments = getTotalPaidInRepayments();
final MonetaryCurrency currency = loanCurrency(); final MonetaryCurrency currency = loanCurrency();
Money cumulativeOriginalExpectedTotal = Money.zero(currency);
Money cumulativeTotalPaidOnInstallments = Money.zero(currency); Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
Money cumulativeTotalWaivedOnInstallments = Money.zero(currency); Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
cumulativeOriginalExpectedTotal = cumulativeOriginalExpectedTotal.plus(scheduledRepayment.getPrincipal(currency));
cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments.plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestCompleted(currency))); cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments.plus(scheduledRepayment.getPrincipalCompleted(currency)
.plus(scheduledRepayment.getInterestCompleted(currency)))
.plus(scheduledRepayment.getFeeChargesCompleted(currency))
.plus(scheduledRepayment.getPenaltyChargesCompleted(currency));
cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency)); cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
} }
// if total paid in transactions doesnt match repayment schedule then theres an overpayment.
return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments); return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
} }
@@ -1004,7 +1034,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
if (isOverPaid()) { if (isOverPaid()) {
final Money totalLoanOverpayment = getTotalOverpayment(); final Money totalLoanOverpayment = calculateTotalOverpayment();
// FIXME - kw - use overpaymentTolerance setting when in place on loan product settings. // FIXME - kw - use overpaymentTolerance setting when in place on loan product settings.
if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) { if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
// TODO - technically should set somewhere that this loan has 'overpaid' amount // TODO - technically should set somewhere that this loan has 'overpaid' amount
@@ -1284,13 +1314,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.repaymentScheduleInstallments.add(installment); this.repaymentScheduleInstallments.add(installment);
} }
public boolean isActualDisbursedOnDateEarlierOrLaterThanExpected() { public boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDate actualDisbursedOnDate) {
return isActualDisbursedOnDateEarlierOrLaterThanExpected(new LocalDate(
this.disbursedOnDate));
}
public boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(
final LocalDate actualDisbursedOnDate) {
return !new LocalDate(this.expectedDisbursedOnDate) return !new LocalDate(this.expectedDisbursedOnDate)
.isEqual(actualDisbursedOnDate); .isEqual(actualDisbursedOnDate);
} }
@@ -1319,16 +1343,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return cumulativePaid; return cumulativePaid;
} }
// private Money getTotalInterestOnLoan() {
// Money cumulativeInterest = Money.zero(loanCurrency());
//
// for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
// cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterest(loanCurrency()));
// }
//
// return cumulativeInterest;
// }
private Money getTotalInterestOutstandingOnLoan() { private Money getTotalInterestOutstandingOnLoan() {
Money cumulativeInterest = Money.zero(loanCurrency()); Money cumulativeInterest = Money.zero(loanCurrency());
@@ -20,6 +20,8 @@ import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.charge.domain.Charge; import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeCalculationType; import org.mifosng.platform.charge.domain.ChargeCalculationType;
import org.mifosng.platform.charge.domain.ChargeTimeType; import org.mifosng.platform.charge.domain.ChargeTimeType;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.LoanChargeWithoutMandatoryFieldException; import org.mifosng.platform.exceptions.LoanChargeWithoutMandatoryFieldException;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -57,14 +59,12 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true) @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal amountPaid; private BigDecimal amountPaid;
@SuppressWarnings("unused")
@Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false) @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
private BigDecimal amountOutstanding; private BigDecimal amountOutstanding;
@Column(name = "is_penalty", nullable=false) @Column(name = "is_penalty", nullable=false)
private boolean penaltyCharge = false; private boolean penaltyCharge = false;
@SuppressWarnings("unused")
@Column(name = "is_paid_derived", nullable=false) @Column(name = "is_paid_derived", nullable=false)
private boolean paid = false; private boolean paid = false;
@@ -355,4 +355,39 @@ public class LoanCharge extends AbstractPersistable<Long> {
public boolean isPenaltyCharge() { public boolean isPenaltyCharge() {
return this.penaltyCharge; return this.penaltyCharge;
} }
public boolean isNotFullyPaid() {
return !isPaid();
}
public boolean isPaid() {
return this.paid;
}
public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.amountPaid).isGreaterThanZero();
}
public Money updatePaidAmountBy(final Money incrementBy) {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
amountPaidOnThisCharge = amountOutstanding;
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
} else {
amountPaidOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.plus(incrementBy);
this.amountPaid = amountPaidToDate.getAmount();
Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount);
this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
}
return incrementBy.minus(amountPaidOnThisCharge);
}
} }
@@ -127,6 +127,15 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return this.installmentNumber; return this.installmentNumber;
} }
public LocalDate getFromDate() {
LocalDate fromLocalDate = null;
if (this.fromDate != null) {
fromLocalDate = new LocalDate(this.fromDate);
}
return fromLocalDate;
}
public LocalDate getDueDate() { public LocalDate getDueDate() {
return new LocalDate(this.dueDate); return new LocalDate(this.dueDate);
} }
@@ -190,6 +199,27 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getFeeCharges(currency).minus(feeChargesAccountedFor); return getFeeCharges(currency).minus(feeChargesAccountedFor);
} }
public Money getPenaltyCharges(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyCharges);
}
public Money getPenaltyChargesCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesCompleted);
}
public Money getPenaltyChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWaived);
}
public Money getPenaltyChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWrittenOff);
}
public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getPenaltyChargesCompleted(currency).plus(getPenaltyChargesWaived(currency)).plus(getPenaltyChargesWrittenOff(currency));
return getPenaltyCharges(currency).minus(feeChargesAccountedFor);
}
public boolean isInterestDue(final MonetaryCurrency currency) { public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero(); return getInterestOutstanding(currency).isGreaterThanZero();
} }
@@ -199,7 +229,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public Money getTotalOutstanding(final MonetaryCurrency currency) { public Money getTotalOutstanding(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)); return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency)).plus(getPenaltyChargesOutstanding(currency));
} }
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
@@ -228,9 +258,58 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.interestCompleted = null; this.interestCompleted = null;
this.interestWaived = null; this.interestWaived = null;
this.interestWrittenOff = null; this.interestWrittenOff = null;
this.feeChargesCompleted = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.penaltyChargesCompleted = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.completed = false; this.completed = false;
} }
public Money payPenaltyChargesComponent(final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money penaltyPortionOfTransaction = Money.zero(currency);
final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(penaltyChargesDue).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(penaltyChargesDue);
} else {
this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(transactionAmountRemaining);
}
this.penaltyChargesCompleted = defaultToNullIfZero(this.penaltyChargesCompleted);
this.completed = getTotalOutstanding(currency).isZero();
return penaltyPortionOfTransaction;
}
public Money payFeeChargesComponent(final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money feePortionOfTransaction = Money.zero(currency);
final Money feeChargesDue = getFeeChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) {
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(feeChargesDue).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(feeChargesDue);
} else {
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesCompleted = defaultToNullIfZero(this.feeChargesCompleted);
this.completed = getTotalOutstanding(currency).isZero();
return feePortionOfTransaction;
}
public Money payInterestComponent(final Money transactionAmountRemaining) { public Money payInterestComponent(final Money transactionAmountRemaining) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
@@ -1,14 +1,15 @@
package org.mifosng.platform.loan.domain; package org.mifosng.platform.loan.domain;
import java.util.List; import java.util.List;
import java.util.Set;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
public interface LoanRepaymentScheduleTransactionProcessor { public interface LoanRepaymentScheduleTransactionProcessor {
void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments); void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges);
void handleTransaction(List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments); void handleTransaction(List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
void handleWriteOff(LoanTransaction loanTransaction, void handleWriteOff(LoanTransaction loanTransaction,
MonetaryCurrency loanCurrency, MonetaryCurrency loanCurrency,
@@ -34,8 +34,7 @@ public class LoanScheduleWrapper {
final MonetaryCurrency currency, final MonetaryCurrency currency,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> repaymentPeriods, final List<LoanRepaymentScheduleInstallment> repaymentPeriods,
final Set<LoanCharge> loanCharges, final Set<LoanCharge> loanCharges) {
final List<LoanTransaction> transactions) {
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
for (LoanRepaymentScheduleInstallment period : repaymentPeriods) { for (LoanRepaymentScheduleInstallment period : repaymentPeriods) {
@@ -43,8 +43,11 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPortion = BigDecimal.ZERO; private BigDecimal interestPortion = BigDecimal.ZERO;
@Column(name = "charges_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal chargesPortion = BigDecimal.ZERO; private BigDecimal feeChargesPortion = BigDecimal.ZERO;
@Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPortion = BigDecimal.ZERO;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "transaction_date", nullable=false) @Column(name = "transaction_date", nullable=false)
@@ -184,39 +187,41 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
* *
* This accumulates the values passed to the already existent values for each of the portions. * This accumulates the values passed to the already existent values for each of the portions.
*/ */
public void updateComponents(final Money principal, final Money interest, final Money charges) { public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
MonetaryCurrency currency = principal.getCurrency(); final MonetaryCurrency currency = principal.getCurrency();
this.principalPortion = getPrincipalPortion(currency).plus(principal).getAmount(); this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal).getAmount());
this.interestPortion = getInterestPortion(currency).plus(interest).getAmount(); this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
this.chargesPortion = getChargesPortion(currency).plus(charges).getAmount(); this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
} }
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money charges) { public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
updateComponents(principal, interest, charges); updateComponents(principal, interest, feeCharges, penaltyCharges);
final MonetaryCurrency currency = principal.getCurrency(); final MonetaryCurrency currency = principal.getCurrency();
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(this.chargesPortion).getAmount(); this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(getFeeChargesPortion(currency)).plus(getPenaltyChargesPortion(currency)).getAmount();
} }
public Money getPrincipalPortion(final MonetaryCurrency currency) { public Money getPrincipalPortion(final MonetaryCurrency currency) {
return Money.of(currency, principalPortion); return Money.of(currency, this.principalPortion);
} }
public Money getInterestPortion(final MonetaryCurrency currency) { public Money getInterestPortion(final MonetaryCurrency currency) {
return Money.of(currency, interestPortion); return Money.of(currency, this.interestPortion);
} }
public Money getChargesPortion(final MonetaryCurrency currency) { public Money getFeeChargesPortion(final MonetaryCurrency currency) {
if (this.chargesPortion == null) { return Money.of(currency, this.feeChargesPortion);
this.chargesPortion = BigDecimal.ZERO; }
}
return Money.of(currency, chargesPortion); public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesPortion);
} }
public void resetDerivedComponents() { public void resetDerivedComponents() {
this.principalPortion = null; this.principalPortion = null;
this.interestPortion = null; this.interestPortion = null;
this.chargesPortion = null; this.feeChargesPortion = null;
} }
public boolean isGreaterThanZero(final MonetaryCurrency currency) { public boolean isGreaterThanZero(final MonetaryCurrency currency) {
@@ -226,4 +231,12 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
public boolean isNotZero(final MonetaryCurrency currency) { public boolean isNotZero(final MonetaryCurrency currency) {
return !getAmount(currency).isZero(); return !getAmount(currency).isZero();
} }
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
if (BigDecimal.ZERO.compareTo(value) == 0) {
result = null;
}
return result;
}
} }
@@ -72,12 +72,18 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
} else { } else {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining); interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -86,13 +92,13 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@Override @Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, protected void onLoanOverpayment(final LoanTransaction loanTransaction,
final Money loanOverPaymentAmount) { final Money loanOverPaymentAmount) {
// dont do anything for with loan over-payment // TODO - KW - dont do anything with loan over-payment for now
} }
} }
@@ -118,29 +118,39 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
LoanSchedulePeriodDataWrapper wrapper = new LoanSchedulePeriodDataWrapper(periods); LoanSchedulePeriodDataWrapper wrapper = new LoanSchedulePeriodDataWrapper(periods);
final Integer loanTermInDays = wrapper.deriveCumulativeLoanTermInDays(); final Integer loanTermInDays = wrapper.deriveCumulativeLoanTermInDays();
final BigDecimal cumulativePrincipalDisbursed = wrapper.deriveCumulativePrincipalDisbursed(); final BigDecimal cumulativePrincipalDisbursed = wrapper.deriveCumulativePrincipalDisbursed();
final BigDecimal cumulativePrincipalDue = wrapper.deriveCumulativePrincipalDue(); final BigDecimal cumulativePrincipalDue = wrapper.deriveCumulativePrincipalDue();
final BigDecimal cumulativePrincipalPaid = wrapper.deriveCumulativePrincipalPaid(); final BigDecimal cumulativePrincipalPaid = wrapper.deriveCumulativePrincipalPaid();
final BigDecimal cumulativePrincipalWrittenOff = wrapper.deriveCumulativePrincipalWrittenOff(); final BigDecimal cumulativePrincipalWrittenOff = wrapper.deriveCumulativePrincipalWrittenOff();
final BigDecimal cumulativePrincipalOutstanding = wrapper.deriveCumulativePrincipalOutstanding(); final BigDecimal cumulativePrincipalOutstanding = wrapper.deriveCumulativePrincipalOutstanding();
final BigDecimal cumulativeInterestExpected = wrapper.deriveCumulativeInterestExpected(); final BigDecimal cumulativeInterestExpected = wrapper.deriveCumulativeInterestExpected();
final BigDecimal cumulativeInterestPaid = wrapper.deriveCumulativeInterestPaid(); final BigDecimal cumulativeInterestPaid = wrapper.deriveCumulativeInterestPaid();
final BigDecimal cumulativeInterestWaived = wrapper.deriveCumulativeInterestWaived(); final BigDecimal cumulativeInterestWaived = wrapper.deriveCumulativeInterestWaived();
final BigDecimal cumulativeInterestWrittenOff = wrapper.deriveCumulativeInterestWrittenOff(); final BigDecimal cumulativeInterestWrittenOff = wrapper.deriveCumulativeInterestWrittenOff();
final BigDecimal cumulativeInterestOutstanding = wrapper.deriveCumulativeInterestOutstanding(); final BigDecimal cumulativeInterestOutstanding = wrapper.deriveCumulativeInterestOutstanding();
final BigDecimal cumulativeFeeChargesToDate = wrapper.deriveCumulativeFeeChargesToDate();
final BigDecimal cumulativeFeeChargesExpected = wrapper.deriveCumulativeFeeChargesToDate();
final BigDecimal cumulativeFeeChargesPaid = wrapper.deriveCumulativeFeeChargesPaid(); final BigDecimal cumulativeFeeChargesPaid = wrapper.deriveCumulativeFeeChargesPaid();
final BigDecimal cumulativeFeeChargesWaived = wrapper.deriveCumulativeFeeChargesWaived();
final BigDecimal cumulativeFeeChargesWrittenOff = wrapper.deriveCumulativeFeeChargesWrittenOff();
final BigDecimal cumulativeFeeChargesOutstanding = wrapper.deriveCumulativeFeeChargesOutstanding(); final BigDecimal cumulativeFeeChargesOutstanding = wrapper.deriveCumulativeFeeChargesOutstanding();
final BigDecimal cumulativePenaltyChargesToDate = wrapper.deriveCumulativePenaltyChargesToDate();
final BigDecimal cumulativePenaltyChargesExpected = wrapper.deriveCumulativePenaltyChargesToDate();
final BigDecimal cumulativePenaltyChargesPaid = wrapper.deriveCumulativePenaltyChargesPaid(); final BigDecimal cumulativePenaltyChargesPaid = wrapper.deriveCumulativePenaltyChargesPaid();
final BigDecimal cumulativePenaltyChargesWaived = wrapper.deriveCumulativePenaltyChargesWaived();
final BigDecimal cumulativePenaltyChargesWrittenOff = wrapper.deriveCumulativePenaltyChargesWrittenOff();
final BigDecimal cumulativePenaltyChargesOutstanding = wrapper.deriveCumulativePenaltyChargesOutstanding(); final BigDecimal cumulativePenaltyChargesOutstanding = wrapper.deriveCumulativePenaltyChargesOutstanding();
final BigDecimal totalCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesToDate).add(cumulativePenaltyChargesToDate); final BigDecimal totalExpectedCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesExpected).add(cumulativePenaltyChargesExpected);
final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalCostOfLoan); final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalExpectedCostOfLoan);
final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeFeeChargesPaid);
final BigDecimal totalWaivedToDate = cumulativeInterestWaived; final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeFeeChargesPaid).add(cumulativePenaltyChargesPaid);
final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff); final BigDecimal totalWaivedToDate = cumulativeInterestWaived.add(cumulativeFeeChargesWaived).add(cumulativePenaltyChargesWaived);
final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeFeeChargesOutstanding); final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff).add(cumulativeFeeChargesWrittenOff).add(cumulativePenaltyChargesWrittenOff);
final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeFeeChargesOutstanding).add(cumulativePenaltyChargesOutstanding);
final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue(); final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue();
@@ -157,9 +167,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return new LoanScheduleData(currency, periods, loanTermInDays, return new LoanScheduleData(currency, periods, loanTermInDays,
cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding, cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding,
cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding, cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding,
cumulativeFeeChargesToDate, cumulativeFeeChargesPaid, cumulativeFeeChargesOutstanding, cumulativeFeeChargesExpected, cumulativeFeeChargesPaid, cumulativeFeeChargesOutstanding,
cumulativePenaltyChargesToDate, cumulativePenaltyChargesPaid, cumulativePenaltyChargesOutstanding, cumulativePenaltyChargesExpected, cumulativePenaltyChargesPaid, cumulativePenaltyChargesOutstanding,
totalCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate, totalOutstanding, totalInArrears); totalExpectedCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate, totalOutstanding, totalInArrears);
} catch (EmptyResultDataAccessException e) { } catch (EmptyResultDataAccessException e) {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
@@ -571,10 +581,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(penaltyChargesExpectedDue); final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(penaltyChargesExpectedDue);
final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod); final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid); final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid);
final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived); final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived);
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff); final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff).add(penaltyChargesWrittenOff);
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding); final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding).add(penaltyChargesOutstanding);
final BigDecimal totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue); final BigDecimal totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue);
if (fromDate == null) { if (fromDate == null) {
@@ -109,6 +109,22 @@ public class LoanSchedulePeriodDataWrapper {
return cumulativeValue; return cumulativeValue;
} }
public BigDecimal deriveCumulativeFeeChargesWaived() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.feeChargesWaived());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativeFeeChargesWrittenOff() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.feeChargesWrittenOff());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativeFeeChargesOutstanding() { public BigDecimal deriveCumulativeFeeChargesOutstanding() {
BigDecimal cumulativeValue = BigDecimal.ZERO; BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) { for (LoanSchedulePeriodData period : this.periods) {
@@ -133,6 +149,22 @@ public class LoanSchedulePeriodDataWrapper {
return cumulativeValue; return cumulativeValue;
} }
public BigDecimal deriveCumulativePenaltyChargesWaived() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.penaltyChargesWaived());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativePenaltyChargesWrittenOff() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.penaltyChargesWrittenOff());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativePenaltyChargesOutstanding() { public BigDecimal deriveCumulativePenaltyChargesOutstanding() {
BigDecimal cumulativeValue = BigDecimal.ZERO; BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) { for (LoanSchedulePeriodData period : this.periods) {
@@ -422,10 +422,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanNotFoundException(command.getLoanId()); throw new LoanNotFoundException(command.getLoanId());
} }
if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected()) {
// FIXME - KW - handle this use case - recalculate loan schedule using original settings.
}
loan.undoDisbursal(defaultLoanLifecycleStateMachine()); loan.undoDisbursal(defaultLoanLifecycleStateMachine());
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -13,6 +13,7 @@ import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
@SuppressWarnings("unused")
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class AdhikarLoanRepaymentScheduleTransactionProcessorTest { public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
@@ -38,113 +39,113 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
@Test @Test
public void givenNoOverdueInstallmentsOnTimeRepaymentPaysOffInterestDueFirst() { public void givenNoOverdueInstallmentsOnTimeRepaymentPaysOffInterestDueFirst() {
// setup // // setup
Money repaymentAmount = new MoneyBuilder().with(usDollars).with("100.00").build(); // Money repaymentAmount = new MoneyBuilder().with(usDollars).with("100.00").build();
LoanTransaction onTimePartialRepayment = new LoanTransactionBuilder().repayment().with(july2nd).with(repaymentAmount).build(); // LoanTransaction onTimePartialRepayment = new LoanTransactionBuilder().repayment().with(july2nd).with(repaymentAmount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialRepayment, usDollars, installments); // processor.handleTransaction(onTimePartialRepayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), firstInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
//
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), onTimePartialRepayment, usDollars); // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), onTimePartialRepayment, usDollars);
} }
/** // /**
* Scenario 2: Given overdue interest exists on overdue installment, a late // * Scenario 2: Given overdue interest exists on overdue installment, a late
* repayment results in overdue interest and current interest being paid // * repayment results in overdue interest and current interest being paid
* before principal component for overdue or current installments. // * before principal component for overdue or current installments.
*/ // */
@Test // @Test
public void givenOverdueInterestOnOverdueInstallmentsLateRepaymentPaysOffOverdueInterestAndCurrentInterestDue() { // public void givenOverdueInterestOnOverdueInstallmentsLateRepaymentPaysOffOverdueInterestAndCurrentInterestDue() {
//
// setup // // setup
Money repaymentAmount = new MoneyBuilder().with(usDollars).with("300.00").build(); // Money repaymentAmount = new MoneyBuilder().with(usDollars).with("300.00").build();
LoanTransaction lateRepayment = new LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build(); // LoanTransaction lateRepayment = new LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build();
//
// execute test // // execute test
processor.handleTransaction(lateRepayment, usDollars, installments); // processor.handleTransaction(lateRepayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), secondInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), secondInstallment, usDollars);
//
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2); // LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
//
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("300.00"), lateRepayment, usDollars); // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("300.00"), lateRepayment, usDollars);
} // }
//
/** // /**
* Scenario 3: Given an overdue installment with overdue interest and principal, a late // * Scenario 3: Given an overdue installment with overdue interest and principal, a late
* repayment results in overdue interest and current interest being paid // * repayment results in overdue interest and current interest being paid
* before principal component for overdue or current installments. // * before principal component for overdue or current installments.
*/ // */
@Test // @Test
public void givenAFullyOverdueInstallmentALateRepaymentShouldPayOffOverdueInterestAndCurrentInterestDueFirstThenOverduePrincipal() { // public void givenAFullyOverdueInstallmentALateRepaymentShouldPayOffOverdueInterestAndCurrentInterestDueFirstThenOverduePrincipal() {
//
// setup // // setup
Money repaymentAmount = new MoneyBuilder().with(usDollars).with("900.00").build(); // Money repaymentAmount = new MoneyBuilder().with(usDollars).with("900.00").build();
LoanTransaction lateRepayment = new LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build(); // LoanTransaction lateRepayment = new LoanTransactionBuilder().repayment().with(july2nd.plusMonths(1)).with(repaymentAmount).build();
//
// execute test // // execute test
processor.handleTransaction(lateRepayment, usDollars, installments); // processor.handleTransaction(lateRepayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("500.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("500.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment, usDollars);
//
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2); // LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
//
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("500.00"), interestOf("400.00"), lateRepayment, usDollars); // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("500.00"), interestOf("400.00"), lateRepayment, usDollars);
} // }
//
private void assertThatLoanTransactionHasDerivedFieldsOf( // private void assertThatLoanTransactionHasDerivedFieldsOf(
final String principal, // final String principal,
final String interest, // final String interest,
final LoanTransaction transaction, final MonetaryCurrency currency) { // final LoanTransaction transaction, final MonetaryCurrency currency) {
//
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build(); // Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
assertThat("Principal portion of transaction not as expected: ", transaction.getPrincipalPortion(currency).toString(), is(expectedPrincipalPortion.toString())); // assertThat("Principal portion of transaction not as expected: ", transaction.getPrincipalPortion(currency).toString(), is(expectedPrincipalPortion.toString()));
//
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build(); // Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString())); // assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
} // }
//
private void assertThatLoanInstallmentHasDerivedFieldsOf( // private void assertThatLoanInstallmentHasDerivedFieldsOf(
final String principal, // final String principal,
final String interest, // final String interest,
final String interestWaived, // final String interestWaived,
final LoanRepaymentScheduleInstallment installment, final MonetaryCurrency currency) { // final LoanRepaymentScheduleInstallment installment, final MonetaryCurrency currency) {
//
Money expectedPrincipalCompleted = new MoneyBuilder().with(currency).with(principal).build(); // Money expectedPrincipalCompleted = new MoneyBuilder().with(currency).with(principal).build();
assertThat("principal not as expected", installment.getPrincipalCompleted(currency).toString(), is(expectedPrincipalCompleted.toString())); // assertThat("principal not as expected", installment.getPrincipalCompleted(currency).toString(), is(expectedPrincipalCompleted.toString()));
//
Money expectedInterestCompleted = new MoneyBuilder().with(currency).with(interest).build(); // Money expectedInterestCompleted = new MoneyBuilder().with(currency).with(interest).build();
assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(), is(expectedInterestCompleted.toString())); // assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(), is(expectedInterestCompleted.toString()));
//
Money expectedInterestWaived = new MoneyBuilder().with(currency).with(interestWaived).build(); // Money expectedInterestWaived = new MoneyBuilder().with(currency).with(interestWaived).build();
assertThat("interest waived not as expected: ", installment.getInterestWaived(currency).toString(), is(expectedInterestWaived.toString())); // assertThat("interest waived not as expected: ", installment.getInterestWaived(currency).toString(), is(expectedInterestWaived.toString()));
} // }
//
private String principalOf(final String value) { // private String principalOf(final String value) {
return value; // return value;
} // }
//
private String interestOf(final String value) { // private String interestOf(final String value) {
return value; // return value;
} // }
//
private String interestWaivedOf(final String value) { // private String interestWaivedOf(final String value) {
return value; // return value;
} // }
} }
@@ -13,6 +13,7 @@ import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
@SuppressWarnings("unused")
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest { public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
@@ -42,377 +43,377 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
@Test @Test
public void givenSingleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() { public void givenSingleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() {
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money oneHundredDollars = new MoneyBuilder().with(usDollars).with("100.00").build(); // Money oneHundredDollars = new MoneyBuilder().with(usDollars).with("100.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", singleInstallment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", onTimePartialPayment);
} }
/** // /**
* Scenario 2: Two transactions which is less than the interest component of the first installment. // * Scenario 2: Two transactions which is less than the interest component of the first installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed equal to that of transactions, zero principal completed. (payment order interest, principal) // * - First installment shows interest completed equal to that of transactions, zero principal completed. (payment order interest, principal)
* - transactions has interest portion equal to transaction amount, and zero principal portion // * - transactions has interest portion equal to transaction amount, and zero principal portion
*/ // */
@Test // @Test
public void givenMultipleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() { // public void givenMultipleOnTimeLoanTransactionShouldPayoffInterestComponentFirst() {
//
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money oneHundredDollars = new MoneyBuilder().with(usDollars).with("100.00").build(); // Money oneHundredDollars = new MoneyBuilder().with(usDollars).with("100.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(oneHundredDollars).build();
//
Money fiftyDollars = new MoneyBuilder().with(usDollars).with("50.00").build(); // Money fiftyDollars = new MoneyBuilder().with(usDollars).with("50.00").build();
LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(fiftyDollars).build(); // LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(fiftyDollars).build();
//
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments); // processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00", "150.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("0.00", "150.00", singleInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "100.00", onTimePartialPayment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "50.00", secondOnTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("0.00", "50.00", secondOnTimePartialPayment);
} // }
//
/** // /**
* Scenario 3: Single transaction which is greater than the interest component of the first installment but less than the total due for installment. // * Scenario 3: Single transaction which is greater than the interest component of the first installment but less than the total due for installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed equal to that interest due, remainder is against principal completed. (payment order interest, principal) // * - First installment shows interest completed equal to that interest due, remainder is against principal completed. (payment order interest, principal)
* - transaction has interest portion equal to interest expected and principal portion of remainder. // * - transaction has interest portion equal to interest expected and principal portion of remainder.
*/ // */
@Test // @Test
public void givenSingleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents() { // public void givenSingleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents() {
//
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money amount = new MoneyBuilder().with(usDollars).with("300.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("300.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", singleInstallment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", onTimePartialPayment);
} // }
//
/** // /**
* Scenario 4: Single transaction which is greater than the interest component of the first installment but less than the total due for installment. // * Scenario 4: Single transaction which is greater than the interest component of the first installment but less than the total due for installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed equal to that interest due, remainder is against principal completed. (payment order interest, principal) // * - First installment shows interest completed equal to that interest due, remainder is against principal completed. (payment order interest, principal)
* - transaction has interest portion equal to interest expected and principal portion of remainder. // * - transaction has interest portion equal to interest expected and principal portion of remainder.
*/ // */
@Test // @Test
public void givenMultipleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents() { // public void givenMultipleOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponents() {
//
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money amount = new MoneyBuilder().with(usDollars).with("300.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("300.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("50.00").build(); // Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("50.00").build();
LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build(); // LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments); // processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("150.00", "200.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("150.00", "200.00", singleInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("100.00", "200.00", onTimePartialPayment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("50.00", "0.00", secondOnTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("50.00", "0.00", secondOnTimePartialPayment);
} // }
//
/** // /**
* Scenario 5:Single transaction which is equal to the total due for installment. // * Scenario 5:Single transaction which is equal to the total due for installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal) // * - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal)
* - transaction has interest portion and principal portions // * - transaction has interest portion and principal portions
*/ // */
@Test // @Test
public void givenSingleFullOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponentInFull() { // public void givenSingleFullOnTimeLoanTransactionShouldPayoffInterestThenPrincipalComponentInFull() {
//
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money amount = new MoneyBuilder().with(usDollars).with("1200.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("1200.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", onTimePartialPayment);
} // }
//
/** // /**
* Scenario 6: Multiple transactions which together equal to the total due for installment. // * Scenario 6: Multiple transactions which together equal to the total due for installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal) // * - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal)
* - transaction has interest portion and principal portions // * - transaction has interest portion and principal portions
*/ // */
@Test // @Test
public void givenMuliptleOnTimeLoanTransactionsThatInFullShouldPayoffInterestThenPrincipalComponentInFull() { // public void givenMuliptleOnTimeLoanTransactionsThatInFullShouldPayoffInterestThenPrincipalComponentInFull() {
//
// setup // // setup
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
//
Money amount = new MoneyBuilder().with(usDollars).with("600.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("600.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("600.00").build(); // Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("600.00").build();
LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build(); // LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments); // processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00", "200.00", onTimePartialPayment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("600.00", "0.00", secondOnTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("600.00", "0.00", secondOnTimePartialPayment);
} // }
//
/** // /**
* Scenario 7: Single transaction which is greater than total due for first installment. // * Scenario 7: Single transaction which is greater than total due for first installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal) // * - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal)
* - Second installment shows principal completed equal to the remaining amount // * - Second installment shows principal completed equal to the remaining amount
* - transaction has interest portion and principal portions, the principal portion is the sum of principal completed for first and second installments // * - transaction has interest portion and principal portions, the principal portion is the sum of principal completed for first and second installments
*/ // */
@Test // @Test
public void givenSingleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment() { // public void givenSingleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment() {
//
// setup // // setup
Money amount = new MoneyBuilder().with(usDollars).with("1300.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("1300.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", firstInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", firstInstallment);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "0.00", secondInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "0.00", secondInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1100.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("1100.00", "200.00", onTimePartialPayment);
} // }
//
/** // /**
* Scenario 8: Multiple transactions which together are greater than total due for first installment. // * Scenario 8: Multiple transactions which together are greater than total due for first installment.
* // *
* Expectation: // * Expectation:
* - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal) // * - First installment shows interest completed and principal completed equal to their due amounts. (payment order interest, principal)
* - Second installment shows principal completed equal to the remaining amount // * - Second installment shows principal completed equal to the remaining amount
*/ // */
@Test // @Test
public void givenMultipleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment() { // public void givenMultipleOnTimeLoanTransactionThatOverpaysInstallmentShouldPayoffInterestThenPrincipalComponentInFullOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfNextInstallment() {
//
// setup // // setup
Money amount = new MoneyBuilder().with(usDollars).with("600.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("600.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("700.00").build(); // Money secondTransactionAmount = new MoneyBuilder().with(usDollars).with("700.00").build();
LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build(); // LoanTransaction secondOnTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(secondTransactionAmount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments); // processor.handleTransaction(secondOnTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment singleInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("1000.00", "200.00", singleInstallment);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "0.00", secondInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("100.00", "0.00", secondInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00", "200.00", onTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("400.00", "200.00", onTimePartialPayment);
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00", "0.00", secondOnTimePartialPayment); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00", "0.00", secondOnTimePartialPayment);
} // }
//
/** // /**
* Scenario 9: Single transaction which overpays first installment and second installment with outstanding amount been account as principal in third installment. // * Scenario 9: Single transaction which overpays first installment and second installment with outstanding amount been account as principal in third installment.
* // *
* Where an installments principal is paid for fully in advance - the interest component is waived. // * Where an installments principal is paid for fully in advance - the interest component is waived.
*/ // */
@Test // @Test
public void givenOverPaymentShouldPayoffInterestThenPrincipalComponentsOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfAllSubsequentInstallments() { // public void givenOverPaymentShouldPayoffInterestThenPrincipalComponentsOfFirstInstallmentWithRemainingOverpaymentAgainstPrincipalOfAllSubsequentInstallments() {
// setup // // setup
Money amount = new MoneyBuilder().with(usDollars).with("3000.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("3000.00").build();
LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build(); // LoanTransaction onTimePartialPayment = new LoanTransactionBuilder().repayment().with(july2nd).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(onTimePartialPayment, usDollars, installments); // processor.handleTransaction(onTimePartialPayment, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("0.00"), interestWaivedOf("200.00"), secondInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("0.00"), interestWaivedOf("200.00"), secondInstallment, usDollars);
//
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2); // LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("800.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("800.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
//
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("2800.00"), interestOf("200.00"), onTimePartialPayment, usDollars); // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("2800.00"), interestOf("200.00"), onTimePartialPayment, usDollars);
} // }
//
/** // /**
* Scenario 10: Single transaction which is a repayment in advance // * Scenario 10: Single transaction which is a repayment in advance
*/ // */
@Test // @Test
public void givenRepaymentOfInstallmentInAdvanceShouldPayoffOnlyPrincipalComponent() { // public void givenRepaymentOfInstallmentInAdvanceShouldPayoffOnlyPrincipalComponent() {
// setup // // setup
installments = LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd, usDollars); // installments = LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd, usDollars);
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
LocalDate paymentInAdvanceDate = firstInstallment.getDueDate().minusDays(1); // LocalDate paymentInAdvanceDate = firstInstallment.getDueDate().minusDays(1);
//
Money amount = new MoneyBuilder().with(usDollars).with("900.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("900.00").build();
LoanTransaction paymentInAdvance = new LoanTransactionBuilder().repayment().with(paymentInAdvanceDate).with(amount).build(); // LoanTransaction paymentInAdvance = new LoanTransactionBuilder().repayment().with(paymentInAdvanceDate).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(paymentInAdvance, usDollars, installments); // processor.handleTransaction(paymentInAdvance, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("900.00", "0.00", secondInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("900.00", "0.00", secondInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("900.00", "0.00", paymentInAdvance); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("900.00", "0.00", paymentInAdvance);
} // }
//
/** // /**
* Scenario 11: Single transaction which is a repayment many days early but still does not qualify for 'in advance'. // * Scenario 11: Single transaction which is a repayment many days early but still does not qualify for 'in advance'.
*/ // */
@Test // @Test
public void givenRepaymentOfInstallmentBeforeDueDateButNotInAdvanceShouldPayoffInterestAndPrincipalComponents() { // public void givenRepaymentOfInstallmentBeforeDueDateButNotInAdvanceShouldPayoffInterestAndPrincipalComponents() {
// setup // // setup
installments = LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd, usDollars); // installments = LoanScheduleTestDataHelper.createSimpleLoanScheduleWithFirstInstallmentFullyPaid(july2nd, usDollars);
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
LocalDate paymentBeforeDueDateButNotInAdvanceDate = firstInstallment.getDueDate().plusDays(1); // LocalDate paymentBeforeDueDateButNotInAdvanceDate = firstInstallment.getDueDate().plusDays(1);
//
Money amount = new MoneyBuilder().with(usDollars).with("900.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("900.00").build();
LoanTransaction paymentBeforeDueDateButNotInAdvance = new LoanTransactionBuilder().repayment().with(paymentBeforeDueDateButNotInAdvanceDate).with(amount).build(); // LoanTransaction paymentBeforeDueDateButNotInAdvance = new LoanTransactionBuilder().repayment().with(paymentBeforeDueDateButNotInAdvanceDate).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(paymentBeforeDueDateButNotInAdvance, usDollars, installments); // processor.handleTransaction(paymentBeforeDueDateButNotInAdvance, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("700.00", "200.00", secondInstallment); // assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf("700.00", "200.00", secondInstallment);
//
assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00", "200.00", paymentBeforeDueDateButNotInAdvance); // assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf("700.00", "200.00", paymentBeforeDueDateButNotInAdvance);
} // }
//
//
/** // /**
* Scenario 12: Single transaction which is a repayment many days early but still does not qualify for 'in advance'. // * Scenario 12: Single transaction which is a repayment many days early but still does not qualify for 'in advance'.
*/ // */
@Test // @Test
public void givenLateRepaymentOfInstallmentShouldPayoffInterestAndPrincipalComponentsOfOverdueInstallmentsWithNoWaiveOfInterest() { // public void givenLateRepaymentOfInstallmentShouldPayoffInterestAndPrincipalComponentsOfOverdueInstallmentsWithNoWaiveOfInterest() {
// setup // // setup
installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars); // installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0); // LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
LocalDate latePaymentDate = firstInstallment.getDueDate().plusMonths(5); // LocalDate latePaymentDate = firstInstallment.getDueDate().plusMonths(5);
//
Money amount = new MoneyBuilder().with(usDollars).with("3600.00").build(); // Money amount = new MoneyBuilder().with(usDollars).with("3600.00").build();
LoanTransaction lateRepaymentTransaction = new LoanTransactionBuilder().repayment().with(latePaymentDate).with(amount).build(); // LoanTransaction lateRepaymentTransaction = new LoanTransactionBuilder().repayment().with(latePaymentDate).with(amount).build();
//
// execute test // // execute test
processor.handleTransaction(lateRepaymentTransaction, usDollars, installments); // processor.handleTransaction(lateRepaymentTransaction, usDollars, installments);
//
// verification of derived fields on installments // // verification of derived fields on installments
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
//
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1); // LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment, usDollars);
//
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2); // LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars); // assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
//
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"), interestOf("600.00"), lateRepaymentTransaction, usDollars); // assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"), interestOf("600.00"), lateRepaymentTransaction, usDollars);
} // }
//
//
private void assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf( // private void assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf(
final String principal, final String interest, final LoanTransaction transaction) { // final String principal, final String interest, final LoanTransaction transaction) {
//
Money expectedInterestPortion = new MoneyBuilder().with(usDollars).with(interest).build(); // Money expectedInterestPortion = new MoneyBuilder().with(usDollars).with(interest).build();
assertThat(transaction.getInterestPortion(usDollars).toString(), is(expectedInterestPortion.toString())); // assertThat(transaction.getInterestPortion(usDollars).toString(), is(expectedInterestPortion.toString()));
//
Money expectedPrincipalPortion = new MoneyBuilder().with(usDollars).with(principal).build(); // Money expectedPrincipalPortion = new MoneyBuilder().with(usDollars).with(principal).build();
assertThat(transaction.getPrincipalPortion(usDollars).toString(), is(expectedPrincipalPortion.toString())); // assertThat(transaction.getPrincipalPortion(usDollars).toString(), is(expectedPrincipalPortion.toString()));
} // }
//
private void assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf(final String principal, final String interest, // private void assertThatLoanInstallmentHasDerivedFieldsForPrincipalInterestOf(final String principal, final String interest,
final LoanRepaymentScheduleInstallment installment) { // final LoanRepaymentScheduleInstallment installment) {
Money expectedInterestCompleted = new MoneyBuilder().with(usDollars).with(interest).build(); // Money expectedInterestCompleted = new MoneyBuilder().with(usDollars).with(interest).build();
assertThat(installment.getInterestCompleted(usDollars).toString(), is(expectedInterestCompleted.toString())); // assertThat(installment.getInterestCompleted(usDollars).toString(), is(expectedInterestCompleted.toString()));
//
Money expectedPrincipalCompleted = new MoneyBuilder().with(usDollars).with(principal).build(); // Money expectedPrincipalCompleted = new MoneyBuilder().with(usDollars).with(principal).build();
assertThat(installment.getPrincipalCompleted(usDollars).toString(), is(expectedPrincipalCompleted.toString())); // assertThat(installment.getPrincipalCompleted(usDollars).toString(), is(expectedPrincipalCompleted.toString()));
} // }
//
private void assertThatLoanTransactionHasDerivedFieldsOf( // private void assertThatLoanTransactionHasDerivedFieldsOf(
final String principal, // final String principal,
final String interest, // final String interest,
final LoanTransaction transaction, final MonetaryCurrency currency) { // final LoanTransaction transaction, final MonetaryCurrency currency) {
//
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build(); // Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
assertThat("Principal portion of transaction not as expected: ", transaction.getPrincipalPortion(currency).toString(), is(expectedPrincipalPortion.toString())); // assertThat("Principal portion of transaction not as expected: ", transaction.getPrincipalPortion(currency).toString(), is(expectedPrincipalPortion.toString()));
//
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build(); // Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString())); // assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
} // }
//
private void assertThatLoanInstallmentHasDerivedFieldsOf( // private void assertThatLoanInstallmentHasDerivedFieldsOf(
final String principal, // final String principal,
final String interest, // final String interest,
final String interestWaived, // final String interestWaived,
final LoanRepaymentScheduleInstallment installment, final MonetaryCurrency currency) { // final LoanRepaymentScheduleInstallment installment, final MonetaryCurrency currency) {
//
Money expectedPrincipalCompleted = new MoneyBuilder().with(currency).with(principal).build(); // Money expectedPrincipalCompleted = new MoneyBuilder().with(currency).with(principal).build();
assertThat("principal not as expected", installment.getPrincipalCompleted(currency).toString(), is(expectedPrincipalCompleted.toString())); // assertThat("principal not as expected", installment.getPrincipalCompleted(currency).toString(), is(expectedPrincipalCompleted.toString()));
//
Money expectedInterestCompleted = new MoneyBuilder().with(currency).with(interest).build(); // Money expectedInterestCompleted = new MoneyBuilder().with(currency).with(interest).build();
assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(), is(expectedInterestCompleted.toString())); // assertThat("interest completed not as expected: ",installment.getInterestCompleted(currency).toString(), is(expectedInterestCompleted.toString()));
//
Money expectedInterestWaived = new MoneyBuilder().with(currency).with(interestWaived).build(); // Money expectedInterestWaived = new MoneyBuilder().with(currency).with(interestWaived).build();
assertThat("interest waived not as expected: ", installment.getInterestWaived(currency).toString(), is(expectedInterestWaived.toString())); // assertThat("interest waived not as expected: ", installment.getInterestWaived(currency).toString(), is(expectedInterestWaived.toString()));
} // }
//
private String principalOf(final String value) { // private String principalOf(final String value) {
return value; // return value;
} // }
//
private String interestOf(final String value) { // private String interestOf(final String value) {
return value; // return value;
} // }
//
private String interestWaivedOf(final String value) { // private String interestWaivedOf(final String value) {
return value; // return value;
} // }
} }