wip: refactored accounting and added charge applied event
This commit is contained in:
+142
-22
@@ -85,34 +85,103 @@ public class AccountingProcessorHelper {
|
||||
newLoanTransactions);
|
||||
}
|
||||
|
||||
public GLAccount getLinkedCashAccountsForLoanProduct(final Long loanProductId, final CASH_ACCOUNTS_FOR_LOAN finAccountType) {
|
||||
final ProductToGLAccountMapping accountMapping = this.accountMappingRepository
|
||||
.findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(),
|
||||
finAccountType.getValue());
|
||||
return accountMapping.getGlAccount();
|
||||
/**
|
||||
* Convenience method that creates a pair of related Debits and Credits for
|
||||
* Accrual Based accounting.
|
||||
*
|
||||
* The target accounts for debits and credits are switched in case of a
|
||||
* reversal
|
||||
*
|
||||
* @param office
|
||||
* @param accountTypeToBeDebited
|
||||
* Enum of the placeholder GLAccount to be debited
|
||||
* @param accountTypeToBeCredited
|
||||
* Enum of the placeholder of the GLAccount to be credited
|
||||
* @param loanProductId
|
||||
* @param loanId
|
||||
* @param transactionId
|
||||
* @param transactionDate
|
||||
* @param amount
|
||||
* @param isReversal
|
||||
*/
|
||||
public void createAccrualBasedJournalEntriesAndReversalsForLoan(final Office office,
|
||||
final ACCRUAL_ACCOUNTS_FOR_LOAN accountTypeToBeDebited, final ACCRUAL_ACCOUNTS_FOR_LOAN accountTypeToBeCredited,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
int accountTypeToDebitId = accountTypeToBeDebited.getValue();
|
||||
int accountTypeToCreditId = accountTypeToBeCredited.getValue();
|
||||
// reverse debits and credits for reversals
|
||||
if (isReversal) {
|
||||
accountTypeToDebitId = accountTypeToBeCredited.getValue();
|
||||
accountTypeToCreditId = accountTypeToBeDebited.getValue();
|
||||
}
|
||||
createJournalEntriesForLoan(office, accountTypeToDebitId, accountTypeToCreditId, loanProductId, loanId, transactionId,
|
||||
transactionDate, amount);
|
||||
}
|
||||
|
||||
public GLAccount getLinkedAccrualAccountsForLoanProduct(final Long loanProductId, final ACCRUAL_ACCOUNTS_FOR_LOAN finAccountType) {
|
||||
final ProductToGLAccountMapping accountMapping = this.accountMappingRepository
|
||||
.findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(),
|
||||
finAccountType.getValue());
|
||||
return accountMapping.getGlAccount();
|
||||
/**
|
||||
* Convenience method that creates a pair of related Debits and Credits for
|
||||
* Cash Based accounting.
|
||||
*
|
||||
* The target accounts for debits and credits are switched in case of a
|
||||
* reversal
|
||||
*
|
||||
* @param office
|
||||
* @param accountTypeToBeDebited
|
||||
* Enum of the placeholder GLAccount to be debited
|
||||
* @param accountTypeToBeCredited
|
||||
* Enum of the placeholder of the GLAccount to be credited
|
||||
* @param loanProductId
|
||||
* @param loanId
|
||||
* @param transactionId
|
||||
* @param transactionDate
|
||||
* @param amount
|
||||
* @param isReversal
|
||||
*/
|
||||
public void createCashBasedJournalEntriesAndReversalsForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountTypeToBeDebited,
|
||||
final CASH_ACCOUNTS_FOR_LOAN accountTypeToBeCredited, final Long loanProductId, final Long loanId, final String transactionId,
|
||||
final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
|
||||
int accountTypeToDebitId = accountTypeToBeDebited.getValue();
|
||||
int accountTypeToCreditId = accountTypeToBeCredited.getValue();
|
||||
// reverse debits and credits for reversals
|
||||
if (isReversal) {
|
||||
accountTypeToDebitId = accountTypeToBeCredited.getValue();
|
||||
accountTypeToCreditId = accountTypeToBeDebited.getValue();
|
||||
}
|
||||
createJournalEntriesForLoan(office, accountTypeToDebitId, accountTypeToCreditId, loanProductId, loanId, transactionId,
|
||||
transactionDate, amount);
|
||||
}
|
||||
|
||||
public void createCreditJournalEntryForLoanProduct(final Office office, final GLAccount account, final Long loanId,
|
||||
final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
final boolean manualEntry = false;
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId);
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
public void createCreditJournalEntryOrReversalForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountMappingType,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
int accountMappingTypeId = accountMappingType.getValue();
|
||||
createCreditJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
}
|
||||
|
||||
public void createDebitJournalEntryForLoanProduct(final Office office, final GLAccount account, final Long loanId,
|
||||
final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
final boolean manualEntry = false;
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId);
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
public void createCreditJournalEntryOrReversalForLoan(final Office office, final ACCRUAL_ACCOUNTS_FOR_LOAN accountMappingType,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
int accountMappingTypeId = accountMappingType.getValue();
|
||||
createCreditJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
}
|
||||
|
||||
public void createDebitJournalEntryOrReversalForLoan(final Office office, final CASH_ACCOUNTS_FOR_LOAN accountMappingType,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
int accountMappingTypeId = accountMappingType.getValue();
|
||||
createDebitJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
}
|
||||
|
||||
public void createDebitJournalEntryOrReversalForLoan(final Office office, final ACCRUAL_ACCOUNTS_FOR_LOAN accountMappingType,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount,
|
||||
final Boolean isReversal) {
|
||||
int accountMappingTypeId = accountMappingType.getValue();
|
||||
createDebitJournalEntryOrReversalForLoan(office, accountMappingTypeId, loanProductId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,4 +207,55 @@ public class AccountingProcessorHelper {
|
||||
return this.officeRepository.findOne(officeId);
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoan(final Office office, final int accountTypeToDebitId, final int accountTypeToCreditId,
|
||||
final Long loanProductId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
GLAccount debitAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToDebitId);
|
||||
GLAccount creditAccount = getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToCreditId);
|
||||
createCreditJournalEntryForLoan(office, debitAccount, loanId, transactionId, transactionDate, amount);
|
||||
createDebitJournalEntryForLoan(office, creditAccount, loanId, transactionId, transactionDate, amount);
|
||||
}
|
||||
|
||||
private void createDebitJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId,
|
||||
final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
|
||||
GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId);
|
||||
if (isReversal) {
|
||||
createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
} else {
|
||||
createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void createCreditJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId,
|
||||
final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) {
|
||||
GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId);
|
||||
if (isReversal) {
|
||||
createDebitJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
} else {
|
||||
createCreditJournalEntryForLoan(office, account, loanId, transactionId, transactionDate, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void createCreditJournalEntryForLoan(final Office office, final GLAccount account, final Long loanId,
|
||||
final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
final boolean manualEntry = false;
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.CREDIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId);
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
}
|
||||
|
||||
private void createDebitJournalEntryForLoan(final Office office, final GLAccount account, final Long loanId,
|
||||
final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
final boolean manualEntry = false;
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate,
|
||||
JournalEntryType.DEBIT, amount, null, PortfolioProductType.LOAN.getValue(), loanId);
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
}
|
||||
|
||||
private GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId) {
|
||||
final ProductToGLAccountMapping accountMapping = this.accountMappingRepository
|
||||
.findByProductIdAndProductTypeAndFinancialAccountType(loanProductId, PortfolioProductType.LOAN.getValue(),
|
||||
accountMappingTypeId);
|
||||
return accountMapping.getGlAccount();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+101
-173
@@ -5,7 +5,6 @@ import java.util.Date;
|
||||
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
@@ -36,7 +35,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
|
||||
/*** Handle Apply charges ***/
|
||||
// TODO
|
||||
else if (loanTransactionDTO.getTransactionType().isApplyCharges()) {
|
||||
createJournalEntriesForApplyCharges(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
|
||||
/***
|
||||
* Handle repayments, repayments at disbursement and reversal of
|
||||
@@ -44,13 +45,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
***/
|
||||
else if (loanTransactionDTO.getTransactionType().isRepayment()
|
||||
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()) {
|
||||
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
|
||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false);
|
||||
}
|
||||
|
||||
/** Handle Write Offs and waivers **/
|
||||
/** Handle Write Offs, waivers and thier reversals **/
|
||||
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest() || loanTransactionDTO
|
||||
.getTransactionType().isWaiveCharges())) {
|
||||
createJournalEntriesForWriteOff(loanDTO, loanTransactionDTO, office);
|
||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,136 +80,38 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
// create journal entries for the disbursement (or disbursement
|
||||
// reversal)
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
||||
if (isReversed) {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, disbursalAmount);
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
}
|
||||
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversed);
|
||||
|
||||
// create journal entries for recognising interest (or reversal)
|
||||
BigDecimal interestApplied = loanDTO.getCalculatedInterest();
|
||||
if (interestApplied != null && !(interestApplied.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
|
||||
final GLAccount interestReceivableAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE);
|
||||
final GLAccount interestOnLoansAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate,
|
||||
interestApplied);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate,
|
||||
interestApplied);
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, interestReceivableAccount, loanId, transactionId, transactionDate,
|
||||
interestApplied);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, interestOnLoansAccount, loanId, transactionId, transactionDate,
|
||||
interestApplied);
|
||||
}
|
||||
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId, transactionId, transactionDate, disbursalAmount,
|
||||
isReversed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single Debit for fund source and multiple credits if applicable
|
||||
* (loan portfolio for principal repayments, Interest receivable for
|
||||
* interest repayments, Fee receivable for fees payment and Penalty
|
||||
* Receivable from penalties for penalty payment)
|
||||
*
|
||||
* Handles repayments using the following posting rules <br/>
|
||||
* <br/>
|
||||
* <br/>
|
||||
*
|
||||
* In case the loan transaction has been reversed, all debits are turned
|
||||
* into credits and vice versa
|
||||
*/
|
||||
private void createJournalEntriesForRepayments(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 principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final boolean isReversed = loanTransactionDTO.isReversed();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
if (isReversed) {
|
||||
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)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
|
||||
final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE);
|
||||
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)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
|
||||
final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE);
|
||||
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)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
|
||||
final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
}
|
||||
}
|
||||
final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
||||
if (isReversed) {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles write offs using the following posting rules <br/>
|
||||
* <b>Principal Repayment</b>: Debits "Fund Source" and Credits
|
||||
* "Loan Portfolio"<br/>
|
||||
*
|
||||
* <b>Interest Repayment</b>:Debits "Fund Source" and and Credits
|
||||
* "Receivable Interest" <br/>
|
||||
*
|
||||
* <b>Fee Write Repayment</b>:Debits "Fund Source" and and Credits
|
||||
* "Receivable Fees" <br/>
|
||||
*
|
||||
* <b>Penalty Repayment</b>: Debits "Fund Source" and and Credits
|
||||
* "Receivable Penalties" <br/>
|
||||
* <br/>
|
||||
* Handles write offs using the following posting rules <br/>
|
||||
* <br/>
|
||||
* <b>Principal Write off</b>: Debits "Losses Written Off" and Credits
|
||||
* "Loan Portfolio"<br/>
|
||||
*
|
||||
@@ -220,12 +123,18 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
*
|
||||
* <b>Penalty Write off</b>: Debits "Losses Written off" and and Credits
|
||||
* "Receivable Penalties" <br/>
|
||||
* <br/>
|
||||
* <br/>
|
||||
* In case the loan transaction has been reversed, all debits are turned
|
||||
* into credits and vice versa
|
||||
*
|
||||
* @param loanTransactionDTO
|
||||
* @param loanDTO
|
||||
* @param office
|
||||
*
|
||||
*/
|
||||
private void createJournalEntriesForWriteOff(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||
private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office, final boolean writeOff) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
@@ -237,71 +146,90 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final boolean isReversed = loanTransactionDTO.isReversed();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
// handle principle payment or writeOff (and reversals)
|
||||
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
}
|
||||
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId,
|
||||
transactionId, transactionDate, principalAmount, isReversal);
|
||||
}
|
||||
|
||||
// handle interest payment of writeOff (and reversals)
|
||||
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
|
||||
final GLAccount interestAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate,
|
||||
interestAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate,
|
||||
interestAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE, loanProductId, loanId,
|
||||
transactionId, transactionDate, interestAmount, isReversal);
|
||||
}
|
||||
|
||||
// handle fees payment of writeOff (and reversals)
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
|
||||
final GLAccount incomeFromFeesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate,
|
||||
feesAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate,
|
||||
feesAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE, loanProductId, loanId,
|
||||
transactionId, transactionDate, feesAmount, isReversal);
|
||||
}
|
||||
|
||||
// handle penalties payment of writeOff (and reversals)
|
||||
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
|
||||
final GLAccount incomeFromPenaltiesAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE, loanProductId, loanId,
|
||||
transactionId, transactionDate, penaltiesAmount, isReversal);
|
||||
}
|
||||
final GLAccount fundSourceAccount = helper.getLinkedAccrualAccountsForLoanProduct(loanProductId,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF);
|
||||
if (isReversed) {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
|
||||
/**
|
||||
* Single DEBIT transaction for write-offs or Repayments (and their
|
||||
* reversals)
|
||||
***/
|
||||
if (writeOff) {
|
||||
helper.createDebitJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF, loanProductId, loanId,
|
||||
transactionId, transactionDate, totalDebitAmount, isReversal);
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
helper.createDebitJournalEntryOrReversalForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId,
|
||||
transactionId, transactionDate, totalDebitAmount, isReversal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Journal Entries for application of charges (fees and payments)
|
||||
* using the following rules <br/>
|
||||
*
|
||||
* <b>Fees:</b> Debit <i>Fees Receivable</i> and credit <i>Income from
|
||||
* Fees</i> <br/>
|
||||
*
|
||||
* <b>Penalties:</b> Debit <i>Penalties Receivable</i> and credit <i>Income
|
||||
* from Penalties</i>
|
||||
*
|
||||
* Also handles reversals for both fees and payment applications
|
||||
*
|
||||
* @param loanDTO
|
||||
* @param loanTransactionDTO
|
||||
* @param office
|
||||
*/
|
||||
private void createJournalEntriesForApplyCharges(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 feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final boolean isReversed = loanTransactionDTO.isReversed();
|
||||
|
||||
// create journal entries for the fees application (or reversal)
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
helper.createAccrualBasedJournalEntriesAndReversalsForLoan(office, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
|
||||
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, feesAmount, isReversed);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-73
@@ -5,7 +5,6 @@ import java.util.Date;
|
||||
|
||||
import org.mifosplatform.accounting.closure.domain.GLClosure;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
|
||||
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanDTO;
|
||||
import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
@@ -53,15 +52,9 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
**/
|
||||
else if (loanTransactionDTO.getTransactionType().isWriteOff() && !loanTransactionDTO.isReversed()) {
|
||||
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final GLAccount lossesWrittenOffAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF);
|
||||
helper.createDebitJournalEntryForLoanProduct(office, lossesWrittenOffAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF,
|
||||
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId, transactionId, transactionDate, principalAmount,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,22 +80,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final Date transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal disbursalAmount = loanTransactionDTO.getAmount();
|
||||
final boolean isReversed = loanTransactionDTO.isReversed();
|
||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
final GLAccount fundSourceAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
||||
if (isReversed) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate, disbursalAmount);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
disbursalAmount);
|
||||
}
|
||||
helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO,
|
||||
CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId, transactionDate, disbursalAmount, isReversal);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,63 +113,30 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
||||
|
||||
if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
|
||||
final GLAccount loanPortfolioAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||
if (isReversal) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, transactionDate,
|
||||
principalAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, loanId,
|
||||
transactionId, transactionDate, principalAmount, isReversal);
|
||||
}
|
||||
|
||||
if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
|
||||
final GLAccount interestAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS);
|
||||
if (isReversal) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate,
|
||||
interestAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, transactionDate,
|
||||
interestAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS, loanProductId, loanId,
|
||||
transactionId, transactionDate, interestAmount, isReversal);
|
||||
}
|
||||
|
||||
if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
|
||||
final GLAccount incomeFromFeesAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES);
|
||||
if (isReversal) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate,
|
||||
feesAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, transactionDate,
|
||||
feesAmount);
|
||||
}
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES, loanProductId, loanId,
|
||||
transactionId, transactionDate, feesAmount, isReversal);
|
||||
}
|
||||
|
||||
if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
helper.createCreditJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES, loanProductId, loanId,
|
||||
transactionId, transactionDate, penaltiesAmount, isReversal);
|
||||
}
|
||||
|
||||
final GLAccount incomeFromPenaltiesAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId,
|
||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES);
|
||||
if (isReversal) {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
} else {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, transactionDate,
|
||||
penaltiesAmount);
|
||||
}
|
||||
}
|
||||
final GLAccount fundSourceAccount = helper.getLinkedCashAccountsForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
||||
if (isReversal) {
|
||||
helper.createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
} else {
|
||||
helper.createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, transactionDate,
|
||||
totalDebitAmount);
|
||||
}
|
||||
/*** create a single debit entry (or reversal) for the entire amount **/
|
||||
helper.createDebitJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, loanId, transactionId,
|
||||
transactionDate, totalDebitAmount, isReversal);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -21,6 +21,7 @@ public class LoanTransactionEnumData {
|
||||
private final boolean contra;
|
||||
private final boolean waiveInterest;
|
||||
private final boolean waiveCharges;
|
||||
private final boolean applyCharges;
|
||||
private final boolean writeOff;
|
||||
private final boolean recoveryRepayment;
|
||||
|
||||
@@ -34,6 +35,7 @@ public class LoanTransactionEnumData {
|
||||
this.contra = Long.valueOf(3).equals(this.id);
|
||||
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.writeOff = Long.valueOf(6).equals(this.id);
|
||||
this.recoveryRepayment = Long.valueOf(8).equals(this.id);
|
||||
}
|
||||
@@ -70,4 +72,8 @@ public class LoanTransactionEnumData {
|
||||
return this.recoveryRepayment;
|
||||
}
|
||||
|
||||
public boolean isApplyCharges() {
|
||||
return this.applyCharges;
|
||||
}
|
||||
|
||||
}
|
||||
+61
-5
@@ -378,7 +378,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
|
||||
}
|
||||
|
||||
public void addLoanCharge(final LoanCharge loanCharge) {
|
||||
public LoanTransaction addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
|
||||
validateLoanIsNotClosed(loanCharge);
|
||||
|
||||
@@ -420,6 +421,53 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
final LoanTransaction applyLoanChargeTransaction = getChargeAppliedTransaction(loanCharge, null);
|
||||
|
||||
return applyLoanChargeTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a loanTransaction for "Apply Charge Event" with transaction date
|
||||
* set to "suppliedTransactionDate"
|
||||
*
|
||||
* 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
|
||||
* not, the transaction date is set to todays date
|
||||
*
|
||||
* @param loanCharge
|
||||
* @param suppliedTransactionDate
|
||||
* @return
|
||||
*/
|
||||
private LoanTransaction getChargeAppliedTransaction(final LoanCharge loanCharge, LocalDate suppliedTransactionDate) {
|
||||
final Money chargeAmount = loanCharge.getAmount(getCurrency());
|
||||
Money feeCharges = chargeAmount;
|
||||
Money penaltyCharges = Money.zero(loanCurrency());
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
penaltyCharges = chargeAmount;
|
||||
feeCharges = Money.zero(loanCurrency());
|
||||
}
|
||||
|
||||
LocalDate transactionDate = null;
|
||||
|
||||
if (suppliedTransactionDate != null) {
|
||||
transactionDate = suppliedTransactionDate;
|
||||
} else {
|
||||
transactionDate = loanCharge.getDueLocalDate();
|
||||
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
|
||||
|
||||
// if loan charge is to be applied on a future date, the loan
|
||||
// transaction would show todays date as applied date
|
||||
if (currentDate.isBefore(transactionDate)) {
|
||||
transactionDate = currentDate;
|
||||
}
|
||||
}
|
||||
|
||||
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.applyLoanCharge(this, chargeAmount, transactionDate, feeCharges,
|
||||
penaltyCharges);
|
||||
return applyLoanChargeTransaction;
|
||||
}
|
||||
|
||||
private void validateLoanIsNotClosed(final LoanCharge loanCharge) {
|
||||
@@ -542,10 +590,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
changes.put("amount", amountWaived.getAmount());
|
||||
|
||||
Money feeChargesWaived = Money.of(loanCurrency(), loanCharge.amount());
|
||||
Money feeChargesWaived = amountWaived;
|
||||
Money penaltyChargesWaived = Money.zero(loanCurrency());
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
penaltyChargesWaived = Money.of(loanCurrency(), loanCharge.amount());
|
||||
penaltyChargesWaived = amountWaived;
|
||||
feeChargesWaived = Money.zero(loanCurrency());
|
||||
}
|
||||
|
||||
@@ -1244,9 +1292,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
chargesPayment.updateLoan(this);
|
||||
this.loanTransactions.add(chargesPayment);
|
||||
|
||||
/**
|
||||
* all Charges repaid at disbursal is marked as repaid and
|
||||
* "APPLY Charge" transactions are created for all other fees (
|
||||
* which are created during disbursal but not repaid)
|
||||
**/
|
||||
for (LoanCharge charge : setOfLoanCharges()) {
|
||||
if (charge.isDueAtDisbursement()) {
|
||||
charge.markAsFullyPaid();
|
||||
} else {
|
||||
LoanTransaction loanChargeAppliedTransaction = getChargeAppliedTransaction(charge, disbursedOn);
|
||||
this.loanTransactions.add(loanChargeAppliedTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1843,7 +1899,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public boolean isSubmittedOnDateAfter(final LocalDate compareDate) {
|
||||
return this.submittedOnDate == null ? false : (new LocalDate(submittedOnDate)).isAfter(compareDate);
|
||||
}
|
||||
|
||||
|
||||
private LocalDate getSubmittedOnDate() {
|
||||
return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
|
||||
}
|
||||
@@ -2080,7 +2136,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastAssignmentRecordLatestEndDate == null){
|
||||
if (lastAssignmentRecordLatestEndDate == null) {
|
||||
lastAssignmentRecordLatestEndDate = historyRecord;
|
||||
} else if (historyRecord.isEndDateAfter(lastAssignmentRecordLatestEndDate.getEndDate())
|
||||
&& !historyRecord.isSameLoanOfficer(newLoanOfficer)) {
|
||||
|
||||
+1
-1
@@ -466,7 +466,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero();
|
||||
}
|
||||
|
||||
private Money getAmount(final MonetaryCurrency currency) {
|
||||
public Money getAmount(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.amount);
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -85,6 +85,14 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) {
|
||||
return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate);
|
||||
}
|
||||
|
||||
public static LoanTransaction applyLoanCharge(final Loan loan, final Money amount, final LocalDate applyDate,
|
||||
final Money feeCharges, final Money penaltyCharges) {
|
||||
LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate);
|
||||
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
|
||||
|
||||
return applyCharge;
|
||||
}
|
||||
|
||||
public static LoanTransaction waiveLoanCharge(final Loan loan, final Money waived, final LocalDate waiveDate,
|
||||
final Money feeChargesWaived, final Money penaltyChargesWaived) {
|
||||
|
||||
+11
-2
@@ -13,13 +13,15 @@ public enum LoanTransactionType {
|
||||
CONTRA(3, "loanTransactionType.contra"), //
|
||||
WAIVE_INTEREST(4, "loanTransactionType.waiver"), //
|
||||
REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement"), //
|
||||
WRITEOFF(6, "loanTransactionType.writeOff"), MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), //
|
||||
WRITEOFF(6, "loanTransactionType.writeOff"), //
|
||||
MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), //
|
||||
/**
|
||||
* This type of transactions is allowed on written-off loans where mfi still
|
||||
* attempts to recover payments from applicant after writing-off.
|
||||
*/
|
||||
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
|
||||
WAIVE_CHARGES(9, "loanTransactionType.waiveCharges");
|
||||
WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), //
|
||||
APPLY_CHARGES(10, "loanTransactionType.applyCharges");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
@@ -70,6 +72,9 @@ public enum LoanTransactionType {
|
||||
case 9:
|
||||
loanTransactionType = LoanTransactionType.WAIVE_CHARGES;
|
||||
break;
|
||||
case 10:
|
||||
loanTransactionType = LoanTransactionType.APPLY_CHARGES;
|
||||
break;
|
||||
default:
|
||||
loanTransactionType = LoanTransactionType.INVALID;
|
||||
break;
|
||||
@@ -101,6 +106,10 @@ public enum LoanTransactionType {
|
||||
return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue());
|
||||
}
|
||||
|
||||
public boolean isApplyCharges() {
|
||||
return this.value.equals(LoanTransactionType.APPLY_CHARGES.getValue());
|
||||
}
|
||||
|
||||
public boolean isWriteOff() {
|
||||
return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
|
||||
}
|
||||
|
||||
+15
-1
@@ -489,11 +489,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
|
||||
}
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<Long>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
|
||||
this.loanChargeRepository.save(loanCharge);
|
||||
|
||||
loan.addLoanCharge(loanCharge);
|
||||
LoanTransaction applyChargeTransaction = loan.addLoanCharge(loanCharge, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
// we want to apply charge transactions only for those loans charges
|
||||
// that are applied when a loan is active
|
||||
if (loan.status().isActive()) {
|
||||
this.loanTransactionRepository.save(applyChargeTransaction);
|
||||
}
|
||||
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
// we post Journal entries only for loans in active status
|
||||
if (loan.status().isActive()) {
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loanCharge.getId()) //
|
||||
|
||||
+6
-4
@@ -206,8 +206,7 @@ public class LoanEnumerations {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.INVALID.getValue().longValue(),
|
||||
LoanTransactionType.INVALID.getCode(),
|
||||
"Invalid");
|
||||
LoanTransactionType.INVALID.getCode(), "Invalid");
|
||||
break;
|
||||
case DISBURSEMENT:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.DISBURSEMENT.getValue().longValue(),
|
||||
@@ -223,8 +222,7 @@ public class LoanEnumerations {
|
||||
break;
|
||||
case CONTRA:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.CONTRA.getValue().longValue(),
|
||||
LoanTransactionType.CONTRA.getCode(),
|
||||
"Reversal");
|
||||
LoanTransactionType.CONTRA.getCode(), "Reversal");
|
||||
break;
|
||||
case WAIVE_INTEREST:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_INTEREST.getValue().longValue(),
|
||||
@@ -246,6 +244,10 @@ public class LoanEnumerations {
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.WAIVE_CHARGES.getValue().longValue(),
|
||||
LoanTransactionType.WAIVE_CHARGES.getCode(), "Waive loan charges");
|
||||
break;
|
||||
case APPLY_CHARGES:
|
||||
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_CHARGES.getValue().longValue(),
|
||||
LoanTransactionType.APPLY_CHARGES.getCode(), "Apply Charge");
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user