FINERACT-2210: Fix incorrect accounting of "buyback" for charged-off loan with fraud

This commit is contained in:
mariiaKraievska
2025-04-24 11:46:14 +02:00
committed by Adam Saghy
parent 30543fd72e
commit 13c99f98ab
4 changed files with 153 additions and 40 deletions
@@ -1274,7 +1274,7 @@ public class LoanProductsRequestFactory {
final List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings = new ArrayList<>(); final List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings = new ArrayList<>();
final PostChargeOffReasonToExpenseAccountMappings chargeOffDelinquentReason = new PostChargeOffReasonToExpenseAccountMappings(); final PostChargeOffReasonToExpenseAccountMappings chargeOffDelinquentReason = new PostChargeOffReasonToExpenseAccountMappings();
chargeOffDelinquentReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.DELINQUENT)); chargeOffDelinquentReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.DELINQUENT));
chargeOffDelinquentReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD)); chargeOffDelinquentReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT));
chargeOffReasonToExpenseAccountMappings.add(chargeOffDelinquentReason); chargeOffReasonToExpenseAccountMappings.add(chargeOffDelinquentReason);
return new PostLoanProductsRequest()// return new PostLoanProductsRequest()//
@@ -1369,7 +1369,7 @@ public class LoanProductsRequestFactory {
.incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))// .incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))//
.incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// .incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))// .chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))//
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))// .chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
.chargeOffReasonToExpenseAccountMappings(chargeOffReasonToExpenseAccountMappings)// .chargeOffReasonToExpenseAccountMappings(chargeOffReasonToExpenseAccountMappings)//
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));// .incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
} }
@@ -7647,7 +7647,7 @@ Feature: Charge-off
| Type | Account code | Account name | Debit | Credit | | Type | Account code | Account name | Debit | Credit |
| ASSET | 112601 | Loans Receivable | | 100.0 | | ASSET | 112601 | Loans Receivable | | 100.0 |
| ASSET | 112603 | Interest/Fee Receivable | | 0.62 | | ASSET | 112603 | Interest/Fee Receivable | | 0.62 |
| EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | 100.0 | | | EXPENSE | 744007 | Credit Loss/Bad Debt | 100.0 | |
| INCOME | 404001 | Interest Income Charge Off | 0.62 | | | INCOME | 404001 | Interest Income Charge Off | 0.62 | |
Then Admin does a charge-off undo the loan Then Admin does a charge-off undo the loan
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
@@ -7656,8 +7656,8 @@ Feature: Charge-off
| ASSET | 112601 | Loans Receivable | 100.0 | | | ASSET | 112601 | Loans Receivable | 100.0 | |
| ASSET | 112603 | Interest/Fee Receivable | | 0.62 | | ASSET | 112603 | Interest/Fee Receivable | | 0.62 |
| ASSET | 112603 | Interest/Fee Receivable | 0.62 | | | ASSET | 112603 | Interest/Fee Receivable | 0.62 | |
| EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | 100.0 | | | EXPENSE | 744007 | Credit Loss/Bad Debt | 100.0 | |
| EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | | 100.0 | | EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 |
| INCOME | 404001 | Interest Income Charge Off | 0.62 | | | INCOME | 404001 | Interest Income Charge Off | 0.62 | |
| INCOME | 404001 | Interest Income Charge Off | | 0.62 | | INCOME | 404001 | Interest Income Charge Off | | 0.62 |
@@ -8345,3 +8345,98 @@ Feature: Charge-off
| 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false | | 14 April 2025 | Accrual | 3.26 | 0.0 | 3.26 | 0.0 | 0.0 | 0.0 | false | false |
| 14 April 2025 | Accrual Adjustment | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false | | 14 April 2025 | Accrual Adjustment | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false |
| 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | | 14 April 2025 | Charge-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
@TestRailId:C3618
Scenario: Charge-off on a fraud loan respects GL mapping based on charge-off reason for buyback
When Admin sets the business date to "1 January 2024"
And Admin creates a client with random data
And 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 |
| LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 |
| 1 | 31 | 01 February 2024 | | 83.59 | 16.41 | 0.59 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.54 | 0.46 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
| 3 | 31 | 01 April 2024 | | 50.45 | 16.6 | 0.4 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
| 4 | 30 | 01 May 2024 | | 33.74 | 16.71 | 0.29 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
| 5 | 31 | 01 June 2024 | | 16.94 | 16.8 | 0.2 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.94 | 0.1 | 0.0 | 0.0 | 17.04 | 0.0 | 0.0 | 0.0 | 17.04 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100 | 2.04 | 0 | 0 | 102.04 | 0 | 0 | 0 | 102.04 |
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
And Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount
When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data:
| Transaction type | settlementDate | purchasePriceRatio |
| sale | 2024-01-21 | 1 |
Then Asset externalization response has the correct Loan ID, transferExternalId
Then Fetching Asset externalization details by loan id gives numberOfElements: 1 with correct ownerExternalId and the following data:
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type |
| 2024-01-21 | 1 | PENDING | 2024-01-01 | 9999-12-31 | SALE |
When Admin sets the business date to "22 January 2024"
When Admin runs inline COB job for Loan
Then LoanOwnershipTransferBusinessEvent is created
Then LoanAccountSnapshotBusinessEvent is created
Then Fetching Asset externalization details by loan id gives numberOfElements: 2 with correct ownerExternalId and the following data:
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type |
| 2024-01-21 | 1 | PENDING | 2024-01-01 | 2024-01-21 | SALE |
| 2024-01-21 | 1 | ACTIVE | 2024-01-22 | 9999-12-31 | SALE |
Then The latest asset externalization transaction with "ACTIVE" status has the following TRANSFER Journal entries:
| glAccountType | glAccountCode | glAccountName | entryType | amount |
| ASSET | 112601 | Loans Receivable | CREDIT | 100.00 |
| ASSET | 112603 | Interest/Fee Receivable | CREDIT | 2.04 |
| ASSET | 146000 | Asset transfer | DEBIT | 102.04 |
| ASSET | 112601 | Loans Receivable | DEBIT | 100.00 |
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 2.04 |
| ASSET | 146000 | Asset transfer | CREDIT | 102.04 |
Then The asset external owner has the following OWNER Journal entries:
| glAccountType | glAccountCode | glAccountName | entryType | amount |
| ASSET | 112601 | Loans Receivable | DEBIT | 100.00 |
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 2.04 |
Then Admin can successfully set Fraud flag to the loan
When Admin sets the business date to "03 February 2024"
And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024"
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
| Type | Account code | Account name | Debit | Credit |
| ASSET | 112601 | Loans Receivable | | 100.0 |
| ASSET | 112603 | Interest/Fee Receivable | | 0.62 |
| EXPENSE | 744007 | Credit Loss/Bad Debt | 100.0 | |
| INCOME | 404001 | Interest Income Charge Off | 0.62 | |
When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data:
| Transaction type | settlementDate | purchasePriceRatio |
| buyback | 2024-02-03 | |
Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data:
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type |
| 2024-01-21 | 1 | PENDING | 2024-01-01 | 2024-01-21 | SALE |
| 2024-01-21 | 1 | ACTIVE | 2024-01-22 | 9999-12-31 | SALE |
| 2024-02-03 | 1 | BUYBACK | 2024-02-03 | 9999-12-31 | BUYBACK |
When Admin sets the business date to "04 May 2024"
When Admin runs inline COB job for Loan
Then LoanOwnershipTransferBusinessEvent is created
Then LoanAccountSnapshotBusinessEvent is created
Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data:
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type |
| 2024-01-21 | 1 | PENDING | 2024-01-01 | 2024-01-21 | SALE |
| 2024-01-21 | 1 | ACTIVE | 2024-01-22 | 2024-02-03 | SALE |
| 2024-02-03 | 1 | BUYBACK | 2024-02-03 | 2024-02-03 | BUYBACK |
Then The latest asset externalization transaction with "BUYBACK" status has the following TRANSFER Journal entries:
| glAccountType | glAccountCode | glAccountName | entryType | amount |
| EXPENSE | 744007 | Credit Loss/Bad Debt | DEBIT | 100.00 |
| INCOME | 404001 | Interest Income Charge Off | DEBIT | 0.62 |
| ASSET | 146000 | Asset transfer | CREDIT | 100.62 |
| EXPENSE | 744007 | Credit Loss/Bad Debt | CREDIT | 100.00 |
| INCOME | 404001 | Interest Income Charge Off | CREDIT | 0.62 |
| ASSET | 146000 | Asset transfer | DEBIT | 100.62 |
Then The asset external owner has the following OWNER Journal entries:
| glAccountType | glAccountCode | glAccountName | entryType | amount |
| ASSET | 112601 | Loans Receivable | DEBIT | 100.00 |
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 2.04 |
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.24 |
| INCOME | 404000 | Interest Income | CREDIT | 0.24 |
| ASSET | 112601 | Loans Receivable | CREDIT | 100.00 |
| ASSET | 112603 | Interest/Fee Receivable | CREDIT | 0.62 |
| EXPENSE | 744007 | Credit Loss/Bad Debt | DEBIT | 100.00 |
| INCOME | 404001 | Interest Income Charge Off | DEBIT | 0.62 |
| EXPENSE | 744007 | Credit Loss/Bad Debt | CREDIT | 100.00 |
| INCOME | 404001 | Interest Income Charge Off | CREDIT | 0.62 |
@@ -90,6 +90,11 @@ public class InvestorAccountingHelper {
} }
} }
public ProductToGLAccountMapping getChargeOffMappingByCodeValue(final Long loanProductId, final PortfolioProductType productType,
final Long chargeOffReasonId) {
return accountMappingRepository.findChargeOffReasonMapping(loanProductId, productType.getValue(), chargeOffReasonId);
}
private JournalEntry createCreditJournalEntryForInvestor(final Office office, final String currencyCode, final GLAccount account, private JournalEntry createCreditJournalEntryForInvestor(final Office office, final String currencyCode, final GLAccount account,
final Long loanId, final Long transactionId, final LocalDate transactionDate, final BigDecimal amount) { final Long loanId, final Long transactionId, final LocalDate transactionDate, final BigDecimal amount) {
final boolean manualEntry = false; final boolean manualEntry = false;
@@ -31,6 +31,8 @@ import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialA
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry; import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.investor.accounting.journalentry.service.InvestorAccountingHelper; import org.apache.fineract.investor.accounting.journalentry.service.InvestorAccountingHelper;
import org.apache.fineract.investor.domain.ExternalAssetOwner; import org.apache.fineract.investor.domain.ExternalAssetOwner;
import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping; import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping;
@@ -39,6 +41,7 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping; import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository; import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository;
import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.PortfolioProductType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -94,7 +97,8 @@ public class AccountingServiceImpl implements AccountingService {
} }
@NotNull @NotNull
private List<JournalEntry> createJournalEntries(Loan loan, ExternalAssetOwnerTransfer transfer, boolean isReversalOrder) { private List<JournalEntry> createJournalEntries(final Loan loan, final ExternalAssetOwnerTransfer transfer,
final boolean isReversalOrder) {
this.helper.checkForBranchClosures(loan.getOffice().getId(), transfer.getSettlementDate()); this.helper.checkForBranchClosures(loan.getOffice().getId(), transfer.getSettlementDate());
// transaction properties // transaction properties
final Long transactionId = transfer.getId(); final Long transactionId = transfer.getId();
@@ -106,8 +110,8 @@ public class AccountingServiceImpl implements AccountingService {
final BigDecimal overPaymentAmount = loan.getTotalOverpaid(); final BigDecimal overPaymentAmount = loan.getTotalOverpaid();
// Moving money to asset transfer account // Moving money to asset transfer account
List<JournalEntry> journalEntryList = createJournalEntries(loan, transactionId, transactionDate, principalAmount, interestAmount, final List<JournalEntry> journalEntryList = createJournalEntries(loan, transactionId, transactionDate, principalAmount,
feesAmount, penaltiesAmount, overPaymentAmount, !isReversalOrder); interestAmount, feesAmount, penaltiesAmount, overPaymentAmount, !isReversalOrder);
// Moving money from asset transfer account // Moving money from asset transfer account
journalEntryList.addAll(createJournalEntries(loan, transactionId, transactionDate, principalAmount, interestAmount, feesAmount, journalEntryList.addAll(createJournalEntries(loan, transactionId, transactionDate, principalAmount, interestAmount, feesAmount,
penaltiesAmount, overPaymentAmount, isReversalOrder)); penaltiesAmount, overPaymentAmount, isReversalOrder));
@@ -171,82 +175,91 @@ public class AccountingServiceImpl implements AccountingService {
}); });
} }
private List<JournalEntry> createJournalEntries(Loan loan, Long transactionId, LocalDate transactionDate, BigDecimal principalAmount, private List<JournalEntry> createJournalEntries(final Loan loan, final Long transactionId, final LocalDate transactionDate,
BigDecimal interestAmount, BigDecimal feesAmount, BigDecimal penaltiesAmount, BigDecimal overPaymentAmount, final BigDecimal principalAmount, final BigDecimal interestAmount, final BigDecimal feesAmount,
boolean isReversalOrder) { final BigDecimal penaltiesAmount, final BigDecimal overPaymentAmount, final boolean isReversalOrder) {
Long loanProductId = loan.productId(); final Long loanProductId = loan.productId();
Long loanId = loan.getId(); final Long loanId = loan.getId();
Office office = loan.getOffice(); final Office office = loan.getOffice();
String currencyCode = loan.getCurrencyCode(); final String currencyCode = loan.getCurrencyCode();
List<JournalEntry> journalEntryList = new ArrayList<>(); final List<JournalEntry> journalEntryList = new ArrayList<>();
BigDecimal totalDebitAmount = BigDecimal.ZERO; BigDecimal totalDebitAmount = BigDecimal.ZERO;
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>(); final Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
// principal entry // principal entry
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(principalAmount)) {
AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.LOAN_PORTFOLIO;
if (loan.isChargedOff()) {
if (loan.isFraud()) {
accrualAccount = AccountingConstants.AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE;
} else {
accrualAccount = AccountingConstants.AccrualAccountsForLoan.CHARGE_OFF_EXPENSE;
}
}
totalDebitAmount = totalDebitAmount.add(principalAmount); totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue()); GLAccount account;
if (loan.isChargedOff()) {
final Long chargeOffReasonId = loan.fetchChargeOffReasonId();
final ProductToGLAccountMapping mapping = chargeOffReasonId != null
? helper.getChargeOffMappingByCodeValue(loanProductId, PortfolioProductType.LOAN, chargeOffReasonId)
: null;
if (mapping != null) {
account = mapping.getGlAccount();
} else {
final AccountingConstants.AccrualAccountsForLoan accrualAccount = loan.isFraud()
? AccountingConstants.AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE
: AccountingConstants.AccrualAccountsForLoan.CHARGE_OFF_EXPENSE;
account = helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue());
}
} else {
account = helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue());
}
accountMap.put(account, principalAmount); accountMap.put(account, principalAmount);
} }
// interest entry // interest entry
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(interestAmount)) {
AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.INTEREST_RECEIVABLE; AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.INTEREST_RECEIVABLE;
if (loan.isChargedOff()) { if (loan.isChargedOff()) {
accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST; accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST;
} }
totalDebitAmount = totalDebitAmount.add(interestAmount); totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue()); final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue());
if (accountMap.containsKey(account)) { if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(interestAmount); final BigDecimal amount = accountMap.get(account).add(interestAmount);
accountMap.put(account, amount); accountMap.put(account, amount);
} else { } else {
accountMap.put(account, interestAmount); accountMap.put(account, interestAmount);
} }
} }
// fee entry // fee entry
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(feesAmount)) {
AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.FEES_RECEIVABLE; AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.FEES_RECEIVABLE;
if (loan.isChargedOff()) { if (loan.isChargedOff()) {
accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES; accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES;
} }
totalDebitAmount = totalDebitAmount.add(feesAmount); totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue()); final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue());
if (accountMap.containsKey(account)) { if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(feesAmount); final BigDecimal amount = accountMap.get(account).add(feesAmount);
accountMap.put(account, amount); accountMap.put(account, amount);
} else { } else {
accountMap.put(account, feesAmount); accountMap.put(account, feesAmount);
} }
} }
// penalty entry // penalty entry
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(penaltiesAmount)) {
AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.PENALTIES_RECEIVABLE; AccountingConstants.AccrualAccountsForLoan accrualAccount = AccountingConstants.AccrualAccountsForLoan.PENALTIES_RECEIVABLE;
if (loan.isChargedOff()) { if (loan.isChargedOff()) {
accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY; accrualAccount = AccountingConstants.AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY;
} }
totalDebitAmount = totalDebitAmount.add(penaltiesAmount); totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue()); final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, accrualAccount.getValue());
if (accountMap.containsKey(account)) { if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(penaltiesAmount); final BigDecimal amount = accountMap.get(account).add(penaltiesAmount);
accountMap.put(account, amount); accountMap.put(account, amount);
} else { } else {
accountMap.put(account, penaltiesAmount); accountMap.put(account, penaltiesAmount);
} }
} }
// overpaid entry // overpaid entry
if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(overPaymentAmount)) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount); totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, final GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccountingConstants.AccrualAccountsForLoan.OVERPAYMENT.getValue()); AccountingConstants.AccrualAccountsForLoan.OVERPAYMENT.getValue());
if (accountMap.containsKey(account)) { if (accountMap.containsKey(account)) {
BigDecimal amount = accountMap.get(account).add(overPaymentAmount); final BigDecimal amount = accountMap.get(account).add(overPaymentAmount);
accountMap.put(account, amount); accountMap.put(account, amount);
} else { } else {
accountMap.put(account, overPaymentAmount); accountMap.put(account, overPaymentAmount);
@@ -257,7 +270,7 @@ public class AccountingServiceImpl implements AccountingService {
journalEntryList.add(this.helper.createCreditJournalEntryOrReversalForInvestor(office, currencyCode, loanId, transactionId, journalEntryList.add(this.helper.createCreditJournalEntryOrReversalForInvestor(office, currencyCode, loanId, transactionId,
transactionDate, entry.getValue(), isReversalOrder, entry.getKey())); transactionDate, entry.getValue(), isReversalOrder, entry.getKey()));
} }
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) { if (MathUtil.isGreaterThanZero(totalDebitAmount)) {
journalEntryList.add(this.helper.createDebitJournalEntryOrReversalForInvestor(office, currencyCode, journalEntryList.add(this.helper.createDebitJournalEntryOrReversalForInvestor(office, currencyCode,
AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId, loanId, transactionId, transactionDate, AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId, loanId, transactionId, transactionDate,
totalDebitAmount, isReversalOrder)); totalDebitAmount, isReversalOrder));