Merge pull request #187 from vishwasbabu/mifosx-183

bug fixes for accrual based accounting
This commit is contained in:
Vishwas Babu A J
2013-03-24 06:43:08 -07:00
9 changed files with 83 additions and 37 deletions
@@ -5,7 +5,6 @@
*/ */
package org.mifosplatform.accounting.journalentry.data; package org.mifosplatform.accounting.journalentry.data;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
public class LoanDTO { public class LoanDTO {
@@ -13,18 +12,15 @@ public class LoanDTO {
private Long loanId; private Long loanId;
private Long loanProductId; private Long loanProductId;
private Long officeId; private Long officeId;
private BigDecimal calculatedInterest;
private boolean cashBasedAccountingEnabled; private boolean cashBasedAccountingEnabled;
private boolean accrualBasedAccountingEnabled; private boolean accrualBasedAccountingEnabled;
private List<LoanTransactionDTO> newLoanTransactions; private List<LoanTransactionDTO> newLoanTransactions;
public LoanDTO(final Long loanId, final Long loanProductId, final Long officeId, final BigDecimal calculatedInterest, public LoanDTO(final Long loanId, final Long loanProductId, final Long officeId, final boolean cashBasedAccountingEnabled,
final boolean cashBasedAccountingEnabled, final boolean accrualBasedAccountingEnabled, final boolean accrualBasedAccountingEnabled, final List<LoanTransactionDTO> newLoanTransactions) {
final List<LoanTransactionDTO> newLoanTransactions) {
this.loanId = loanId; this.loanId = loanId;
this.loanProductId = loanProductId; this.loanProductId = loanProductId;
this.officeId = officeId; this.officeId = officeId;
this.calculatedInterest = calculatedInterest;
this.cashBasedAccountingEnabled = cashBasedAccountingEnabled; this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled; this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled;
this.newLoanTransactions = newLoanTransactions; this.newLoanTransactions = newLoanTransactions;
@@ -78,11 +74,4 @@ public class LoanDTO {
this.newLoanTransactions = newLoanTransactions; this.newLoanTransactions = newLoanTransactions;
} }
public BigDecimal getCalculatedInterest() {
return this.calculatedInterest;
}
public void setCalculatedInterest(BigDecimal calculatedInterest) {
this.calculatedInterest = calculatedInterest;
}
} }
@@ -56,8 +56,6 @@ public class AccountingProcessorHelper {
final Long loanId = (Long) accountingBridgeData.get("loanId"); final Long loanId = (Long) accountingBridgeData.get("loanId");
final Long loanProductId = (Long) accountingBridgeData.get("loanProductId"); final Long loanProductId = (Long) accountingBridgeData.get("loanProductId");
final Long officeId = (Long) accountingBridgeData.get("officeId"); final Long officeId = (Long) accountingBridgeData.get("officeId");
final BigDecimal calculatedInterest = (BigDecimal) accountingBridgeData.get("calculatedInterest");
final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<LoanTransactionDTO>(); final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<LoanTransactionDTO>();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -81,8 +79,7 @@ public class AccountingProcessorHelper {
} }
return new LoanDTO(loanId, loanProductId, officeId, calculatedInterest, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, return new LoanDTO(loanId, loanProductId, officeId, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, newLoanTransactions);
newLoanTransactions);
} }
/** /**
@@ -34,6 +34,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office); createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
} }
/*** Handle Interest Postings ***/
if (loanTransactionDTO.getTransactionType().isApplyInterest()) {
createJournalEntriesForInterestPostings(loanDTO, loanTransactionDTO, office);
}
/*** Handle Apply charges ***/ /*** Handle Apply charges ***/
else if (loanTransactionDTO.getTransactionType().isApplyCharges()) { else if (loanTransactionDTO.getTransactionType().isApplyCharges()) {
createJournalEntriesForApplyCharges(loanDTO, loanTransactionDTO, office); createJournalEntriesForApplyCharges(loanDTO, loanTransactionDTO, office);
@@ -58,9 +63,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
} }
/** /**
* Debit loan Portfolio and credit Fund source for Disbursement. Also * Debit loan Portfolio and credit Fund source for Disbursement.
* recognise the receivable interest (Debit "Interest Receivable" and credit
* "Income from Interest")
* *
* @param loanDTO * @param loanDTO
* @param loanTransactionDTO * @param loanTransactionDTO
@@ -84,12 +87,33 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversed); ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversed);
}
/**
* Recognise the receivable interest <br/>
* Debit "Interest Receivable" and Credit "Income from Interest"
*
* @param loanDTO
* @param loanTransactionDTO
* @param office
*/
private void createJournalEntriesForInterestPostings(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 amount = loanTransactionDTO.getAmount();
final boolean isReversed = loanTransactionDTO.isReversed();
// create journal entries for recognising interest (or reversal) // create journal entries for recognising interest (or reversal)
BigDecimal interestApplied = loanDTO.getCalculatedInterest(); if (amount != null && !(amount.compareTo(BigDecimal.ZERO) == 0)) {
if (interestApplied != null && !(interestApplied.compareTo(BigDecimal.ZERO) == 0)) {
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE, helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, amount, isReversed);
isReversed);
} }
} }
@@ -237,8 +261,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// create journal entries for the penalties application (or reversal) // create journal entries for the penalties application (or reversal)
else if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { else if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE, helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, transactionId, transactionDate, feesAmount, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, transactionId, transactionDate,
isReversed); penaltiesAmount, isReversed);
} }
} }
@@ -19,6 +19,7 @@ public class LoanTransactionEnumData {
private final boolean repaymentAtDisbursement; private final boolean repaymentAtDisbursement;
private final boolean repayment; private final boolean repayment;
private final boolean contra; private final boolean contra;
private final boolean applyInterest;
private final boolean waiveInterest; private final boolean waiveInterest;
private final boolean waiveCharges; private final boolean waiveCharges;
private final boolean applyCharges; private final boolean applyCharges;
@@ -36,6 +37,7 @@ public class LoanTransactionEnumData {
this.waiveInterest = Long.valueOf(4).equals(this.id); this.waiveInterest = Long.valueOf(4).equals(this.id);
this.waiveCharges = Long.valueOf(9).equals(this.id); this.waiveCharges = Long.valueOf(9).equals(this.id);
this.applyCharges = Long.valueOf(10).equals(this.id); this.applyCharges = Long.valueOf(10).equals(this.id);
this.applyInterest = Long.valueOf(11).equals(this.id);
this.writeOff = Long.valueOf(6).equals(this.id); this.writeOff = Long.valueOf(6).equals(this.id);
this.recoveryRepayment = Long.valueOf(8).equals(this.id); this.recoveryRepayment = Long.valueOf(8).equals(this.id);
} }
@@ -76,4 +78,8 @@ public class LoanTransactionEnumData {
return this.applyCharges; return this.applyCharges;
} }
public boolean isApplyInterest() {
return this.applyInterest;
}
} }
@@ -411,6 +411,11 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
// store Id's of existing loan transactions and existing reversed loan
// transactions
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -426,16 +431,13 @@ public class Loan extends AbstractPersistable<Long> {
} }
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
return changedTransactionDetail; return changedTransactionDetail;
} }
/** /**
* Creates a loanTransaction for "Apply Charge Event" with transaction date * Creates a loanTransaction for "Apply Charge Event" with transaction date
* set to "suppliedTransactionDate" * set to "suppliedTransactionDate". The newly created transaction is also
* added to the Loan on which this method is called.
* *
* If "suppliedTransactionDate" is not passed Id, the transaction date is * 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 * set to the loans due date if the due date is lesser than todays date. If
@@ -445,7 +447,7 @@ public class Loan extends AbstractPersistable<Long> {
* @param suppliedTransactionDate * @param suppliedTransactionDate
* @return * @return
*/ */
public LoanTransaction getChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
final Money chargeAmount = loanCharge.getAmount(getCurrency()); final Money chargeAmount = loanCharge.getAmount(getCurrency());
Money feeCharges = chargeAmount; Money feeCharges = chargeAmount;
Money penaltyCharges = Money.zero(loanCurrency()); Money penaltyCharges = Money.zero(loanCurrency());
@@ -471,6 +473,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.applyLoanCharge(this, chargeAmount, transactionDate, feeCharges, final LoanTransaction applyLoanChargeTransaction = LoanTransaction.applyLoanCharge(this, chargeAmount, transactionDate, feeCharges,
penaltyCharges); penaltyCharges);
this.loanTransactions.add(applyLoanChargeTransaction);
return applyLoanChargeTransaction; return applyLoanChargeTransaction;
} }
@@ -629,6 +632,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate, final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived); feeChargesWaived, penaltyChargesWaived);
this.loanTransactions.add(waiveLoanChargeTransaction);
// Waive of charges whose due date falls after latest 'repayment' // Waive of charges whose due date falls after latest 'repayment'
// transaction dont require entire loan schedule to be reprocessed. // transaction dont require entire loan schedule to be reprocessed.
@@ -1211,6 +1215,11 @@ public class Loan extends AbstractPersistable<Long> {
private void disburse(final LocalDate expectedDisbursedOnLocalDate) { private void disburse(final LocalDate expectedDisbursedOnLocalDate) {
this.actualDisbursementDate = expectedDisbursedOnLocalDate.toDate(); this.actualDisbursementDate = expectedDisbursedOnLocalDate.toDate();
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
/****
* Vishwas TODO: Refactor to skip unnecessary creation of Disbursement
* and fees applied transactions (though they are not persisted) on
* modifications to loan application modification
*****/
handleDisbursementTransaction(expectedDisbursedOnLocalDate); handleDisbursementTransaction(expectedDisbursedOnLocalDate);
} }
@@ -1271,6 +1280,17 @@ public class Loan extends AbstractPersistable<Long> {
} else { } else {
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
/***
* Adding a temporary Interest applied transaction (applying the
* full Interest as soon as the loan is disbursed), will have to
* re-look into this logic once loans with Interest calculation need
* to be created or more advances accrual accounting support has to
* be provided
***/
Money interestApplied = Money.of(this.getCurrency(), this.summary.getTotalInterestCharged());
final LoanTransaction interestAppliedTransaction = LoanTransaction.applyInterest(this, interestApplied, actualDisbursementDate);
this.loanTransactions.add(interestAppliedTransaction);
} }
return actualChanges; return actualChanges;
@@ -1338,8 +1358,7 @@ public class Loan extends AbstractPersistable<Long> {
if (charge.isDueAtDisbursement()) { if (charge.isDueAtDisbursement()) {
charge.markAsFullyPaid(); charge.markAsFullyPaid();
} else { } else {
LoanTransaction loanChargeAppliedTransaction = getChargeAppliedTransaction(charge, disbursedOn); handleChargeAppliedTransaction(charge, disbursedOn);
this.loanTransactions.add(loanChargeAppliedTransaction);
} }
} }
} }
@@ -86,6 +86,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate); return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate);
} }
public static LoanTransaction applyInterest(final Loan loan, final Money amount, final LocalDate interestAppliedDate) {
return new LoanTransaction(loan, LoanTransactionType.APPLY_INTEREST, amount.getAmount(), interestAppliedDate);
}
public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) { public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount, return new LoanTransaction(loanTransaction.loan, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount,
loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion, loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion,
@@ -96,7 +100,6 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
final Money penaltyCharges) { final Money penaltyCharges) {
LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate); LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate);
applyCharge.updateChargesComponents(feeCharges, penaltyCharges); applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
return applyCharge; return applyCharge;
} }
@@ -21,7 +21,8 @@ public enum LoanTransactionType {
*/ */
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), // RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), // WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), //
APPLY_CHARGES(10, "loanTransactionType.applyCharges"); APPLY_CHARGES(10, "loanTransactionType.applyCharges"), //
APPLY_INTEREST(11, "loanTransactionType.applyInterest");
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -75,6 +76,9 @@ public enum LoanTransactionType {
case 10: case 10:
loanTransactionType = LoanTransactionType.APPLY_CHARGES; loanTransactionType = LoanTransactionType.APPLY_CHARGES;
break; break;
case 11:
loanTransactionType = LoanTransactionType.APPLY_INTEREST;
break;
default: default:
loanTransactionType = LoanTransactionType.INVALID; loanTransactionType = LoanTransactionType.INVALID;
break; break;
@@ -552,7 +552,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
this.loanChargeRepository.save(loanCharge); this.loanChargeRepository.save(loanCharge);
final LoanTransaction applyLoanChargeTransaction = loan.getChargeAppliedTransaction(loanCharge, null); final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds, final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds); existingReversedTransactionIds);
@@ -248,6 +248,10 @@ public class LoanEnumerations {
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_CHARGES.getValue().longValue(), optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_CHARGES.getValue().longValue(),
LoanTransactionType.APPLY_CHARGES.getCode(), "Apply Charge"); LoanTransactionType.APPLY_CHARGES.getCode(), "Apply Charge");
break; break;
case APPLY_INTEREST:
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_INTEREST.getValue().longValue(),
LoanTransactionType.APPLY_INTEREST.getCode(), "Apply Interest");
break;
} }
return optionData; return optionData;
} }