accounting wip:cash basedJournal entries for disbursal and repayments

This commit is contained in:
Vishwas Babu AJ
2013-01-16 14:02:05 +05:30
parent 38ee2d98ac
commit 5240d308b8
5 changed files with 173 additions and 4 deletions
@@ -1,6 +1,10 @@
package org.mifosplatform.accounting.service; package org.mifosplatform.accounting.service;
import java.util.List;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand; 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; import org.springframework.security.access.prepost.PreAuthorize;
public interface GLJournalEntryWritePlatformService { public interface GLJournalEntryWritePlatformService {
@@ -11,4 +15,8 @@ public interface GLJournalEntryWritePlatformService {
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REVERT_JOURNAL_ENTRY')") @PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REVERT_JOURNAL_ENTRY')")
String revertJournalEntry(String transactionId); String revertJournalEntry(String transactionId);
void createJournalEntriesForLoan(Loan loan, List<LoanTransaction> loanTransactions);
void createJournalEntriesForLoan(Loan loan, LoanTransaction loanTransaction);
} }
@@ -1,13 +1,16 @@
package org.mifosplatform.accounting.service.impl; package org.mifosplatform.accounting.service.impl;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; 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.JOURNAL_ENTRY_TYPE;
import org.mifosplatform.accounting.AccountingConstants.PORTFOLIO_PRODUCT_TYPE;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand; import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.mifosplatform.accounting.api.commands.SingleDebitOrCreditEntryCommand; import org.mifosplatform.accounting.api.commands.SingleDebitOrCreditEntryCommand;
import org.mifosplatform.accounting.domain.GLAccount; 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.GLClosureRepository;
import org.mifosplatform.accounting.domain.GLJournalEntry; import org.mifosplatform.accounting.domain.GLJournalEntry;
import org.mifosplatform.accounting.domain.GLJournalEntryRepository; 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.GLAccountNotFoundException;
import org.mifosplatform.accounting.exceptions.GLJournalEntriesNotFoundException; import org.mifosplatform.accounting.exceptions.GLJournalEntriesNotFoundException;
import org.mifosplatform.accounting.exceptions.GLJournalEntryInvalidException; 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.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -42,15 +49,17 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
private final GLAccountRepository glAccountRepository; private final GLAccountRepository glAccountRepository;
private final GLJournalEntryRepository glJournalEntryRepository; private final GLJournalEntryRepository glJournalEntryRepository;
private final OfficeRepository officeRepository; private final OfficeRepository officeRepository;
private final ProductToGLAccountMappingRepository accountMappingRepository;
@Autowired @Autowired
public GLJournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository, public GLJournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
final GLAccountRepository glAccountRepository, final GLJournalEntryRepository glJournalEntryRepository, final GLAccountRepository glAccountRepository, final GLJournalEntryRepository glJournalEntryRepository,
final OfficeRepository officeRepository) { final OfficeRepository officeRepository, final ProductToGLAccountMappingRepository accountMappingRepository) {
this.glClosureRepository = glClosureRepository; this.glClosureRepository = glClosureRepository;
this.officeRepository = officeRepository; this.officeRepository = officeRepository;
this.glAccountRepository = glAccountRepository; this.glAccountRepository = glAccountRepository;
this.glJournalEntryRepository = glJournalEntryRepository; this.glJournalEntryRepository = glJournalEntryRepository;
this.accountMappingRepository = accountMappingRepository;
} }
@Transactional @Transactional
@@ -115,6 +124,113 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
return reversalTransactionId; return reversalTransactionId;
} }
@Transactional
@Override
public void createJournalEntriesForLoan(Loan loan, List<LoanTransaction> 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<LoanTransaction> loanTransactions = new ArrayList<LoanTransaction>();
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 * @param command
*/ */
@@ -212,4 +328,5 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
private String generateTransactionId() { private String generateTransactionId() {
return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
} }
} }
@@ -946,7 +946,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private void disburse(final LocalDate expectedDisbursedOnLocalDate) { private void disburse(final LocalDate expectedDisbursedOnLocalDate) {
this.disbursedOnDate = expectedDisbursedOnLocalDate.toDate(); this.disbursedOnDate = expectedDisbursedOnLocalDate.toDate();
this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
handleDisbursementTransaction(expectedDisbursedOnLocalDate); handleDisbursementTransaction(expectedDisbursedOnLocalDate);
} }
@@ -1916,7 +1915,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode(); return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode();
} }
private MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
return this.loanRepaymentScheduleDetail.getCurrency(); return this.loanRepaymentScheduleDetail.getCurrency();
} }
@@ -2000,4 +1999,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.accountNumber = newAccountNo; this.accountNumber = newAccountNo;
this.accountNumberRequiresAutoGeneration=false; this.accountNumberRequiresAutoGeneration=false;
} }
public List<LoanTransaction> getLoanTransactions() {
return this.loanTransactions;
}
} }
@@ -1,13 +1,17 @@
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.accounting.service.GLJournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; 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.LoanChargeCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanTransactionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanTransactionCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -61,6 +66,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final ChargeRepository chargeRepository; private final ChargeRepository chargeRepository;
private final LoanChargeRepository loanChargeRepository; private final LoanChargeRepository loanChargeRepository;
private final ApplicationCurrencyRepository applicationCurrencyRepository; private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final GLJournalEntryWritePlatformService journalEntryWritePlatformService;
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -69,7 +75,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler, final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler,
final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository,
final NoteRepository noteRepository, final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository, final NoteRepository noteRepository, final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository,
final ApplicationCurrencyRepository applicationCurrencyRepository) { final ApplicationCurrencyRepository applicationCurrencyRepository,
final GLJournalEntryWritePlatformService journalEntryWritePlatformService) {
this.context = context; this.context = context;
this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer; this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer;
this.loanTransactionCommandFromApiJsonDeserializer = loanTransactionCommandFromApiJsonDeserializer; this.loanTransactionCommandFromApiJsonDeserializer = loanTransactionCommandFromApiJsonDeserializer;
@@ -81,6 +88,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.chargeRepository = chargeRepository; this.chargeRepository = chargeRepository;
this.loanChargeRepository = loanChargeRepository; this.loanChargeRepository = loanChargeRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
} }
private boolean isBeforeToday(final LocalDate date) { 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 ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode());
final Map<String, Object> changes = loan.disburse(command, defaultLoanLifecycleStateMachine(), currency); final Map<String, Object> 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<Long> existingLoanTransactionIds = new HashSet<Long>();
for(LoanTransaction loanTransaction:loan.getLoanTransactions()){
if(loanTransaction.getId()==null){
existingLoanTransactionIds.add(loanTransaction.getId());
}
}
this.loanRepository.save(loan); this.loanRepository.save(loan);
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
Note note = Note.loanNote(loan, noteText); Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note); 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<LoanTransaction> newLoanTransactions = new ArrayList<LoanTransaction>();
for(LoanTransaction loanTransaction:loan.getLoanTransactions()){
if(!existingLoanTransactionIds.contains(loanTransaction.getId())){
newLoanTransactions.add(loanTransaction);
}
}
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, newLoanTransactions);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -193,6 +224,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("locale", command.locale()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat()); changes.put("dateFormat", command.dateFormat());
// make a call to accounting
if (loan.loanProduct().getAccountingType() != AccountingRuleType.NONE.getValue()) {
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, loanRepayment);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -146,6 +146,10 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
public void update(final Set<Charge> charges) { public void update(final Set<Charge> charges) {
this.charges = charges; this.charges = charges;
} }
public Integer getAccountingType() {
return this.accountingType;
}
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) { public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {