/** * 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.accounting.journalentry.service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY; import org.mifosplatform.accounting.journalentry.data.ChargePaymentDTO; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.organisation.office.domain.Office; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CashBasedAccountingProcessorForLoan implements AccountingProcessorForLoan { private final AccountingProcessorHelper helper; @Autowired public CashBasedAccountingProcessorForLoan(final AccountingProcessorHelper accountingProcessorHelper) { this.helper = accountingProcessorHelper; } @Override public void createJournalEntriesForLoan(final LoanDTO loanDTO) { final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(loanDTO.getOfficeId()); // final Office office = // this.helper.getOfficeById(loanDTO.getOfficeId()); final Long loanProductId = loanDTO.getLoanProductId(); final String currencyCode = loanDTO.getCurrencyCode(); for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) { final Date transactionDate = loanTransactionDTO.getTransactionDate(); final String transactionId = loanTransactionDTO.getTransactionId(); final Office office = this.helper.getOfficeById(loanTransactionDTO.getOfficeId()); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); final Long loanId = loanDTO.getLoanId(); this.helper.checkForBranchClosures(latestGLClosure, transactionDate); /** Handle Disbursements and reversals of disbursements **/ if (loanTransactionDTO.getTransactionType().isDisbursement()) { createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office); } /*** * Logic for repayments, repayments at disbursement and reversal of * Repayments and Repayments at disbursement ***/ else if (loanTransactionDTO.getTransactionType().isRepayment() || loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement() || loanTransactionDTO.getTransactionType().isChargePayment()) { createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office); } /** Logic for handling recovery payments **/ else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) { createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office); } /** Logic for Refunds of Overpayments **/ else if (loanTransactionDTO.getTransactionType().isRefund()) { createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office); } /*** * Only principal write off affects cash based accounting (interest * and fee write off need not be considered). Debit losses written * off and credit Loan Portfolio **/ else if (loanTransactionDTO.getTransactionType().isWriteOff()) { final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, loanTransactionDTO.isReversed()); } } else if (loanTransactionDTO.getTransactionType().isInitiateTransfer() || loanTransactionDTO.getTransactionType().isApproveTransfer() || loanTransactionDTO.getTransactionType().isWithdrawTransfer()) { createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office); } /** Logic for Refunds of Active Loans **/ else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) { createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office); } } } /** * Debit loan Portfolio and credit Fund source for a Disbursement <br/> * * All debits are turned into credits and vice versa in case of disbursement * reversals * * * @param loanDTO * @param loanTransactionDTO * @param office */ private void createJournalEntriesForDisbursements(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal disbursalAmount = loanTransactionDTO.getAmount(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); if (loanTransactionDTO.isAccountTransfer()) { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversal); } else { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, disbursalAmount, isReversal); } } /** * Debit loan Portfolio and credit Fund source for a Disbursement <br/> * * All debits are turned into credits and vice versa in case of disbursement * reversals * * * @param loanDTO * @param loanTransactionDTO * @param office */ private void createJournalEntriesForRefund(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal refundAmount = loanTransactionDTO.getAmount(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); if (loanTransactionDTO.isAccountTransfer()) { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal); } else { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal); } } /** * Create a single Debit to fund source and multiple credits if applicable * (loan portfolio for principal repayments, Interest on loans for interest * repayments, Income from fees for fees payment and Income from penalties * for penalty payment) * * In case the loan transaction is a reversal, all debits are turned into * credits and vice versa */ private void createJournalEntriesForRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); final BigDecimal interestAmount = loanTransactionDTO.getInterest(); final BigDecimal feesAmount = loanTransactionDTO.getFees(); final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); BigDecimal totalDebitAmount = new BigDecimal(0); if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(principalAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, isReversal); } if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(interestAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversal); } if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversal, loanTransactionDTO.getFeePayments()); } if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments()); } if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal); } /*** create a single debit entry (or reversal) for the entire amount **/ if (loanTransactionDTO.isAccountTransfer()) { this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FINANCIAL_ACTIVITY.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); } else { this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); } } /** * Create a single Debit to fund source and a single credit to * "Income from Recovery" * * In case the loan transaction is a reversal, all debits are turned into * credits and vice versa */ private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal amount = loanTransactionDTO.getAmount(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, amount, isReversal); } /** * Credit loan Portfolio and Debit Suspense Account for a Transfer * Initiation. A Transfer acceptance would be treated the opposite i.e Debit * Loan Portfolio and Credit Suspense Account <br/> * * All debits are turned into credits and vice versa in case of Transfer * Initiation disbursals * * * @param loanDTO * @param loanTransactionDTO * @param office */ private void createJournalEntriesForTransfers(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); final boolean isReversal = loanTransactionDTO.isReversed(); // final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); if (loanTransactionDTO.getTransactionType().isInitiateTransfer()) { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), loanProductId, null, loanId, transactionId, transactionDate, principalAmount, isReversal); } else if (loanTransactionDTO.getTransactionType().isApproveTransfer() || loanTransactionDTO.getTransactionType().isWithdrawTransfer()) { this.helper.createCashBasedJournalEntriesAndReversalsForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), loanProductId, null, loanId, transactionId, transactionDate, principalAmount, isReversal); } } private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) { // TODO Auto-generated method stub // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); final String currencyCode = loanDTO.getCurrencyCode(); // transaction properties final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); final BigDecimal interestAmount = loanTransactionDTO.getInterest(); final BigDecimal feesAmount = loanTransactionDTO.getFees(); final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment(); final boolean isReversal = loanTransactionDTO.isReversed(); final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); BigDecimal totalDebitAmount = new BigDecimal(0); if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(principalAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal); } if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(interestAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal); } if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>(); for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) { chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() )); } this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount, !isReversal, chargePaymentDTOs); } if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); List<ChargePaymentDTO> chargePaymentDTOs = new ArrayList<>(); for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) { chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() )); } this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, !isReversal, chargePaymentDTOs); } if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal); } /*** create a single debit entry (or reversal) for the entire amount **/ this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal); } }