From 6f677a3dd118641ae65d1d93db0b330b6106ffa3 Mon Sep 17 00:00:00 2001 From: Ruchi Dhamankar Date: Thu, 8 Jun 2023 16:37:34 +0530 Subject: [PATCH] FINERACT-1806-Post-ChargeOff-Scenarios-1 --- .../portfolio/loanaccount/domain/Loan.java | 177 +++ .../domain/LoanAccountDomainServiceJpa.java | 24 +- ...WritePlatformServiceJpaRepositoryImpl.java | 21 +- .../ClientLoanIntegrationTest.java | 22 - .../LoanPostChargeOffScenariosTest.java | 1263 +++++++++++++++++ .../common/loans/LoanProductHelper.java | 32 + 6 files changed, 1498 insertions(+), 41 deletions(-) create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPostChargeOffScenariosTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index ee278d5c1..7c3361e14 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -59,7 +59,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer; @@ -4480,6 +4482,181 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return this.proposedPrincipal; } + public List> deriveAccountingBridgeDataForChargeOff(final String currencyCode, + final List existingTransactionIds, final List existingReversedTransactionIds, boolean isAccountTransfer) { + + final List> accountingBridgeData = new ArrayList<>(); + + // get map before charge-off + final List> newLoanTransactionsBeforeChargeOff = new ArrayList<>(); + final Map accountingBridgeDataBeforeChargeOff = getAccountingMapForChargeOffDateCriteria(currencyCode, + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, newLoanTransactionsBeforeChargeOff, true); + + // get map after charge-off + final List> newLoanTransactionsAfterChargeOff = new ArrayList<>(); + final Map accountingBridgeDataAfterChargeOff = getAccountingMapForChargeOffDateCriteria(currencyCode, + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, newLoanTransactionsAfterChargeOff, false); + + // get map onCharge off date + getAccountingMapDataOnChargeOffDate(currencyCode, existingTransactionIds, existingReversedTransactionIds, + newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff); + + accountingBridgeDataBeforeChargeOff.put("newLoanTransactions", newLoanTransactionsBeforeChargeOff); + accountingBridgeData.add(accountingBridgeDataBeforeChargeOff); + + accountingBridgeDataAfterChargeOff.put("newLoanTransactions", newLoanTransactionsAfterChargeOff); + accountingBridgeData.add(accountingBridgeDataAfterChargeOff); + + return accountingBridgeData; + } + + private Map getAccountingBridgeDataGenericAttributes(final String currencyCode, boolean isAccountTransfer) { + final Map accountingBridgeDataGenericAttributes = new LinkedHashMap<>(); + accountingBridgeDataGenericAttributes.put("loanId", getId()); + accountingBridgeDataGenericAttributes.put("loanProductId", productId()); + accountingBridgeDataGenericAttributes.put("officeId", getOfficeId()); + accountingBridgeDataGenericAttributes.put("currencyCode", currencyCode); + accountingBridgeDataGenericAttributes.put("calculatedInterest", this.summary.getTotalInterestCharged()); + accountingBridgeDataGenericAttributes.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnLoanProduct()); + accountingBridgeDataGenericAttributes.put("upfrontAccrualBasedAccountingEnabled", isUpfrontAccrualAccountingEnabledOnLoanProduct()); + accountingBridgeDataGenericAttributes.put("periodicAccrualBasedAccountingEnabled", + isPeriodicAccrualAccountingEnabledOnLoanProduct()); + accountingBridgeDataGenericAttributes.put("isAccountTransfer", isAccountTransfer); + return accountingBridgeDataGenericAttributes; + } + + private Map getAccountingMapForChargeOffDateCriteria(final String currencyCode, final List existingTransactionIds, + final List existingReversedTransactionIds, boolean isAccountTransfer, List> newLoanTransactions, + boolean isBeforeChargeOffDate) { + final Map accountingBridgeDataChargeOff = new LinkedHashMap<>( + getAccountingBridgeDataGenericAttributes(currencyCode, isAccountTransfer)); + if (isBeforeChargeOffDate) { + accountingBridgeDataChargeOff.put("isChargeOff", false); + accountingBridgeDataChargeOff.put("isFraud", false); + } else { + accountingBridgeDataChargeOff.put("isChargeOff", isChargedOff()); + accountingBridgeDataChargeOff.put("isFraud", isFraud()); + } + Predicate chargeOffDateCriteria = isBeforeChargeOffDate + ? transaction -> transaction.getTransactionDate().isBefore(getChargedOffOnDate()) + : transaction -> transaction.getTransactionDate().isAfter(getChargedOffOnDate()); + getTransactionsForAccountingBridgeData(currencyCode, existingTransactionIds, existingReversedTransactionIds, newLoanTransactions, + chargeOffDateCriteria); + return accountingBridgeDataChargeOff; + } + + private void getAccountingMapDataOnChargeOffDate(String currencyCode, List existingTransactionIds, + List existingReversedTransactionIds, List> newLoanTransactionsBeforeChargeOff, + List> newLoanTransactionsAfterChargeOff) { + Predicate isOnChargeOff = transaction -> transaction.getTransactionDate().isEqual(getChargedOffOnDate()); + List transactionsOnChargeOffDate = this.loanTransactions.stream().filter(isOnChargeOff) + .collect(Collectors.toList()); + /** + * + * TODO: Modify logic to retrieve correct charge-off transaction once reverse replay of charge-off is + * implemented + */ + LoanTransaction chargeOffTransaction = this.loanTransactions.stream().filter(LoanTransaction::isChargeOff).findFirst().get(); + for (final LoanTransaction transaction : transactionsOnChargeOffDate) { + checkAndAddReversedTransactionOnChargeOffDate(currencyCode, transaction, existingTransactionIds, existingReversedTransactionIds, + newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, chargeOffTransaction); + checkAndAddNewTransactionOnChargeOffDate(currencyCode, transaction, existingTransactionIds, newLoanTransactionsBeforeChargeOff, + newLoanTransactionsAfterChargeOff, chargeOffTransaction); + checkAndAddChargeOffTransaction(currencyCode, transaction, existingTransactionIds, newLoanTransactionsAfterChargeOff); + } + } + + private void checkAndAddReversedTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction, + List existingTransactionIds, List existingReversedTransactionIds, + List> newLoanTransactionsBeforeChargeOff, List> newLoanTransactionsAfterChargeOff, + LoanTransaction chargeOffTransaction) { + if (!transaction.isChargeOff()) { + if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId()) + && !existingReversedTransactionIds.contains(transaction.getId())) { + compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff, + newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId()); + + } + } + } + + private void checkAndAddReverseReplayedTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction, + List> newLoanTransactionsBeforeChargeOff, List> newLoanTransactionsAfterChargeOff, + LoanTransaction chargeOffTransaction) { + Predicate isReplayed = transactionRelation -> LoanTransactionRelationTypeEnum.REPLAYED + .equals(transactionRelation.getRelationType()); + List replayedTransactionRelations = transaction.getLoanTransactionRelations().stream().filter(isReplayed) + .sorted(Comparator.comparing(LoanTransactionRelation::getToTransaction, Comparator.comparingLong(LoanTransaction::getId))) + .toList(); + if (!replayedTransactionRelations.isEmpty()) { + // Transaction Id for first reversed transaction + Long transactionIdForReversedTransaction = replayedTransactionRelations.get(0).getToTransaction().getId(); + if (transactionIdForReversedTransaction > chargeOffTransaction.getId()) { + newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode)); + } else { + newLoanTransactionsBeforeChargeOff.add(transaction.toMapData(currencyCode)); + } + } else { + // new transaction + compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff, + newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId()); + } + } + + private void checkAndAddNewTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction, + List existingTransactionIds, List> newLoanTransactionsBeforeChargeOff, + List> newLoanTransactionsAfterChargeOff, LoanTransaction chargeOffTransaction) { + if (!transaction.isChargeOff()) { + if (!existingTransactionIds.contains(transaction.getId())) { + if (!transaction.getLoanTransactionRelations().isEmpty()) { + checkAndAddReverseReplayedTransactionOnChargeOffDate(currencyCode, transaction, newLoanTransactionsBeforeChargeOff, + newLoanTransactionsAfterChargeOff, chargeOffTransaction); + } else { + // new transaction + compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff, + newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId()); + } + } + } + } + + private void checkAndAddChargeOffTransaction(String currencyCode, LoanTransaction transaction, List existingTransactionIds, + List> newLoanTransactionsAfterChargeOff) { + if (transaction.isChargeOff()) { + /** + * + * TODO: Modify logic for reverse replay of charge-off + */ + if (!existingTransactionIds.contains(transaction.getId())) { + newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode)); + } + } + } + + private void compareWithChargeOffIdAndAddTransactionForAccountingData(final String currencyCode, + List> newLoanTransactionsBeforeChargeOff, List> newLoanTransactionsAfterChargeOff, + LoanTransaction transaction, Long chargeOffTransactionId) { + if (transaction.getId() > chargeOffTransactionId) { + newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode)); + } else { + newLoanTransactionsBeforeChargeOff.add(transaction.toMapData(currencyCode)); + } + } + + private void getTransactionsForAccountingBridgeData(final String currencyCode, final List existingTransactionIds, + final List existingReversedTransactionIds, final List> newLoanTransactions, + Predicate chargeOffDateCriteria) { + Consumer addTransactionForAccounting = transaction -> { + if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId()) + && !existingReversedTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(transaction.toMapData(currencyCode)); + } else if (!existingTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(transaction.toMapData(currencyCode)); + } + }; + this.loanTransactions.stream().filter(chargeOffDateCriteria).forEach(addTransactionForAccounting); + } + public Map deriveAccountingBridgeData(final String currencyCode, final List existingTransactionIds, final List existingReversedTransactionIds, boolean isAccountTransfer) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index b49497411..2270c22fb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -166,12 +166,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final boolean isRecoveryRepayment, final String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) { checkClientOrGroupActive(loan); - if (loan.isChargedOff() && transactionDate.isBefore(loan.getChargedOffOnDate())) { - throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: " - + loan.getId() - + " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan", - loan.getId()); - } + LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan); businessEventNotifierService.notifyPreBusinessEvent(repaymentEvent); @@ -427,10 +422,19 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final MonetaryCurrency currency = loanAccount.getCurrency(); - final Map accountingBridgeData = loanAccount.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); - accountingBridgeData.put("isLoanToLoanTransfer", isLoanToLoanTransfer); - this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + List> accountingBridgeData = new ArrayList<>(); + if (loanAccount.isChargedOff()) { + accountingBridgeData = loanAccount.deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, + existingReversedTransactionIds, isAccountTransfer); + } else { + accountingBridgeData.add(loanAccount.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, + existingReversedTransactionIds, isAccountTransfer)); + } + for (Map accountingData : accountingBridgeData) { + accountingData.put("isLoanToLoanTransfer", isLoanToLoanTransfer); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingData); + } + } private void checkClientOrGroupActive(final Loan loan) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 95d0a5f59..5f3da7fc1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1080,12 +1080,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed", "Loan transaction:" + transactionId + " update not allowed as loan status is written off", transactionId); } - if (loan.isChargedOff() && (transactionToAdjust.getTransactionDate().isBefore(loan.getChargedOffOnDate()) - || transactionToAdjust.getTransactionDate().isEqual(loan.getChargedOffOnDate()))) { - throw new GeneralPlatformDomainRuleException("error.msg.adjusted.transaction.date.cannot.be.earlier.than.charge.off.date", - "Loan transaction: " + transactionId + " adjustment is not allowed before or on the date when the loan got charged-off", - transactionId); - } if (transactionToAdjust.hasChargebackLoanTransactionRelations()) { throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed", @@ -1935,9 +1929,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final MonetaryCurrency currency = loan.getCurrency(); boolean isAccountTransfer = false; - final Map accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, - existingReversedTransactionIds, isAccountTransfer); - this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + List> accountingBridgeData = new ArrayList<>(); + if (loan.isChargedOff()) { + accountingBridgeData = loan.deriveAccountingBridgeDataForChargeOff(currency.getCode(), existingTransactionIds, + existingReversedTransactionIds, isAccountTransfer); + } else { + accountingBridgeData.add(loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, + existingReversedTransactionIds, isAccountTransfer)); + } + for (Map accountingData : accountingBridgeData) { + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingData); + } + } @Transactional diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 3a343d8d9..b4980aa8a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -7208,14 +7208,6 @@ public class ClientLoanIntegrationTest { assertEquals(403, exception.getResponse().code()); assertTrue(exception.getMessage().contains("error.msg.loan.charge.off.is.not.the.last.user.transaction")); - exception = assertThrows(CallFailedRuntimeException.class, () -> { - errorLoanTransactionHelper.adjustLoanTransaction((long) loanID, loanRepaymentResponse.getResourceId(), - new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("06 September 2022").locale("en") - .dateFormat("dd MMMM yyyy").transactionAmount(0.0)); - }); - assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("error.msg.adjusted.transaction.date.cannot.be.earlier.than.charge.off.date")); - exception = assertThrows(CallFailedRuntimeException.class, () -> { errorLoanTransactionHelper.makeWriteoff((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy") .transactionDate("05 September 2022").locale("en")); @@ -7262,20 +7254,6 @@ public class ClientLoanIntegrationTest { assertEquals(403, exception.getResponse().code()); assertTrue(exception.getMessage().contains("error.msg.loan.is.charged.off")); - exception = assertThrows(CallFailedRuntimeException.class, () -> { - errorLoanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest() - .dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0)); - }); - assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date")); - - exception = assertThrows(CallFailedRuntimeException.class, () -> { - errorLoanTransactionHelper.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest() - .dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0)); - }); - assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date")); - exception = assertThrows(CallFailedRuntimeException.class, () -> { errorLoanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest() .dateFormat("dd MMMM yyyy").transactionDate("05 September 2022").locale("en").transactionAmount(5.0)); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPostChargeOffScenariosTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPostChargeOffScenariosTest.java new file mode 100644 index 000000000..a759e3486 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPostChargeOffScenariosTest.java @@ -0,0 +1,1263 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.apache.fineract.client.models.AllowAttributeOverrides; +import org.apache.fineract.client.models.ChargeData; +import org.apache.fineract.client.models.ChargeToGLAccountMapper; +import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; +import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings; +import org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings; +import org.apache.fineract.client.models.GetLoanTransactionRelation; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; +import org.apache.fineract.client.models.JournalEntryTransactionItem; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.PaymentTypeHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.AccountHelper; +import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.funds.FundsHelper; +import org.apache.fineract.integrationtests.common.funds.FundsResourceHandler; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; +import org.apache.fineract.integrationtests.common.system.CodeHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class LoanPostChargeOffScenariosTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private ClientHelper clientHelper; + private LoanTransactionHelper loanTransactionHelper; + private JournalEntryHelper journalEntryHelper; + private AccountHelper accountHelper; + private LoanProductHelper loanProductHelper; + // asset + private Account loansReceivable; + private Account interestFeeReceivable; + private Account otherReceivables; + private Account uncReceivable; + private Account suspenseAccount; + private Account fundReceivables; + + // liability + private Account aaSuspenseBalance; + private Account suspenseClearingAccount; + private Account overpaymentAccount; + + // income + private Account deferredInterestRevenue; + private Account retainedEarningsPriorYear; + private Account interestIncome; + private Account feeIncome; + private Account feeChargeOff; + private Account recoveries; + private Account interestIncomeChargeOff; + + // expense + private Account creditLossBadDebt; + private Account creditLossBadDebtFraud; + private Account writtenOff; + private Account goodwillExpenseAccount; + + private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.loanProductHelper = new LoanProductHelper(); + + // Asset + this.loansReceivable = this.accountHelper.createAssetAccount(); + this.interestFeeReceivable = this.accountHelper.createAssetAccount(); + this.otherReceivables = this.accountHelper.createAssetAccount(); + this.uncReceivable = this.accountHelper.createAssetAccount(); + this.suspenseAccount = this.accountHelper.createAssetAccount(); + this.fundReceivables = this.accountHelper.createAssetAccount(); + + // Liability + this.aaSuspenseBalance = this.accountHelper.createLiabilityAccount(); + this.suspenseClearingAccount = this.accountHelper.createLiabilityAccount(); + this.overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + // income + this.deferredInterestRevenue = this.accountHelper.createIncomeAccount(); + this.retainedEarningsPriorYear = this.accountHelper.createIncomeAccount(); + this.interestIncome = this.accountHelper.createIncomeAccount(); + this.feeIncome = this.accountHelper.createIncomeAccount(); + this.feeChargeOff = this.accountHelper.createIncomeAccount(); + this.recoveries = this.accountHelper.createIncomeAccount(); + this.interestIncomeChargeOff = this.accountHelper.createIncomeAccount(); + + // expense + this.creditLossBadDebt = this.accountHelper.createExpenseAccount(); + this.creditLossBadDebtFraud = this.accountHelper.createExpenseAccount(); + this.writtenOff = this.accountHelper.createExpenseAccount(); + this.goodwillExpenseAccount = this.accountHelper.createExpenseAccount(); + + this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec); + this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + } + + @Test + public void postChargeOffAddBackdatedTransactionTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("14 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + List journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(4, journalEntries.size()); + + assertEquals(1000, journalEntries.get(3).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(3).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(3).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(3).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(2).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(1000, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(1).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(0).getTransactionDate()); + assertEquals(feeChargeOff.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment before chargeoff date + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // Goodwill Credit before chargeoff date + final PostLoansLoanIdTransactionsResponse goodwillCredit = loanTransactionHelper.makeGoodwillCredit((long) loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("10 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Goodwill Credit + GetJournalEntriesTransactionIdResponse journalEntriesForGoodWillCredit = journalEntryHelper + .getJournalEntries("L" + goodwillCredit.getResourceId().toString()); + assertNotNull(journalEntriesForGoodWillCredit); + + journalEntries = journalEntriesForGoodWillCredit.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(100, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 10), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 10), journalEntries.get(0).getTransactionDate()); + assertEquals(goodwillExpenseAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment after chargeoff date + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("15 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction_1.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(100, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 15), journalEntries.get(1).getTransactionDate()); + assertEquals(recoveries.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 15), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // Goodwill Credit after chargeoff date + final PostLoansLoanIdTransactionsResponse goodwillCredit_1 = loanTransactionHelper.makeGoodwillCredit((long) loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("16 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Goodwill Credit + journalEntriesForGoodWillCredit = journalEntryHelper.getJournalEntries("L" + goodwillCredit_1.getResourceId().toString()); + assertNotNull(journalEntriesForGoodWillCredit); + + journalEntries = journalEntriesForGoodWillCredit.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(100, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 16), journalEntries.get(1).getTransactionDate()); + assertEquals(recoveries.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 16), journalEntries.get(0).getTransactionDate()); + assertEquals(goodwillExpenseAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + } + + @Test + public void postChargeOffBackdatedTransactionReverseTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // make Repayment + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(100.0)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + List journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("14 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(910, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(910, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(0).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // reverse Repayment + loanTransactionHelper.reverseRepayment(loanId, repaymentTransaction.getResourceId().intValue(), "7 September 2022"); + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Reversed Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(6, journalEntries.size()); + + assertEquals(90, journalEntries.get(5).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(5).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(5).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(5).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(4).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(4).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(4).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(4).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(3).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(3).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(3).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(3).getEntryType().getValue()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(0).getEntryType().getValue()); + + } + + @Test + public void postChargeOffBackdatedTransactionReverseReplayTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // Set Loan transaction externalId for transaction getting reversed and replayed + String loanTransactionExternalIdStr = UUID.randomUUID().toString(); + + // make Repayment + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(11.0).externalId(loanTransactionExternalIdStr)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + List journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(1, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("14 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(999, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(999, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 14), journalEntries.get(0).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment backdated for reverse replay + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") + .transactionAmount(5.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction_1.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(5, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(5, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // check reverse replay + Long reversedAndReplayedTransactionId = repaymentTransaction.getResourceId(); + GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper + .getLoanTransactionDetails((long) loanId, loanTransactionExternalIdStr); + assertNotNull(getLoansTransactionResponse); + assertNotNull(getLoansTransactionResponse.getTransactionRelations()); + + // test replayed relationship + GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next(); + assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction()); + assertEquals("REPLAYED", transactionRelation.getRelationType()); + + // verify Journal Entries for new Transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + getLoansTransactionResponse.getId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(6, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(5, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + } + + @Test + public void transactionOnChargeOfDatePreChargeOffReverseReplayTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // Set Loan transaction externalId for transaction getting reversed and replayed + String loanTransactionExternalIdStr = UUID.randomUUID().toString(); + + // make Repayment on Chargeoff date before charge off + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(11.0).externalId(loanTransactionExternalIdStr)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + List journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(1, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("7 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(999, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(999, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment backdated for reverse replay + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") + .transactionAmount(5.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction_1.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(5, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(5, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // check reverse replay + Long reversedAndReplayedTransactionId = repaymentTransaction.getResourceId(); + GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper + .getLoanTransactionDetails((long) loanId, loanTransactionExternalIdStr); + assertNotNull(getLoansTransactionResponse); + assertNotNull(getLoansTransactionResponse.getTransactionRelations()); + + // test replayed relationship + GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next(); + assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction()); + assertEquals("REPLAYED", transactionRelation.getRelationType()); + + // verify Journal Entries for new Transaction + + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + getLoansTransactionResponse.getId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(6, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(5, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + } + + @Test + public void transactionOnChargeOfDatePostChargeOffReverseReplayTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("7 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + List journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(4, journalEntries.size()); + + assertEquals(1000, journalEntries.get(3).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(3).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(3).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(3).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(1000, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(feeChargeOff.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // Set Loan transaction externalId for transaction getting reversed and replayed + String loanTransactionExternalIdStr = UUID.randomUUID().toString(); + + // make Repayment after charge-off on charge off date + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(11.0).externalId(loanTransactionExternalIdStr)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(11, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(recoveries.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment backdated for reverse replay + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") + .transactionAmount(5.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction_1.getResourceId().toString()); + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(5, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(5, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 5), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // check reverse replay + Long reversedAndReplayedTransactionId = repaymentTransaction.getResourceId(); + GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper + .getLoanTransactionDetails((long) loanId, loanTransactionExternalIdStr); + assertNotNull(getLoansTransactionResponse); + assertNotNull(getLoansTransactionResponse.getTransactionRelations()); + + // test replayed relationship + GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next(); + assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction()); + assertEquals("REPLAYED", transactionRelation.getRelationType()); + + // verify Journal Entries for new Transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + getLoansTransactionResponse.getId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(11, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(recoveries.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(11, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + } + + @Test + public void transactionsOnChargeOffDatePreAndPostChargeOffDateTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // make Repayment before charge-off on charge off date + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(100.0)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + List journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("7 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(910, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(910, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // make Repayment after charge-off on charge off date + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(90.0)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction_1.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(90, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(recoveries.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(90, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + } + + @Test + public void transactionOnChargeOffDateReverseTest() { + String loanExternalIdStr = UUID.randomUUID().toString(); + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // make Repayment before charge-off on charge off date + final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en") + .transactionAmount(100.0)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + + // verify Journal Entries for Repayment transaction + GetJournalEntriesTransactionIdResponse journalEntriesForRepayment = journalEntryHelper + .getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForRepayment); + + List journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(3, journalEntries.size()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse chargeOffTransaction = loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("7 September 2022").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries For ChargeOff Transaction + GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper + .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString()); + + assertNotNull(journalEntriesForChargeOff); + journalEntries = journalEntriesForChargeOff.getPageItems(); + assertEquals(2, journalEntries.size()); + + assertEquals(910, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(910, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(creditLossBadDebt.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(0).getEntryType().getValue()); + + // reverse Repayment + loanTransactionHelper.reverseRepayment(loanId, repaymentTransaction.getResourceId().intValue(), "7 September 2022"); + loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // verify Journal Entries for Reversed Repayment transaction + journalEntriesForRepayment = journalEntryHelper.getJournalEntries("L" + repaymentTransaction.getResourceId().toString()); + assertNotNull(journalEntriesForRepayment); + + journalEntries = journalEntriesForRepayment.getPageItems(); + assertEquals(6, journalEntries.size()); + + assertEquals(90, journalEntries.get(5).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(5).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(5).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(5).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(4).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(4).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(4).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(4).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(3).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(3).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(3).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(3).getEntryType().getValue()); + + assertEquals(90, journalEntries.get(2).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(2).getTransactionDate()); + assertEquals(loansReceivable.getAccountID().longValue(), journalEntries.get(2).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(2).getEntryType().getValue()); + + assertEquals(10, journalEntries.get(1).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(1).getTransactionDate()); + assertEquals(interestFeeReceivable.getAccountID().longValue(), journalEntries.get(1).getGlAccountId().longValue()); + assertEquals("DEBIT", journalEntries.get(1).getEntryType().getValue()); + + assertEquals(100, journalEntries.get(0).getAmount()); + assertEquals(LocalDate.of(2022, 9, 7), journalEntries.get(0).getTransactionDate()); + assertEquals(suspenseClearingAccount.getAccountID().longValue(), journalEntries.get(0).getGlAccountId().longValue()); + assertEquals("CREDIT", journalEntries.get(0).getEntryType().getValue()); + + } + + private Integer createLoanAccount(final Integer clientID, final Integer loanProductID, final String externalId) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30") + .withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays() + .withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03 September 2022") + .withSubmittedOnDate("01 September 2022").withLoanType("individual").withExternalId(externalId) + .build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null); + loanTransactionHelper.disburseLoanWithTransactionAmount("03 September 2022", loanId, "1000"); + return loanId; + } + + private Integer createLoanProductWithPeriodicAccrualAccounting() { + + String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); + String shortName = Utils.uniqueRandomStringGenerator("", 4); + + List principalVariationsForBorrowerCycle = new ArrayList<>(); + List numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>(); + List interestRateVariationsForBorrowerCycle = new ArrayList<>(); + List charges = new ArrayList<>(); + List penaltyToIncomeAccountMappings = new ArrayList<>(); + List feeToIncomeAccountMappings = new ArrayList<>(); + + String paymentTypeName = PaymentTypeHelper.randomNameGenerator("P_T", 5); + String description = PaymentTypeHelper.randomNameGenerator("PT_Desc", 15); + Boolean isCashPayment = false; + Integer position = 1; + + Integer paymentTypeIdOne = PaymentTypeHelper.createPaymentType(requestSpec, responseSpec, paymentTypeName, description, + isCashPayment, position); + Assertions.assertNotNull(paymentTypeIdOne); + + List paymentChannelToFundSourceMappings = new ArrayList<>(); + GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings(); + loanPaymentChannelToFundSourceMappings.fundSourceAccountId(fundReceivables.getAccountID().longValue()); + loanPaymentChannelToFundSourceMappings.paymentTypeId(paymentTypeIdOne.longValue()); + paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings); + + // fund + FundsHelper fh = FundsHelper.create(Utils.uniqueRandomStringGenerator("", 10)).externalId(UUID.randomUUID().toString()).build(); + String jsonData = fh.toJSON(); + + final Long fundID = createFund(jsonData, this.requestSpec, this.responseSpec); + Assertions.assertNotNull(fundID); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + + PostLoanProductsRequest loanProductsRequest = new PostLoanProductsRequest().name(name)// + .shortName(shortName)// + .description("Loan Product Description")// + .fundId(fundID)// + .startDate(null)// + .closeDate(null)// + .includeInBorrowerCycle(false)// + .currencyCode("USD")// + .digitsAfterDecimal(2)// + .inMultiplesOf(0)// + .installmentAmountInMultiplesOf(1)// + .useBorrowerCycle(false)// + .minPrincipal(100.0)// + .principal(1000.0)// + .maxPrincipal(10000.0)// + .minNumberOfRepayments(1)// + .numberOfRepayments(1)// + .maxNumberOfRepayments(30)// + .isLinkedToFloatingInterestRates(false)// + .minInterestRatePerPeriod((double) 0)// + .interestRatePerPeriod((double) 0)// + .maxInterestRatePerPeriod((double) 0)// + .interestRateFrequencyType(2)// + .repaymentEvery(30)// + .repaymentFrequencyType(0)// + .principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)// + .numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)// + .interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)// + .amortizationType(1)// + .interestType(0)// + .isEqualAmortization(false)// + .interestCalculationPeriodType(1)// + .transactionProcessingStrategyCode("mifos-standard-strategy")// + .daysInYearType(1)// + .daysInMonthType(1)// + .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(3)// + .fundSourceAccountId(suspenseClearingAccount.getAccountID().longValue())// + .loanPortfolioAccountId(loansReceivable.getAccountID().longValue())// + .transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())// + .interestOnLoanAccountId(interestIncome.getAccountID().longValue())// + .incomeFromFeeAccountId(feeIncome.getAccountID().longValue())// + .incomeFromPenaltyAccountId(feeIncome.getAccountID().longValue())// + .incomeFromRecoveryAccountId(recoveries.getAccountID().longValue())// + .writeOffAccountId(writtenOff.getAccountID().longValue())// + .overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())// + .receivableInterestAccountId(interestFeeReceivable.getAccountID().longValue())// + .receivableFeeAccountId(interestFeeReceivable.getAccountID().longValue())// + .receivablePenaltyAccountId(interestFeeReceivable.getAccountID().longValue())// + .dateFormat("dd MMMM yyyy")// + .locale("en_GB")// + .disallowExpectedDisbursements(true)// + .allowApprovedDisbursedAmountsOverApplied(true)// + .overAppliedCalculationType("percentage")// + .overAppliedNumber(50)// + .delinquencyBucketId(delinquencyBucketId.longValue())// + .goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditInterestAccountId(interestIncomeChargeOff.getAccountID().longValue())// + .incomeFromGoodwillCreditFeesAccountId(feeChargeOff.getAccountID().longValue())// + .incomeFromGoodwillCreditPenaltyAccountId(feeChargeOff.getAccountID().longValue())// + .paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)// + .penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)// + .feeToIncomeAccountMappings(feeToIncomeAccountMappings)// + .incomeFromChargeOffInterestAccountId(interestIncomeChargeOff.getAccountID().longValue())// + .incomeFromChargeOffFeesAccountId(feeChargeOff.getAccountID().longValue())// + .chargeOffExpenseAccountId(creditLossBadDebt.getAccountID().longValue())// + .chargeOffFraudExpenseAccountId(creditLossBadDebtFraud.getAccountID().longValue())// + .incomeFromChargeOffPenaltyAccountId(feeChargeOff.getAccountID().longValue());// + + PostLoanProductsResponse loanProductCreateResponse = loanProductHelper.createLoanProduct(loanProductsRequest); + return loanProductCreateResponse.getResourceId().intValue(); + } + + private Long createFund(final String fundJSON, final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { + String fundId = String.valueOf(FundsResourceHandler.createFund(fundJSON, requestSpec, responseSpec)); + if (fundId.equals("null")) { + // Invalid JSON data parameters + return null; + } + + return Long.valueOf(fundId); + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java new file mode 100644 index 000000000..d3f0a1be3 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.loans; + +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.integrationtests.client.IntegrationTest; + +public class LoanProductHelper extends IntegrationTest { + + public LoanProductHelper() {} + + public PostLoanProductsResponse createLoanProduct(PostLoanProductsRequest request) { + return ok(fineract().loanProducts.createLoanProduct(request)); + } +}