From d90a07ba6c00f7572d153749ada5c0f9fe657c38 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 28 Feb 2013 17:25:17 +0000 Subject: [PATCH] MIFOSX-175: move derived fields into loan summary object (but still part of loan table for now. --- .../portfolio/loanaccount/domain/Loan.java | 321 +++--------------- .../loanaccount/domain/LoanSummary.java | 295 ++++++++++++++++ 2 files changed, 340 insertions(+), 276 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java 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 e417be196..01dcb4f5b 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 @@ -239,114 +239,8 @@ public class Loan extends AbstractPersistable { @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) private final List loanTransactions = new ArrayList(); - // derived totals fields - @Column(name = "principal_disbursed_derived", scale = 6, precision = 19) - private BigDecimal totalPrincipalDisbursed; - - @Column(name = "principal_repaid_derived", scale = 6, precision = 19) - private BigDecimal totalPrincipalRepaid; - - @Column(name = "principal_writtenoff_derived", scale = 6, precision = 19) - private BigDecimal totalPrincipalWrittenOff; - - @Column(name = "principal_outstanding_derived", scale = 6, precision = 19) - private BigDecimal totalPrincipalOutstanding; - - @Column(name = "principal_overdue_derived", scale = 6, precision = 19) - private BigDecimal totalPrincipalOverdue; - - @Column(name = "interest_charged_derived", scale = 6, precision = 19) - private BigDecimal totalInterestCharged; - - @Column(name = "interest_repaid_derived", scale = 6, precision = 19) - private BigDecimal totalInterestRepaid; - - @Column(name = "interest_waived_derived", scale = 6, precision = 19) - private BigDecimal totalInterestWaived; - - @Column(name = "interest_writtenoff_derived", scale = 6, precision = 19) - private BigDecimal totalInterestWrittenOff; - - @Column(name = "interest_outstanding_derived", scale = 6, precision = 19) - private BigDecimal totalInterestOutstanding; - - @Column(name = "interest_overdue_derived", scale = 6, precision = 19) - private BigDecimal totalInterestOverdue; - - @Column(name = "fee_charges_charged_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesCharged; - - @Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19) - private BigDecimal totalChargesDueAtDisbursement; - - @Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesRepaid; - - @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesWaived; - - @Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesWrittenOff; - - @Column(name = "fee_charges_outstanding_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesOutstanding; - - @Column(name = "fee_charges_overdue_derived", scale = 6, precision = 19) - private BigDecimal totalFeeChargesOverdue; - - @Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesCharged; - - @Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesRepaid; - - @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesWaived; - - @Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesWrittenOff; - - @Column(name = "penalty_charges_outstanding_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesOutstanding; - - @Column(name = "penalty_charges_overdue_derived", scale = 6, precision = 19) - private BigDecimal totalPenaltyChargesOverdue; - - @SuppressWarnings("unused") - @Column(name = "total_expected_repayment_derived", scale = 6, precision = 19) - private BigDecimal totalExpectedRepayment; - - @SuppressWarnings("unused") - @Column(name = "total_repayment_derived", scale = 6, precision = 19) - private BigDecimal totalRepayment; - - @SuppressWarnings("unused") - @Column(name = "total_expected_costofloan_derived", scale = 6, precision = 19) - private BigDecimal totalExpectedCostOfLoan; - - @SuppressWarnings("unused") - @Column(name = "total_costofloan_derived", scale = 6, precision = 19) - private BigDecimal totalCostOfLoan; - - @SuppressWarnings("unused") - @Column(name = "total_waived_derived", scale = 6, precision = 19) - private BigDecimal totalWaived; - - @SuppressWarnings("unused") - @Column(name = "total_writtenoff_derived", scale = 6, precision = 19) - private BigDecimal totalWrittenOff; - - @Column(name = "total_outstanding_derived", scale = 6, precision = 19) - private BigDecimal totalOutstanding; - - @SuppressWarnings("unused") - @Column(name = "total_overdue_derived", scale = 6, precision = 19) - private BigDecimal totalOverdue; - - @SuppressWarnings("unused") - @Temporal(TemporalType.DATE) - @Column(name = "overdue_since_date_derived") - private Date overdueSinceDate; + @Embedded + private LoanSummary summary; @Transient private boolean accountNumberRequiresAutoGeneration = false; @@ -420,9 +314,10 @@ public class Loan extends AbstractPersistable { } if (loanCharges != null && !loanCharges.isEmpty()) { this.charges = associateChargesWithThisLoan(loanCharges); - this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); + this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } else { this.charges = null; + this.summary = new LoanSummary(); } if (collateral != null && !collateral.isEmpty()) { this.collateral = associateWithThisLoan(collateral); @@ -432,6 +327,15 @@ public class Loan extends AbstractPersistable { this.loanOfficerHistory = null; } + private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) { + if (this.summary == null) { + this.summary = LoanSummary.create(feeChargesDueAtDisbursement); + } else { + this.summary.updateTotalFeeChargesDueAtDisbursement(feeChargesDueAtDisbursement); + } + return this.summary; + } + private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() { Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO); @@ -459,10 +363,6 @@ public class Loan extends AbstractPersistable { return collateral; } - private void updateTotalChargesDueAtDisbursement() { - this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); - } - public boolean isAccountNumberRequiresAutoGeneration() { return this.accountNumberRequiresAutoGeneration; } @@ -497,7 +397,7 @@ public class Loan extends AbstractPersistable { loanCharge.update(this); setOfLoanCharges().add(loanCharge); - updateTotalChargesDueAtDisbursement(); + this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); @@ -512,7 +412,7 @@ public class Loan extends AbstractPersistable { wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); } private void validateLoanIsNotClosed(final LoanCharge loanCharge) { @@ -554,7 +454,7 @@ public class Loan extends AbstractPersistable { boolean removed = setOfLoanCharges().remove(loanCharge); if (removed) { - updateTotalChargesDueAtDisbursement(); + updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge); @@ -567,7 +467,7 @@ public class Loan extends AbstractPersistable { getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); } - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); } private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final LoanCharge loanCharge) { @@ -606,7 +506,7 @@ public class Loan extends AbstractPersistable { if (setOfLoanCharges().contains(loanCharge)) { final Map loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount()); actualChanges.putAll(loanChargeChanges); - updateTotalChargesDueAtDisbursement(); + updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory @@ -621,7 +521,7 @@ public class Loan extends AbstractPersistable { wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); return actualChanges; } @@ -647,7 +547,7 @@ public class Loan extends AbstractPersistable { transactionDate = loanCharge.getDueLocalDate(); } - updateTotalChargesDueAtDisbursement(); + updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate, feeChargesWaived, penaltyChargesWaived); @@ -666,7 +566,7 @@ public class Loan extends AbstractPersistable { wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); } - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); @@ -720,7 +620,7 @@ public class Loan extends AbstractPersistable { public void updateLoanCharges(final Set loanCharges) { setOfLoanCharges().clear(); setOfLoanCharges().addAll(associateChargesWithThisLoan(loanCharges)); - this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); + updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } public void updateLoanCollateral(final Set loanCollateral) { @@ -756,7 +656,7 @@ public class Loan extends AbstractPersistable { this.repaymentScheduleInstallments, setOfLoanCharges()); updateLoanScheduleDependentDerivedFields(); - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); } private void updateLoanScheduleDependentDerivedFields() { @@ -764,138 +664,13 @@ public class Loan extends AbstractPersistable { this.actualMaturityDate = determineExpectedMaturityDate().toDate(); } - private void updateLoanSummaryDerivedFields(final LoanSummaryWrapper summary) { - - final Money principal = this.loanRepaymentScheduleDetail.getPrincipal(); - final MonetaryCurrency currency = loanCurrency(); + private void updateLoanSummaryDerivedFields() { if (isNotDisbursed()) { - this.totalPrincipalDisbursed = BigDecimal.ZERO; - this.totalPrincipalRepaid = BigDecimal.ZERO; - this.totalPrincipalWrittenOff = BigDecimal.ZERO; - this.totalPrincipalOutstanding = BigDecimal.ZERO; - this.totalPrincipalOverdue = BigDecimal.ZERO; - this.totalInterestCharged = BigDecimal.ZERO; - this.totalInterestRepaid = BigDecimal.ZERO; - this.totalInterestWaived = BigDecimal.ZERO; - this.totalInterestWrittenOff = BigDecimal.ZERO; - this.totalInterestOutstanding = BigDecimal.ZERO; - this.totalInterestOverdue = BigDecimal.ZERO; - this.totalFeeChargesCharged = BigDecimal.ZERO; - this.totalFeeChargesRepaid = BigDecimal.ZERO; - this.totalFeeChargesWaived = BigDecimal.ZERO; - this.totalFeeChargesWrittenOff = BigDecimal.ZERO; - this.totalFeeChargesOutstanding = BigDecimal.ZERO; - this.totalFeeChargesOverdue = BigDecimal.ZERO; - this.totalPenaltyChargesCharged = BigDecimal.ZERO; - this.totalPenaltyChargesRepaid = BigDecimal.ZERO; - this.totalPenaltyChargesWaived = BigDecimal.ZERO; - this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO; - this.totalPenaltyChargesOutstanding = BigDecimal.ZERO; - this.totalPenaltyChargesOverdue = BigDecimal.ZERO; - this.totalExpectedRepayment = BigDecimal.ZERO; - this.totalRepayment = BigDecimal.ZERO; - this.totalExpectedCostOfLoan = BigDecimal.ZERO; - this.totalCostOfLoan = BigDecimal.ZERO; - this.totalWaived = BigDecimal.ZERO; - this.totalWrittenOff = BigDecimal.ZERO; - this.totalOutstanding = BigDecimal.ZERO; - this.totalOverdue = BigDecimal.ZERO; - this.overdueSinceDate = null; + this.summary.zeroFields(); } else { - this.totalPrincipalDisbursed = principal.getAmount(); - this.totalPrincipalRepaid = summary.calculateTotalPrincipalRepaid(this.repaymentScheduleInstallments, currency).getAmount(); - this.totalPrincipalWrittenOff = summary.calculateTotalPrincipalWrittenOff(this.repaymentScheduleInstallments, currency) - .getAmount(); - - this.totalPrincipalOutstanding = principal.minus(totalPrincipalRepaid).minus(totalPrincipalWrittenOff).getAmount(); - this.totalPrincipalOverdue = summary.calculateTotalPrincipalOverdueOn(this.repaymentScheduleInstallments, currency, - DateUtils.getLocalDateOfTenant()).getAmount(); - - final Money totalInterestCharged = summary.calculateTotalInterestCharged(this.repaymentScheduleInstallments, currency); - this.totalInterestCharged = totalInterestCharged.getAmount(); - this.totalInterestRepaid = summary.calculateTotalInterestRepaid(this.repaymentScheduleInstallments, currency).getAmount(); - this.totalInterestWaived = summary.calculateTotalInterestWaived(this.repaymentScheduleInstallments, currency).getAmount(); - this.totalInterestWrittenOff = summary.calculateTotalInterestWrittenOff(this.repaymentScheduleInstallments, currency) - .getAmount(); - - if (totalInterestCharged.isGreaterThanZero()) { - this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived) - .minus(this.totalInterestWrittenOff).getAmount(); - this.totalInterestOverdue = summary.calculateTotalInterestOverdueOn(this.repaymentScheduleInstallments, currency, - DateUtils.getLocalDateOfTenant()).getAmount(); - } - - final Money totalFeeChargesCharged = summary.calculateTotalFeeChargesCharged(this.repaymentScheduleInstallments, currency); - this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount(); - this.totalFeeChargesRepaid = summary.calculateTotalFeeChargesRepaid(this.repaymentScheduleInstallments, currency).getAmount(); - this.totalFeeChargesWaived = summary.calculateTotalFeeChargesWaived(this.repaymentScheduleInstallments, currency).getAmount(); - this.totalFeeChargesWrittenOff = summary.calculateTotalFeeChargesWrittenOff(this.repaymentScheduleInstallments, currency) - .getAmount(); - - if (totalFeeChargesCharged.isGreaterThanZero()) { - this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid) - .minus(this.totalFeeChargesWaived).minus(this.totalFeeChargesWrittenOff).getAmount(); - this.totalFeeChargesOverdue = summary.calculateTotalFeeChargesOverdueOn(this.repaymentScheduleInstallments, currency, - DateUtils.getLocalDateOfTenant()).getAmount(); - } - - final Money totalPenaltyChargesCharged = summary.calculateTotalPenaltyChargesCharged(this.repaymentScheduleInstallments, - currency); - this.totalPenaltyChargesCharged = totalPenaltyChargesCharged.getAmount(); - this.totalPenaltyChargesRepaid = summary.calculateTotalPenaltyChargesRepaid(this.repaymentScheduleInstallments, currency) - .getAmount(); - this.totalPenaltyChargesWaived = summary.calculateTotalPenaltyChargesWaived(this.repaymentScheduleInstallments, currency) - .getAmount(); - this.totalPenaltyChargesWrittenOff = summary.calculateTotalPenaltyChargesWrittenOff(this.repaymentScheduleInstallments, - currency).getAmount(); - - if (totalPenaltyChargesCharged.isGreaterThanZero()) { - this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid) - .minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount(); - this.totalPenaltyChargesOverdue = summary.calculateTotalPenaltyChargesOverdueOn(this.repaymentScheduleInstallments, - currency, DateUtils.getLocalDateOfTenant()).getAmount(); - } - - final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged) - .plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged); - this.totalExpectedRepayment = totalExpectedRepayment.getAmount(); - - final Money totalRepayment = Money.of(currency, this.totalPrincipalRepaid).plus(this.totalInterestRepaid) - .plus(this.totalFeeChargesRepaid).plus(this.totalPenaltyChargesRepaid); - this.totalRepayment = totalRepayment.getAmount(); - - final Money totalExpectedCostOfLoan = Money.of(currency, this.totalInterestCharged).plus(this.totalFeeChargesCharged) - .plus(this.totalPenaltyChargesCharged); - this.totalExpectedCostOfLoan = totalExpectedCostOfLoan.getAmount(); - - final Money totalCostOfLoan = Money.of(currency, this.totalInterestRepaid).plus(this.totalFeeChargesRepaid) - .plus(this.totalPenaltyChargesRepaid); - this.totalCostOfLoan = totalCostOfLoan.getAmount(); - - final Money totalWaived = Money.of(currency, this.totalInterestWaived).plus(this.totalFeeChargesWaived) - .plus(this.totalPenaltyChargesWaived); - this.totalWaived = totalWaived.getAmount(); - - final Money totalWrittenOff = Money.of(currency, this.totalPrincipalWrittenOff).plus(this.totalInterestWrittenOff) - .plus(this.totalFeeChargesWrittenOff).plus(this.totalPenaltyChargesWrittenOff); - this.totalWrittenOff = totalWrittenOff.getAmount(); - - final Money totalOutstanding = Money.of(currency, this.totalPrincipalOutstanding).plus(this.totalInterestOutstanding) - .plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding); - this.totalOutstanding = totalOutstanding.getAmount(); - - final Money totalOverdue = Money.of(currency, this.totalPrincipalOverdue).plus(this.totalInterestOverdue) - .plus(this.totalFeeChargesOverdue).plus(this.totalPenaltyChargesOverdue); - this.totalOverdue = totalOverdue.getAmount(); - - final LocalDate overdueSinceLocalDate = summary.determineOverdueSinceDateFrom(this.repaymentScheduleInstallments, currency, - DateUtils.getLocalDateOfTenant()); - if (overdueSinceLocalDate != null) { - this.overdueSinceDate = overdueSinceLocalDate.toDate(); - } else { - this.overdueSinceDate = null; - } + final Money principal = this.loanRepaymentScheduleDetail.getPrincipal(); + this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper); } } @@ -1365,7 +1140,7 @@ public class Loan extends AbstractPersistable { final Map actualChanges = new LinkedHashMap(); - updateLoanToPreDisbursalState(this.loanSummaryWrapper); + updateLoanToPreDisbursalState(); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { @@ -1391,7 +1166,7 @@ public class Loan extends AbstractPersistable { if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) { regenerateRepaymentSchedule(currency); } else { - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); } } @@ -1442,11 +1217,13 @@ public class Loan extends AbstractPersistable { // add repayment transaction to track incoming money from client to mfi // for (charges due at time of disbursement) - if (getTotalChargesDueAtDisbursement().isGreaterThanZero()) { + final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency()); + if (totalFeeChargesDueAtDisbursement.isGreaterThanZero()) { - LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn); + LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(totalFeeChargesDueAtDisbursement, + disbursedOn); Money zero = Money.zero(getCurrency()); - chargesPayment.updateComponents(zero, zero, getTotalChargesDueAtDisbursement(), zero); + chargesPayment.updateComponents(zero, zero, totalFeeChargesDueAtDisbursement, zero); chargesPayment.updateLoan(this); this.loanTransactions.add(chargesPayment); @@ -1499,7 +1276,7 @@ public class Loan extends AbstractPersistable { reverseExistingTransactions(); - updateLoanToPreDisbursalState(this.loanSummaryWrapper); + updateLoanToPreDisbursalState(); } return actualChanges; @@ -1512,7 +1289,7 @@ public class Loan extends AbstractPersistable { } } - private void updateLoanToPreDisbursalState(final LoanSummaryWrapper summaryWrapper) { + private void updateLoanToPreDisbursalState() { this.actualDisbursementDate = null; for (LoanCharge charge : setOfLoanCharges()) { @@ -1526,7 +1303,7 @@ public class Loan extends AbstractPersistable { LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, setOfLoanCharges()); - updateLoanSummaryDerivedFields(summaryWrapper); + updateLoanSummaryDerivedFields(); } public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -1613,7 +1390,7 @@ public class Loan extends AbstractPersistable { getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); } - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); } @@ -1634,7 +1411,7 @@ public class Loan extends AbstractPersistable { if (this.isOverPaid()) { handleLoanOverpayment(loanLifecycleStateMachine); - } else if (this.isRepaidInFull()) { + } else if (this.summary.isRepaidInFull(loanCurrency())) { // TODO - KW - probably should not close the loan automatically but // let user decide if loan is closed or not and provide closing // date. @@ -1792,10 +1569,6 @@ public class Loan extends AbstractPersistable { return newTransactionDetail; } - private boolean isRepaidInFull() { - return Money.of(loanCurrency(), this.totalOutstanding).isZero(); - } - private boolean isOverPaid() { return calculateTotalOverpayment().isGreaterThanZero(); } @@ -1873,7 +1646,7 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments); - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); + updateLoanSummaryDerivedFields(); } return loanTransaction; @@ -1904,8 +1677,8 @@ public class Loan extends AbstractPersistable { LoanTransaction loanTransaction = null; if (isOpen()) { - final Money outstanding = Money.of(loanCurrency(), this.totalOutstanding); - if (outstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(outstanding)) { + final Money totalOutstanding = this.summary.getTotalOutstanding(loanCurrency()); + if (totalOutstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) { final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus)); if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { @@ -1929,10 +1702,10 @@ public class Loan extends AbstractPersistable { loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments); - updateLoanSummaryDerivedFields(this.loanSummaryWrapper); - } else if (outstanding.isGreaterThanZero()) { + updateLoanSummaryDerivedFields(); + } else if (totalOutstanding.isGreaterThanZero()) { final String errorMessage = "A loan with money outstanding cannot be closed"; - throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage, outstanding.toString()); + throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage, totalOutstanding.toString()); } } @@ -2161,10 +1934,6 @@ public class Loan extends AbstractPersistable { return this.loanRepaymentScheduleDetail.getPrincipal(); } - private Money getTotalChargesDueAtDisbursement() { - return Money.of(getCurrency(), this.totalChargesDueAtDisbursement); - } - public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { return getCurrencyCode().equalsIgnoreCase(matchingCurrencyCode); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java new file mode 100644 index 000000000..9318a71ab --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java @@ -0,0 +1,295 @@ +package org.mifosplatform.portfolio.loanaccount.domain; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; + +/** + * LoanSummary encapsulates all the summary details of a {@link Loan}. + */ +@Embeddable +public final class LoanSummary { + + // derived totals fields + @Column(name = "principal_disbursed_derived", scale = 6, precision = 19) + private BigDecimal totalPrincipalDisbursed; + + @Column(name = "principal_repaid_derived", scale = 6, precision = 19) + private BigDecimal totalPrincipalRepaid; + + @Column(name = "principal_writtenoff_derived", scale = 6, precision = 19) + private BigDecimal totalPrincipalWrittenOff; + + @Column(name = "principal_outstanding_derived", scale = 6, precision = 19) + private BigDecimal totalPrincipalOutstanding; + + @Column(name = "principal_overdue_derived", scale = 6, precision = 19) + private BigDecimal totalPrincipalOverdue; + + @Column(name = "interest_charged_derived", scale = 6, precision = 19) + private BigDecimal totalInterestCharged; + + @Column(name = "interest_repaid_derived", scale = 6, precision = 19) + private BigDecimal totalInterestRepaid; + + @Column(name = "interest_waived_derived", scale = 6, precision = 19) + private BigDecimal totalInterestWaived; + + @Column(name = "interest_writtenoff_derived", scale = 6, precision = 19) + private BigDecimal totalInterestWrittenOff; + + @Column(name = "interest_outstanding_derived", scale = 6, precision = 19) + private BigDecimal totalInterestOutstanding; + + @Column(name = "interest_overdue_derived", scale = 6, precision = 19) + private BigDecimal totalInterestOverdue; + + @Column(name = "fee_charges_charged_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesCharged; + + @Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesDueAtDisbursement; + + @Column(name = "fee_charges_repaid_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesRepaid; + + @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesWaived; + + @Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesWrittenOff; + + @Column(name = "fee_charges_outstanding_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesOutstanding; + + @Column(name = "fee_charges_overdue_derived", scale = 6, precision = 19) + private BigDecimal totalFeeChargesOverdue; + + @Column(name = "penalty_charges_charged_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesCharged; + + @Column(name = "penalty_charges_repaid_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesRepaid; + + @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesWaived; + + @Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesWrittenOff; + + @Column(name = "penalty_charges_outstanding_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesOutstanding; + + @Column(name = "penalty_charges_overdue_derived", scale = 6, precision = 19) + private BigDecimal totalPenaltyChargesOverdue; + + @SuppressWarnings("unused") + @Column(name = "total_expected_repayment_derived", scale = 6, precision = 19) + private BigDecimal totalExpectedRepayment; + + @SuppressWarnings("unused") + @Column(name = "total_repayment_derived", scale = 6, precision = 19) + private BigDecimal totalRepayment; + + @SuppressWarnings("unused") + @Column(name = "total_expected_costofloan_derived", scale = 6, precision = 19) + private BigDecimal totalExpectedCostOfLoan; + + @SuppressWarnings("unused") + @Column(name = "total_costofloan_derived", scale = 6, precision = 19) + private BigDecimal totalCostOfLoan; + + @SuppressWarnings("unused") + @Column(name = "total_waived_derived", scale = 6, precision = 19) + private BigDecimal totalWaived; + + @SuppressWarnings("unused") + @Column(name = "total_writtenoff_derived", scale = 6, precision = 19) + private BigDecimal totalWrittenOff; + + @Column(name = "total_outstanding_derived", scale = 6, precision = 19) + private BigDecimal totalOutstanding; + + @SuppressWarnings("unused") + @Column(name = "total_overdue_derived", scale = 6, precision = 19) + private BigDecimal totalOverdue; + + @SuppressWarnings("unused") + @Temporal(TemporalType.DATE) + @Column(name = "overdue_since_date_derived") + private Date overdueSinceDate; + + public static LoanSummary create(final BigDecimal totalFeeChargesDueAtDisbursement) { + return new LoanSummary(totalFeeChargesDueAtDisbursement); + } + + protected LoanSummary() { + // + } + + private LoanSummary(final BigDecimal totalFeeChargesDueAtDisbursement) { + this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement; + } + + public void updateTotalFeeChargesDueAtDisbursement(final BigDecimal totalFeeChargesDueAtDisbursement) { + this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement; + } + + public Money getTotalFeeChargesDueAtDisbursement(final MonetaryCurrency currency) { + return Money.of(currency, this.totalFeeChargesDueAtDisbursement); + } + + public Money getTotalOutstanding(final MonetaryCurrency currency) { + return Money.of(currency, this.totalOutstanding); + } + + public boolean isRepaidInFull(final MonetaryCurrency currency) { + return getTotalOutstanding(currency).isZero(); + } + + /** + * All fields but totalFeeChargesDueAtDisbursement should be + * reset. + */ + public void zeroFields() { + this.totalPrincipalDisbursed = BigDecimal.ZERO; + this.totalPrincipalRepaid = BigDecimal.ZERO; + this.totalPrincipalWrittenOff = BigDecimal.ZERO; + this.totalPrincipalOutstanding = BigDecimal.ZERO; + this.totalPrincipalOverdue = BigDecimal.ZERO; + this.totalInterestCharged = BigDecimal.ZERO; + this.totalInterestRepaid = BigDecimal.ZERO; + this.totalInterestWaived = BigDecimal.ZERO; + this.totalInterestWrittenOff = BigDecimal.ZERO; + this.totalInterestOutstanding = BigDecimal.ZERO; + this.totalInterestOverdue = BigDecimal.ZERO; + this.totalFeeChargesCharged = BigDecimal.ZERO; + this.totalFeeChargesRepaid = BigDecimal.ZERO; + this.totalFeeChargesWaived = BigDecimal.ZERO; + this.totalFeeChargesWrittenOff = BigDecimal.ZERO; + this.totalFeeChargesOutstanding = BigDecimal.ZERO; + this.totalFeeChargesOverdue = BigDecimal.ZERO; + this.totalPenaltyChargesCharged = BigDecimal.ZERO; + this.totalPenaltyChargesRepaid = BigDecimal.ZERO; + this.totalPenaltyChargesWaived = BigDecimal.ZERO; + this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO; + this.totalPenaltyChargesOutstanding = BigDecimal.ZERO; + this.totalPenaltyChargesOverdue = BigDecimal.ZERO; + this.totalExpectedRepayment = BigDecimal.ZERO; + this.totalRepayment = BigDecimal.ZERO; + this.totalExpectedCostOfLoan = BigDecimal.ZERO; + this.totalCostOfLoan = BigDecimal.ZERO; + this.totalWaived = BigDecimal.ZERO; + this.totalWrittenOff = BigDecimal.ZERO; + this.totalOutstanding = BigDecimal.ZERO; + this.totalOverdue = BigDecimal.ZERO; + this.overdueSinceDate = null; + } + + public void updateSummary(final MonetaryCurrency currency, final Money principal, + final List repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper) { + + this.totalPrincipalDisbursed = principal.getAmount(); + this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount(); + this.totalPrincipalWrittenOff = summaryWrapper.calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency) + .getAmount(); + + this.totalPrincipalOutstanding = principal.minus(totalPrincipalRepaid).minus(totalPrincipalWrittenOff).getAmount(); + this.totalPrincipalOverdue = summaryWrapper.calculateTotalPrincipalOverdueOn(repaymentScheduleInstallments, currency, + DateUtils.getLocalDateOfTenant()).getAmount(); + + final Money totalInterestCharged = summaryWrapper.calculateTotalInterestCharged(repaymentScheduleInstallments, currency); + this.totalInterestCharged = totalInterestCharged.getAmount(); + this.totalInterestRepaid = summaryWrapper.calculateTotalInterestRepaid(repaymentScheduleInstallments, currency).getAmount(); + this.totalInterestWaived = summaryWrapper.calculateTotalInterestWaived(repaymentScheduleInstallments, currency).getAmount(); + this.totalInterestWrittenOff = summaryWrapper.calculateTotalInterestWrittenOff(repaymentScheduleInstallments, currency).getAmount(); + + if (totalInterestCharged.isGreaterThanZero()) { + this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived) + .minus(this.totalInterestWrittenOff).getAmount(); + this.totalInterestOverdue = summaryWrapper.calculateTotalInterestOverdueOn(repaymentScheduleInstallments, currency, + DateUtils.getLocalDateOfTenant()).getAmount(); + } + + final Money totalFeeChargesCharged = summaryWrapper.calculateTotalFeeChargesCharged(repaymentScheduleInstallments, currency); + this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount(); + this.totalFeeChargesRepaid = summaryWrapper.calculateTotalFeeChargesRepaid(repaymentScheduleInstallments, currency).getAmount(); + this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(repaymentScheduleInstallments, currency).getAmount(); + this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency) + .getAmount(); + + if (totalFeeChargesCharged.isGreaterThanZero()) { + this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid).minus(this.totalFeeChargesWaived) + .minus(this.totalFeeChargesWrittenOff).getAmount(); + this.totalFeeChargesOverdue = summaryWrapper.calculateTotalFeeChargesOverdueOn(repaymentScheduleInstallments, currency, + DateUtils.getLocalDateOfTenant()).getAmount(); + } + + final Money totalPenaltyChargesCharged = summaryWrapper + .calculateTotalPenaltyChargesCharged(repaymentScheduleInstallments, currency); + this.totalPenaltyChargesCharged = totalPenaltyChargesCharged.getAmount(); + this.totalPenaltyChargesRepaid = summaryWrapper.calculateTotalPenaltyChargesRepaid(repaymentScheduleInstallments, currency) + .getAmount(); + this.totalPenaltyChargesWaived = summaryWrapper.calculateTotalPenaltyChargesWaived(repaymentScheduleInstallments, currency) + .getAmount(); + this.totalPenaltyChargesWrittenOff = summaryWrapper.calculateTotalPenaltyChargesWrittenOff(repaymentScheduleInstallments, currency) + .getAmount(); + + if (totalPenaltyChargesCharged.isGreaterThanZero()) { + this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid) + .minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount(); + this.totalPenaltyChargesOverdue = summaryWrapper.calculateTotalPenaltyChargesOverdueOn(repaymentScheduleInstallments, currency, + DateUtils.getLocalDateOfTenant()).getAmount(); + } + + final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged) + .plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged); + this.totalExpectedRepayment = totalExpectedRepayment.getAmount(); + + final Money totalRepayment = Money.of(currency, this.totalPrincipalRepaid).plus(this.totalInterestRepaid) + .plus(this.totalFeeChargesRepaid).plus(this.totalPenaltyChargesRepaid); + this.totalRepayment = totalRepayment.getAmount(); + + final Money totalExpectedCostOfLoan = Money.of(currency, this.totalInterestCharged).plus(this.totalFeeChargesCharged) + .plus(this.totalPenaltyChargesCharged); + this.totalExpectedCostOfLoan = totalExpectedCostOfLoan.getAmount(); + + final Money totalCostOfLoan = Money.of(currency, this.totalInterestRepaid).plus(this.totalFeeChargesRepaid) + .plus(this.totalPenaltyChargesRepaid); + this.totalCostOfLoan = totalCostOfLoan.getAmount(); + + final Money totalWaived = Money.of(currency, this.totalInterestWaived).plus(this.totalFeeChargesWaived) + .plus(this.totalPenaltyChargesWaived); + this.totalWaived = totalWaived.getAmount(); + + final Money totalWrittenOff = Money.of(currency, this.totalPrincipalWrittenOff).plus(this.totalInterestWrittenOff) + .plus(this.totalFeeChargesWrittenOff).plus(this.totalPenaltyChargesWrittenOff); + this.totalWrittenOff = totalWrittenOff.getAmount(); + + final Money totalOutstanding = Money.of(currency, this.totalPrincipalOutstanding).plus(this.totalInterestOutstanding) + .plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding); + this.totalOutstanding = totalOutstanding.getAmount(); + + final Money totalOverdue = Money.of(currency, this.totalPrincipalOverdue).plus(this.totalInterestOverdue) + .plus(this.totalFeeChargesOverdue).plus(this.totalPenaltyChargesOverdue); + this.totalOverdue = totalOverdue.getAmount(); + + final LocalDate overdueSinceLocalDate = summaryWrapper.determineOverdueSinceDateFrom(repaymentScheduleInstallments, currency, + DateUtils.getLocalDateOfTenant()); + if (overdueSinceLocalDate != null) { + this.overdueSinceDate = overdueSinceLocalDate.toDate(); + } else { + this.overdueSinceDate = null; + } + } +} \ No newline at end of file