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;
import java.math.BigDecimal;
import java.util.List;
public class LoanDTO {
@@ -13,18 +12,15 @@ public class LoanDTO {
private Long loanId;
private Long loanProductId;
private Long officeId;
private BigDecimal calculatedInterest;
private boolean cashBasedAccountingEnabled;
private boolean accrualBasedAccountingEnabled;
private List<LoanTransactionDTO> newLoanTransactions;
public LoanDTO(final Long loanId, final Long loanProductId, final Long officeId, final BigDecimal calculatedInterest,
final boolean cashBasedAccountingEnabled, final boolean accrualBasedAccountingEnabled,
final List<LoanTransactionDTO> newLoanTransactions) {
public LoanDTO(final Long loanId, final Long loanProductId, final Long officeId, final boolean cashBasedAccountingEnabled,
final boolean accrualBasedAccountingEnabled, final List<LoanTransactionDTO> newLoanTransactions) {
this.loanId = loanId;
this.loanProductId = loanProductId;
this.officeId = officeId;
this.calculatedInterest = calculatedInterest;
this.cashBasedAccountingEnabled = cashBasedAccountingEnabled;
this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled;
this.newLoanTransactions = newLoanTransactions;
@@ -78,11 +74,4 @@ public class LoanDTO {
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 loanProductId = (Long) accountingBridgeData.get("loanProductId");
final Long officeId = (Long) accountingBridgeData.get("officeId");
final BigDecimal calculatedInterest = (BigDecimal) accountingBridgeData.get("calculatedInterest");
final List<LoanTransactionDTO> newLoanTransactions = new ArrayList<LoanTransactionDTO>();
@SuppressWarnings("unchecked")
@@ -81,8 +79,7 @@ public class AccountingProcessorHelper {
}
return new LoanDTO(loanId, loanProductId, officeId, calculatedInterest, cashBasedAccountingEnabled, accrualBasedAccountingEnabled,
newLoanTransactions);
return new LoanDTO(loanId, loanProductId, officeId, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, newLoanTransactions);
}
/**
@@ -34,6 +34,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
}
/*** Handle Interest Postings ***/
if (loanTransactionDTO.getTransactionType().isApplyInterest()) {
createJournalEntriesForInterestPostings(loanDTO, loanTransactionDTO, office);
}
/*** Handle Apply charges ***/
else if (loanTransactionDTO.getTransactionType().isApplyCharges()) {
createJournalEntriesForApplyCharges(loanDTO, loanTransactionDTO, office);
@@ -58,9 +63,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
/**
* Debit loan Portfolio and credit Fund source for Disbursement. Also
* recognise the receivable interest (Debit "Interest Receivable" and credit
* "Income from Interest")
* Debit loan Portfolio and credit Fund source for Disbursement.
*
* @param loanDTO
* @param loanTransactionDTO
@@ -84,12 +87,33 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
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)
BigDecimal interestApplied = loanDTO.getCalculatedInterest();
if (interestApplied != null && !(interestApplied.compareTo(BigDecimal.ZERO) == 0)) {
if (amount != null && !(amount.compareTo(BigDecimal.ZERO) == 0)) {
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, disbursalAmount,
isReversed);
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, amount, isReversed);
}
}
@@ -237,8 +261,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// create journal entries for the penalties application (or reversal)
else if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, transactionId, transactionDate, feesAmount,
isReversed);
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId, transactionId, transactionDate,
penaltiesAmount, isReversed);
}
}
@@ -19,6 +19,7 @@ public class LoanTransactionEnumData {
private final boolean repaymentAtDisbursement;
private final boolean repayment;
private final boolean contra;
private final boolean applyInterest;
private final boolean waiveInterest;
private final boolean waiveCharges;
private final boolean applyCharges;
@@ -36,6 +37,7 @@ public class LoanTransactionEnumData {
this.waiveInterest = Long.valueOf(4).equals(this.id);
this.waiveCharges = Long.valueOf(9).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.recoveryRepayment = Long.valueOf(8).equals(this.id);
}
@@ -76,4 +78,8 @@ public class LoanTransactionEnumData {
return this.applyCharges;
}
public boolean isApplyInterest() {
return this.applyInterest;
}
}
@@ -411,6 +411,11 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
// store Id's of existing loan transactions and existing reversed loan
// transactions
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
@@ -426,16 +431,13 @@ public class Loan extends AbstractPersistable<Long> {
}
updateLoanSummaryDerivedFields();
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
return changedTransactionDetail;
}
/**
* 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
* 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
* @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());
Money feeCharges = chargeAmount;
Money penaltyCharges = Money.zero(loanCurrency());
@@ -471,6 +473,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.applyLoanCharge(this, chargeAmount, transactionDate, feeCharges,
penaltyCharges);
this.loanTransactions.add(applyLoanChargeTransaction);
return applyLoanChargeTransaction;
}
@@ -629,6 +632,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived);
this.loanTransactions.add(waiveLoanChargeTransaction);
// Waive of charges whose due date falls after latest 'repayment'
// 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) {
this.actualDisbursementDate = expectedDisbursedOnLocalDate.toDate();
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);
}
@@ -1271,6 +1280,17 @@ public class Loan extends AbstractPersistable<Long> {
} else {
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;
@@ -1338,8 +1358,7 @@ public class Loan extends AbstractPersistable<Long> {
if (charge.isDueAtDisbursement()) {
charge.markAsFullyPaid();
} else {
LoanTransaction loanChargeAppliedTransaction = getChargeAppliedTransaction(charge, disbursedOn);
this.loanTransactions.add(loanChargeAppliedTransaction);
handleChargeAppliedTransaction(charge, disbursedOn);
}
}
}
@@ -86,6 +86,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
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) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount,
loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion,
@@ -96,7 +100,6 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
final Money penaltyCharges) {
LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate);
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
return applyCharge;
}
@@ -21,7 +21,8 @@ public enum LoanTransactionType {
*/
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
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 String code;
@@ -75,6 +76,9 @@ public enum LoanTransactionType {
case 10:
loanTransactionType = LoanTransactionType.APPLY_CHARGES;
break;
case 11:
loanTransactionType = LoanTransactionType.APPLY_INTEREST;
break;
default:
loanTransactionType = LoanTransactionType.INVALID;
break;
@@ -552,7 +552,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
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,
existingReversedTransactionIds);
@@ -248,6 +248,10 @@ public class LoanEnumerations {
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_CHARGES.getValue().longValue(),
LoanTransactionType.APPLY_CHARGES.getCode(), "Apply Charge");
break;
case APPLY_INTEREST:
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_INTEREST.getValue().longValue(),
LoanTransactionType.APPLY_INTEREST.getCode(), "Apply Interest");
break;
}
return optionData;
}