From b7e1ff3fa858e6558a8e9d9ebaa6f839968f5e56 Mon Sep 17 00:00:00 2001 From: Vishwas Babu AJ Date: Sun, 17 Mar 2013 21:23:34 +0530 Subject: [PATCH] wip: refactored accounting and added charge applied event --- .../service/AccountingProcessorHelper.java | 164 +++++++++-- ...ccrualBasedAccountingProcessorForLoan.java | 274 +++++++----------- .../CashBasedAccountingProcessorForLoan.java | 94 ++---- .../data/LoanTransactionEnumData.java | 6 + .../portfolio/loanaccount/domain/Loan.java | 66 ++++- .../loanaccount/domain/LoanCharge.java | 2 +- .../loanaccount/domain/LoanTransaction.java | 8 + .../domain/LoanTransactionType.java | 13 +- ...WritePlatformServiceJpaRepositoryImpl.java | 16 +- .../loanproduct/service/LoanEnumerations.java | 10 +- 10 files changed, 372 insertions(+), 281 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java index d1121617f..b0fbb8820 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java @@ -85,34 +85,103 @@ public class AccountingProcessorHelper { newLoanTransactions); } - public GLAccount getLinkedCashAccountsForLoanProduct(final Long loanProductId, final CASH_ACCOUNTS_FOR_LOAN finAccountType) { - final ProductToGLAccountMapping accountMapping = this.accountMappingRepository - .findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(), - finAccountType.getValue()); - return accountMapping.getGlAccount(); + /** + * Convenience method that creates a pair of related Debits and Credits for + * Accrual Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param loanProductId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createAccrualBasedJournalEntriesAndReversalsForLoan(final Office office, + final ACCRUAL_ACCOUNTS_FOR_LOAN accountTypeToBeDebited, final ACCRUAL_ACCOUNTS_FOR_LOAN accountTypeToBeCredited, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { + int accountTypeToDebitId = accountTypeToBeDebited.getValue(); + int accountTypeToCreditId = accountTypeToBeCredited.getValue(); + // reverse debits and credits for reversals + if (isReversal) { + accountTypeToDebitId = accountTypeToBeCredited.getValue(); + accountTypeToCreditId = accountTypeToBeDebited.getValue(); + } + createJournalEntriesForLoan(office, accountTypeToDebitId, accountTypeToCreditId, loanProductId, loanId, transactionId, + transactionDate, amount); } - public GLAccount getLinkedAccrualAccountsForLoanProduct(final Long loanProductId, final ACCRUAL_ACCOUNTS_FOR_LOAN finAccountType) { - final ProductToGLAccountMapping accountMapping = this.accountMappingRepository - .findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(), - finAccountType.getValue()); - return accountMapping.getGlAccount(); + /** + * Convenience method that creates a pair of related Debits and Credits for + * Cash Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param loanProductId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createCashBasedJournalEntriesAndReversalsForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountTypeToBeDebited, + final CASH_ACCOUNTS_FOR_LOAN accountTypeToBeCredited, final Long loanProductId, final Long loanId, final String transactionId, + final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + int accountTypeToDebitId = accountTypeToBeDebited.getValue(); + int accountTypeToCreditId = accountTypeToBeCredited.getValue(); + // reverse debits and credits for reversals + if (isReversal) { + accountTypeToDebitId = accountTypeToBeCredited.getValue(); + accountTypeToCreditId = accountTypeToBeDebited.getValue(); + } + createJournalEntriesForLoan(office, accountTypeToDebitId, accountTypeToCreditId, loanProductId, loanId, transactionId, + transactionDate, amount); } - public void createCreditJournalEntryForLoanProduct(final Office office, final GLAccount account, final Long loanId, - final String transactionId, final Date transactionDate, final BigDecimal amount) { - final boolean manualEntry = false; - final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, - JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId); - this.glJournalEntryRepository.saveAndFlush(journalEntry); + public void createCreditJournalEntryOrReversalForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountMappingType, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { + int accountMappingTypeId = accountMappingType.getValue(); + createCreditJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate, + amount, isReversal); } - public void createDebitJournalEntryForLoanProduct(final Office office, final GLAccount account, final Long loanId, - final String transactionId, final Date transactionDate, final BigDecimal amount) { - final boolean manualEntry = false; - final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, - JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId); - this.glJournalEntryRepository.saveAndFlush(journalEntry); + public void createCreditJournalEntryOrReversalForLoan(final Office office, final ACCRUAL_ACCOUNTS_FOR_LOAN accountMappingType, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { + int accountMappingTypeId = accountMappingType.getValue(); + createCreditJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate, + amount, isReversal); + } + + public void createDebitJournalEntryOrReversalForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountMappingType, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { + int accountMappingTypeId = accountMappingType.getValue(); + createDebitJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate, + amount, isReversal); + } + + public void createDebitJournalEntryOrReversalForLoan(final Office office, final ACCRUAL_ACCOUNTS_FOR_LOAN accountMappingType, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { + int accountMappingTypeId = accountMappingType.getValue(); + createDebitJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate, + amount, isReversal); } /** @@ -138,4 +207,55 @@ public class AccountingProcessorHelper { return this.officeRepository.findOne(officeId); } + private void createJournalEntriesForLoan(final Office office, final int accountTypeToDebitId, final int accountTypeToCreditId, + final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) { + GLAccount debitAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToDebitId); + GLAccount creditAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToCreditId); + createCreditJournalEntryForLoan(office, debitAccount, loanId, transactionId, transactionDate, amount); + createDebitJournalEntryForLoan(office, creditAccount, loanId, transactionId, transactionDate, amount); + } + + private void createDebitJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId, + final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId); + if (isReversal) { + createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } else { + createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } + } + + private void createCreditJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId, + final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId); + if (isReversal) { + createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } else { + createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount); + } + } + + private void createCreditJournalEntryForLoan(final Office office, final GLAccount account, final Long loanId, + final String transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, + JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + + private void createDebitJournalEntryForLoan(final Office office, final GLAccount account, final Long loanId, + final String transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, + JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + + private GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId) { + final ProductToGLAccountMapping accountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(), + accountMappingTypeId); + return accountMapping.getGlAccount(); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index e7a352613..f156be428 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -5,7 +5,6 @@ import java.util.Date; import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; -import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.organisation.office.domain.Office; @@ -36,7 +35,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } /*** Handle Apply charges ***/ - // TODO + else if (loanTransactionDTO.getTransactionType().isApplyCharges()) { + createJournalEntriesForApplyCharges(loanDTO, loanTransactionDTO, office); + } /*** * Handle repayments, repayments at disbursement and reversal of @@ -44,13 +45,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess ***/ else if (loanTransactionDTO.getTransactionType().isRepayment() || loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()) { - createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office); + createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false); } - /** Handle Write Offs and waivers **/ + /** Handle Write Offs, waivers and thier reversals **/ else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest() || loanTransactionDTO .getTransactionType().isWaiveCharges())) { - createJournalEntriesForWriteOff(loanDTO, loanTransactionDTO, office); + createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true); } } } @@ -79,136 +80,38 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess // create journal entries for the disbursement (or disbursement // reversal) - final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE); - if (isReversed) { - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - disbursalAmount); - helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, disbursalAmount); - } else { - helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - disbursalAmount); - helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - disbursalAmount); - } + helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, + ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversed); // create journal entries for recognising interest (or reversal) BigDecimal interestApplied = loanDTO.getCalculatedInterest(); if (interestApplied != null && !(interestApplied.compareTo(BigDecimal.ZERO) == 0)) { - - final GLAccount interestReceivableAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE); - final GLAccount interestOnLoansAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate, - interestApplied); - helper.createCreditJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate, - interestApplied); - } else { - helper.createDebitJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate, - interestApplied); - helper.createCreditJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate, - interestApplied); - } + helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE, + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, + isReversed); } } /** - * Create a single Debit for fund source and multiple credits if applicable - * (loan portfolio for principal repayments, Interest receivable for - * interest repayments, Fee receivable for fees payment and Penalty - * Receivable from penalties for penalty payment) * + * Handles repayments using the following posting rules
+ *
*
* - * In case the loan transaction has been reversed, 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(); - - // 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 boolean isReversed = loanTransactionDTO.isReversed(); - - BigDecimal totalDebitAmount = new BigDecimal(0); - - if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(principalAmount); - - final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } - } - if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(interestAmount); - - final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } - } - if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(feesAmount); - - final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } - } - if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(penaltiesAmount); - - final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } - } - final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE); - if (isReversed) { - helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); - } else { - helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); - } - } - - /** - * Handles write offs using the following posting rules
+ * Principal Repayment: Debits "Fund Source" and Credits + * "Loan Portfolio"
* + * Interest Repayment:Debits "Fund Source" and and Credits + * "Receivable Interest"
+ * + * Fee Write Repayment:Debits "Fund Source" and and Credits + * "Receivable Fees"
+ * + * Penalty Repayment: Debits "Fund Source" and and Credits + * "Receivable Penalties"
+ *
+ * Handles write offs using the following posting rules
+ *
* Principal Write off: Debits "Losses Written Off" and Credits * "Loan Portfolio"
* @@ -220,12 +123,18 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess * * Penalty Write off: Debits "Losses Written off" and and Credits * "Receivable Penalties"
+ *
+ *
+ * In case the loan transaction has been reversed, all debits are turned + * into credits and vice versa * * @param loanTransactionDTO * @param loanDTO * @param office + * */ - private void createJournalEntriesForWriteOff(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { + private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, + final Office office, final boolean writeOff) { // loan properties final Long loanProductId = loanDTO.getLoanProductId(); final Long loanId = loanDTO.getLoanId(); @@ -237,71 +146,90 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess final BigDecimal interestAmount = loanTransactionDTO.getInterest(); final BigDecimal feesAmount = loanTransactionDTO.getFees(); final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); - final boolean isReversed = loanTransactionDTO.isReversed(); + final boolean isReversal = loanTransactionDTO.isReversed(); BigDecimal totalDebitAmount = new BigDecimal(0); + // handle principle payment or writeOff (and reversals) if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(principalAmount); - - final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } - + helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId, + transactionId, transactionDate, principalAmount, isReversal); } + + // handle interest payment of writeOff (and reversals) if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(interestAmount); - - final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE, loanProductId, loanId, + transactionId, transactionDate, interestAmount, isReversal); } + + // handle fees payment of writeOff (and reversals) if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); - - final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE, loanProductId, loanId, + transactionId, transactionDate, feesAmount, isReversal); } + + // handle penalties payment of writeOff (and reversals) if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); - - final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE, loanProductId, loanId, + transactionId, transactionDate, penaltiesAmount, isReversal); } - final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF); - if (isReversed) { - helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); + + /** + * Single DEBIT transaction for write-offs or Repayments (and their + * reversals) + ***/ + if (writeOff) { + helper.createDebitJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF, loanProductId, loanId, + transactionId, transactionDate, totalDebitAmount, isReversal); } else { - helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); + helper.createDebitJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, + transactionId, transactionDate, totalDebitAmount, isReversal); + } + } + + /** + * Create Journal Entries for application of charges (fees and payments) + * using the following rules
+ * + * Fees: Debit Fees Receivable and credit Income from + * Fees
+ * + * Penalties: Debit Penalties Receivable and credit Income + * from Penalties + * + * Also handles reversals for both fees and payment applications + * + * @param loanDTO + * @param loanTransactionDTO + * @param office + */ + private void createJournalEntriesForApplyCharges(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { + + // loan properties + final Long loanProductId = loanDTO.getLoanProductId(); + final Long loanId = loanDTO.getLoanId(); + + // transaction properties + final String transactionId = loanTransactionDTO.getTransactionId(); + final Date transactionDate = loanTransactionDTO.getTransactionDate(); + final BigDecimal feesAmount = loanTransactionDTO.getFees(); + final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); + final boolean isReversed = loanTransactionDTO.isReversed(); + + // create journal entries for the fees application (or reversal) + if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { + helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, + ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversed); + } + // create journal entries for the penalties application (or reversal) + else if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { + helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, transactionId, transactionDate, feesAmount, + isReversed); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index 0779fb328..236c658a4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -5,7 +5,6 @@ import java.util.Date; import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; -import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.organisation.office.domain.Office; @@ -53,15 +52,9 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF **/ else if (loanTransactionDTO.getTransactionType().isWriteOff() && !loanTransactionDTO.isReversed()) { final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); - final GLAccount lossesWrittenOffAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF); - helper.createDebitJournalEntryForLoanProduct(office, lossesWrittenOffAccount, loanId, transactionId, transactionDate, - principalAmount); - - final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); + helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF, + CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId, transactionId, transactionDate, principalAmount, + false); } } } @@ -87,22 +80,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF final String transactionId = loanTransactionDTO.getTransactionId(); final Date transactionDate = loanTransactionDTO.getTransactionDate(); final BigDecimal disbursalAmount = loanTransactionDTO.getAmount(); - final boolean isReversed = loanTransactionDTO.isReversed(); + final boolean isReversal = loanTransactionDTO.isReversed(); - final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - final GLAccount fundSourceAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE); - if (isReversed) { - helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, disbursalAmount); - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - disbursalAmount); - - } else { - helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - disbursalAmount); - helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - disbursalAmount); - } + helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversal); } /** @@ -132,63 +113,30 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(principalAmount); - - final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - if (isReversal) { - helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, - principalAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId, + transactionId, transactionDate, principalAmount, isReversal); } + if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(interestAmount); - - final GLAccount interestAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS); - if (isReversal) { - helper.createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, - interestAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, + transactionId, transactionDate, interestAmount, isReversal); } + if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); - - final GLAccount incomeFromFeesAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES); - if (isReversal) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, - feesAmount); - } + helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES, loanProductId, loanId, + transactionId, transactionDate, feesAmount, isReversal); } + if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); + helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, + transactionId, transactionDate, penaltiesAmount, isReversal); + } - final GLAccount incomeFromPenaltiesAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES); - if (isReversal) { - helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } else { - helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, - penaltiesAmount); - } - } - final GLAccount fundSourceAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE); - if (isReversal) { - helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); - } else { - helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, - totalDebitAmount); - } + /*** create a single debit entry (or reversal) for the entire amount **/ + helper.createDebitJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, + transactionDate, totalDebitAmount, isReversal); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java index 560611bd7..95e6c20d5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java @@ -21,6 +21,7 @@ public class LoanTransactionEnumData { private final boolean contra; private final boolean waiveInterest; private final boolean waiveCharges; + private final boolean applyCharges; private final boolean writeOff; private final boolean recoveryRepayment; @@ -34,6 +35,7 @@ public class LoanTransactionEnumData { this.contra = Long.valueOf(3).equals(this.id); this.waiveInterest = Long.valueOf(4).equals(this.id); this.waiveCharges = Long.valueOf(9).equals(this.id); + this.applyCharges = Long.valueOf(10).equals(this.id); this.writeOff = Long.valueOf(6).equals(this.id); this.recoveryRepayment = Long.valueOf(8).equals(this.id); } @@ -70,4 +72,8 @@ public class LoanTransactionEnumData { return this.recoveryRepayment; } + public boolean isApplyCharges() { + return this.applyCharges; + } + } \ No newline at end of file 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 6fd0dd243..61d288758 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 @@ -378,7 +378,8 @@ public class Loan extends AbstractPersistable { this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; } - public void addLoanCharge(final LoanCharge loanCharge) { + public LoanTransaction addLoanCharge(final LoanCharge loanCharge, final List existingTransactionIds, + final List existingReversedTransactionIds) { validateLoanIsNotClosed(loanCharge); @@ -420,6 +421,53 @@ public class Loan extends AbstractPersistable { } updateLoanSummaryDerivedFields(); + + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + final LoanTransaction applyLoanChargeTransaction = getChargeAppliedTransaction(loanCharge, null); + + return applyLoanChargeTransaction; + } + + /** + * Creates a loanTransaction for "Apply Charge Event" with transaction date + * set to "suppliedTransactionDate" + * + * If "suppliedTransactionDate" is not passed Id, the transaction date is + * set to the loans due date if the due date is lesser than todays date. If + * not, the transaction date is set to todays date + * + * @param loanCharge + * @param suppliedTransactionDate + * @return + */ + private LoanTransaction getChargeAppliedTransaction(final LoanCharge loanCharge, LocalDate suppliedTransactionDate) { + final Money chargeAmount = loanCharge.getAmount(getCurrency()); + Money feeCharges = chargeAmount; + Money penaltyCharges = Money.zero(loanCurrency()); + if (loanCharge.isPenaltyCharge()) { + penaltyCharges = chargeAmount; + feeCharges = Money.zero(loanCurrency()); + } + + LocalDate transactionDate = null; + + if (suppliedTransactionDate != null) { + transactionDate = suppliedTransactionDate; + } else { + transactionDate = loanCharge.getDueLocalDate(); + LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + + // if loan charge is to be applied on a future date, the loan + // transaction would show todays date as applied date + if (currentDate.isBefore(transactionDate)) { + transactionDate = currentDate; + } + } + + final LoanTransaction applyLoanChargeTransaction = LoanTransaction.applyLoanCharge(this, chargeAmount, transactionDate, feeCharges, + penaltyCharges); + return applyLoanChargeTransaction; } private void validateLoanIsNotClosed(final LoanCharge loanCharge) { @@ -542,10 +590,10 @@ public class Loan extends AbstractPersistable { changes.put("amount", amountWaived.getAmount()); - Money feeChargesWaived = Money.of(loanCurrency(), loanCharge.amount()); + Money feeChargesWaived = amountWaived; Money penaltyChargesWaived = Money.zero(loanCurrency()); if (loanCharge.isPenaltyCharge()) { - penaltyChargesWaived = Money.of(loanCurrency(), loanCharge.amount()); + penaltyChargesWaived = amountWaived; feeChargesWaived = Money.zero(loanCurrency()); } @@ -1244,9 +1292,17 @@ public class Loan extends AbstractPersistable { chargesPayment.updateLoan(this); this.loanTransactions.add(chargesPayment); + /** + * all Charges repaid at disbursal is marked as repaid and + * "APPLY Charge" transactions are created for all other fees ( + * which are created during disbursal but not repaid) + **/ for (LoanCharge charge : setOfLoanCharges()) { if (charge.isDueAtDisbursement()) { charge.markAsFullyPaid(); + } else { + LoanTransaction loanChargeAppliedTransaction = getChargeAppliedTransaction(charge, disbursedOn); + this.loanTransactions.add(loanChargeAppliedTransaction); } } } @@ -1843,7 +1899,7 @@ public class Loan extends AbstractPersistable { public boolean isSubmittedOnDateAfter(final LocalDate compareDate) { return this.submittedOnDate == null ? false : (new LocalDate(submittedOnDate)).isAfter(compareDate); } - + private LocalDate getSubmittedOnDate() { return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null); } @@ -2080,7 +2136,7 @@ public class Loan extends AbstractPersistable { break; } - if (lastAssignmentRecordLatestEndDate == null){ + if (lastAssignmentRecordLatestEndDate == null) { lastAssignmentRecordLatestEndDate = historyRecord; } else if (historyRecord.isEndDateAfter(lastAssignmentRecordLatestEndDate.getEndDate()) && !historyRecord.isSameLoanOfficer(newLoanOfficer)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index b383ca3be..454c8e937 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -466,7 +466,7 @@ public class LoanCharge extends AbstractPersistable { return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero(); } - private Money getAmount(final MonetaryCurrency currency) { + public Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 3be66bb50..90037fc16 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -85,6 +85,14 @@ public final class LoanTransaction extends AbstractPersistable { public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) { return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate); } + + public static LoanTransaction applyLoanCharge(final Loan loan, final Money amount, final LocalDate applyDate, + final Money feeCharges, final Money penaltyCharges) { + LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate); + applyCharge.updateChargesComponents(feeCharges, penaltyCharges); + + return applyCharge; + } public static LoanTransaction waiveLoanCharge(final Loan loan, final Money waived, final LocalDate waiveDate, final Money feeChargesWaived, final Money penaltyChargesWaived) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java index 62f6c3cad..1343fa6f7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java @@ -13,13 +13,15 @@ public enum LoanTransactionType { CONTRA(3, "loanTransactionType.contra"), // WAIVE_INTEREST(4, "loanTransactionType.waiver"), // REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement"), // - WRITEOFF(6, "loanTransactionType.writeOff"), MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), // + WRITEOFF(6, "loanTransactionType.writeOff"), // + MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), // /** * This type of transactions is allowed on written-off loans where mfi still * attempts to recover payments from applicant after writing-off. */ RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), // - WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"); + WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), // + APPLY_CHARGES(10, "loanTransactionType.applyCharges"); private final Integer value; private final String code; @@ -70,6 +72,9 @@ public enum LoanTransactionType { case 9: loanTransactionType = LoanTransactionType.WAIVE_CHARGES; break; + case 10: + loanTransactionType = LoanTransactionType.APPLY_CHARGES; + break; default: loanTransactionType = LoanTransactionType.INVALID; break; @@ -101,6 +106,10 @@ public enum LoanTransactionType { return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue()); } + public boolean isApplyCharges() { + return this.value.equals(LoanTransactionType.APPLY_CHARGES.getValue()); + } + public boolean isWriteOff() { return this.value.equals(LoanTransactionType.WRITEOFF.getValue()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index ec68ed406..724ec2e77 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -489,11 +489,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage); } + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); this.loanChargeRepository.save(loanCharge); - loan.addLoanCharge(loanCharge); + LoanTransaction applyChargeTransaction = loan.addLoanCharge(loanCharge, existingTransactionIds, existingReversedTransactionIds); + + // we want to apply charge transactions only for those loans charges + // that are applied when a loan is active + if (loan.status().isActive()) { + this.loanTransactionRepository.save(applyChargeTransaction); + } + this.loanRepository.save(loan); + // we post Journal entries only for loans in active status + if (loan.status().isActive()) { + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loanCharge.getId()) // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index ebe19ab67..93df32e6a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -206,8 +206,7 @@ public class LoanEnumerations { switch (type) { case INVALID: optionData = new LoanTransactionEnumData(LoanTransactionType.INVALID.getValue().longValue(), - LoanTransactionType.INVALID.getCode(), - "Invalid"); + LoanTransactionType.INVALID.getCode(), "Invalid"); break; case DISBURSEMENT: optionData = new LoanTransactionEnumData(LoanTransactionType.DISBURSEMENT.getValue().longValue(), @@ -223,8 +222,7 @@ public class LoanEnumerations { break; case CONTRA: optionData = new LoanTransactionEnumData(LoanTransactionType.CONTRA.getValue().longValue(), - LoanTransactionType.CONTRA.getCode(), - "Reversal"); + LoanTransactionType.CONTRA.getCode(), "Reversal"); break; case WAIVE_INTEREST: optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_INTEREST.getValue().longValue(), @@ -246,6 +244,10 @@ public class LoanEnumerations { optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_CHARGES.getValue().longValue(), LoanTransactionType.WAIVE_CHARGES.getCode(), "Waive loan charges"); break; + case APPLY_CHARGES: + optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_CHARGES.getValue().longValue(), + LoanTransactionType.APPLY_CHARGES.getCode(), "Apply Charge"); + break; } return optionData; }