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 72293ba10..421ad54d0 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 @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -165,7 +166,7 @@ public class Loan extends AbstractAuditableCustom { @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) - private Set charges; + private Set charges = new HashSet(); @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) @@ -276,7 +277,7 @@ public class Loan extends AbstractAuditableCustom { Money chargesDue = Money.of(getCurrency(), BigDecimal.ZERO); - for (LoanCharge charge : this.charges) { + for (LoanCharge charge : getNullPointerSafeLoanCharges()) { if (charge.isDueAtDisbursement()) { chargesDue = chargesDue.plus(charge.amount()); } @@ -319,7 +320,7 @@ public class Loan extends AbstractAuditableCustom { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); loanCharge.update(this); - this.charges.add(loanCharge); + getNullPointerSafeLoanCharges().add(loanCharge); updateTotalChargesDueAtDisbursement(); @@ -334,11 +335,11 @@ public class Loan extends AbstractAuditableCustom { // transaction. final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, this.charges); + getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } else { // just reprocess the loan schedule only for now. LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } } @@ -378,7 +379,7 @@ public class Loan extends AbstractAuditableCustom { // if you want ability to remove loan charges that are waived. validateLoanChargeIsNotWaived(loanCharge); - boolean removed = this.charges.remove(loanCharge); + boolean removed = getNullPointerSafeLoanCharges().remove(loanCharge); if (removed) { updateTotalChargesDueAtDisbursement(); } @@ -390,7 +391,7 @@ public class Loan extends AbstractAuditableCustom { if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, this.charges); + getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } } @@ -427,7 +428,7 @@ public class Loan extends AbstractAuditableCustom { validateLoanIsNotClosed(loanCharge); - if (this.charges.contains(loanCharge)) { + if (getNullPointerSafeLoanCharges().contains(loanCharge)) { final Map loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount()); actualChanges.putAll(loanChargeChanges); updateTotalChargesDueAtDisbursement(); @@ -438,11 +439,11 @@ public class Loan extends AbstractAuditableCustom { if (!loanCharge.isDueAtDisbursement()) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, this.charges); + getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } else { // reprocess loan schedule based on charge been waived. LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } return actualChanges; @@ -480,11 +481,11 @@ public class Loan extends AbstractAuditableCustom { if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, this.charges); + getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } else { // reprocess loan schedule based on charge been waived. LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); @@ -521,8 +522,8 @@ public class Loan extends AbstractAuditableCustom { } public void updateLoanCharges(final Set loanCharges) { - this.charges.clear(); - this.charges.addAll(associateChargesWithThisLoan(loanCharges)); + getNullPointerSafeLoanCharges().clear(); + getNullPointerSafeLoanCharges().addAll(associateChargesWithThisLoan(loanCharges)); this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); } @@ -548,7 +549,7 @@ public class Loan extends AbstractAuditableCustom { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor .determineProcessor(this.transactionProcessingStrategy); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), repaymentsOrWaivers, getCurrency(), - this.repaymentScheduleInstallments, this.charges); + this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } public void updateLoanScheduleDependentDerivedFields() { @@ -677,14 +678,22 @@ public class Loan extends AbstractAuditableCustom { Arrays.sort(newLoanChargeData); return !Arrays.equals(existingLoanChargeData, newLoanChargeData); } + + private Set getNullPointerSafeLoanCharges() { + Set loanCharges = this.charges; + if (this.charges == null) { + loanCharges = new HashSet(); + } + return loanCharges; + } private LoanChargeCommand[] getLoanCharges() { LoanChargeCommand[] existingLoanCharges = null; - if (this.charges != null && !this.charges.isEmpty()) { + if (!getNullPointerSafeLoanCharges().isEmpty()) { List loanChargesList = new ArrayList(); - for (LoanCharge loanCharge : this.charges) { + for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) { loanChargesList.add(loanCharge.toCommand()); } @@ -748,10 +757,8 @@ public class Loan extends AbstractAuditableCustom { } // charges are optional - if (this.charges != null) { - for (LoanCharge loanCharge : this.charges) { - validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); - } + for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) { + validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); } this.externalId = externalId; @@ -974,7 +981,7 @@ public class Loan extends AbstractAuditableCustom { final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod, interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency, - loanTermPeriodFrequencyType, this.charges, this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(), + loanTermPeriodFrequencyType, getNullPointerSafeLoanCharges(), this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance); final LoanScheduleData generatedData = loanSchedule.generate(); @@ -982,7 +989,7 @@ public class Loan extends AbstractAuditableCustom { updateLoanSchedule(generatedData); } - private void handleDisbursementTransaction(LocalDate disbursedOn) { + private void handleDisbursementTransaction(final LocalDate disbursedOn) { // track disbursement transaction final LoanTransaction loanTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(), disbursedOn); loanTransaction.updateLoan(this); @@ -998,7 +1005,7 @@ public class Loan extends AbstractAuditableCustom { chargesPayment.updateLoan(this); this.loanTransactions.add(chargesPayment); - for (LoanCharge charge : this.charges) { + for (LoanCharge charge : getNullPointerSafeLoanCharges()) { if (charge.isDueAtDisbursement()) { charge.markAsFullyPaid(); } @@ -1051,7 +1058,7 @@ public class Loan extends AbstractAuditableCustom { this.loanTransactions.clear(); this.disbursedOnDate = null; - for (LoanCharge charge : this.charges) { + for (LoanCharge charge : getNullPointerSafeLoanCharges()) { charge.resetToOriginal(loanCurrency()); } @@ -1060,7 +1067,7 @@ public class Loan extends AbstractAuditableCustom { } LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -1136,11 +1143,11 @@ public class Loan extends AbstractAuditableCustom { .determineProcessor(this.transactionProcessingStrategy); if (isTransactionChronologicallyLatest && adjustedTransaction == null) { loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, - this.charges); + getNullPointerSafeLoanCharges()); } else { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, this.charges); + getCurrency(), this.repaymentScheduleInstallments, getNullPointerSafeLoanCharges()); } doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanScheduleWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanScheduleWrapper.java index c7b5c9072..855787efb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanScheduleWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanScheduleWrapper.java @@ -8,151 +8,138 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; /** - * A wrapper around loan schedule related data exposing needed behaviour by loan. + * A wrapper around loan schedule related data exposing needed behaviour by + * loan. * - * This wrapper should be side-effect free. It should not attempt to change state of loan parts passed through to it. + * This wrapper should be side-effect free. It should not attempt to change + * state of loan parts passed through to it. */ public class LoanScheduleWrapper { - public Money calculateTotalOutstanding( - final MonetaryCurrency currency, - final List installments) { + public Money calculateTotalOutstanding(final MonetaryCurrency currency, final List installments) { - Money cumulativeValue = Money.zero(currency); + Money cumulativeValue = Money.zero(currency); - for (LoanRepaymentScheduleInstallment scheduledRepayment : installments) { - cumulativeValue = cumulativeValue.plus(scheduledRepayment.getTotalOutstanding(currency)); - } + for (LoanRepaymentScheduleInstallment scheduledRepayment : installments) { + cumulativeValue = cumulativeValue.plus(scheduledRepayment.getTotalOutstanding(currency)); + } - return cumulativeValue; - } + return cumulativeValue; + } - /** - * FIXME - this is not a side effect free approach so move this into a specific wrapper than makes this obvious from the read-only wrapper. - */ - public void reprocess( - final MonetaryCurrency currency, - final LocalDate disbursementDate, - final List repaymentPeriods, - final Set loanCharges) { - - LocalDate startDate = disbursementDate; - for (LoanRepaymentScheduleInstallment period : repaymentPeriods) { - - final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency); - final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency); - final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), loanCharges, currency); - - final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency); - final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency); - final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, period.getDueDate(), loanCharges, currency); - - period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, feeChargesWrittenOffForRepaymentPeriod, - penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, penaltyChargesWrittenOffForRepaymentPeriod); + /** + * FIXME - this is not a side effect free approach so move this into a + * specific wrapper than makes this obvious from the read-only wrapper. + */ + public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, + final List repaymentPeriods, final Set loanCharges) { - startDate = period.getDueDate(); - } - } - - private Money cumulativeFeeChargesDueWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency monetaryCurrency) { - - Money cumulative = Money.zero(monetaryCurrency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { - cumulative = cumulative.plus(loanCharge.amount()); - } - } - - return cumulative; - } - - private Money cumulativeFeeChargesWaivedWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency currency) { - - Money cumulative = Money.zero(currency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { - cumulative = cumulative.plus(loanCharge.getAmountWaived(currency)); - } - } - - return cumulative; - } - - private Money cumulativeFeeChargesWrittenOffWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency currency) { - - Money cumulative = Money.zero(currency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { - cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency)); - } - } - - return cumulative; - } - - private Money cumulativePenaltyChargesDueWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency currency) { - - Money cumulative = Money.zero(currency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { - cumulative = cumulative.plus(loanCharge.amount()); - } - } - - return cumulative; - } - - private Money cumulativePenaltyChargesWaivedWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency currency) { - - Money cumulative = Money.zero(currency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { - cumulative = cumulative.plus(loanCharge.getAmountWaived(currency)); - } - } - - return cumulative; - } - - private Money cumulativePenaltyChargesWrittenOffWithin( - final LocalDate periodStart, - final LocalDate periodEnd, - final Set loanCharges, - final MonetaryCurrency currency) { - - Money cumulative = Money.zero(currency); - - for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { - cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency)); - } - } - - return cumulative; - } + LocalDate startDate = disbursementDate; + for (LoanRepaymentScheduleInstallment period : repaymentPeriods) { + + final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, + currency); + final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, + currency); + final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), + loanCharges, currency); + + final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), + loanCharges, currency); + final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), + loanCharges, currency); + final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, + period.getDueDate(), loanCharges, currency); + + period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, + feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, + penaltyChargesWrittenOffForRepaymentPeriod); + + startDate = period.getDueDate(); + } + } + + private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, + final MonetaryCurrency monetaryCurrency) { + + Money cumulative = Money.zero(monetaryCurrency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { + cumulative = cumulative.plus(loanCharge.amount()); + } + } + + return cumulative; + } + + private Money cumulativeFeeChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency currency) { + + Money cumulative = Money.zero(currency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { + cumulative = cumulative.plus(loanCharge.getAmountWaived(currency)); + } + } + + return cumulative; + } + + private Money cumulativeFeeChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency currency) { + + Money cumulative = Money.zero(currency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { + cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency)); + } + } + + return cumulative; + } + + private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency currency) { + + Money cumulative = Money.zero(currency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { + cumulative = cumulative.plus(loanCharge.amount()); + } + } + + return cumulative; + } + + private Money cumulativePenaltyChargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency currency) { + + Money cumulative = Money.zero(currency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { + cumulative = cumulative.plus(loanCharge.getAmountWaived(currency)); + } + } + + return cumulative; + } + + private Money cumulativePenaltyChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency currency) { + + Money cumulative = Money.zero(currency); + + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { + cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency)); + } + } + + return cumulative; + } } \ No newline at end of file