FINERACT-2356: Fix incorrect accounting during write-off in case the loan was already charged-off
This commit is contained in:
committed by
Adam Saghy
parent
567e1c9800
commit
b9564f806b
+1
@@ -42,6 +42,7 @@ public enum TransactionType {
|
||||
BUY_DOWN_FEE_ADJUSTMENT("buyDownFeeAdjustment"), //
|
||||
BUY_DOWN_FEE_AMORTIZATION("buyDownFeeAmortization"), //
|
||||
INTEREST_REFUND("interestRefund"), //
|
||||
WRITE_OFF("writeOff"), //
|
||||
;
|
||||
|
||||
public final String value;
|
||||
|
||||
@@ -60,8 +60,64 @@
|
||||
| Close (as written-off) | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
Then Admin fails to undo "1"th transaction made on "29 January 2023"
|
||||
|
||||
@TestRailId:C4006
|
||||
Scenario: Verify accounting during Write-off when loan was already charged-off
|
||||
When Admin sets the business date to "1 January 2023"
|
||||
And Admin creates a client with random data
|
||||
When Admin creates a fully customized loan with the following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||
| LP1_INTEREST_FLAT | 1 January 2023 | 1000 | 12 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER |
|
||||
And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023"
|
||||
And Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount
|
||||
And Admin adds an NSF fee because of payment bounce with "1 January 2023" transaction date
|
||||
When Admin sets the business date to "22 February 2023"
|
||||
And Admin adds a 10 % Processing charge to the loan with "en" locale on date: "22 February 2023"
|
||||
And Admin does charge-off the loan on "22 February 2023"
|
||||
Then Loan marked as charged-off on "22 February 2023"
|
||||
Then Loan status will be "ACTIVE"
|
||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 1000.0 | |
|
||||
| LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 |
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "22 February 2023" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 143.0 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 1000.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 30.0 | |
|
||||
| INCOME | 404008 | Fee Charge Off | 113.0 | |
|
||||
When Admin sets the business date to "1 March 2023"
|
||||
And Admin does write-off the loan on "1 March 2023"
|
||||
Then Loan status will be "CLOSED_WRITTEN_OFF"
|
||||
Then Loan Transactions tab has a "WRITE_OFF" transaction with date "01 March 2023" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 1000.0 |
|
||||
| INCOME | 404001 | Interest Income Charge Off | | 30.0 |
|
||||
| INCOME | 404008 | Fee Charge Off | | 113.0 |
|
||||
| EXPENSE | e4 | Written off | 1143.0 | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@TestRailId:C4007
|
||||
Scenario: Verify accounting during Write-off when loan was not charged-off before
|
||||
When Admin sets the business date to "1 January 2023"
|
||||
And Admin creates a client with random data
|
||||
When Admin creates a fully customized loan with the following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||
| LP1_INTEREST_FLAT | 1 January 2023 | 1000 | 12 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER |
|
||||
And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023"
|
||||
And Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount
|
||||
And Admin adds an NSF fee because of payment bounce with "1 January 2023" transaction date
|
||||
When Admin sets the business date to "22 February 2023"
|
||||
And Admin adds a 10 % Processing charge to the loan with "en" locale on date: "22 February 2023"
|
||||
Then Loan status will be "ACTIVE"
|
||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 1000.0 | |
|
||||
| LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 |
|
||||
When Admin sets the business date to "1 March 2023"
|
||||
And Admin does write-off the loan on "1 March 2023"
|
||||
Then Loan status will be "CLOSED_WRITTEN_OFF"
|
||||
Then Loan Transactions tab has a "WRITE_OFF" transaction with date "01 March 2023" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 143.0 |
|
||||
| EXPENSE | e4 | Written off | 1143.0 | |
|
||||
|
||||
+243
-85
@@ -79,8 +79,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
*/
|
||||
else if ((transactionType.isRepaymentType() && !transactionType.isChargeAdjustment())
|
||||
|| transactionType.isRepaymentAtDisbursement() || transactionType.isChargePayment()) {
|
||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false,
|
||||
transactionType.isRepaymentAtDisbursement());
|
||||
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office, transactionType.isRepaymentAtDisbursement());
|
||||
}
|
||||
|
||||
// Logic for handling recovery payments
|
||||
@@ -100,7 +99,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
// Handle Write Offs
|
||||
else if ((transactionType.isWriteOff() || transactionType.isWaiveInterest() || transactionType.isWaiveCharges())) {
|
||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
|
||||
createJournalEntriesForWriteOffs(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
|
||||
// Logic for Refunds of Active Loans
|
||||
@@ -1168,35 +1167,32 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
*
|
||||
* <b>Penalty Repayment</b>: Debits "Fund Source" 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/>
|
||||
*
|
||||
* <b>Interest Write off</b>:Debits "Losses Written off" and Credits "Receivable Interest" <br/>
|
||||
*
|
||||
* <b>Fee Write off</b>:Debits "Losses Written off" and Credits "Receivable Fees" <br/>
|
||||
*
|
||||
* <b>Penalty Write off</b>: Debits "Losses Written off" and Credits "Receivable Penalties" <br/>
|
||||
* <br/>
|
||||
* <br/>
|
||||
*
|
||||
* @param loanTransactionDTO
|
||||
* @param loanDTO
|
||||
* @param office
|
||||
*/
|
||||
private void createJournalEntriesForRepaymentsAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office, final boolean writeOff, final boolean isIncomeFromFee) {
|
||||
private void createJournalEntriesForRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office,
|
||||
final boolean isIncomeFromFee) {
|
||||
final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
|
||||
if (isMarkedChargeOff) {
|
||||
createJournalEntriesForChargeOffLoanRepaymentAndWriteOffs(loanDTO, loanTransactionDTO, office, writeOff, isIncomeFromFee);
|
||||
|
||||
createJournalEntriesForRepaymentWhenLoanIsChargedOff(loanDTO, loanTransactionDTO, office, isIncomeFromFee);
|
||||
} else {
|
||||
createJournalEntriesForLoansRepaymentAndWriteOffs(loanDTO, loanTransactionDTO, office, writeOff, isIncomeFromFee);
|
||||
createJournalEntriesForLoanRepayments(loanDTO, loanTransactionDTO, office, isIncomeFromFee);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForChargeOffLoanRepaymentAndWriteOffs(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO,
|
||||
Office office, boolean writeOff, boolean isIncomeFromFee) {
|
||||
private void createJournalEntriesForWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||
final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
|
||||
if (isMarkedChargeOff) {
|
||||
createJournalEntriesForWriteOffsWhenLoanIsChargedOff(loanDTO, loanTransactionDTO, office);
|
||||
} else {
|
||||
createJournalEntriesForLoanWriteOffs(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForRepaymentWhenLoanIsChargedOff(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office, final boolean isIncomeFromFee) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
@@ -1212,7 +1208,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
final GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
@@ -1240,17 +1236,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
} else if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
|
||||
} else if (loanTransactionDTO.getTransactionType().isRepayment()) {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
|
||||
} else {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), glAccountBalanceHolder);
|
||||
@@ -1314,7 +1307,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode,
|
||||
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate,
|
||||
feesAmount, loanTransactionDTO.getFeePayments());
|
||||
GLAccount debitAccount = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount debitAccount = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), paymentTypeId);
|
||||
glAccountBalanceHolder.addToDebit(debitAccount, feesAmount);
|
||||
|
||||
@@ -1322,7 +1315,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1389,30 +1381,25 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
final GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
|
||||
if (writeOff) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
|
||||
if (loanTransactionDTO.isLoanToLoanTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else if (loanTransactionDTO.isAccountTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else {
|
||||
if (loanTransactionDTO.isLoanToLoanTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else if (loanTransactionDTO.isAccountTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else {
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
debitEntry.getValue(), glAccount);
|
||||
}
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
final GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
debitEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1423,7 +1410,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
***/
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
|
||||
if (loanTransactionDTO.getTransactionType().isChargeRefund()) {
|
||||
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
|
||||
final Integer incomeAccount = this.helper
|
||||
.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
|
||||
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
}
|
||||
@@ -1431,8 +1419,87 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoansRepaymentAndWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office, final boolean writeOff, final boolean isIncomeFromFee) {
|
||||
private void createJournalEntriesForWriteOffsWhenLoanIsChargedOff(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
|
||||
|
||||
// transaction properties
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
final GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
// principal payment
|
||||
if (MathUtil.isGreaterThanZero(principalAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
if (isMarkedFraud) {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
} else {
|
||||
populateCreditDebitMaps(loanProductId, principalAmount, paymentTypeId, AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
// interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// handle penalties
|
||||
if (MathUtil.isGreaterThanZero(penaltiesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
populateCreditDebitMaps(loanProductId, penaltiesAmount, paymentTypeId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// overpayment
|
||||
if (MathUtil.isGreaterThanZero(overPaymentAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
|
||||
populateCreditDebitMaps(loanProductId, overPaymentAmount, paymentTypeId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
final GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoanRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office, final boolean isIncomeFromFee) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
@@ -1450,13 +1517,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
|
||||
Map<Integer, BigDecimal> debitAccountMapForGoodwillCredit = new LinkedHashMap<>();
|
||||
final Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
|
||||
final Map<Integer, BigDecimal> debitAccountMapForGoodwillCredit = new LinkedHashMap<>();
|
||||
|
||||
// handle principal payment or writeOff
|
||||
// handle principal payment
|
||||
if (MathUtil.isGreaterThanZero(principalAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId);
|
||||
accountMap.put(account, principalAmount);
|
||||
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
|
||||
@@ -1465,13 +1532,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
}
|
||||
|
||||
// handle interest payment of writeOff
|
||||
// handle interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
BigDecimal amount = accountMap.get(account).add(interestAmount);
|
||||
final BigDecimal amount = accountMap.get(account).add(interestAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, interestAmount);
|
||||
@@ -1483,17 +1550,17 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
}
|
||||
|
||||
// handle fees payment of writeOff
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
if (isIncomeFromFee) {
|
||||
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(),
|
||||
loanProductId, loanId, transactionId, transactionDate, feesAmount, loanTransactionDTO.getFeePayments());
|
||||
} else {
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
BigDecimal amount = accountMap.get(account).add(feesAmount);
|
||||
final BigDecimal amount = accountMap.get(account).add(feesAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, feesAmount);
|
||||
@@ -1505,23 +1572,23 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
}
|
||||
|
||||
// handle penalties payment of writeOff
|
||||
// handle penalties payment
|
||||
if (MathUtil.isGreaterThanZero(penaltiesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
if (isIncomeFromFee) {
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
|
||||
final BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, penaltiesAmount);
|
||||
}
|
||||
} else {
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
|
||||
final BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, penaltiesAmount);
|
||||
@@ -1537,10 +1604,10 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
if (MathUtil.isGreaterThanZero(overPaymentAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
|
||||
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||
paymentTypeId);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
|
||||
final BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, overPaymentAmount);
|
||||
@@ -1559,31 +1626,26 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
|
||||
/**
|
||||
* Single DEBIT transaction for write-offs or Repayments
|
||||
* Single DEBIT transaction for Repayments
|
||||
***/
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
|
||||
if (writeOff) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
|
||||
if (loanTransactionDTO.isLoanToLoanTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else if (loanTransactionDTO.isAccountTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else {
|
||||
if (loanTransactionDTO.isLoanToLoanTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else if (loanTransactionDTO.isAccountTransfer()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
} else {
|
||||
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
|
||||
// create debit entries
|
||||
for (Map.Entry<Integer, BigDecimal> debitEntry : debitAccountMapForGoodwillCredit.entrySet()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, debitEntry.getKey().intValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue());
|
||||
}
|
||||
|
||||
} else {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
|
||||
// create debit entries
|
||||
for (Map.Entry<Integer, BigDecimal> debitEntry : debitAccountMapForGoodwillCredit.entrySet()) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, debitEntry.getKey().intValue(), loanProductId,
|
||||
paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue());
|
||||
}
|
||||
|
||||
} else {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1593,12 +1655,108 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
* repayment above
|
||||
***/
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount) && loanTransactionDTO.getTransactionType().isChargeRefund()) {
|
||||
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
|
||||
final Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
|
||||
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoanWriteOffs(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
|
||||
// transaction properties
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
|
||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||
|
||||
final Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
|
||||
|
||||
// handle principal payment of writeOff
|
||||
if (MathUtil.isGreaterThanZero(principalAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), paymentTypeId);
|
||||
accountMap.put(account, principalAmount);
|
||||
}
|
||||
|
||||
// handle interest payment of writeOff
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
final BigDecimal amount = accountMap.get(account).add(interestAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, interestAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// handle fees payment of writeOff
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
final BigDecimal amount = accountMap.get(account).add(feesAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, feesAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// handle penalties payment of writeOff
|
||||
if (MathUtil.isGreaterThanZero(penaltiesAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
final BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, penaltiesAmount);
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtil.isGreaterThanZero(overPaymentAmount)) {
|
||||
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
|
||||
final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.OVERPAYMENT.getValue(), paymentTypeId);
|
||||
if (accountMap.containsKey(account)) {
|
||||
final BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
|
||||
accountMap.put(account, amount);
|
||||
} else {
|
||||
accountMap.put(account, overPaymentAmount);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(entry.getValue())) {
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
|
||||
entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single DEBIT transaction for write-offs
|
||||
***/
|
||||
if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
|
||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateDebitAccountEntry(Long loanProductId, BigDecimal transactionPartAmount, Integer debitAccountType,
|
||||
Map<Integer, BigDecimal> accountMapForDebit, Long paymentTypeId) {
|
||||
Integer accountDebit = returnExistingDebitAccountInMapMatchingGLAccount(loanProductId, paymentTypeId, debitAccountType,
|
||||
|
||||
Reference in New Issue
Block a user