Bug fixes for accrual based accounting

This commit is contained in:
Vishwas Babu AJ
2013-03-07 15:37:39 +05:30
parent 2b784cc0f9
commit 856f406e31
3 changed files with 95 additions and 40 deletions
@@ -8,7 +8,6 @@ import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_
import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.glaccount.domain.GLAccount;
import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanDTO;
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.Office;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -50,7 +49,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
/** Handle Write Offs and waivers **/ /** Handle Write Offs and waivers **/
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest() || loanTransactionDTO else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest() || loanTransactionDTO
.getTransactionType().isWaiveCharges()) && !loanTransactionDTO.isReversed()) { .getTransactionType().isWaiveCharges())) {
createJournalEntriesForWriteOff(loanDTO, loanTransactionDTO, office); createJournalEntriesForWriteOff(loanDTO, loanTransactionDTO, office);
} }
} }
@@ -68,8 +67,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
private void createJournalEntriesForDisbursements(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, private void createJournalEntriesForDisbursements(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) { final Office office) {
// TODO: remove the junk transaction Id
String junkTransactionId = "junk" + FileUtils.generateRandomString();
// loan properties // loan properties
final Long loanProductId = loanDTO.getLoanProductId(); final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId(); final Long loanId = loanDTO.getLoanId();
@@ -106,14 +103,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final GLAccount interestOnLoansAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount interestOnLoansAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS); ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS);
if (isReversed) { if (isReversed) {
helper.createDebitJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, junkTransactionId, transactionDate, helper.createDebitJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate,
interestApplied);
helper.createCreditJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate,
interestApplied); interestApplied);
helper.createCreditJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, junkTransactionId,
transactionDate, interestApplied);
} else { } else {
helper.createDebitJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, junkTransactionId, transactionDate, helper.createDebitJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate,
interestApplied); interestApplied);
helper.createCreditJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, junkTransactionId, transactionDate, helper.createCreditJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate,
interestApplied); interestApplied);
} }
} }
@@ -240,6 +237,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal interestAmount = loanTransactionDTO.getInterest(); final BigDecimal interestAmount = loanTransactionDTO.getInterest();
final BigDecimal feesAmount = loanTransactionDTO.getFees(); final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties(); final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final boolean isReversed = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0); BigDecimal totalDebitAmount = new BigDecimal(0);
@@ -248,34 +246,63 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate, if (isReversed) {
principalAmount); 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)) { if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(interestAmount); totalDebitAmount = totalDebitAmount.add(interestAmount);
final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE); ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE);
helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate, interestAmount); 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)) { if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(feesAmount); totalDebitAmount = totalDebitAmount.add(feesAmount);
final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE); ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE);
helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate, feesAmount); 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)) { if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount); totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE); ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE);
helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate, if (isReversed) {
penaltiesAmount); helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
penaltiesAmount);
} else {
helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
penaltiesAmount);
}
} }
final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId, final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF); ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF);
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, totalDebitAmount); if (isReversed) {
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
totalDebitAmount);
} else {
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
totalDebitAmount);
}
} }
} }
@@ -251,6 +251,7 @@ public class Loan extends AbstractPersistable<Long> {
private boolean accountNumberRequiresAutoGeneration = false; private boolean accountNumberRequiresAutoGeneration = false;
@Transient @Transient
private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
@Transient @Transient
private LoanLifecycleStateMachine loanLifecycleStateMachine; private LoanLifecycleStateMachine loanLifecycleStateMachine;
@Transient @Transient
@@ -532,7 +533,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes) { final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
@@ -554,6 +555,9 @@ public class Loan extends AbstractPersistable<Long> {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate, final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived); feeChargesWaived, penaltyChargesWaived);
@@ -1319,7 +1323,8 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public LoanTransaction waiveInterest(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
@@ -1327,6 +1332,9 @@ public class Loan extends AbstractPersistable<Long> {
final Money amountToWaive = Money.of(loanCurrency(), transactionAmount); final Money amountToWaive = Money.of(loanCurrency(), transactionAmount);
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(this, amountToWaive, transactionDate); final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(this, amountToWaive, transactionDate);
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null); handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null);
return waiveInterestTransaction; return waiveInterestTransaction;
@@ -2061,6 +2069,10 @@ public class Loan extends AbstractPersistable<Long> {
return this.loanOfficer; return this.loanOfficer;
} }
public LoanSummary getSummary() {
return this.summary;
}
public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds, public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) { final List<Long> existingReversedTransactionIds) {
@@ -122,9 +122,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds, postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -145,6 +143,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
context.authenticatedUser(); context.authenticatedUser();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final BigDecimal totalInterestCharged = loan.getSummary().getTotalInterestCharged();
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
@@ -161,6 +160,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds, final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds); existingReversedTransactionIds);
// add the previously calculated net charged interest back to
// accountingBridgedate, this would not work if interest
// recalculation in introduced
accountingBridgeData.put("calculatedInterest", totalInterestCharged);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
@@ -209,10 +213,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -266,10 +267,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -298,7 +296,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LoanTransaction waiveTransaction = loan.waiveInterest(command, defaultLoanLifecycleStateMachine()); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction waiveTransaction = loan.waiveInterest(command, defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds);
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -310,6 +311,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(waiveTransaction.getId()) // .withEntityId(waiveTransaction.getId()) //
@@ -350,10 +353,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -399,10 +399,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
CommandProcessingResult result = null; CommandProcessingResult result = null;
@@ -540,11 +537,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = new LinkedHashMap<String, Object>(3); final Map<String, Object> changes = new LinkedHashMap<String, Object>(3);
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds);
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
@@ -556,6 +558,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
@Transactional @Transactional
@Override @Override
public CommandProcessingResult deleteLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) { public CommandProcessingResult deleteLoanCharge(final Long loanId, final Long loanChargeId, final JsonCommand command) {
@@ -685,4 +688,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withLoanId(loanId) // .withLoanId(loanId) //
.build(); .build();
} }
/**
* @param loan
* @param existingTransactionIds
* @param existingReversedTransactionIds
*/
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
} }