blob: a71016f71b29a5c7ae9e026d12be207a58bed715 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.fineract.accounting.closure.domain.GLClosure;
import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
import org.apache.fineract.accounting.journalentry.data.LoanDTO;
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
import org.apache.fineract.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);
}
}