diff --git a/mifosng-db/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql b/mifosng-db/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql new file mode 100644 index 000000000..a383eec13 --- /dev/null +++ b/mifosng-db/migrations/core_db/V51__track-additional-details-related-to-installment-performance.sql @@ -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` ; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java index a9d4fea52..bfc23e4af 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -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 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)); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/CreocoreLoanRepaymentScheduleTransactionProcessor.java index 9501569b7..53e17cc6a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/CreocoreLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/CreocoreLoanRepaymentScheduleTransactionProcessor.java @@ -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 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 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 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 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 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 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 + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java index 52dc3abd9..4c18cf57c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java @@ -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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 1a59b08fa..918076ea6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1296,17 +1296,12 @@ public class Loan extends AbstractPersistable { 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 { 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 { 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 { 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 { 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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 9ab626877..504afc65f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -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(); + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/MifosStyleLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/MifosStyleLoanRepaymentScheduleTransactionProcessor.java index 4c402ea5f..30601d626 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/MifosStyleLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/MifosStyleLoanRepaymentScheduleTransactionProcessor.java @@ -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 installments, - final LocalDate transactionDate, final Money transactionAmount) { + @Override + protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, + final List 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 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 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 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 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 + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/RBILoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/RBILoanRepaymentScheduleTransactionProcessor.java index b5bd93db5..f40220847 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/RBILoanRepaymentScheduleTransactionProcessor.java @@ -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 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 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 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 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 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 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 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 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 + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java index 6729921d3..8659c33ee 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java @@ -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 { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java index 571c8d51c..74a8c6733 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java @@ -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 transactionParameters = new HashSet(Arrays.asList("transactionDate", "transactionAmount", "note", + "locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber")); + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters); + + final List dataValidationErrors = new ArrayList(); + 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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 65cb2f02a..451c1dad6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 3ad2152e5..30ec6b896 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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"); diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java index 187ba37da..1b4b0b91c 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java @@ -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; + } +} \ No newline at end of file