diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLJournalEntryWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLJournalEntryWritePlatformService.java index ae96bc04a..4463a38cc 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLJournalEntryWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLJournalEntryWritePlatformService.java @@ -1,6 +1,10 @@ package org.mifosplatform.accounting.service; +import java.util.List; + import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.springframework.security.access.prepost.PreAuthorize; public interface GLJournalEntryWritePlatformService { @@ -11,4 +15,8 @@ public interface GLJournalEntryWritePlatformService { @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REVERT_JOURNAL_ENTRY')") String revertJournalEntry(String transactionId); + void createJournalEntriesForLoan(Loan loan, List loanTransactions); + + void createJournalEntriesForLoan(Loan loan, LoanTransaction loanTransaction); + } 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 27d1cedc0..6748be773 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 @@ -1,13 +1,16 @@ package org.mifosplatform.accounting.service.impl; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.mifosplatform.accounting.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.AccountingConstants.JOURNAL_ENTRY_TYPE; +import org.mifosplatform.accounting.AccountingConstants.PORTFOLIO_PRODUCT_TYPE; import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand; import org.mifosplatform.accounting.api.commands.SingleDebitOrCreditEntryCommand; import org.mifosplatform.accounting.domain.GLAccount; @@ -16,6 +19,8 @@ import org.mifosplatform.accounting.domain.GLClosure; import org.mifosplatform.accounting.domain.GLClosureRepository; import org.mifosplatform.accounting.domain.GLJournalEntry; import org.mifosplatform.accounting.domain.GLJournalEntryRepository; +import org.mifosplatform.accounting.domain.ProductToGLAccountMapping; +import org.mifosplatform.accounting.domain.ProductToGLAccountMappingRepository; import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException; import org.mifosplatform.accounting.exceptions.GLJournalEntriesNotFoundException; import org.mifosplatform.accounting.exceptions.GLJournalEntryInvalidException; @@ -26,6 +31,8 @@ import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityExce import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; +import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -42,15 +49,17 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo private final GLAccountRepository glAccountRepository; private final GLJournalEntryRepository glJournalEntryRepository; private final OfficeRepository officeRepository; + private final ProductToGLAccountMappingRepository accountMappingRepository; @Autowired public GLJournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, final GLAccountRepository glAccountRepository, final GLJournalEntryRepository glJournalEntryRepository, - final OfficeRepository officeRepository) { + final OfficeRepository officeRepository, final ProductToGLAccountMappingRepository accountMappingRepository) { this.glClosureRepository = glClosureRepository; this.officeRepository = officeRepository; this.glAccountRepository = glAccountRepository; this.glJournalEntryRepository = glJournalEntryRepository; + this.accountMappingRepository = accountMappingRepository; } @Transactional @@ -115,6 +124,113 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo return reversalTransactionId; } + @Transactional + @Override + public void createJournalEntriesForLoan(Loan loan, List loanTransactions) { + // shouldn't be before an accounting closure + GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(loan.getOfficeId()); + Office office = loan.getClient().getOffice(); + Long loanProductId = loan.loanProduct().getId(); + // TODO: Check for accounting type + for (LoanTransaction loanTransaction : loanTransactions) { + Date entryDate = loanTransaction.getDateOf(); + String transactionId = loanTransaction.getId().toString(); + Long loanId = loan.getId(); + /** + * check if an accounting closure has happened for this branch after + * the transaction Date + **/ + if (latestGLClosure != null) { + if (latestGLClosure.getClosingDate().after(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()) { + + GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); + GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE); + + BigDecimal disbursalAmount = loanTransaction.getAmount(); + createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, disbursalAmount); + createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, disbursalAmount); + }/*** + * 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) + **/ + else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement()) { + 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(); + + BigDecimal totalDebitAmount = new BigDecimal(0); + + if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { + totalDebitAmount = totalDebitAmount.add(principalAmount); + + 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); + + 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 + } + + } + + @Transactional + @Override + public void createJournalEntriesForLoan(Loan loan, LoanTransaction loanTransaction) { + List loanTransactions = new ArrayList(); + loanTransactions.add(loanTransaction); + createJournalEntriesForLoan(loan, loanTransactions); + } + + private GLAccount getLinkedFinAccountForLoanProduct(Long loanProductId, CASH_ACCOUNTS_FOR_LOAN finAccountType) { + ProductToGLAccountMapping accountMapping = accountMappingRepository.findByProductIdAndProductTypeAndFinancialAccountType( + loanProductId, PORTFOLIO_PRODUCT_TYPE.LOAN.getValue(), finAccountType.getValue()); + return accountMapping.getGlAccount(); + } + + private void createCreditJournalEntryForLoanProduct(Office office, GLAccount account, Long loanId, String transactionId, + Date entryDate, BigDecimal amount) { + GLJournalEntry journalEntry = GLJournalEntry.createNew(office, account, transactionId, true, entryDate, + JOURNAL_ENTRY_TYPE.CREDIT.toString(), amount, null, PORTFOLIO_PRODUCT_TYPE.LOAN.toString(), loanId); + glJournalEntryRepository.saveAndFlush(journalEntry); + } + + private void createDebitJournalEntryForLoanProduct(Office office, GLAccount account, Long loanId, String transactionId, Date entryDate, + BigDecimal amount) { + GLJournalEntry journalEntry = GLJournalEntry.createNew(office, account, transactionId, true, entryDate, + JOURNAL_ENTRY_TYPE.DEBIT.toString(), amount, null, PORTFOLIO_PRODUCT_TYPE.LOAN.toString(), loanId); + glJournalEntryRepository.saveAndFlush(journalEntry); + } + /** * @param command */ @@ -212,4 +328,5 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo private String generateTransactionId() { return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); } + } 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 45e0312b1..eecc9ca3b 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 @@ -946,7 +946,6 @@ public class Loan extends AbstractAuditableCustom { private void disburse(final LocalDate expectedDisbursedOnLocalDate) { this.disbursedOnDate = expectedDisbursedOnLocalDate.toDate(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); - handleDisbursementTransaction(expectedDisbursedOnLocalDate); } @@ -1916,7 +1915,7 @@ public class Loan extends AbstractAuditableCustom { return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode(); } - private MonetaryCurrency getCurrency() { + public MonetaryCurrency getCurrency() { return this.loanRepaymentScheduleDetail.getCurrency(); } @@ -2000,4 +1999,9 @@ public class Loan extends AbstractAuditableCustom { this.accountNumber = newAccountNo; this.accountNumberRequiresAutoGeneration=false; } + + public List getLoanTransactions() { + return this.loanTransactions; + } + } \ No newline at end of file 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 4d5867538..f709af563 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 @@ -1,13 +1,17 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; +import org.mifosplatform.accounting.service.GLJournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -41,6 +45,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFound import org.mifosplatform.portfolio.loanaccount.serialization.LoanChargeCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanTransactionCommandFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; @@ -61,6 +66,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final ChargeRepository chargeRepository; private final LoanChargeRepository loanChargeRepository; private final ApplicationCurrencyRepository applicationCurrencyRepository; + private final GLJournalEntryWritePlatformService journalEntryWritePlatformService; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -69,7 +75,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository, final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository, - final ApplicationCurrencyRepository applicationCurrencyRepository) { + final ApplicationCurrencyRepository applicationCurrencyRepository, + final GLJournalEntryWritePlatformService journalEntryWritePlatformService) { this.context = context; this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer; this.loanTransactionCommandFromApiJsonDeserializer = loanTransactionCommandFromApiJsonDeserializer; @@ -81,6 +88,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.chargeRepository = chargeRepository; this.loanChargeRepository = loanChargeRepository; this.applicationCurrencyRepository = applicationCurrencyRepository; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; } private boolean isBeforeToday(final LocalDate date) { @@ -111,13 +119,36 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode()); + final Map changes = loan.disburse(command, defaultLoanLifecycleStateMachine(), currency); + //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){ + existingLoanTransactionIds.add(loanTransaction.getId()); + } + } this.loanRepository.save(loan); if (StringUtils.isNotBlank(noteText)) { Note note = Note.loanNote(loan, noteText); this.noteRepository.save(note); } + + // make a call to accounting + if (loan.loanProduct().getAccountingType() != AccountingRuleType.NONE.getValue()) { + /*** + * Variable holds list of all newly created loan transactions during this + * disbursal + **/ + List newLoanTransactions = new ArrayList(); + for(LoanTransaction loanTransaction:loan.getLoanTransactions()){ + if(!existingLoanTransactionIds.contains(loanTransaction.getId())){ + newLoanTransactions.add(loanTransaction); + } + } + journalEntryWritePlatformService.createJournalEntriesForLoan(loan, newLoanTransactions); + } return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // @@ -193,6 +224,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); + + // make a call to accounting + if (loan.loanProduct().getAccountingType() != AccountingRuleType.NONE.getValue()) { + journalEntryWritePlatformService.createJournalEntriesForLoan(loan, loanRepayment); + } 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 ff52e38a8..9d97080e8 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 @@ -146,6 +146,10 @@ public class LoanProduct extends AbstractAuditableCustom { public void update(final Set charges) { this.charges = charges; } + + public Integer getAccountingType() { + return this.accountingType; + } public Map update(final JsonCommand command, final AprCalculator aprCalculator) {