mifosx-90: validation of loan and loan charges state when adding and removing loan charges and when creating, modifying loan applications.

This commit is contained in:
Keith Woodlock
2012-11-06 15:12:16 +00:00
parent 81f7b614b4
commit b185bc5f5b
6 changed files with 59 additions and 11 deletions
@@ -88,6 +88,11 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
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);
@@ -90,6 +90,12 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
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);
@@ -120,6 +126,11 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
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);
@@ -261,6 +261,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
public void addLoanCharge(final LoanCharge loanCharge) { public void addLoanCharge(final LoanCharge loanCharge) {
validateLoanIsNotClosed(loanCharge);
if (isDisbursed() && loanCharge.isDueAtDisbursement()) { if (isDisbursed() && loanCharge.isDueAtDisbursement()) {
// Note: added this constraint to restrict adding charges to a loan after it is disbursed // Note: added this constraint to restrict adding charges to a loan after it is disbursed
// if the loan charge payment type is 'Disbursement'. // if the loan charge payment type is 'Disbursement'.
@@ -272,10 +274,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
throw new LoanChargeCannotBeAddedException("loanCharge", "due.at.disbursement.and.loan.is.disbursed", defaultUserMessage, getId(), loanCharge.name()); throw new LoanChargeCannotBeAddedException("loanCharge", "due.at.disbursement.and.loan.is.disbursed", defaultUserMessage, getId(), loanCharge.name());
} }
if (loanCharge.isSpecifiedDueDate() && !loanCharge.isDueForCollectionBetween(getDisbursementDate(), getLastRepaymentPeriodDueDate())) { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
final String defaultUserMessage = "This charge which is due at disbursement cannot be added as the loan is already disbursed.";
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage, getDisbursementDate(), getLastRepaymentPeriodDueDate(), loanCharge.name());
}
loanCharge.update(this); loanCharge.update(this);
this.charges.add(loanCharge); this.charges.add(loanCharge);
@@ -294,11 +293,28 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
private void validateLoanIsNotClosed(final LoanCharge loanCharge) {
if (isClosed()) {
final String defaultUserMessage = "This charge cannot be added as the loan is already closed.";
throw new LoanChargeCannotBeAddedException("loanCharge", "loan.is.closed", defaultUserMessage, getId(), loanCharge.name());
}
}
private void validateChargeHasValidSpecifiedDateIfApplicable(final LoanCharge loanCharge, final LocalDate disbursementDate, final LocalDate lastRepaymentPeriodDueDate) {
if (loanCharge.isSpecifiedDueDate() && !loanCharge.isDueForCollectionBetween(disbursementDate, lastRepaymentPeriodDueDate)) {
final String defaultUserMessage = "This charge which is due at disbursement cannot be added as the loan is already disbursed.";
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage, getDisbursementDate(), getLastRepaymentPeriodDueDate(), loanCharge.name());
}
}
private LocalDate getLastRepaymentPeriodDueDate() { private LocalDate getLastRepaymentPeriodDueDate() {
return this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size()-1).getDueDate(); return this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size()-1).getDueDate();
} }
public void removeLoanCharge(final LoanCharge loanCharge) { public void removeLoanCharge(final LoanCharge loanCharge) {
validateLoanIsNotClosed(loanCharge);
boolean removed = this.charges.remove(loanCharge); boolean removed = this.charges.remove(loanCharge);
if (removed) { if (removed) {
updateTotalChargesDueAtDisbursement(); updateTotalChargesDueAtDisbursement();
@@ -531,6 +547,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
"cannot.be.after.expected.disbursement.date", errorMessage, "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOn, getExpectedDisbursedOnLocalDate()); submittedOn, getExpectedDisbursedOnLocalDate());
} }
for (LoanCharge loanCharge : this.charges) {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
} }
public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
@@ -25,7 +25,7 @@ public class LoanRepaymentScheduleTransactionProcessorFactory {
} }
if (transactionProcessingStrategy.isIndianRBIStrategy()) { if (transactionProcessingStrategy.isIndianRBIStrategy()) {
processor = new AdhikarLoanRepaymentScheduleTransactionProcessor(); processor = new RBILoanRepaymentScheduleTransactionProcessor();
} }
} }
@@ -7,7 +7,7 @@ import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
/** /**
* Adhikar style {@link LoanRepaymentScheduleTransactionProcessor}. * Adhikar/RBI style {@link LoanRepaymentScheduleTransactionProcessor}.
* *
* From https://mifosforge.jira.com/browse/MIFOS-5636: * From https://mifosforge.jira.com/browse/MIFOS-5636:
* *
@@ -19,7 +19,7 @@ import org.mifosng.platform.currency.domain.Money;
* 20 Payment to interest on Installment #1 (200 principal remaining) * 20 Payment to interest on Installment #1 (200 principal remaining)
* 20 Payment to interest on Installment #2 (200 principal remaining) * 20 Payment to interest on Installment #2 (200 principal remaining)
*/ */
public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor { public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
/** /**
* For creocore, early is defined as any date before the installment due * For creocore, early is defined as any date before the installment due
@@ -84,6 +84,12 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
installment.isInterestDue(currency) && installment.isInterestDue(currency) &&
(installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(currentInstallmentBasedOnTransactionDate.getInstallmentNumber())) (installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(currentInstallmentBasedOnTransactionDate.getInstallmentNumber()))
) { ) {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
Money interestPortion = installment.payInterestComponent(transactionAmountRemaining); Money interestPortion = installment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -140,6 +146,12 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
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);
@@ -18,7 +18,7 @@ import org.mockito.runners.MockitoJUnitRunner;
public class AdhikarLoanRepaymentScheduleTransactionProcessorTest { public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
// class under test // class under test
private AdhikarLoanRepaymentScheduleTransactionProcessor processor; private RBILoanRepaymentScheduleTransactionProcessor processor;
// //
private LocalDate july2nd = new LocalDate(2012, 7, 2); private LocalDate july2nd = new LocalDate(2012, 7, 2);
@@ -30,7 +30,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars); installments = LoanScheduleTestDataHelper.createSimpleLoanSchedule(july2nd, usDollars);
processor = new AdhikarLoanRepaymentScheduleTransactionProcessor(); processor = new RBILoanRepaymentScheduleTransactionProcessor();
} }
/** /**