diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryWritePlatformServiceJpaRepositoryImpl.java index 28dc7b7a1..2c93df709 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLJournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -128,6 +128,18 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo @Override public void createJournalEntriesForLoan(Loan loan, List loanTransactions) { // shouldn't be before an accounting closure + if (loan.isCashBasedAccountingEnabledOnLoanProduct()) { + createJournalEntriesUsingCashRules(loan, loanTransactions); + } else if (loan.isAccrualBasedAccountingEnabledOnLoanProduct()) { + // TODO Vishwas: add accrual based accounting rules + } + } + + /** + * @param loan + * @param loanTransactions + */ + private void createJournalEntriesUsingCashRules(Loan loan, List loanTransactions) { GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(loan.getOfficeId()); Office office = loan.getClient().getOffice(); Long loanProductId = loan.productId(); @@ -141,9 +153,11 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo * the transaction Date **/ if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf())) { throw new GLJournalEntryInvalidException( + if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf()) + || latestGLClosure.getClosingDate().equals(loanTransaction.getDateOf())) { throw new GLJournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } + /*** Debit loan Portfolio and credit Fund source for Disbursal **/ if (loanTransaction.isDisbursement()) { @@ -153,54 +167,104 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo BigDecimal disbursalAmount = loanTransaction.getAmount(); createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, disbursalAmount); createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, disbursalAmount); + } else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement() || loanTransaction.isContra()) { + createJournalEntriesForRepaymentOrContraUsingCashRules(loan, office, loanProductId, loanTransaction, entryDate, + transactionId, loanId); }/*** - * Create a single Debit to fund source and multiple credits if - * applicable (loan portfolio for principal repayment, Interest on - * loans for interest repayments, Income from fees for fees payment - * and Income from penalties for penalty payment) + * 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 (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement()) { + else if (loanTransaction.isWriteOff()) { BigDecimal principalAmount = loanTransaction.getPrincipalPortion(loan.getCurrency()).getAmount(); - BigDecimal interestAmount = loanTransaction.getInterestPortion(loan.getCurrency()).getAmount(); - BigDecimal feesAmount = loanTransaction.getFeeChargesPortion(loan.getCurrency()).getAmount(); - BigDecimal penaltiesAmount = loanTransaction.getPenaltyChargesPortion(loan.getCurrency()).getAmount(); + GLAccount lossesWrittenOffAccount = getLinkedFinAccountForLoanProduct(loanProductId, + CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF); + createDebitJournalEntryForLoanProduct(office, lossesWrittenOffAccount, loanId, transactionId, entryDate, principalAmount); - BigDecimal totalDebitAmount = new BigDecimal(0); + GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); + createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount); + } + } + } - if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(principalAmount); + /** + * Create a single Debit to fund source and multiple credits if applicable + * (loan portfolio for principal repayment, 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 contra, all debits are turned into + * credits and vice versa + * + * @param loan + * @param office + * @param loanProductId + * @param loanTransaction + * @param entryDate + * @param transactionId + * @param loanId + */ + private void createJournalEntriesForRepaymentOrContraUsingCashRules(Loan loan, Office office, Long loanProductId, + LoanTransaction loanTransaction, Date entryDate, String transactionId, Long loanId) { + BigDecimal principalAmount = loanTransaction.getPrincipalPortion(loan.getCurrency()).getAmount(); + BigDecimal interestAmount = loanTransaction.getInterestPortion(loan.getCurrency()).getAmount(); + BigDecimal feesAmount = loanTransaction.getFeeChargesPortion(loan.getCurrency()).getAmount(); + BigDecimal penaltiesAmount = loanTransaction.getPenaltyChargesPortion(loan.getCurrency()).getAmount(); - GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); - createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount); - } - if (!(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(interestAmount); + BigDecimal totalDebitAmount = new BigDecimal(0); - GLAccount interestAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS); - createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount); - } - if (!(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(feesAmount); - - GLAccount incomeFromFeesAccount = getLinkedFinAccountForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES); - createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount); - } - if (!(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { - totalDebitAmount = totalDebitAmount.add(penaltiesAmount); - - GLAccount incomeFromPenaltiesAccount = getLinkedFinAccountForLoanProduct(loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES); - createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate, - penaltiesAmount); - } - GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE); - createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount); - } // TODO Vishwas: Add conditions for write off, contra waive - // interest - // and waive charges + if (loanTransaction.isContra()) { + transactionId = "reversal-" + transactionId; } + if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(principalAmount); + + GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); + if (loanTransaction.isContra()) { + createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount); + } else { + createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount); + } + } + if (!(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(interestAmount); + + GLAccount interestAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS); + if (loanTransaction.isContra()) { + createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount); + } else { + createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount); + } + } + if (!(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(feesAmount); + + GLAccount incomeFromFeesAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES); + if (loanTransaction.isContra()) { + createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount); + } else { + createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount); + } + } + if (!(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(penaltiesAmount); + + GLAccount incomeFromPenaltiesAccount = getLinkedFinAccountForLoanProduct(loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES); + if (loanTransaction.isContra()) { + createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate, penaltiesAmount); + } else { + createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate, + penaltiesAmount); + } + } + GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE); + if (loanTransaction.isContra()) { + createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount); + } else { + createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount); + } } @Transactional @@ -246,7 +310,7 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo // shouldn't be before an accounting closure GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId()); if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().after(entryDate)) { throw new GLJournalEntryInvalidException( + if (latestGLClosure.getClosingDate().after(entryDate) || latestGLClosure.getClosingDate().equals(entryDate)) { throw new GLJournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } /*** check if credits and debits are valid **/ 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 e87c33e5d..da7d4bcf5 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 @@ -2003,6 +2003,14 @@ public class Loan extends AbstractAuditableCustom { public boolean isAccountingEnabledOnLoanProduct() { return this.loanProduct.isAccountingEnabled(); } + + public boolean isCashBasedAccountingEnabledOnLoanProduct(){ + return this.loanProduct.isCashBasedAccountingEnabled(); + } + + public boolean isAccrualBasedAccountingEnabledOnLoanProduct(){ + return this.loanProduct.isAccrualBasedAccountingEnabled(); + } public Long productId() { return this.loanProduct.getId(); 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 d657e843f..0239f3673 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 @@ -146,6 +146,10 @@ public class LoanTransaction extends AbstractAuditableCustom { public boolean isNotContra() { return this.contra == null; } + + public boolean isContra() { + return this.contra != null; + } public boolean isDisbursement() { return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()); 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 e23706838..cee77e0c8 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 @@ -122,8 +122,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf // variable stores Id's of all existing loan transactions (newly created // loan transactions would not have an Id before save) Set existingLoanTransactionIds = new HashSet(); - for (LoanTransaction loanTransaction : loan.getLoanTransactions()) { - if (loanTransaction.getId() == null) { + for(LoanTransaction loanTransaction:loan.getLoanTransactions()){ + if(!(loanTransaction.getId()==null)){ existingLoanTransactionIds.add(loanTransaction.getId()); } } @@ -261,13 +261,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Map changes = new LinkedHashMap(); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); + + boolean transactionIsRepayment= transactionToAdjust.isRepayment(); final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount, defaultLoanLifecycleStateMachine(), transactionToAdjust); if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { this.loanTransactionRepository.save(newTransactionDetail); } - + this.loanRepository.save(loan); final String noteText = command.stringValueOfParameterNamed("note"); @@ -279,7 +281,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); - + + // make a call to accounting only if transaction being adjusted is a repayment (not waiver) + if (loan.isAccountingEnabledOnLoanProduct() && transactionIsRepayment) { + List newLoanTransactions = new ArrayList(); + if (newTransactionDetail.getId() != null) { + newLoanTransactions.add(newTransactionDetail); + } + newLoanTransactions.add(transactionToAdjust); + journalEntryWritePlatformService.createJournalEntriesForLoan(loan, newLoanTransactions); + } + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(transactionId) // @@ -361,6 +373,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); + + // make a call to accounting + if (loan.isAccountingEnabledOnLoanProduct()) { + journalEntryWritePlatformService.createJournalEntriesForLoan(loan, writeoff); + } return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 776e2872c..ef0f5d067 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -218,6 +218,14 @@ public class LoanProduct extends AbstractAuditableCustom { } public boolean isAccountingEnabled() { - return AccountingRuleType.NONE.getValue().equals(this.accountingType); + return !AccountingRuleType.NONE.getValue().equals(this.accountingType); + } + + public boolean isCashBasedAccountingEnabled(){ + return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingType); + } + + public boolean isAccrualBasedAccountingEnabled(){ + return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingType); } } \ No newline at end of file