FINERACT-2210: Incorrect accounting of reversed charge-off transaction.

This commit is contained in:
mariiaKraievska
2025-04-01 10:01:13 +02:00
committed by Adam Saghy
parent b7ba9bd570
commit 957666df18
12 changed files with 578 additions and 481 deletions
@@ -27,6 +27,7 @@ import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@@ -54,6 +55,7 @@ public class JournalEntry extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "currency_code", length = 3, nullable = false)
private String currencyCode;
@Setter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reversal_id")
private JournalEntry reversalJournalEntry;
@@ -73,6 +75,7 @@ public class JournalEntry extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "share_transaction_id")
private Long shareTransactionId;
@Setter
@Column(name = "reversed", nullable = false)
private boolean reversed = false;
@@ -151,11 +154,4 @@ public class JournalEntry extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return JournalEntryType.CREDIT.getValue().equals(this.type);
}
public void setReversalJournalEntry(final JournalEntry reversalJournalEntry) {
this.reversalJournalEntry = reversalJournalEntry;
}
public void setReversed(final boolean reversed) {
this.reversed = reversed;
}
}
@@ -105,6 +105,7 @@ public enum DefaultLoanProduct implements LoanProduct {
LP2_INTEREST_RECALCULATION_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR_LAST_INSTALLMENT_STRATEGY, //
LP2_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR_LAST_INSTALLMENT_STRATEGY, //
LP2_ADV_PYMNT_INTEREST_DAILY_INT_RECALCULATION_ZERO_INT_CHARGE_OFF_INT_RECOGNITION_FROM_DISB_DATE, //
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON, //
;
@Override
@@ -1250,4 +1250,126 @@ public class LoanProductsRequestFactory {
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))//
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
}
public PostLoanProductsRequest defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappings() {
final String name = Utils.randomNameGenerator(NAME_PREFIX_LP2, 4);
final String shortName = Utils.randomNameGenerator(SHORT_NAME_PREFIX, 3);
final List<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
final List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
final List<Integer> interestRateVariationsForBorrowerCycle = new ArrayList<>();
final List<LoanProductChargeData> charges = new ArrayList<>();
final List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>();
final List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings = new ArrayList<>();
final List<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings = new ArrayList<>();
final GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings();
loanPaymentChannelToFundSourceMappings.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.FUND_RECEIVABLES));
loanPaymentChannelToFundSourceMappings.paymentTypeId(paymentTypeResolver.resolve(DefaultPaymentType.MONEY_TRANSFER));
paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings);
final Long chargeOffReasonId = codeHelper.retrieveCodeByName(CHARGE_OFF_REASONS).getId();
final List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings = new ArrayList<>();
final PostChargeOffReasonToExpenseAccountMappings chargeOffDelinquentReason = new PostChargeOffReasonToExpenseAccountMappings();
chargeOffDelinquentReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.DELINQUENT));
chargeOffDelinquentReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD));
chargeOffReasonToExpenseAccountMappings.add(chargeOffDelinquentReason);
return new PostLoanProductsRequest()//
.name(name)//
.shortName(shortName)//
.description(DESCRIPTION_LP2)//
.enableDownPayment(true)//
.enableAutoRepaymentForDownPayment(true)//
.disbursedAmountPercentageForDownPayment(new BigDecimal(25))//
.fundId(FUND_ID)//
.startDate(null)//
.closeDate(null)//
.includeInBorrowerCycle(false)//
.currencyCode(CURRENCY_CODE)//
.digitsAfterDecimal(2)//
.inMultiplesOf(0)//
.installmentAmountInMultiplesOf(1)//
.useBorrowerCycle(false)//
.minPrincipal(100.0)//
.principal(1000.0)//
.maxPrincipal(10000.0)//
.minNumberOfRepayments(1)//
.numberOfRepayments(3)//
.maxNumberOfRepayments(30)//
.isLinkedToFloatingInterestRates(false)//
.minInterestRatePerPeriod((double) 0)//
.interestRatePerPeriod((double) 0)//
.maxInterestRatePerPeriod((double) 0)//
.interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_MONTH)//
.repaymentEvery(15)//
.repaymentFrequencyType(REPAYMENT_FREQUENCY_TYPE_DAYS)//
.principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)//
.numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)//
.interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)//
.amortizationType(AMORTIZATION_TYPE)//
.interestType(INTEREST_TYPE_DECLINING_BALANCE)//
.isEqualAmortization(false)//
.interestCalculationPeriodType(INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT)//
.transactionProcessingStrategyCode(TRANSACTION_PROCESSING_STRATEGY_CODE)//
.daysInYearType(DAYS_IN_YEAR_TYPE)//
.daysInMonthType(DAYS_IN_MONTH_TYPE)//
.canDefineInstallmentAmount(true)//
.graceOnArrearsAgeing(3)//
.overdueDaysForNPA(179)//
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
.principalThresholdForLastInstallment(50)//
.allowVariableInstallments(false)//
.canUseForTopup(false)//
.isInterestRecalculationEnabled(false)//
.holdGuaranteeFunds(false)//
.multiDisburseLoan(true)//
.allowAttributeOverrides(new AllowAttributeOverrides()//
.amortizationType(true)//
.interestType(true)//
.transactionProcessingStrategyCode(true)//
.interestCalculationPeriodType(true)//
.inArrearsTolerance(true)//
.repaymentEvery(true)//
.graceOnPrincipalAndInterestPayment(true)//
.graceOnArrearsAgeing(true))//
.allowPartialPeriodInterestCalcualtion(true)//
.maxTrancheCount(10)//
.outstandingLoanBalance(10000.0)//
.charges(charges)//
.accountingRule(LOAN_ACCOUNTING_RULE)//
.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT))//
.loanPortfolioAccountId(accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE))//
.transfersInSuspenseAccountId(accountTypeResolver.resolve(DefaultAccountType.TRANSFER_IN_SUSPENSE_ACCOUNT))//
.interestOnLoanAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))//
.incomeFromFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))//
.incomeFromPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))//
.incomeFromRecoveryAccountId(accountTypeResolver.resolve(DefaultAccountType.RECOVERIES))//
.writeOffAccountId(accountTypeResolver.resolve(DefaultAccountType.WRITTEN_OFF))//
.overpaymentLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))//
.receivableInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
.receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
.receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))//
.dateFormat(DATE_FORMAT)//
.locale(LOCALE_EN)//
.disallowExpectedDisbursements(true)//
.allowApprovedDisbursedAmountsOverApplied(true)//
.overAppliedCalculationType(OVER_APPLIED_CALCULATION_TYPE)//
.overAppliedNumber(OVER_APPLIED_NUMBER)//
.delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())//
.goodwillCreditAccountId(accountTypeResolver.resolve(DefaultAccountType.GOODWILL_EXPENSE_ACCOUNT))//
.incomeFromGoodwillCreditInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))//
.incomeFromGoodwillCreditFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.incomeFromGoodwillCreditPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)//
.penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)//
.feeToIncomeAccountMappings(feeToIncomeAccountMappings)//
.incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))//
.incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))//
.chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))//
.chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))//
.chargeOffReasonToExpenseAccountMappings(chargeOffReasonToExpenseAccountMappings)//
.incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));//
}
}
@@ -1975,6 +1975,34 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
.createLoanProduct(loanProductsRequestInterest36030DecliningPeriodDailyIntRecalc).execute();
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_360_30__DECLINING_PERIOD_DAILY_INT_RECALC,
responseInterest36030DecliningPeriodDailyIntRecalc);
// LP2 + zero-interest chargeOff behaviour + progressive loan schedule + horizontal + delinquent charge-off
// reason to GL account mapping
// (LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON)
final String name83 = DefaultLoanProduct.LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON.getName();
final PostLoanProductsRequest loanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReason = loanProductsRequestFactory
.defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappings()//
.name(name83)//
.enableDownPayment(false)//
.enableAutoRepaymentForDownPayment(null)//
.disbursedAmountPercentageForDownPayment(null)//
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
.loanScheduleType("PROGRESSIVE") //
.loanScheduleProcessingType("HORIZONTAL")//
.interestRateFrequencyType(3)//
.maxInterestRatePerPeriod(10.0)//
.paymentAllocation(List.of(//
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))//
.chargeOffBehaviour("ZERO_INTEREST");//
final Response<PostLoanProductsResponse> responseLoanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReason = loanProductsApi
.createLoanProduct(loanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReason).execute();
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON,
responseLoanProductsRequestAdvZeroInterestChargeOffProgressiveDelinquentReason);
}
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
@@ -116,6 +116,7 @@ public abstract class TestContextKey {
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationZeroInterestChargeOff";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffBehaviour";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOff";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffDelinquentReason";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ALLOW_PARTIAL_PERIOD = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyAllowPartialPeriod";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ACCRUAL_ACTIVITY_POSTING = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyAccrualActivityPosting";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_CHARGEBACK_INTEREST_FEE_PRINCIPAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030ChargebackInterestFeePrincipal";
@@ -7541,4 +7541,46 @@ Feature: Charge-off
| 20 January 2024 | Accrual Adjustment | 12.58 | 0.0 | 12.58 | 0.0 | 0.0 | 0.0 | false | false |
| 20 January 2024 | Charge-off | 790.71 | 757.67 | 33.04 | 0.0 | 0.0 | 0.0 | false | false |
Then Loan marked as charged-off on "20 January 2024"
Then LoanAccrualAdjustmentTransactionBusinessEvent is raised on "20 January 2024"
Then LoanAccrualAdjustmentTransactionBusinessEvent is raised on "20 January 2024"
@TestRailId:C3567
Scenario: Charge-off reversal on a fraud loan respects GL mapping based on charge-off reason
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
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 | 744037 | Credit Loss/Bad Debt-Fraud | 100.0 | |
| INCOME | 404001 | Interest Income Charge Off | 0.62 | |
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:
| 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 | 744037 | Credit Loss/Bad Debt-Fraud | | 100.0 |
| INCOME | 404001 | Interest Income Charge Off | 0.62 | |
| INCOME | 404001 | Interest Income Charge Off | | 0.62 |
@@ -68,14 +68,11 @@ import org.apache.fineract.portfolio.PortfolioProductType;
import org.apache.fineract.portfolio.account.PortfolioAccountType;
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.ClientTransaction;
import org.apache.fineract.portfolio.client.domain.ClientTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData;
import org.springframework.dao.DataAccessException;
@@ -356,15 +353,12 @@ public class AccountingProcessorHelper {
* transactionDate
* @param totalAmount
* totalAmount
* @param isReversal
* isReversal
* @param chargePaymentDTOs
* chargePaymentDTOs
*/
public void createJournalEntriesAndReversalsForLoanCharges(final Office office, final String currencyCode,
final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
final List<ChargePaymentDTO> chargePaymentDTOs) {
public void createJournalEntriesForLoanCharges(final Office office, final String currencyCode, final Integer accountTypeToBeDebited,
final Integer accountTypeToBeCredited, final Long loanProductId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal totalAmount, final List<ChargePaymentDTO> chargePaymentDTOs) {
GLAccount receivableAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountTypeToBeDebited, null);
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
@@ -387,13 +381,8 @@ public class AccountingProcessorHelper {
final GLAccount account = entry.getKey();
final BigDecimal amount = entry.getValue();
totalCreditedAmount = totalCreditedAmount.add(amount);
if (isReversal) {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
createCreditJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
createDebitJournalEntryForLoan(office, currencyCode, receivableAccount, loanId, transactionId, transactionDate, amount);
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
if (totalAmount.compareTo(totalCreditedAmount) != 0) {
@@ -453,61 +442,43 @@ public class AccountingProcessorHelper {
* @param transactionId
* @param transactionDate
* @param amount
* @param isReversal
*/
public void createJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode,
final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId,
final Long loanId, final String transactionId, final LocalDate transactionDate, final BigDecimal amount,
final Boolean isReversal) {
public void createJournalEntriesForLoan(final Office office, final String currencyCode, final Integer accountTypeToBeDebited,
final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount) {
int accountTypeToDebitId = accountTypeToBeDebited;
int accountTypeToCreditId = accountTypeToBeCredited;
// reverse debits and credits for reversals
if (isReversal) {
accountTypeToDebitId = accountTypeToBeCredited;
accountTypeToCreditId = accountTypeToBeDebited;
}
createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, amount);
}
public void createSplitJournalEntriesAndReversalsForLoan(Office office, String currencyCode,
List<JournalAmountHolder> splitAccountsHolder, JournalAmountHolder totalAccountHolder, Long loanProductId, Long paymentTypeId,
Long loanId, String transactionId, LocalDate transactionDate, Boolean isReversal) {
public void createSplitJournalEntriesForLoan(Office office, String currencyCode, List<JournalAmountHolder> splitAccountsHolder,
JournalAmountHolder totalAccountHolder, Long loanProductId, Long paymentTypeId, Long loanId, String transactionId,
LocalDate transactionDate) {
splitAccountsHolder.forEach(journalItemHolder -> {
final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, journalItemHolder.getAccountType(), paymentTypeId);
if (isReversal) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate,
journalItemHolder.getAmount());
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate,
journalItemHolder.getAmount());
}
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate,
journalItemHolder.getAmount());
});
final GLAccount totalAccount = getLinkedGLAccountForLoanProduct(loanProductId, totalAccountHolder.getAccountType(), paymentTypeId);
if (isReversal) {
createDebitJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate,
totalAccountHolder.getAmount());
} else {
createCreditJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate,
totalAccountHolder.getAmount());
}
createCreditJournalEntryForLoan(office, currencyCode, totalAccount, loanId, transactionId, transactionDate,
totalAccountHolder.getAmount());
}
public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode,
public void createCreditJournalEntryForLoan(final Office office, final String currencyCode,
final CashAccountsForLoan accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
final String transactionId, final LocalDate transactionDate, final BigDecimal amount) {
final int accountMappingTypeId = accountMappingType.getValue();
createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, amount, isReversal);
createCreditJournalEntryForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, amount);
}
public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode,
public void createCreditJournalEntryForLoan(final Office office, final String currencyCode,
final AccrualAccountsForLoan accountMappingType, final Long loanProductId, final Long paymentTypeId, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
final String transactionId, final LocalDate transactionDate, final BigDecimal amount) {
final int accountMappingTypeId = accountMappingType.getValue();
createCreditJournalEntryOrReversalForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, amount, isReversal);
createCreditJournalEntryForLoan(office, currencyCode, accountMappingTypeId, loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, amount);
}
/**
@@ -635,18 +606,6 @@ public class AccountingProcessorHelper {
}
}
public void createCashBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
final Long debitAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
final BigDecimal amount, final Boolean isReversal) {
// reverse debits and credits for reversals
final GLAccount debitAccount = getGLAccountById(debitAccountId);
if (isReversal) {
createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
}
}
public void createCashBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
final Long creditAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
final BigDecimal amount, final Boolean isReversal) {
@@ -685,18 +644,6 @@ public class AccountingProcessorHelper {
}
}
public void createAccrualBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
final Long debitAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
final BigDecimal amount, final Boolean isReversal) {
// reverse debits and credits for reversals
final GLAccount debitAccount = getGLAccountById(debitAccountId);
if (isReversal) {
createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
}
}
public void createAccrualBasedBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
final Long creditAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
final BigDecimal amount, final Boolean isReversal) {
@@ -723,80 +670,37 @@ public class AccountingProcessorHelper {
createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
}
public void createDebitJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
public void createDebitJournalEntryForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
final LocalDate transactionDate, final BigDecimal amount) {
final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId);
if (isReversal) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
public void createDebitJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal,
final GLAccount account) {
if (isReversal) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
public void createDebitJournalEntryForLoan(final Office office, final String currencyCode, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final GLAccount account) {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
public void createDebitJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode,
final int accountMappingTypeId, final Long loanProductId, final Long chargeId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
public void createDebitJournalEntryForLoanCharges(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long chargeId, final Long loanId, final String transactionId, final LocalDate transactionDate,
final BigDecimal amount) {
final GLAccount account = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
if (isReversal) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
public void createCreditJournalEntryOrReversalForLoanCharges(final Office office, final String currencyCode,
final int accountMappingTypeId, final Long loanProductId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
final List<ChargePaymentDTO> chargePaymentDTOs) {
/***
* Map to track each account and the net credit to be made for a particular account
***/
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
final Long chargeId = chargePaymentDTO.getChargeId();
final GLAccount chargeSpecificAccount = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount();
public void createCreditJournalEntryForLoanCharges(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long loanId, final String transactionId, final LocalDate transactionDate,
final BigDecimal totalAmount, final List<ChargePaymentDTO> chargePaymentDTOs) {
createJournalEntriesForLoanChargesInternal(office, currencyCode, accountMappingTypeId, loanProductId, loanId, transactionId,
transactionDate, totalAmount, chargePaymentDTOs, true);
}
// adjust net credit amount if the account is already present in the
// map
if (creditDetailsMap.containsKey(chargeSpecificAccount)) {
final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount);
chargeSpecificAmount = chargeSpecificAmount.add(existingAmount);
}
creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount);
}
BigDecimal totalCreditedAmount = BigDecimal.ZERO;
for (final Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
final GLAccount account = entry.getKey();
final BigDecimal amount = entry.getValue();
totalCreditedAmount = totalCreditedAmount.add(amount);
if (isReversal) {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
}
// TODO: Vishwas Temporary validation to be removed before moving to
// release branch
if (totalAmount.compareTo(totalCreditedAmount) != 0) {
throw new PlatformDataIntegrityException(
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
totalCreditedAmount, totalAmount);
}
public void createDebitJournalEntryForLoanCharges(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long loanId, final String transactionId, final LocalDate transactionDate,
final BigDecimal totalAmount, final List<ChargePaymentDTO> chargePaymentDTOs) {
createJournalEntriesForLoanChargesInternal(office, currencyCode, accountMappingTypeId, loanProductId, loanId, transactionId,
transactionDate, totalAmount, chargePaymentDTOs, false);
}
/**
@@ -899,38 +803,20 @@ public class AccountingProcessorHelper {
}
}
public LoanTransaction getLoanTransactionById(final Long loanTransactionId) {
return this.loanTransactionRepository.getReferenceById(loanTransactionId);
}
public SavingsAccountTransaction getSavingsTransactionById(final Long savingsTransactionId) {
return this.savingsAccountTransactionRepository.getReferenceById(savingsTransactionId);
}
public ClientTransaction getClientTransactionById(final Long clientTransactionId) {
return this.clientTransactionRepository.getReferenceById(clientTransactionId);
}
public Office getOfficeById(final long officeId) {
return this.officeRepository.getReferenceById(officeId);
}
public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
public void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId,
final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
final LocalDate transactionDate, final BigDecimal amount) {
final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId);
createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate, amount, isReversal,
account);
createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, amount, account);
}
public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal,
final GLAccount account) {
if (isReversal) {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
public void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final Long loanId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final GLAccount account) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
private void createCreditJournalEntryForClientPayments(final Office office, final String currencyCode, final GLAccount account,
@@ -1403,4 +1289,38 @@ public class AccountingProcessorHelper {
return savedJournalEntry;
}
private void createJournalEntriesForLoanChargesInternal(final Office office, final String currencyCode, final int accountMappingTypeId,
final Long loanProductId, final Long loanId, final String transactionId, final LocalDate transactionDate,
final BigDecimal totalAmount, final List<ChargePaymentDTO> chargePaymentDTOs, final boolean isCredit) {
final Map<GLAccount, BigDecimal> creditDetailsMap = new LinkedHashMap<>();
for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) {
final Long chargeId = chargePaymentDTO.getChargeId();
final GLAccount account = getLinkedGLAccountForLoanCharges(loanProductId, accountMappingTypeId, chargeId);
BigDecimal amount = chargePaymentDTO.getAmount();
creditDetailsMap.merge(account, amount, BigDecimal::add);
}
BigDecimal totalCreditedAmount = BigDecimal.ZERO;
for (Map.Entry<GLAccount, BigDecimal> entry : creditDetailsMap.entrySet()) {
GLAccount account = entry.getKey();
BigDecimal amount = entry.getValue();
totalCreditedAmount = totalCreditedAmount.add(amount);
if (isCredit) {
createCreditJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
} else {
createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount);
}
}
if (totalAmount.compareTo(totalCreditedAmount) != 0) {
throw new PlatformDataIntegrityException(
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
"Meltdown in advanced accounting...sum of all charges is not equal to the fee charge for a transaction",
totalCreditedAmount, totalAmount);
}
}
}
@@ -46,16 +46,24 @@ import org.springframework.stereotype.Component;
public class AccrualBasedAccountingProcessorForLoan implements AccountingProcessorForLoan {
private final AccountingProcessorHelper helper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@Override
public void createJournalEntriesForLoan(final LoanDTO loanDTO) {
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(loanDTO.getOfficeId());
final Office office = this.helper.getOfficeById(loanDTO.getOfficeId());
final Long officeId = loanDTO.getOfficeId();
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(officeId);
final Office office = this.helper.getOfficeById(officeId);
for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
final LoanTransactionEnumData transactionType = loanTransactionDTO.getTransactionType();
if (loanTransactionDTO.isReversed()) {
journalEntryWritePlatformService.createJournalEntryForReversedLoanTransaction(transactionDate,
loanTransactionDTO.getTransactionId(), officeId);
continue;
}
// Handle Disbursements
if (transactionType.isDisbursement()) {
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
@@ -67,8 +75,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
/*
* Handle repayments, loan refunds, repayments at disbursement and reversal of Repayments and Repayments at
* disbursement (except charge adjustment)
* Handle repayments, loan refunds, repayments at disbursement (except charge adjustment)
*/
else if ((transactionType.isRepaymentType() && !transactionType.isChargeAdjustment())
|| transactionType.isRepaymentAtDisbursement() || transactionType.isChargePayment()) {
@@ -91,7 +98,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
createJournalEntriesForCreditBalanceRefund(loanDTO, loanTransactionDTO, office);
}
// Handle Write Offs, waivers and their reversals
// Handle Write Offs
else if ((transactionType.isWriteOff() || transactionType.isWaiveInterest() || transactionType.isWaiveCharges())) {
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
}
@@ -135,7 +142,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPayment = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
if (isMarkedAsChargeOff) {
@@ -202,14 +208,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// create credit entries
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), isReversal, glAccount);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), glAccount);
}
// create debit entries
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), isReversal, glAccount);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, debitEntry.getValue(),
glAccount);
}
}
@@ -227,7 +233,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
// need to fetch if there are account mappings (always one)
@@ -272,14 +277,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// create credit entries
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), isReversal, glAccount);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), glAccount);
}
// create debit entries
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), isReversal, glAccount);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, debitEntry.getValue(),
glAccount);
}
}
@@ -318,7 +323,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
@@ -386,8 +390,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
entry.getKey());
}
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -398,8 +402,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
} else {
accountMappingTypeId = AccrualAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId, loanId,
transactionId, transactionDate, totalDebitAmount);
}
}
@@ -418,13 +422,12 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
// handle principal payment (and reversals)
// handle principal payment
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -432,7 +435,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
accountMap.put(account, principalAmount);
}
// handle interest payment (and reversals)
// handle interest payment
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -445,7 +448,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
// handle fees payment (and reversals)
// handle fees payment
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -458,7 +461,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
// handle penalties payment (and reversals)
// handle penalties payment
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -485,8 +488,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
entry.getKey());
}
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -497,8 +500,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
} else {
accountMappingTypeId = AccrualAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId, loanId,
transactionId, transactionDate, totalDebitAmount);
}
}
@@ -519,7 +522,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal amount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final BigDecimal overpaidAmount = Objects.isNull(loanTransactionDTO.getOverPayment()) ? BigDecimal.ZERO
: loanTransactionDTO.getOverPayment();
@@ -536,36 +538,36 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltyPaid = Objects.isNull(loanTransactionDTO.getPenaltyPaid()) ? BigDecimal.ZERO
: loanTransactionDTO.getPenaltyPaid();
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, amount, isReversal);
helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE, loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, amount);
if (overpaidAmount.compareTo(BigDecimal.ZERO) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaidAmount, isReversal);
helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overpaidAmount);
}
if (principalCredited.compareTo(principalPaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalCredited.subtract(principalPaid), isReversal);
helper.createDebitJournalEntryForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, principalCredited.subtract(principalPaid));
} else if (principalCredited.compareTo(principalPaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalPaid.subtract(principalCredited), isReversal);
helper.createCreditJournalEntryForLoan(office, currencyCode, getPrincipalAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, principalPaid.subtract(principalCredited));
}
if (feeCredited.compareTo(feePaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, feeCredited.subtract(feePaid), isReversal);
helper.createDebitJournalEntryForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, feeCredited.subtract(feePaid));
} else if (feeCredited.compareTo(feePaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, feePaid.subtract(feeCredited), isReversal);
helper.createCreditJournalEntryForLoan(office, currencyCode, getFeeAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, feePaid.subtract(feeCredited));
}
if (penaltyCredited.compareTo(penaltyPaid) > 0) {
helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, penaltyCredited.subtract(penaltyPaid), isReversal);
helper.createDebitJournalEntryForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, penaltyCredited.subtract(penaltyPaid));
} else if (penaltyCredited.compareTo(penaltyPaid) < 0) {
helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, penaltyPaid.subtract(penaltyCredited), isReversal);
helper.createCreditJournalEntryForLoan(office, currencyCode, getPenaltyAccount(loanDTO), loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, penaltyPaid.subtract(penaltyCredited));
}
}
@@ -616,29 +618,27 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal overpaymentPortion = loanTransactionDTO.getOverPayment() != null ? loanTransactionDTO.getOverPayment()
: BigDecimal.ZERO;
final BigDecimal principalPortion = loanTransactionDTO.getAmount().subtract(overpaymentPortion);
final boolean isReversed = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
// create journal entries for the disbursement (or disbursement
// reversal)
// create journal entries for the disbursement
if (MathUtil.isGreaterThanZero(principalPortion)) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversed);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion);
}
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion, isReversed);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion);
}
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversed);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversed);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
} else {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversed);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
}
}
@@ -667,7 +667,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
* <b>Penalty Write off</b>: Debits "Losses Written off" and Credits "Receivable Penalties" <br/>
* <br/>
* <br/>
* In case the loan transaction has been reversed, all debits are turned into credits and vice versa
*
* @param loanTransactionDTO
* @param loanDTO
@@ -701,7 +700,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
BigDecimal totalDebitAmount = new BigDecimal(0);
@@ -801,9 +799,9 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
} else {
if (isIncomeFromFee) {
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate,
feesAmount, isReversal, loanTransactionDTO.getFeePayments());
feesAmount, loanTransactionDTO.getFeePayments());
GLAccount debitAccount = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.FUND_SOURCE.getValue(), paymentTypeId);
glAccountBalanceHolder.addToDebit(debitAccount, feesAmount);
@@ -879,44 +877,41 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// create credit entries
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), isReversal, glAccount);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), glAccount);
}
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
if (writeOff) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else {
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, totalDebitAmount, isReversal);
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.createDebitJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), isReversal, glAccount);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), glAccount);
}
}
}
}
/**
* Charge Refunds (and their reversals) have an extra refund related pair of journal entries in addition to
* those related to the repayment above
* Charge Refunds have an extra refund related pair of journal entries in addition to those related to the
* repayment above
***/
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
if (loanTransactionDTO.getTransactionType().isChargeRefund()) {
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, incomeAccount,
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
totalDebitAmount, isReversal);
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
@@ -938,14 +933,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
Map<Integer, BigDecimal> debitAccountMapForGoodwillCredit = new LinkedHashMap<>();
// handle principal payment or writeOff (and reversals)
// handle principal payment or writeOff
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -957,7 +951,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
// handle interest payment of writeOff (and reversals)
// handle interest payment of writeOff
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -975,15 +969,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
// handle fees payment of writeOff (and reversals)
// handle fees payment of writeOff
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
if (isIncomeFromFee) {
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate,
feesAmount, isReversal, loanTransactionDTO.getFeePayments());
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(),
loanProductId, loanId, transactionId, transactionDate, feesAmount, loanTransactionDTO.getFeePayments());
} else {
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), paymentTypeId);
@@ -1000,7 +993,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
}
// handle penalties payment of writeOff (and reversals)
// handle penalties payment of writeOff
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
if (isIncomeFromFee) {
@@ -1047,53 +1040,48 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
entry.getKey());
}
/**
* Single DEBIT transaction for write-offs or Repayments (and their reversals)
* Single DEBIT transaction for write-offs or Repayments
***/
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
if (writeOff) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else {
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, totalDebitAmount, isReversal);
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.createDebitJournalEntryOrReversalForLoan(office, currencyCode, debitEntry.getKey().intValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue(),
isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, debitEntry.getKey().intValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue());
}
} else {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode,
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
}
}
/**
* Charge Refunds (and their reversals) have an extra refund related pair of journal entries in addition to
* those related to the repayment above
* Charge Refunds have an extra refund related pair of journal entries in addition to those related to the
* repayment above
***/
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0 && loanTransactionDTO.getTransactionType().isChargeRefund()) {
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, incomeAccount,
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
totalDebitAmount, isReversal);
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
@@ -1120,8 +1108,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
/**
* Create a single Debit to fund source and a single credit to "Income from Recovery"
*
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -1134,12 +1120,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal amount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
AccrualAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, amount, isReversal);
transactionDate, amount);
}
@@ -1151,8 +1136,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
*
* <b>Penalties:</b> Debit <i>Penalties Receivable</i> and credit <i>Income from Penalties</i>
*
* Also handles reversals for both fees and payment applications
*
* @param loanDTO
* @param loanTransactionDTO
* @param office
@@ -1170,28 +1153,43 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final boolean isReversed = transactionType.isAccrualAdjustment() != loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
// create journal entries for recognizing interest (or reversal)
// create journal entries for recognizing interest
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(),
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, interestAmount, isReversed);
if (transactionType.isAccrualAdjustment()) {
this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(),
AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, interestAmount);
} else {
this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue(),
AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, interestAmount);
}
}
// create journal entries for the fees application (or reversal)
// create journal entries for the fees application
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
this.helper.createJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId,
loanId, transactionId, transactionDate, feesAmount, isReversed, loanTransactionDTO.getFeePayments());
if (transactionType.isAccrualAdjustment()) {
this.helper.createJournalEntriesForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(),
AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(), loanProductId, loanId, transactionId, transactionDate,
feesAmount, loanTransactionDTO.getFeePayments());
} else {
this.helper.createJournalEntriesForLoanCharges(office, currencyCode, AccrualAccountsForLoan.FEES_RECEIVABLE.getValue(),
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate,
feesAmount, loanTransactionDTO.getFeePayments());
}
}
// create journal entries for the penalties application (or reversal)
// create journal entries for the penalties application
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
this.helper.createJournalEntriesAndReversalsForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(), AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, isReversed,
loanTransactionDTO.getPenaltyPayments());
if (transactionType.isAccrualAdjustment()) {
this.helper.createJournalEntriesForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(),
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, loanTransactionDTO.getPenaltyPayments());
} else {
this.helper.createJournalEntriesForLoanCharges(office, currencyCode, AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue(),
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
penaltiesAmount, loanTransactionDTO.getPenaltyPayments());
}
}
}
@@ -1205,17 +1203,16 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal refundAmount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
refundAmount, isReversal);
refundAmount);
} else {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
refundAmount, isReversal);
refundAmount);
}
}
@@ -1236,7 +1233,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
BigDecimal overpaymentAmount = loanTransactionDTO.getOverPayment();
@@ -1257,8 +1253,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
}
JournalAmountHolder totalAmountHolder = new JournalAmountHolder(AccrualAccountsForLoan.FUND_SOURCE.getValue(), totalAmount);
helper.createSplitJournalEntriesAndReversalsForLoan(office, currencyCode, journalAmountHolders, totalAmountHolder, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, isReversal);
helper.createSplitJournalEntriesForLoan(office, currencyCode, journalAmountHolders, totalAmountHolder, loanProductId, paymentTypeId,
loanId, transactionId, transactionDate);
}
@@ -1293,21 +1289,20 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
BigDecimal totalDebitAmount = new BigDecimal(0);
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO,
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount);
}
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_ON_LOANS,
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.INTEREST_ON_LOANS.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount);
}
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -1321,9 +1316,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
: chargePaymentDTO.getAmount(),
chargePaymentDTO.getLoanChargeId()));
}
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
!isReversal, chargePaymentDTOs);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_FEES.getValue(),
loanProductId, loanId, transactionId, transactionDate, feesAmount, chargePaymentDTOs);
}
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -1336,21 +1330,18 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
: chargePaymentDTO.getAmount(),
chargePaymentDTO.getLoanChargeId()));
}
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
penaltiesAmount, !isReversal, chargePaymentDTOs);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, AccrualAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, chargePaymentDTOs);
}
if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount);
}
/*** create a single debit entry (or reversal) for the entire amount **/
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
@@ -35,6 +35,7 @@ import org.apache.fineract.accounting.journalentry.data.LoanDTO;
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.springframework.stereotype.Component;
@Component
@@ -42,48 +43,53 @@ import org.springframework.stereotype.Component;
public class CashBasedAccountingProcessorForLoan implements AccountingProcessorForLoan {
private final AccountingProcessorHelper helper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
@Override
public void createJournalEntriesForLoan(final LoanDTO loanDTO) {
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(loanDTO.getOfficeId());
final Long officeId = loanDTO.getOfficeId();
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(officeId);
final Long loanProductId = loanDTO.getLoanProductId();
final String currencyCode = loanDTO.getCurrencyCode();
final Office office = this.helper.getOfficeById(officeId);
for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final String transactionId = loanTransactionDTO.getTransactionId();
final Office office = this.helper.getOfficeById(loanTransactionDTO.getOfficeId());
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final Long loanId = loanDTO.getLoanId();
final LoanTransactionEnumData transactionType = loanTransactionDTO.getTransactionType();
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
/** Handle Disbursements and reversals of disbursements **/
if (loanTransactionDTO.getTransactionType().isDisbursement()) {
if (loanTransactionDTO.isReversed()) {
journalEntryWritePlatformService.createJournalEntryForReversedLoanTransaction(transactionDate, transactionId, officeId);
continue;
}
/** Handle Disbursements **/
if (transactionType.isDisbursement()) {
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
}
/***
* Logic for repayments, repayments at disbursement and reversal of Repayments and Repayments at
* disbursement (except charge adjustment)
* Logic for repayments, repayments at disbursement (except charge adjustment)
***/
else if ((loanTransactionDTO.getTransactionType().isRepaymentType()
&& !loanTransactionDTO.getTransactionType().isChargeAdjustment())
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
else if ((transactionType.isRepaymentType() && !transactionType.isChargeAdjustment())
|| transactionType.isRepaymentAtDisbursement() || transactionType.isChargePayment()) {
createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office);
}
/** Logic for handling recovery payments **/
else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
else if (transactionType.isRecoveryRepayment()) {
createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
}
/** Logic for Refunds of Overpayments **/
else if (loanTransactionDTO.getTransactionType().isRefund()) {
else if (transactionType.isRefund()) {
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
}
/** Logic for Credit Balance Refunds **/
else if (loanTransactionDTO.getTransactionType().isCreditBalanceRefund()) {
else if (transactionType.isCreditBalanceRefund()) {
createJournalEntriesForCreditBalanceRefund(loanDTO, loanTransactionDTO, office);
}
@@ -91,33 +97,32 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
* Only principal write off affects cash based accounting (interest and fee write off need not be
* considered). Debit losses written off and credit Loan Portfolio
**/
else if (loanTransactionDTO.getTransactionType().isWriteOff()) {
else if (transactionType.isWriteOff()) {
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode,
CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalAmount, loanTransactionDTO.isReversed());
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(),
CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
transactionDate, principalAmount);
}
} else if (loanTransactionDTO.getTransactionType().isInitiateTransfer()
|| loanTransactionDTO.getTransactionType().isApproveTransfer()
|| loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
} else if (transactionType.isInitiateTransfer() || transactionType.isApproveTransfer()
|| transactionType.isWithdrawTransfer()) {
createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office);
}
/** Logic for Refunds of Active Loans **/
else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
else if (transactionType.isRefundForActiveLoans()) {
createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
}
// Logic for Chargebacks
else if (loanTransactionDTO.getTransactionType().isChargeback()) {
else if (transactionType.isChargeback()) {
createJournalEntriesForChargeback(loanDTO, loanTransactionDTO, office);
}
// Logic for Charge Adjustment
else if (loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
else if (transactionType.isChargeAdjustment()) {
createJournalEntriesForChargeAdjustment(loanDTO, loanTransactionDTO, office);
}
// Logic for Charge-Off
else if (loanTransactionDTO.getTransactionType().isChargeoff()) {
else if (transactionType.isChargeoff()) {
createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, office);
}
}
@@ -138,7 +143,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
@@ -174,14 +178,14 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
// create credit entries
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), isReversal, glAccount);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), glAccount);
}
// create debit entries
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), isReversal, glAccount);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, debitEntry.getValue(),
glAccount);
}
}
@@ -230,13 +234,12 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
// handle principal payment (and reversals)
// handle principal payment
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -244,7 +247,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
accountMap.put(account, principalAmount);
}
// handle interest payment (and reversals)
// handle interest payment
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -257,7 +260,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
}
// handle fees payment (and reversals)
// handle fees payment
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -297,8 +300,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
entry.getKey());
}
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -309,8 +312,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
} else {
accountMappingTypeId = CashAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId, loanId,
transactionId, transactionDate, totalDebitAmount);
}
}
@@ -329,13 +332,12 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
BigDecimal totalDebitAmount = new BigDecimal(0);
Map<GLAccount, BigDecimal> accountMap = new LinkedHashMap<>();
// handle principal payment (and reversals)
// handle principal payment
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
@@ -343,7 +345,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
accountMap.put(account, principalAmount);
}
// handle interest payment (and reversals)
// handle interest payment
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -356,7 +358,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
}
// handle fees payment (and reversals)
// handle fees payment
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
@@ -369,7 +371,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
}
// handle penalties payment (and reversals)
// handle penalties payment
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
@@ -396,8 +398,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
for (Map.Entry<GLAccount, BigDecimal> entry : accountMap.entrySet()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
entry.getValue(), isReversal, entry.getKey());
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate, entry.getValue(),
entry.getKey());
}
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -408,8 +410,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
} else {
accountMappingTypeId = CashAccountsForLoan.INCOME_FROM_FEES.getValue();
}
this.helper.createDebitJournalEntryOrReversalForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId,
loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, accountMappingTypeId, loanProductId, chargeId, loanId,
transactionId, transactionDate, totalDebitAmount);
}
}
@@ -430,20 +432,15 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal amount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, amount,
isReversal);
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, amount);
}
/**
* Debit loan Portfolio and credit Fund source for a Disbursement <br/>
*
* All debits are turned into credits and vice versa in case of disbursement reversals
*
*
* @param loanDTO
* @param loanTransactionDTO
* @param office
@@ -461,36 +458,32 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal overpaymentPortion = loanTransactionDTO.getOverPayment() != null ? loanTransactionDTO.getOverPayment()
: BigDecimal.ZERO;
final BigDecimal principalPortion = loanTransactionDTO.getAmount().subtract(overpaymentPortion);
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
if (MathUtil.isGreaterThanZero(principalPortion)) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalPortion);
}
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion);
}
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
} else {
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount(), isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, loanTransactionDTO.getAmount());
}
}
/**
* Debit loan Portfolio and credit Fund source for a Disbursement <br/>
*
* All debits are turned into credits and vice versa in case of disbursement reversals
*
*
* @param loanDTO
* @param loanTransactionDTO
* @param office
@@ -505,17 +498,16 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal refundAmount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
refundAmount, isReversal);
refundAmount);
} else {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
refundAmount, isReversal);
refundAmount);
}
}
@@ -523,8 +515,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
* Create a single Debit to fund source and multiple credits if applicable (loan portfolio for principal repayments,
* Interest on loans for interest repayments, Income from fees for fees payment and Income from penalties for
* penalty payment)
*
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -532,7 +522,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
if (isMarkedChargeOff) {
createJournalEntriesForChargeOffLoanRepayments(loanDTO, loanTransactionDTO, office);
} else {
createJournalEntriesForLoanRepayments(loanDTO, loanTransactionDTO, office);
}
@@ -555,7 +544,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
final boolean isReversal = loanTransactionDTO.isReversed();
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
BigDecimal totalDebitAmount = new BigDecimal(0);
@@ -707,36 +695,35 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
// create credit entries
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), isReversal, glAccount);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
creditEntry.getValue(), glAccount);
}
/*** create a single debit entry (or reversal) for the entire amount **/
/*** create a single debit entry for the entire amount **/
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
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.createDebitJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), isReversal, glAccount);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
debitEntry.getValue(), glAccount);
}
}
/**
* Charge Refunds (and their reversals) have an extra refund related pair of journal entries in addition to
* those related to the repayment above
* Charge Refunds have an extra refund related pair of journal entries in addition to those related to the
* repayment above
***/
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0) {
if (loanTransactionDTO.getTransactionType().isChargeRefund()) {
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, incomeAccount,
CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
totalDebitAmount, isReversal);
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, CashAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
}
@@ -755,7 +742,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
BigDecimal totalDebitAmount = new BigDecimal(0);
@@ -763,8 +749,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalAmount, isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalAmount);
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
populateDebitAccountEntry(loanProductId, principalAmount, CashAccountsForLoan.GOODWILL_CREDIT.getValue(),
debitAccountMapForGoodwillCredit, paymentTypeId);
@@ -773,8 +759,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.INTEREST_ON_LOANS,
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.INTEREST_ON_LOANS, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, interestAmount);
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
populateDebitAccountEntry(loanProductId, interestAmount,
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(), debitAccountMapForGoodwillCredit,
@@ -784,9 +770,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(feesAmount);
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
CashAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
isReversal, loanTransactionDTO.getFeePayments());
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode, CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
loanProductId, loanId, transactionId, transactionDate, feesAmount, loanTransactionDTO.getFeePayments());
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
populateDebitAccountEntry(loanProductId, feesAmount, CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(),
debitAccountMapForGoodwillCredit, paymentTypeId);
@@ -795,9 +780,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments());
this.helper.createCreditJournalEntryForLoanCharges(office, currencyCode, CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, loanTransactionDTO.getPenaltyPayments());
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
populateDebitAccountEntry(loanProductId, penaltiesAmount,
CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(), debitAccountMapForGoodwillCredit,
@@ -807,45 +791,44 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT, loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, overPaymentAmount);
if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) {
populateDebitAccountEntry(loanProductId, overPaymentAmount, CashAccountsForLoan.GOODWILL_CREDIT.getValue(),
debitAccountMapForGoodwillCredit, paymentTypeId);
}
}
/*** create a single debit entry (or reversal) for the entire amount **/
/*** create a single debit entry for the entire amount **/
if (loanTransactionDTO.isLoanToLoanTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, FinancialActivity.ASSET_TRANSFER.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
} else if (loanTransactionDTO.isAccountTransfer()) {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
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.createDebitJournalEntryOrReversalForLoan(office, currencyCode, debitEntry.getKey().intValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, debitEntry.getValue(), isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, debitEntry.getKey(), loanProductId, paymentTypeId,
loanId, transactionId, transactionDate, debitEntry.getValue());
}
} else {
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
/**
* Charge Refunds (and their reversals) have an extra refund related pair of journal entries in addition to
* those related to the repayment above
* Charge Refunds have an extra refund related pair of journal entries in addition to those related to the
* repayment above
***/
if (totalDebitAmount.compareTo(BigDecimal.ZERO) > 0 && loanTransactionDTO.getTransactionType().isChargeRefund()) {
Integer incomeAccount = this.helper.getValueForFeeOrPenaltyIncomeAccount(loanTransactionDTO.getChargeRefundChargeType());
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, incomeAccount,
CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
totalDebitAmount, isReversal);
this.helper.createJournalEntriesForLoan(office, currencyCode, incomeAccount, CashAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
@@ -863,8 +846,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
/**
* Create a single Debit to fund source and a single credit to "Income from Recovery"
*
* In case the loan transaction is a reversal, all debits are turned into credits and vice versa
*/
private void createJournalEntriesForRecoveryRepayments(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
@@ -877,12 +858,11 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal amount = loanTransactionDTO.getAmount();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(),
CashAccountsForLoan.INCOME_FROM_RECOVERY.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
amount, isReversal);
amount);
}
@@ -907,18 +887,16 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
final boolean isReversal = loanTransactionDTO.isReversed();
// final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
if (loanTransactionDTO.getTransactionType().isInitiateTransfer()) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.TRANSFERS_SUSPENSE.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.TRANSFERS_SUSPENSE.getValue(),
CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId, null, loanId, transactionId, transactionDate,
principalAmount, isReversal);
principalAmount);
} else if (loanTransactionDTO.getTransactionType().isApproveTransfer()
|| loanTransactionDTO.getTransactionType().isWithdrawTransfer()) {
this.helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
this.helper.createJournalEntriesForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
CashAccountsForLoan.TRANSFERS_SUSPENSE.getValue(), loanProductId, null, loanId, transactionId, transactionDate,
principalAmount, isReversal);
principalAmount);
}
}
@@ -932,7 +910,6 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
BigDecimal overpaymentAmount = loanTransactionDTO.getOverPayment();
@@ -951,8 +928,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
}
JournalAmountHolder totalAmountHolder = new JournalAmountHolder(CashAccountsForLoan.FUND_SOURCE.getValue(), totalAmount);
helper.createSplitJournalEntriesAndReversalsForLoan(office, currencyCode, journalAmountHolders, totalAmountHolder, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, isReversal);
helper.createSplitJournalEntriesForLoan(office, currencyCode, journalAmountHolders, totalAmountHolder, loanProductId, paymentTypeId,
loanId, transactionId, transactionDate);
}
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) {
@@ -969,21 +946,20 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
final BigDecimal feesAmount = loanTransactionDTO.getFees();
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
final BigDecimal overPaymentAmount = loanTransactionDTO.getOverPayment();
final boolean isReversal = loanTransactionDTO.isReversed();
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
BigDecimal totalDebitAmount = new BigDecimal(0);
if (principalAmount != null && principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(principalAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, principalAmount);
}
if (interestAmount != null && interestAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(interestAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.INTEREST_ON_LOANS,
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.INTEREST_ON_LOANS.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount);
}
if (feesAmount != null && feesAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -997,9 +973,8 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
: chargePaymentDTO.getAmount(),
chargePaymentDTO.getLoanChargeId()));
}
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
CashAccountsForLoan.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount,
!isReversal, chargePaymentDTOs);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, CashAccountsForLoan.INCOME_FROM_FEES.getValue(),
loanProductId, loanId, transactionId, transactionDate, feesAmount, chargePaymentDTOs);
}
if (penaltiesAmount != null && penaltiesAmount.compareTo(BigDecimal.ZERO) > 0) {
@@ -1013,20 +988,18 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
chargePaymentDTO.getLoanChargeId()));
}
this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode,
CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate,
penaltiesAmount, !isReversal, chargePaymentDTOs);
this.helper.createDebitJournalEntryForLoanCharges(office, currencyCode, CashAccountsForLoan.INCOME_FROM_PENALTIES.getValue(),
loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, chargePaymentDTOs);
}
if (overPaymentAmount != null && overPaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
totalDebitAmount = totalDebitAmount.add(overPaymentAmount);
this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT, loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, !isReversal);
this.helper.createDebitJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount);
}
/*** create a single debit entry (or reversal) for the entire amount **/
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(),
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal);
this.helper.createCreditJournalEntryForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(), loanProductId,
paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount);
}
}
@@ -39,6 +39,8 @@ public interface JournalEntryWritePlatformService {
CommandProcessingResult defineOpeningBalance(JsonCommand command);
void createJournalEntryForReversedLoanTransaction(LocalDate transactionDate, String loanTransactionId, Long officeId);
String revertProvisioningJournalEntries(LocalDate reversalTransactionDate, Long entityId, Integer entityType);
String createProvisioningJournalEntries(ProvisioningEntry entry);
@@ -298,6 +298,28 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build();
}
@Override
public void createJournalEntryForReversedLoanTransaction(final LocalDate transactionDate, final String loanTransactionId,
final Long officeId) {
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(officeId);
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
final String transactionId = AccountingProcessorHelper.LOAN_TRANSACTION_IDENTIFIER + loanTransactionId;
final List<JournalEntry> journalEntries = this.glJournalEntryRepository.findJournalEntries(transactionId,
PortfolioProductType.LOAN.getValue());
if (journalEntries == null || journalEntries.isEmpty()) {
return;
}
for (final JournalEntry journalEntry : journalEntries) {
final JournalEntry reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetail(),
journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), transactionId, Boolean.FALSE, transactionDate,
journalEntry.isDebitEntry() ? JournalEntryType.CREDIT : JournalEntryType.DEBIT, journalEntry.getAmount(),
journalEntry.getDescription(), journalEntry.getEntityType(), journalEntry.getEntityId(),
journalEntry.getReferenceNumber(), journalEntry.getLoanTransactionId(), journalEntry.getSavingsTransactionId(),
journalEntry.getClientTransactionId(), journalEntry.getShareTransactionId());
helper.persistJournalEntry(reversalJournalEntry);
}
}
public String revertJournalEntry(final List<JournalEntry> journalEntries, String reversalComment) {
final Long officeId = journalEntries.get(0).getOffice().getId();
final String reversalTransactionId = generateTransactionId(officeId);
@@ -99,12 +99,11 @@ class CreateJournalEntriesForChargeOffLoanTest {
verify(helper, times(1)).getChargeOffMappingByCodeValue(1L, PortfolioProductType.LOAN, chargeOffReasonId);
verify(helper, times(1)).getLinkedGLAccountForLoanProduct(1L, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(), 1L);
verify(helper, times(1)).createCreditJournalEntryOrReversalForLoan(helper.getOfficeById(1L), "USD",
AccrualAccountsForLoan.LOAN_PORTFOLIO, 1L, null, 1L, "txn-123", LocalDate.now(ZoneId.systemDefault()),
new BigDecimal("500.00"), false);
verify(helper, times(1)).createDebitJournalEntryOrReversalForLoan(helper.getOfficeById(1L), "USD",
verify(helper, times(1)).createCreditJournalEntryForLoan(helper.getOfficeById(1L), "USD", AccrualAccountsForLoan.LOAN_PORTFOLIO, 1L,
null, 1L, "txn-123", LocalDate.now(ZoneId.systemDefault()), new BigDecimal("500.00"));
verify(helper, times(1)).createDebitJournalEntryForLoan(helper.getOfficeById(1L), "USD",
AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue(), 1L, null, 1L, "txn-123", LocalDate.now(ZoneId.systemDefault()),
new BigDecimal("500.00"), false);
new BigDecimal("500.00"));
}
@Test