MIFOSX-376: track when an installment is completed in full.
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
ADD COLUMN `obligations_met_on_date` DATE NULL DEFAULT NULL AFTER `completed_derived`;
|
||||
|
||||
ALTER TABLE `m_loan_repayment_schedule`
|
||||
CHANGE COLUMN `interest_waived_derived` `interest_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `interest_writtenoff_derived` ;
|
||||
+9
-6
@@ -43,6 +43,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
currentInstallment.updateDerivedFields(currency, disbursementDate);
|
||||
}
|
||||
|
||||
// re-process loan charges over repayment periods (picking up on waived
|
||||
@@ -108,7 +109,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
|
||||
if (currentInstallment.isNotFullyCompleted()) {
|
||||
if (currentInstallment.isNotFullyPaidOff()) {
|
||||
|
||||
// is this transaction early/late/on-time with respect to the
|
||||
// current installment?
|
||||
@@ -205,6 +206,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
public void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money principalPortion = Money.zero(currency);
|
||||
Money interestPortion = Money.zero(currency);
|
||||
Money feeChargesPortion = Money.zero(currency);
|
||||
@@ -214,11 +216,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
// principal, interest and charges
|
||||
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
|
||||
if (currentInstallment.isNotFullyCompleted()) {
|
||||
principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(currency));
|
||||
interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(currency));
|
||||
feeChargesPortion = feeChargesPortion.plus(currentInstallment.writeOffOutstandingFeeCharges(currency));
|
||||
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.writeOffOutstandingPenaltyCharges(currency));
|
||||
if (currentInstallment.isNotFullyPaidOff()) {
|
||||
principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(transactionDate, currency));
|
||||
interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(transactionDate, currency));
|
||||
feeChargesPortion = feeChargesPortion.plus(currentInstallment.writeOffOutstandingFeeCharges(transactionDate, currency));
|
||||
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.writeOffOutstandingPenaltyCharges(transactionDate,
|
||||
currency));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+83
-92
@@ -24,105 +24,96 @@ import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
* If the entire principal of an installment is paid in advance then the
|
||||
* interest component is waived.
|
||||
*/
|
||||
public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
|
||||
AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
/**
|
||||
* For creocore, early is defined as any date before the installment due
|
||||
* date
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate, final Money transactionAmount) {
|
||||
/**
|
||||
* For creocore, early is defined as any date before the installment due
|
||||
* date
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@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 early/'in advance' repayments, pay off in the same way as on-time
|
||||
* payments, interest first then principal.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate, final Money paymentInAdvance) {
|
||||
|
||||
/**
|
||||
* For late repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected Money handleTransactionThatIsALateRepaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance);
|
||||
}
|
||||
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed);
|
||||
}
|
||||
/**
|
||||
* For late repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
|
||||
final Money 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) {
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed);
|
||||
}
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
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) {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction,
|
||||
final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
}
|
||||
+25
-21
@@ -55,7 +55,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
lastInstallmentDueDate = previousInstallment.getDueDate();
|
||||
|
||||
isInAdvance = !(transactionDate.isAfter(lastInstallmentDueDate) || (transactionDate.isEqual(lastInstallmentDueDate)));
|
||||
|
||||
|
||||
return isInAdvance;
|
||||
}
|
||||
|
||||
@@ -75,16 +75,16 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
Money penaltyChargesPortion = Money.zero(currency);
|
||||
|
||||
if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
|
||||
if (currentInstallment.isPrincipalNotCompleted(currency)) {
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
if (currentInstallment.isPrincipalCompleted(currency)) {
|
||||
// FIXME - KW - if auto waiving interest need to create
|
||||
// another transaction to handle this.
|
||||
currentInstallment.waiveInterestComponent(currentInstallment.getInterestCharged(currency));
|
||||
currentInstallment.waiveInterestComponent(transactionDate, currentInstallment.getInterestCharged(currency));
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
@@ -93,16 +93,16 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
}
|
||||
|
||||
// 1. pay of principal with over payment.
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
@@ -125,27 +126,30 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(loanTransaction.getFeeChargesPortion(currency));
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
// 1. pay of principal before interest.
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
}
|
||||
|
||||
|
||||
+12
-10
@@ -1296,17 +1296,12 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
|
||||
/***
|
||||
* Adding a temporary Interest applied transaction (applying the
|
||||
* full Interest as soon as the loan is disbursed), will have to
|
||||
* re-look into this logic once loans with Interest calculation need
|
||||
* to be created or more advances accrual accounting support has to
|
||||
* be provided
|
||||
***/
|
||||
Money interestApplied = Money.of(this.getCurrency(), this.summary.getTotalInterestCharged());
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.applyInterest(this, interestApplied, actualDisbursementDate);
|
||||
this.loanTransactions.add(interestAppliedTransaction);
|
||||
@@ -1315,6 +1310,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||
|
||||
for (LoanRepaymentScheduleInstallment repaymentPeriod : this.repaymentScheduleInstallments) {
|
||||
repaymentPeriod.updateDerivedFields(this.loanCurrency(), actualDisbursementDate);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ability to regenerate the repayment schedule based on the loans current
|
||||
* details/state.
|
||||
@@ -1623,7 +1625,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public LocalDate possibleNextRepaymentDate() {
|
||||
LocalDate earliestUnpaidInstallmentDate = new LocalDate();
|
||||
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||
if (installment.isNotFullyCompleted()) {
|
||||
if (installment.isNotFullyPaidOff()) {
|
||||
earliestUnpaidInstallmentDate = installment.getDueDate();
|
||||
break;
|
||||
}
|
||||
@@ -1649,7 +1651,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
Money possibleNextRepaymentAmount = Money.zero(currency);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||
if (installment.isNotFullyCompleted()) {
|
||||
if (installment.isNotFullyPaidOff()) {
|
||||
possibleNextRepaymentAmount = installment.getTotalOutstanding(currency);
|
||||
break;
|
||||
}
|
||||
@@ -1670,7 +1672,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
|
||||
|
||||
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
|
||||
if (scheduledRepayment.isOverdueOn(new LocalDate()) && scheduledRepayment.isNotFullyCompleted()
|
||||
if (scheduledRepayment.isOverdueOn(new LocalDate()) && scheduledRepayment.isNotFullyPaidOff()
|
||||
&& outstandingForPeriod.isGreaterThanZero()) {
|
||||
transactionDate = scheduledRepayment.getDueDate();
|
||||
possibleInterestToWaive = outstandingForPeriod;
|
||||
|
||||
+58
-34
@@ -87,13 +87,18 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
private BigDecimal penaltyChargesWaived;
|
||||
|
||||
@Column(name = "completed_derived", nullable = false)
|
||||
private boolean completed;
|
||||
private boolean obligationsMet;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Temporal(TemporalType.DATE)
|
||||
@Column(name = "obligations_met_on_date")
|
||||
private Date obligationsMetOnDate;
|
||||
|
||||
protected LoanRepaymentScheduleInstallment() {
|
||||
this.installmentNumber = null;
|
||||
this.fromDate = null;
|
||||
this.dueDate = null;
|
||||
this.completed = false;
|
||||
this.obligationsMet = false;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
|
||||
@@ -107,7 +112,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.interestCharged = defaultToNullIfZero(interest);
|
||||
this.feeChargesCharged = defaultToNullIfZero(feeCharges);
|
||||
this.penaltyCharges = defaultToNullIfZero(penaltyCharges);
|
||||
this.completed = false;
|
||||
this.obligationsMet = false;
|
||||
}
|
||||
|
||||
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
|
||||
@@ -173,8 +178,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
}
|
||||
|
||||
public Money getInterestOutstanding(final MonetaryCurrency currency) {
|
||||
final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency)).plus(
|
||||
getInterestWrittenOff(currency));
|
||||
final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency))
|
||||
.plus(getInterestWrittenOff(currency));
|
||||
return getInterestCharged(currency).minus(interestAccountedFor);
|
||||
}
|
||||
|
||||
@@ -239,12 +244,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.loan = loan;
|
||||
}
|
||||
|
||||
public boolean isFullyCompleted() {
|
||||
return this.completed;
|
||||
public boolean isObligationsMet() {
|
||||
return this.obligationsMet;
|
||||
}
|
||||
|
||||
public boolean isNotFullyCompleted() {
|
||||
return !this.completed;
|
||||
public boolean isNotFullyPaidOff() {
|
||||
return !this.obligationsMet;
|
||||
}
|
||||
|
||||
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
|
||||
@@ -268,10 +273,11 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.penaltyChargesWaived = null;
|
||||
this.penaltyChargesWrittenOff = null;
|
||||
|
||||
this.completed = false;
|
||||
this.obligationsMet = false;
|
||||
this.obligationsMetOnDate = null;
|
||||
}
|
||||
|
||||
public Money payPenaltyChargesComponent(final Money transactionAmountRemaining) {
|
||||
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money penaltyPortionOfTransaction = Money.zero(currency);
|
||||
@@ -287,12 +293,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.penaltyChargesPaid = defaultToNullIfZero(this.penaltyChargesPaid);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return penaltyPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money payFeeChargesComponent(final Money transactionAmountRemaining) {
|
||||
public Money payFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money feePortionOfTransaction = Money.zero(currency);
|
||||
@@ -308,12 +314,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.feeChargesPaid = defaultToNullIfZero(this.feeChargesPaid);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return feePortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money payInterestComponent(final Money transactionAmountRemaining) {
|
||||
public Money payInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money interestPortionOfTransaction = Money.zero(currency);
|
||||
@@ -329,12 +335,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.interestPaid = defaultToNullIfZero(this.interestPaid);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return interestPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money payPrincipalComponent(final Money transactionAmountRemaining) {
|
||||
public Money payPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money principalPortionOfTransaction = Money.zero(currency);
|
||||
@@ -350,12 +356,19 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return principalPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money waiveInterestComponent(final Money transactionAmountRemaining) {
|
||||
private void checkIfRepaymentPeriodObligationsAreMet(final LocalDate transactionDate, final MonetaryCurrency currency) {
|
||||
this.obligationsMet = getTotalOutstanding(currency).isZero();
|
||||
if (this.obligationsMet) {
|
||||
this.obligationsMetOnDate = transactionDate.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
public Money waiveInterestComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money waivedInterestPortionOfTransaction = Money.zero(currency);
|
||||
|
||||
@@ -370,12 +383,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.interestWaived = defaultToNullIfZero(this.interestWaived);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return waivedInterestPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money waivePenaltyChargesComponent(final Money transactionAmountRemaining) {
|
||||
public Money waivePenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
|
||||
|
||||
@@ -390,12 +403,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.penaltyChargesWaived = defaultToNullIfZero(this.penaltyChargesWaived);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return waivedPenaltyChargesPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money waiveFeeChargesComponent(final Money transactionAmountRemaining) {
|
||||
public Money waiveFeeChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
|
||||
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
|
||||
|
||||
@@ -410,47 +423,51 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
|
||||
this.feeChargesWaived = defaultToNullIfZero(this.feeChargesWaived);
|
||||
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return waivedFeeChargesPortionOfTransaction;
|
||||
}
|
||||
|
||||
public Money writeOffOutstandingPrincipal(final MonetaryCurrency currency) {
|
||||
public Money writeOffOutstandingPrincipal(final LocalDate transactionDate, final MonetaryCurrency currency) {
|
||||
|
||||
final Money principalDue = getPrincipalOutstanding(currency);
|
||||
this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount());
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return principalDue;
|
||||
}
|
||||
|
||||
public Money writeOffOutstandingInterest(final MonetaryCurrency currency) {
|
||||
public Money writeOffOutstandingInterest(final LocalDate transactionDate, final MonetaryCurrency currency) {
|
||||
|
||||
final Money interestDue = getInterestOutstanding(currency);
|
||||
this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount());
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return interestDue;
|
||||
}
|
||||
|
||||
public Money writeOffOutstandingFeeCharges(final MonetaryCurrency currency) {
|
||||
public Money writeOffOutstandingFeeCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
|
||||
final Money feeChargesOutstanding = getFeeChargesOutstanding(currency);
|
||||
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesOutstanding.getAmount());
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return feeChargesOutstanding;
|
||||
}
|
||||
|
||||
public Money writeOffOutstandingPenaltyCharges(final MonetaryCurrency currency) {
|
||||
public Money writeOffOutstandingPenaltyCharges(final LocalDate transactionDate, final MonetaryCurrency currency) {
|
||||
final Money penaltyChargesOutstanding = getPenaltyChargesOutstanding(currency);
|
||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesOutstanding.getAmount());
|
||||
this.completed = getTotalOutstanding(currency).isZero();
|
||||
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||
|
||||
return penaltyChargesOutstanding;
|
||||
}
|
||||
|
||||
public boolean isOverdueOn(final LocalDate transactionDate) {
|
||||
return this.getDueDate().isBefore(transactionDate);
|
||||
public boolean isOverdueOn(final LocalDate date) {
|
||||
return this.getDueDate().isBefore(date);
|
||||
}
|
||||
|
||||
public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
||||
@@ -462,4 +479,11 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
|
||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
|
||||
}
|
||||
|
||||
public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
|
||||
if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
|
||||
this.obligationsMet = true;
|
||||
this.obligationsMetOnDate = actualDisbursementDate.toDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
-92
@@ -17,107 +17,101 @@ import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
* For ALL types of transactions, pays off components in order of interest, then
|
||||
* principal.
|
||||
*
|
||||
* Other formulas exist on mifos where you can choose 'Declining-Balance Interest Recalculation' which simply
|
||||
* means, recalculate the interest component based on the how much principal is outstanding at a point in time;
|
||||
* Other formulas exist on mifos where you can choose 'Declining-Balance
|
||||
* Interest Recalculation' which simply means, recalculate the interest
|
||||
* component based on the how much principal is outstanding at a point in time;
|
||||
* but this isnt trying to model that option only the basic one for now.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
|
||||
AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate, final Money transactionAmount) {
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments, pay off in the same way as on-time payments,
|
||||
* interest first then principal.
|
||||
*/
|
||||
@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 early/'in advance' repayments, pay off in the same way as on-time
|
||||
* payments, interest first then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate, final Money 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, paymentInAdvance);
|
||||
}
|
||||
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed);
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
// transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
//
|
||||
// feeChargesPortion = currentInstallment.waiveFeeChargesComponent(loanTransaction.getFeeChargesPortion(currency));
|
||||
// transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
//
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
} else {
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction,
|
||||
final Money loanOverPaymentAmount) {
|
||||
// TODO - KW - dont do anything with loan over-payment for now
|
||||
}
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// penaltyChargesPortion =
|
||||
// currentInstallment.waivePenaltyChargesComponent(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
// transactionAmountRemaining =
|
||||
// transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
//
|
||||
// feeChargesPortion =
|
||||
// currentInstallment.waiveFeeChargesComponent(loanTransaction.getFeeChargesPortion(currency));
|
||||
// transactionAmountRemaining =
|
||||
// transactionAmountRemaining.minus(feeChargesPortion);
|
||||
//
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
} else {
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
|
||||
// TODO - KW - dont do anything with loan over-payment for now
|
||||
}
|
||||
}
|
||||
+153
-155
@@ -16,171 +16,169 @@ import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
*
|
||||
* From https://mifosforge.jira.com/browse/MIFOS-5636:
|
||||
*
|
||||
* Per RBI regulations, all interest must be paid (both current and overdue) before principal is paid.
|
||||
* Per RBI regulations, all interest must be paid (both current and overdue)
|
||||
* before principal is paid.
|
||||
*
|
||||
* For example on a loan with two installments due (one current and one overdue) of 220 each (200 principal + 20 interest):
|
||||
* For example on a loan with two installments due (one current and one overdue)
|
||||
* of 220 each (200 principal + 20 interest):
|
||||
*
|
||||
* Partial Payment of 40
|
||||
* 20 Payment to interest on Installment #1 (200 principal remaining)
|
||||
* 20 Payment to interest on Installment #2 (200 principal remaining)
|
||||
* Partial Payment of 40 20 Payment to interest on Installment #1 (200 principal
|
||||
* remaining) 20 Payment to interest on Installment #2 (200 principal remaining)
|
||||
*/
|
||||
public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
/**
|
||||
* For creocore, early is defined as any date before the installment due
|
||||
* date
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(
|
||||
final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LocalDate transactionDate, final Money transactionAmount) {
|
||||
/**
|
||||
* For creocore, early is defined as any date before the installment due
|
||||
* date
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate, final Money transactionAmount) {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
|
||||
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
return transactionDate.isBefore(currentInstallment.getDueDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* For early/'in advance' repayments, pays off principal component only.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@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 early/'in advance' repayments, pays off principal component only.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
|
||||
final LocalDate transactionDate, final Money 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) {
|
||||
|
||||
// pay of overdue and current interest due given transaction date
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money interestWaivedPortion = Money.zero(currency);
|
||||
Money feeChargesPortion = Money.zero(currency);
|
||||
Money penaltyChargesPortion = Money.zero(currency);
|
||||
|
||||
if (loanTransaction.isInterestWaiver()) {
|
||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
||||
|
||||
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
loanTransaction.updateComponents(principalPortion, interestWaivedPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
} else {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (
|
||||
installment.isInterestDue(currency) &&
|
||||
(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);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
Money principalPortion = Money.zero(currency);
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
|
||||
}
|
||||
}
|
||||
|
||||
// With whatever is remaining, pay off principal components of installments
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
|
||||
Money principalPortion = installment.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
Money interestPortion = Money.zero(currency);
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance);
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment nearestInstallment(final LocalDate transactionDate, List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
LoanRepaymentScheduleInstallment nearest = installments.get(0);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.getDueDate().isAfter(transactionDate) || installment.getDueDate().isEqual(transactionDate)) {
|
||||
nearest = installment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction,
|
||||
final Money transactionAmountUnprocessed) {
|
||||
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
// pay of overdue and current interest due given transaction date
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money interestWaivedPortion = Money.zero(currency);
|
||||
Money feeChargesPortion = Money.zero(currency);
|
||||
Money penaltyChargesPortion = Money.zero(currency);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction,
|
||||
final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
if (loanTransaction.isInterestWaiver()) {
|
||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
||||
|
||||
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
loanTransaction.updateComponents(principalPortion, interestWaivedPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
} else {
|
||||
|
||||
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(
|
||||
loanTransaction.getTransactionDate(), installments);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.isInterestDue(currency)
|
||||
&& (installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(
|
||||
currentInstallmentBasedOnTransactionDate.getInstallmentNumber()))) {
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
Money interestPortion = installment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
Money principalPortion = Money.zero(currency);
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
|
||||
}
|
||||
}
|
||||
|
||||
// With whatever is remaining, pay off principal components of
|
||||
// installments
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
|
||||
Money principalPortion = installment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
|
||||
Money interestPortion = Money.zero(currency);
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(currency), Money.zero(currency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment nearestInstallment(final LocalDate transactionDate,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
LoanRepaymentScheduleInstallment nearest = installments.get(0);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (installment.getDueDate().isAfter(transactionDate) || installment.getDueDate().isEqual(transactionDate)) {
|
||||
nearest = installment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/**
|
||||
* For normal on-time repayments, pays off interest first, then principal.
|
||||
*/
|
||||
@Override
|
||||
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
|
||||
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
|
||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
} else {
|
||||
|
||||
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||
}
|
||||
|
||||
loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
return transactionAmountRemaining;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
|
||||
// dont do anything for with loan over-payment
|
||||
}
|
||||
}
|
||||
+33
-17
@@ -19,6 +19,10 @@ public class LoanSchedulePeriodData {
|
||||
private final Integer period;
|
||||
private final LocalDate fromDate;
|
||||
private final LocalDate dueDate;
|
||||
@SuppressWarnings("unused")
|
||||
private final LocalDate obligationsMetOnDate;
|
||||
@SuppressWarnings("unused")
|
||||
private final Boolean complete;
|
||||
private final Integer daysInPeriod;
|
||||
private final BigDecimal principalDisbursed;
|
||||
@SuppressWarnings("unused")
|
||||
@@ -79,8 +83,9 @@ public class LoanSchedulePeriodData {
|
||||
}
|
||||
|
||||
public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
|
||||
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principalOriginalDue,
|
||||
final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
|
||||
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
|
||||
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
|
||||
final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
|
||||
final BigDecimal outstandingPrincipalBalanceOfLoan, final BigDecimal interestDueOnPrincipalOutstanding,
|
||||
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
|
||||
final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid,
|
||||
@@ -90,12 +95,12 @@ public class LoanSchedulePeriodData {
|
||||
final BigDecimal totalPaid, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
|
||||
final BigDecimal totalActualCostOfLoanForPeriod) {
|
||||
|
||||
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalOriginalDue, principalPaid, principalWrittenOff,
|
||||
principalOutstanding, outstandingPrincipalBalanceOfLoan, interestDueOnPrincipalOutstanding, interestPaid, interestWaived,
|
||||
interestWrittenOff, interestOutstanding, feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff,
|
||||
feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff,
|
||||
penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding,
|
||||
totalActualCostOfLoanForPeriod);
|
||||
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue,
|
||||
principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
|
||||
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue,
|
||||
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid,
|
||||
penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalWaived,
|
||||
totalWrittenOff, totalOutstanding, totalActualCostOfLoanForPeriod);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -107,6 +112,8 @@ public class LoanSchedulePeriodData {
|
||||
this.period = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.obligationsMetOnDate = null;
|
||||
this.complete = null;
|
||||
if (fromDate != null) {
|
||||
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
|
||||
} else {
|
||||
@@ -160,6 +167,10 @@ public class LoanSchedulePeriodData {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* used for repayment only period when creating an empty loan schedule for
|
||||
* preview etc
|
||||
*/
|
||||
private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
|
||||
final BigDecimal principalOriginalDue, final BigDecimal principalOutstanding,
|
||||
final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal feeChargesDueForPeriod,
|
||||
@@ -167,6 +178,8 @@ public class LoanSchedulePeriodData {
|
||||
this.period = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.obligationsMetOnDate = null;
|
||||
this.complete = null;
|
||||
if (fromDate != null) {
|
||||
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
|
||||
} else {
|
||||
@@ -219,18 +232,21 @@ public class LoanSchedulePeriodData {
|
||||
* principal, interest & charges along with what portion of each is paid.
|
||||
*/
|
||||
private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate,
|
||||
final BigDecimal principalOriginalDue, final BigDecimal principalPaid, final BigDecimal principalWrittenOff,
|
||||
final BigDecimal principalOutstanding, final BigDecimal principalLoanBalanceOutstanding,
|
||||
final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal interestPaid, final BigDecimal interestWaived,
|
||||
final BigDecimal interestWrittenOff, final BigDecimal interestOutstanding, final BigDecimal feeChargesDue,
|
||||
final BigDecimal feeChargesPaid, final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff,
|
||||
final BigDecimal feeChargesOutstanding, final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid,
|
||||
final BigDecimal penaltyChargesWaived, final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding,
|
||||
final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalWaived, final BigDecimal totalWrittenOff,
|
||||
final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod) {
|
||||
final LocalDate obligationsMetOnDate, final boolean complete, final BigDecimal principalOriginalDue,
|
||||
final BigDecimal principalPaid, final BigDecimal principalWrittenOff, final BigDecimal principalOutstanding,
|
||||
final BigDecimal principalLoanBalanceOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
|
||||
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
|
||||
final BigDecimal interestOutstanding, final BigDecimal feeChargesDue, final BigDecimal feeChargesPaid,
|
||||
final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding,
|
||||
final BigDecimal penaltyChargesDue, final BigDecimal penaltyChargesPaid, final BigDecimal penaltyChargesWaived,
|
||||
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod,
|
||||
final BigDecimal totalPaid, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
|
||||
final BigDecimal totalActualCostOfLoanForPeriod) {
|
||||
this.period = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.obligationsMetOnDate = obligationsMetOnDate;
|
||||
this.complete = complete;
|
||||
if (fromDate != null) {
|
||||
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
|
||||
} else {
|
||||
|
||||
+27
-4
@@ -94,10 +94,33 @@ public final class LoanEventApiJsonValidator {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseDataValidator
|
||||
* @param element
|
||||
*/
|
||||
public void validateNewRepaymentTransaction(final String json) {
|
||||
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Set<String> transactionParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note",
|
||||
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
|
||||
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
|
||||
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
final String note = fromApiJsonHelper.extractStringNamed("note", element);
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
|
||||
|
||||
+9
-7
@@ -672,7 +672,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
|
||||
public String schema() {
|
||||
|
||||
return " ls.loan_id as loanId, ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, "
|
||||
return " ls.loan_id as loanId, ls.installment as period, ls.fromdate as fromDate, ls.duedate as dueDate, ls.obligations_met_on_date as obligationsMetOnDate, ls.completed_derived as complete,"
|
||||
+ " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, ls.principal_writtenoff_derived as principalWrittenOff, "
|
||||
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, "
|
||||
+ " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, "
|
||||
@@ -716,6 +716,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final Integer period = JdbcSupport.getInteger(rs, "period");
|
||||
LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate");
|
||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
|
||||
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
|
||||
final boolean complete = rs.getBoolean("complete");
|
||||
|
||||
Integer daysInPeriod = Integer.valueOf(0);
|
||||
if (fromDate != null) {
|
||||
@@ -788,12 +790,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
outstandingLoanPrincipalBalance = outstandingLoanPrincipalBalance.subtract(principalDue);
|
||||
|
||||
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate,
|
||||
dueDate, principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
|
||||
interestExpectedDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesExpectedDue,
|
||||
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesExpectedDue,
|
||||
penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod,
|
||||
totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod,
|
||||
totalActualCostOfLoanForPeriod);
|
||||
dueDate, obligationsMetOnDate, complete, principalDue, principalPaid, principalWrittenOff, principalOutstanding,
|
||||
outstandingPrincipalBalanceOfLoan, interestExpectedDue, interestPaid, interestWaived, interestWrittenOff,
|
||||
interestOutstanding, feeChargesExpectedDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff,
|
||||
feeChargesOutstanding, penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived,
|
||||
penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod,
|
||||
totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod);
|
||||
|
||||
periods.add(periodData);
|
||||
}
|
||||
|
||||
+1
-1
@@ -297,7 +297,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
context.authenticatedUser();
|
||||
|
||||
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
||||
this.loanEventApiJsonValidator.validateNewRepaymentTransaction(command.json());
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
|
||||
+48
-46
@@ -8,54 +8,56 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInsta
|
||||
|
||||
public class LoanRepaymentScheduleInstallmentBuilder {
|
||||
|
||||
private Loan loan = null;
|
||||
private Integer installmentNumber = Integer.valueOf(1);
|
||||
private LocalDate fromDate = LocalDate.now();
|
||||
private LocalDate dueDate = LocalDate.now();
|
||||
private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
|
||||
private Money principal = new MoneyBuilder().build();
|
||||
private Money interest = new MoneyBuilder().build();
|
||||
private Money feeCharges = new MoneyBuilder().build();
|
||||
private Money penaltyCharges = new MoneyBuilder().build();
|
||||
private boolean completed = false;
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder(MonetaryCurrency currencyDetail) {
|
||||
this.currencyDetail = currencyDetail;
|
||||
this.principal = new MoneyBuilder().with(currencyDetail).build();
|
||||
this.interest = new MoneyBuilder().with(currencyDetail).build();
|
||||
}
|
||||
private final Loan loan = null;
|
||||
private Integer installmentNumber = Integer.valueOf(1);
|
||||
private final LocalDate fromDate = LocalDate.now();
|
||||
private LocalDate dueDate = LocalDate.now();
|
||||
private final LocalDate latestTransactionDate = LocalDate.now();
|
||||
private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
|
||||
private Money principal = new MoneyBuilder().build();
|
||||
private Money interest = new MoneyBuilder().build();
|
||||
private final Money feeCharges = new MoneyBuilder().build();
|
||||
private final Money penaltyCharges = new MoneyBuilder().build();
|
||||
private boolean completed = false;
|
||||
|
||||
public LoanRepaymentScheduleInstallment build() {
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal.getAmount(), interest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount());
|
||||
if (completed) {
|
||||
installment.payPrincipalComponent(principal);
|
||||
installment.payInterestComponent(interest);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
public LoanRepaymentScheduleInstallmentBuilder(final MonetaryCurrency currencyDetail) {
|
||||
this.currencyDetail = currencyDetail;
|
||||
this.principal = new MoneyBuilder().with(currencyDetail).build();
|
||||
this.interest = new MoneyBuilder().with(currencyDetail).build();
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder withPrincipal(String withPrincipal) {
|
||||
this.principal = new MoneyBuilder().with(currencyDetail).with(withPrincipal).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder withInterest(String withInterest) {
|
||||
this.interest = new MoneyBuilder().with(currencyDetail).with(withInterest).build();
|
||||
return this;
|
||||
}
|
||||
public LoanRepaymentScheduleInstallment build() {
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate,
|
||||
principal.getAmount(), interest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount());
|
||||
if (completed) {
|
||||
installment.payPrincipalComponent(latestTransactionDate, principal);
|
||||
installment.payInterestComponent(latestTransactionDate, interest);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder withDueDate(LocalDate withDueDate) {
|
||||
this.dueDate = withDueDate;
|
||||
return this;
|
||||
}
|
||||
public LoanRepaymentScheduleInstallmentBuilder withPrincipal(final String withPrincipal) {
|
||||
this.principal = new MoneyBuilder().with(currencyDetail).with(withPrincipal).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder completed() {
|
||||
this.completed = true;
|
||||
return this;
|
||||
}
|
||||
public LoanRepaymentScheduleInstallmentBuilder withInterest(final String withInterest) {
|
||||
this.interest = new MoneyBuilder().with(currencyDetail).with(withInterest).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder withInstallmentNumber(final int withInstallmentNumber) {
|
||||
this.installmentNumber = withInstallmentNumber;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
public LoanRepaymentScheduleInstallmentBuilder withDueDate(final LocalDate withDueDate) {
|
||||
this.dueDate = withDueDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder completed() {
|
||||
this.completed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallmentBuilder withInstallmentNumber(final int withInstallmentNumber) {
|
||||
this.installmentNumber = withInstallmentNumber;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user