accounting wip : completed cash based posting includes writeoffs and adjustments

This commit is contained in:
Vishwas Babu AJ
2013-01-17 15:38:38 +05:30
parent 0b9d76582f
commit bc20ae399e
5 changed files with 147 additions and 46 deletions
@@ -128,6 +128,18 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
@Override
public void createJournalEntriesForLoan(Loan loan, List<LoanTransaction> loanTransactions) {
// shouldn't be before an accounting closure
if (loan.isCashBasedAccountingEnabledOnLoanProduct()) {
createJournalEntriesUsingCashRules(loan, loanTransactions);
} else if (loan.isAccrualBasedAccountingEnabledOnLoanProduct()) {
// TODO Vishwas: add accrual based accounting rules
}
}
/**
* @param loan
* @param loanTransactions
*/
private void createJournalEntriesUsingCashRules(Loan loan, List<LoanTransaction> loanTransactions) {
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(loan.getOfficeId());
Office office = loan.getClient().getOffice();
Long loanProductId = loan.productId();
@@ -141,9 +153,11 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
* the transaction Date
**/
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf())) { throw new GLJournalEntryInvalidException(
if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf())
|| latestGLClosure.getClosingDate().equals(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()) {
@@ -153,54 +167,104 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
BigDecimal disbursalAmount = loanTransaction.getAmount();
createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, disbursalAmount);
createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, disbursalAmount);
} else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement() || loanTransaction.isContra()) {
createJournalEntriesForRepaymentOrContraUsingCashRules(loan, office, loanProductId, loanTransaction, entryDate,
transactionId, loanId);
}/***
* 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)
* Only principal write off affects cash based accounting (interest
* and fee write off need not be considered). Debit losses written
* off and credit Loan Portfolio
**/
else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement()) {
else if (loanTransaction.isWriteOff()) {
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();
GLAccount lossesWrittenOffAccount = getLinkedFinAccountForLoanProduct(loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF);
createDebitJournalEntryForLoanProduct(office, lossesWrittenOffAccount, loanId, transactionId, entryDate, principalAmount);
BigDecimal totalDebitAmount = new BigDecimal(0);
GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
}
}
}
if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
/**
* 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)
*
* In case the loan transaction is a contra, all debits are turned into
* credits and vice versa
*
* @param loan
* @param office
* @param loanProductId
* @param loanTransaction
* @param entryDate
* @param transactionId
* @param loanId
*/
private void createJournalEntriesForRepaymentOrContraUsingCashRules(Loan loan, Office office, Long loanProductId,
LoanTransaction loanTransaction, Date entryDate, String transactionId, Long loanId) {
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();
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);
BigDecimal totalDebitAmount = new BigDecimal(0);
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
if (loanTransaction.isContra()) {
transactionId = "reversal-" + transactionId;
}
if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
if (loanTransaction.isContra()) {
createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
} else {
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);
if (loanTransaction.isContra()) {
createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount);
} else {
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);
if (loanTransaction.isContra()) {
createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount);
} else {
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);
if (loanTransaction.isContra()) {
createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate, penaltiesAmount);
} else {
createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate,
penaltiesAmount);
}
}
GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
if (loanTransaction.isContra()) {
createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount);
} else {
createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount);
}
}
@Transactional
@@ -246,7 +310,7 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
// shouldn't be before an accounting closure
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(entryDate)) { throw new GLJournalEntryInvalidException(
if (latestGLClosure.getClosingDate().after(entryDate) || latestGLClosure.getClosingDate().equals(entryDate)) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
}
/*** check if credits and debits are valid **/
@@ -2003,6 +2003,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
public boolean isAccountingEnabledOnLoanProduct() {
return this.loanProduct.isAccountingEnabled();
}
public boolean isCashBasedAccountingEnabledOnLoanProduct(){
return this.loanProduct.isCashBasedAccountingEnabled();
}
public boolean isAccrualBasedAccountingEnabledOnLoanProduct(){
return this.loanProduct.isAccrualBasedAccountingEnabled();
}
public Long productId() {
return this.loanProduct.getId();
@@ -146,6 +146,10 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
public boolean isNotContra() {
return this.contra == null;
}
public boolean isContra() {
return this.contra != null;
}
public boolean isDisbursement() {
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf());
@@ -122,8 +122,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// 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) {
for(LoanTransaction loanTransaction:loan.getLoanTransactions()){
if(!(loanTransaction.getId()==null)){
existingLoanTransactionIds.add(loanTransaction.getId());
}
}
@@ -261,13 +261,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
boolean transactionIsRepayment= transactionToAdjust.isRepayment();
final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount,
defaultLoanLifecycleStateMachine(), transactionToAdjust);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
this.loanTransactionRepository.save(newTransactionDetail);
}
this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note");
@@ -279,7 +281,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
// make a call to accounting only if transaction being adjusted is a repayment (not waiver)
if (loan.isAccountingEnabledOnLoanProduct() && transactionIsRepayment) {
List<LoanTransaction> newLoanTransactions = new ArrayList<LoanTransaction>();
if (newTransactionDetail.getId() != null) {
newLoanTransactions.add(newTransactionDetail);
}
newLoanTransactions.add(transactionToAdjust);
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, newLoanTransactions);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(transactionId) //
@@ -361,6 +373,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
// make a call to accounting
if (loan.isAccountingEnabledOnLoanProduct()) {
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, writeoff);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -218,6 +218,14 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
}
public boolean isAccountingEnabled() {
return AccountingRuleType.NONE.getValue().equals(this.accountingType);
return !AccountingRuleType.NONE.getValue().equals(this.accountingType);
}
public boolean isCashBasedAccountingEnabled(){
return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingType);
}
public boolean isAccrualBasedAccountingEnabled(){
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingType);
}
}