org.mifosplatform.portfolio.loanaccount.domain.AbstractLoanRepaymentScheduleTransactionProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.mifosplatform.portfolio.loanaccount.domain.AbstractLoanRepaymentScheduleTransactionProcessor.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.mifosplatform.portfolio.loanaccount.domain;

import java.util.List;
import java.util.Set;

import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;

/**
 * Abstract implementation of {@link LoanRepaymentScheduleTransactionProcessor}
 * which is more convenient for concrete implementations to extend.
 * 
 * @see HeavensFamilyLoanRepaymentScheduleTransactionProcessor
 * @see CreocoreLoanRepaymentScheduleTransactionProcessor
 */
public abstract class AbstractLoanRepaymentScheduleTransactionProcessor
        implements LoanRepaymentScheduleTransactionProcessor {

    /**
     * Provides support for passing all {@link LoanTransaction}'s so it will
     * completely re-process the entire loan schedule. This is required in cases
     * where the {@link LoanTransaction} being processed is in the past and
     * falls before existing transactions or and adjustment is made to an
     * existing in which case the entire loan schedule needs to be re-processed.
     */
    @Override
    public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
            final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {

        if (charges != null) {
            for (LoanCharge loanCharge : charges) {
                if (!loanCharge.isDueAtDisbursement()) {
                    loanCharge.resetPaidAmount(currency);
                }
            }
        }

        for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
            currentInstallment.resetDerivedComponents();
        }

        // re-process loan charges over repayment periods (picking up on waived
        // loan charges)
        LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
        wrapper.reprocess(currency, disbursementDate, installments, charges);

        ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
        for (LoanTransaction loanTransaction : transactionsPostDisbursement) {

            if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) {
                // pass through for new transactions
                if (loanTransaction.getId() == null) {
                    loanTransaction.resetDerivedComponents();
                    handleTransaction(loanTransaction, currency, installments, charges);
                } else {
                    /**
                     * For existing transactions, check if the re-payment
                     * breakup (principal, interest, fees, penalties) has
                     * changed.<br>
                     **/
                    LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);

                    // Reset derived component of new loan transaction and
                    // re-process transaction
                    newLoanTransaction.resetDerivedComponents();
                    handleTransaction(newLoanTransaction, currency, installments, charges);

                    /**
                     * Check if the transaction amounts have changed. If so,
                     * reverse the original transaction and update
                     * changedTransactionDetail accordingly
                     **/
                    if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
                        loanTransaction.reverse();
                        changedTransactionDetail.getReversedTransactions().add(loanTransaction);
                        changedTransactionDetail.getNewTransactions().add(newLoanTransaction);
                    }
                }

            } else if (loanTransaction.isWriteOff()) {
                loanTransaction.resetDerivedComponents();
                handleWriteOff(loanTransaction, currency, installments);
            }
        }
        return changedTransactionDetail;
    }

    /**
     * Provides support for processing the latest transaction (which should be
     * latest transaction) against the loan schedule.
     */
    @Override
    public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
            final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {

        // find earliest unpaid installment for which to apply this transaction
        // to.
        int installmentIndex = 0;

        final LocalDate transactionDate = loanTransaction.getTransactionDate();
        Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);

        for (LoanRepaymentScheduleInstallment currentInstallment : installments) {

            if (currentInstallment.isNotFullyCompleted()) {

                // is this transaction early/late/on-time with respect to the
                // current installment?
                if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate,
                        transactionAmountUnprocessed)) {
                    transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(
                            currentInstallment, installments, loanTransaction, transactionDate,
                            transactionAmountUnprocessed);
                } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments,
                        loanTransaction.getTransactionDate())) {
                    // does this result in a late payment of existing
                    // installment?
                    transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(
                            currentInstallment, installments, loanTransaction, transactionAmountUnprocessed);
                } else {
                    // standard transaction
                    transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(
                            currentInstallment, loanTransaction, transactionAmountUnprocessed);
                }
            }

            installmentIndex++;
        }

        if (loanTransaction.isNotWaiver()) {
            Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
            if (feeCharges.isGreaterThanZero()) {
                updateFeeChargesPaidAmountBy(feeCharges, charges);
            }

            Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
            if (penaltyCharges.isGreaterThanZero()) {
                updatePenaltyChargesPaidAmountBy(penaltyCharges, charges);
            }
        }

        if (transactionAmountUnprocessed.isGreaterThanZero()) {
            onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
        }
    }

    private void updateFeeChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {

        Money amountRemaining = feeCharges;
        for (LoanCharge loanCharge : charges) {
            if (!loanCharge.isDueAtDisbursement()) {
                if (loanCharge.isFeeCharge() && loanCharge.isNotFullyPaid()
                        && amountRemaining.isGreaterThanZero()) {
                    final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
                    amountRemaining = unpaidCharge.updatePaidAmountBy(amountRemaining);
                }
            }
        }
    }

    private void updatePenaltyChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {

        Money amountRemaining = feeCharges;
        for (LoanCharge loanCharge : charges) {
            if (!loanCharge.isDueAtDisbursement()) {

                if (loanCharge.isPenaltyCharge() && amountRemaining.isGreaterThanZero()) {
                    final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
                    amountRemaining = unpaidCharge.updatePaidAmountBy(amountRemaining);
                }
            }
        }
    }

    private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
        LoanCharge earliestUnpaidCharge = null;

        for (LoanCharge loanCharge : charges) {
            if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
                if (earliestUnpaidCharge == null
                        || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
                    earliestUnpaidCharge = loanCharge;
                }
            }
        }

        return earliestUnpaidCharge;
    }

    @Override
    public void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
            final List<LoanRepaymentScheduleInstallment> installments) {

        Money principalPortion = Money.zero(currency);
        Money interestPortion = Money.zero(currency);
        Money feeChargesPortion = Money.zero(currency);
        Money penaltychargesPortion = Money.zero(currency);

        // determine how much is written off in total and breakdown for
        // 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));
            }
        }

        loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion,
                penaltychargesPortion);
    }

    // abstract interface
    /**
     * This method is responsible for checking if the current transaction is 'an
     * advance/early payment' based on the details passed through.
     * 
     * Default implementation simply processes transactions as 'Late' if the
     * transaction date is after the installment due date.
     */
    protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex,
            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {

        LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);

        return transactionDate.isAfter(currentInstallment.getDueDate());
    }

    /**
     * For late repayments, how should components of installment be paid off
     */
    protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(
            final LoanRepaymentScheduleInstallment currentInstallment,
            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
            final Money transactionAmountUnprocessed);

    /**
     * This method is responsible for checking if the current transaction is 'an
     * advance/early payment' based on the details passed through.
     */
    protected abstract boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
            final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate,
            final Money transactionAmount);

    /**
     * For early/'in advance' repayments.
     */
    protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(
            final LoanRepaymentScheduleInstallment currentInstallment,
            final List<LoanRepaymentScheduleInstallment> installments, final LoanTransaction loanTransaction,
            final LocalDate transactionDate, final Money paymentInAdvance);

    /**
     * For normal on-time repayments.
     */
    protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(
            final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction,
            final Money transactionAmountUnprocessed);

    /**
     * Invoked when a transaction results in an over-payment of the full loan.
     * 
     * transaction amount is greater than the total expected principal and
     * interest of the loan.
     */
    protected abstract void onLoanOverpayment(final LoanTransaction loanTransaction,
            final Money loanOverPaymentAmount);
}