create two types of repayment strategy from abstract template.
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
package org.mifosng.platform.loan.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||
import org.mifosng.platform.currency.domain.Money;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link LoanRepaymentScheduleTransactionProcessor} which is more convenient for concrete implementations to extend.
|
||||
*
|
||||
* @see HeavensFamilyLoanRepaymentScheduleTransactionProcessor
|
||||
* @see CreocoreLoanRepaymentScheduleTransactionProcessor
|
||||
*/
|
||||
public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
/**
|
||||
* Provides support for passing all {@link LoanTransaction}'s so it will
|
||||
* completely re-process the entire loan schedule. This is required in cases
|
||||
* where the {@link LoanTransaction} being processed is in the past and
|
||||
* falls before existing transactions or and adjustment is made to an
|
||||
* existing in which case the entire loan schedule needs to be re-processed.
|
||||
*/
|
||||
@Override
|
||||
public void handleTransaction(
|
||||
final List<LoanTransaction> repaymentsOrWaivers,
|
||||
final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
}
|
||||
|
||||
for (LoanTransaction loanTransaction : repaymentsOrWaivers) {
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
handleTransaction(loanTransaction, currency, installments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides support for processing the latest transaction (which should be latest transaction) against the loan schedule.
|
||||
*/
|
||||
@Override
|
||||
public void handleTransaction(
|
||||
final LoanTransaction loanTransaction,
|
||||
final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
// find earliest unpaid installment for which to apply this transaction to.
|
||||
int installmentIndex = 0;
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
|
||||
if (currentInstallment.isNotFullyCompleted()) {
|
||||
|
||||
// is this transaction early/late/on-time with respect to the current installment?
|
||||
if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) {
|
||||
transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment, installments, loanTransaction, transactionDate, transactionAmountUnprocessed);
|
||||
} else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, loanTransaction.getTransactionDate())) {
|
||||
// does this result in a late payment of existing installment?
|
||||
transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments, loanTransaction, transactionAmountUnprocessed);
|
||||
} else {
|
||||
// standard transaction
|
||||
transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed);
|
||||
}
|
||||
}
|
||||
|
||||
installmentIndex++;
|
||||
}
|
||||
|
||||
if (transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
|
||||
}
|
||||
}
|
||||
|
||||
// abstract interface
|
||||
/**
|
||||
* This method is responsible for checking if the current transaction is 'an
|
||||
* advance/early payment' based on the details passed through.
|
||||
*
|
||||
* Default implementation simply processes transactions as 'Late' if the
|
||||
* transaction date is after the installment due date.
|
||||
*/
|
||||
protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
|
||||
|
||||
return transactionDate.isAfter(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* For late repayments, how should components of installment be paid off
|
||||
*/
|
||||
protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed);
|
||||
|
||||
/**
|
||||
* This method is responsible for checking if the current transaction is 'an
|
||||
* advance/early payment' based on the details passed through.
|
||||
*/
|
||||
protected abstract boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate,
|
||||
final Money transactionAmount);
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments.
|
||||
*/
|
||||
protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate,
|
||||
final Money paymentInAdvance);
|
||||
|
||||
/**
|
||||
* For normal on-time repayments.
|
||||
*/
|
||||
protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed);
|
||||
|
||||
/**
|
||||
* Invoked when a transaction results in an over-payment of the full loan.
|
||||
*
|
||||
* transaction amount is greater than the total expected principal and interest of the loan.
|
||||
*/
|
||||
protected abstract void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.mifosng.platform.loan.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.currency.domain.Money;
|
||||
|
||||
/**
|
||||
* Creocore style {@link LoanRepaymentScheduleTransactionProcessor}.
|
||||
*
|
||||
* For standard transactions, pays off components in order of interest, then
|
||||
* principal.
|
||||
*
|
||||
* If a transaction results in an advance payment or over-payment for a given
|
||||
* installment, the over paid amount is pay off on the principal component of
|
||||
* subsequent installments.
|
||||
*
|
||||
* If the entire principal of an installment is paid in advance then the
|
||||
* interest component is waived.
|
||||
*/
|
||||
public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
|
||||
AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
/**
|
||||
* For creocore, early is defined as any date before the installment due
|
||||
* date
|
||||
*/
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate, final Money transactionAmount) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments, pays off principal component only.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate, final Money paymentInAdvance) {
|
||||
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance);
|
||||
}
|
||||
|
||||
/**
|
||||
* For late repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsALateRepaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed);
|
||||
}
|
||||
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
|
||||
Money interestPortion = currentInstallment
|
||||
.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining
|
||||
.minus(interestPortion);
|
||||
|
||||
Money principalPortion = currentInstallment
|
||||
.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining
|
||||
.minus(principalPortion);
|
||||
|
||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining
|
||||
.getCurrency());
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion,
|
||||
interestWaivedPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction,
|
||||
final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
}
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
package org.mifosng.platform.loan.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||
import org.mifosng.platform.currency.domain.Money;
|
||||
|
||||
/**
|
||||
* Heavensfamily style {@link LoanRepaymentScheduleTransactionProcessor}.
|
||||
*
|
||||
* For standard transactions, pays off components in order of interest, then principal.
|
||||
*
|
||||
* If a transaction results in an overpayment for a given installment, the over paid amount is pay off on the principal component of subsequent installments.
|
||||
*/
|
||||
public class DefaultLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
@Override
|
||||
public void handleTransaction(final List<LoanTransaction> repaymentsOrWaivers,
|
||||
final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
}
|
||||
|
||||
for (LoanTransaction loanTransaction : repaymentsOrWaivers) {
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
handleTransaction(loanTransaction, currency, installments);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
// find earliest unpaid installment for which to apply this transaction to.
|
||||
boolean transactionProcessed = false;
|
||||
int installmentIndex = 0;
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
|
||||
if (currentInstallment.isNotFullyCompleted() && !transactionProcessed) {
|
||||
|
||||
if (isTransactionInAdvance(installmentIndex, installments, loanTransaction)) {
|
||||
handleTransactionInAdvance(installments, loanTransaction, currency);
|
||||
transactionProcessed = true;
|
||||
} else if (isTransactionLate(installmentIndex, installments, loanTransaction.getTransactionDate())) {
|
||||
// does this result in a late payment of existing installment?
|
||||
handleLateTransaction(currentInstallment, installments, loanTransaction, currency);
|
||||
transactionProcessed = true;
|
||||
} else {
|
||||
// standard transaction
|
||||
Money installmentOverPaymentAmount = handleOnTimeTransaction(currentInstallment, loanTransaction, currency);
|
||||
Money loanOverPaymentAmount = handleOverpaymentTransaction(installments, loanTransaction, currency, installmentOverPaymentAmount);
|
||||
if (loanOverPaymentAmount.isGreaterThanZero()) {
|
||||
onLoanOverpayment(loanTransaction, currency, loanOverPaymentAmount);
|
||||
}
|
||||
transactionProcessed = true;
|
||||
}
|
||||
}
|
||||
|
||||
installmentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dont do anything special for late payments e.g. no fees, charges or penalties are applied
|
||||
* @param currentInstallment2
|
||||
*/
|
||||
protected void handleLateTransaction(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final MonetaryCurrency currency) {
|
||||
|
||||
Money installmentOverPaymentAmount = handleOnTimeTransaction(currentInstallment, loanTransaction, currency);
|
||||
Money loanOverPaymentAmount = handleOverpaymentTransaction(installments, loanTransaction, currency, installmentOverPaymentAmount);
|
||||
if (loanOverPaymentAmount.isGreaterThanZero()) {
|
||||
onLoanOverpayment(loanTransaction, currency, loanOverPaymentAmount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* just process transactions paid in advance the same as you would handle an overpayment.
|
||||
*/
|
||||
protected void handleTransactionInAdvance(
|
||||
List<LoanRepaymentScheduleInstallment> installments,
|
||||
LoanTransaction loanTransaction, MonetaryCurrency currency) {
|
||||
|
||||
final Money paymentInAdvance = loanTransaction.getAmount(currency);
|
||||
Money loanOverPaymentAmount = handleOverpaymentTransaction(installments, loanTransaction, currency, paymentInAdvance);
|
||||
if (loanOverPaymentAmount.isGreaterThanZero()) {
|
||||
onLoanOverpayment(loanTransaction, currency, loanOverPaymentAmount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* called when a transaction results in a overpayment of the full loan i.e.
|
||||
* transaction amount is greater than the total expected principal +
|
||||
* interest of the loan.
|
||||
*/
|
||||
protected void onLoanOverpayment(@SuppressWarnings("unused") final LoanTransaction loanTransaction, @SuppressWarnings("unused") final MonetaryCurrency currency,
|
||||
@SuppressWarnings("unused") final Money loanOverPaymentAmount) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles overpayment scenario by paying of the principal of the next/subsequent installments that are not fully completed.
|
||||
*/
|
||||
protected Money handleOverpaymentTransaction(
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
final Money overPaymentAmount) {
|
||||
|
||||
Money transactionAmountRemaining = overPaymentAmount;
|
||||
Money principalPortion = Money.zero(currency);
|
||||
Money interestPortion = Money.zero(currency);
|
||||
Money interestWaivedPortion = Money.zero(currency);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
|
||||
principalPortion = installment.payPrincipalComponent(transactionAmountRemaining);
|
||||
if (installment.isPrincipalCompleted(currency)) {
|
||||
interestWaivedPortion = installment.waiveInterestComponent(installment.getInterest(currency));
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion);
|
||||
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
// reset portions for next installment
|
||||
principalPortion = Money.zero(currency);
|
||||
interestPortion = Money.zero(currency);
|
||||
interestWaivedPortion = Money.zero(currency);
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected Money handleOnTimeTransaction(final LoanRepaymentScheduleInstallment installment, final LoanTransaction loanTransaction, MonetaryCurrency currency) {
|
||||
|
||||
Money transactionAmountRemaining = loanTransaction.getAmount(currency);
|
||||
|
||||
Money interestPortion = installment.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
Money principalPortion = installment.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is overpayment if the amount in the transactions is greater than amount due.
|
||||
*/
|
||||
protected boolean isTransactionOverpayment(final int installmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Money transactionAmount) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
|
||||
|
||||
return transactionAmount.isGreaterThan(currentInstallment.getTotalDue(transactionAmount.getCurrency()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is late if the date of the transactions is after that of the installment due date.
|
||||
*/
|
||||
protected boolean isTransactionLate(final int installmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
|
||||
|
||||
return transactionDate.isAfter(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* is in advance if the transaction date is before or equal to last installment due date (in other words not after last installment date)
|
||||
*/
|
||||
protected boolean isTransactionInAdvance(final int installmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction transaction) {
|
||||
|
||||
boolean isInAdvance = false;
|
||||
|
||||
LocalDate lastInstallmentDueDate = null;
|
||||
if (installmentIndex > 0) {
|
||||
int previousInstallmentIndex = installmentIndex - 1;
|
||||
LoanRepaymentScheduleInstallment previousInstallment = installments.get(previousInstallmentIndex);
|
||||
lastInstallmentDueDate = previousInstallment.getDueDate();
|
||||
|
||||
return !transaction.getTransactionDate().isAfter(lastInstallmentDueDate);
|
||||
}
|
||||
|
||||
return isInAdvance;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package org.mifosng.platform.loan.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||
import org.mifosng.platform.currency.domain.Money;
|
||||
|
||||
/**
|
||||
* Heavensfamily style {@link LoanRepaymentScheduleTransactionProcessor}.
|
||||
*
|
||||
* For standard transactions, pays off components in order of interest, then
|
||||
* principal.
|
||||
*
|
||||
* If a transaction results in an advance payment or overpayment for a given
|
||||
* installment, the over paid amount is pay off on the principal component of
|
||||
* subsequent installments.
|
||||
*
|
||||
* If the entire principal of an installment is paid in advance then the interest component is waived.
|
||||
*/
|
||||
public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
/**
|
||||
* For late repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsALateRepaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate,
|
||||
final Money transactionAmount) {
|
||||
|
||||
boolean isInAdvance = false;
|
||||
|
||||
LocalDate lastInstallmentDueDate = null;
|
||||
if (currentInstallmentIndex > 0) {
|
||||
int previousInstallmentIndex = currentInstallmentIndex - 1;
|
||||
LoanRepaymentScheduleInstallment previousInstallment = installments.get(previousInstallmentIndex);
|
||||
lastInstallmentDueDate = previousInstallment.getDueDate();
|
||||
|
||||
return !transactionDate.isAfter(lastInstallmentDueDate);
|
||||
}
|
||||
|
||||
return isInAdvance;
|
||||
}
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments, pays off principal component only.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate, final Money paymentInAdvance) {
|
||||
|
||||
Money transactionAmountRemaining = paymentInAdvance;
|
||||
MonetaryCurrency currency = paymentInAdvance.getCurrency();
|
||||
|
||||
// placeholders for tracking the breakdown of components relevant to
|
||||
// this transaction.
|
||||
Money principalPortion = Money.zero(currency);
|
||||
Money interestPortion = Money.zero(currency);
|
||||
Money interestWaivedPortion = Money.zero(currency);
|
||||
|
||||
if (currentInstallment.isPrincipalNotCompleted(currency)) {
|
||||
principalPortion = currentInstallment
|
||||
.payPrincipalComponent(transactionAmountRemaining);
|
||||
if (currentInstallment.isPrincipalCompleted(currency)) {
|
||||
interestWaivedPortion = currentInstallment
|
||||
.waiveInterestComponent(currentInstallment
|
||||
.getInterest(currency));
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion,
|
||||
interestWaivedPortion);
|
||||
|
||||
transactionAmountRemaining = transactionAmountRemaining
|
||||
.minus(principalPortion);
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
|
||||
Money interestPortion = currentInstallment
|
||||
.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining
|
||||
.minus(interestPortion);
|
||||
|
||||
Money principalPortion = currentInstallment
|
||||
.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining
|
||||
.minus(principalPortion);
|
||||
|
||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining
|
||||
.getCurrency());
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion,
|
||||
interestWaivedPortion);
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoanOverpayment(
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money loanOverPaymentAmount) {
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
||||
|
||||
@Transient
|
||||
private final InterestRebateCalculatorFactory interestRebateCalculatorFactory = new DailyEquivalentInterestRebateCalculatorFactory();
|
||||
|
||||
@Transient
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory();
|
||||
|
||||
public static Loan createNew(final Fund fund,
|
||||
final LoanProduct loanProduct, final Client client,
|
||||
@@ -441,7 +444,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
||||
LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
|
||||
Collections.sort(repaymentsOrWaivers, transactionComparator);
|
||||
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = new DefaultLoanRepaymentScheduleTransactionProcessor();
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor();
|
||||
if (isTransactionChronologicallyLatest && !adjusted) {
|
||||
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments);
|
||||
} else {
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package org.mifosng.platform.loan.domain;
|
||||
|
||||
import org.mifosng.platform.infrastructure.MifosPlatformTenant;
|
||||
import org.mifosng.platform.infrastructure.ThreadLocalContextUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class LoanRepaymentScheduleTransactionProcessorFactory {
|
||||
|
||||
public LoanRepaymentScheduleTransactionProcessor determineProcessor() {
|
||||
|
||||
LoanRepaymentScheduleTransactionProcessor processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
|
||||
|
||||
// FIXME - Just temp way of deciding what 'repayment strategy' to use for now.
|
||||
MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||
if ("mifostenant-creocore".equalsIgnoreCase(tenant.getSchemaName())) {
|
||||
processor = new CreocoreLoanRepaymentScheduleTransactionProcessor();
|
||||
}
|
||||
|
||||
return processor;
|
||||
}
|
||||
|
||||
}
|
||||
+33
-3
@@ -14,10 +14,10 @@ import org.mifosng.platform.currency.domain.Money;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultLoanRepaymentScheduleTransactionProcessorTest {
|
||||
public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
|
||||
|
||||
// class under test
|
||||
private DefaultLoanRepaymentScheduleTransactionProcessor processor;
|
||||
private HeavensFamilyLoanRepaymentScheduleTransactionProcessor processor;
|
||||
|
||||
//
|
||||
private LocalDate july2nd = new LocalDate(2012, 7, 2);
|
||||
@@ -29,7 +29,7 @@ public class DefaultLoanRepaymentScheduleTransactionProcessorTest {
|
||||
|
||||
installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
|
||||
|
||||
processor = new DefaultLoanRepaymentScheduleTransactionProcessor();
|
||||
processor = new HeavensFamilyLoanRepaymentScheduleTransactionProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +326,36 @@ public class DefaultLoanRepaymentScheduleTransactionProcessorTest {
|
||||
|
||||
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'.
|
||||
*/
|
||||
@Test
|
||||
public void givenLateRepaymentOfInstallmentShouldPayoffInterestAndPrincipalComponentsOfOverdueInstallmentsWithNoWaiveOfInterest() {
|
||||
// setup
|
||||
installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
|
||||
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
|
||||
LocalDate latePaymentDate = firstInstallment.getDueDate().plusMonths(5);
|
||||
|
||||
Money amount = new MoneyBuilder().with(usDollars).with("3600.00").build();
|
||||
LoanTransaction lateRepaymentTransaction = new LoanTransactionBuilder().repayment().with(latePaymentDate).with(amount).build();
|
||||
|
||||
// execute test
|
||||
processor.handleTransaction(lateRepaymentTransaction, usDollars, installments);
|
||||
|
||||
// verification of derived fields on installments
|
||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
|
||||
|
||||
LoanRepaymentScheduleInstallment secondInstallment = this.installments.get(1);
|
||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), secondInstallment, usDollars);
|
||||
|
||||
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
||||
|
||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"), interestOf("600.00"), interestWaivedOf("0.00"), lateRepaymentTransaction, usDollars);
|
||||
}
|
||||
|
||||
|
||||
private void assertThatLoanTransactionHasDerivedFieldsForPrincipalInterestOf(
|
||||
final String principal, final String interest, final LoanTransaction transaction) {
|
||||
Reference in New Issue
Block a user