diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
index 8029dd6d7..d2ba12699 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
@@ -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;
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature
index cbaebe493..d24f5925e 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature
@@ -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 | |
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index c518f00a6..40ebb2214 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -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
*
* Penalty Repayment: Debits "Fund Source" and Credits "Receivable Penalties"
*
- * Handles write offs using the following posting rules
- *
- * Principal Write off: Debits "Losses Written Off" and Credits "Loan Portfolio"
- *
- * Interest Write off:Debits "Losses Written off" and Credits "Receivable Interest"
- *
- * Fee Write off:Debits "Losses Written off" and Credits "Receivable Fees"
- *
- * Penalty Write off: Debits "Losses Written off" and Credits "Receivable Penalties"
- *
- *
*
* @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 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 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 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 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 accountMap = new LinkedHashMap<>();
- Map debitAccountMapForGoodwillCredit = new LinkedHashMap<>();
+ final Map accountMap = new LinkedHashMap<>();
+ final Map 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 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 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 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 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 accountMapForDebit, Long paymentTypeId) {
Integer accountDebit = returnExistingDebitAccountInMapMatchingGLAccount(loanProductId, paymentTypeId, debitAccountType,