You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2023/10/09 12:20:20 UTC
[fineract] branch develop updated: FINERACT-1968-Advanced-Payment-Allocation-Charge-Off
This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new d2fe39362 FINERACT-1968-Advanced-Payment-Allocation-Charge-Off
d2fe39362 is described below
commit d2fe39362cafcf9edf104d6a98ce3ba64c5877ea
Author: Ruchi Dhamankar <ru...@gmail.com>
AuthorDate: Thu Oct 5 18:18:13 2023 +0530
FINERACT-1968-Advanced-Payment-Allocation-Charge-Off
---
...dvancedPaymentScheduleTransactionProcessor.java | 21 +
...ChargeOffWithAdvancedPaymentAllocationTest.java | 759 +++++++++++++++++++++
2 files changed, 780 insertions(+)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 6a9c9d8a5..d51a8fb2f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -156,6 +156,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
WAIVE_INTEREST, RECOVERY_REPAYMENT ->
handleRepayment(loanTransaction, currency, installments, charges);
+ case CHARGE_OFF -> handleChargeOff(loanTransaction, currency, installments);
// TODO: Cover rest of the transaction types
default -> {
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
@@ -363,6 +364,26 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
}
+ private void handleChargeOff(LoanTransaction loanTransaction, MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments) {
+ loanTransaction.resetDerivedComponents();
+ // determine how much is outstanding total and breakdown for principal, interest and charges
+ Money principalPortion = Money.zero(currency);
+ Money interestPortion = Money.zero(currency);
+ Money feeChargesPortion = Money.zero(currency);
+ Money penaltychargesPortion = Money.zero(currency);
+ for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+ if (currentInstallment.isNotFullyPaidOff()) {
+ principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
+ interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
+ feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
+ penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
+ }
+ }
+
+ loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
+ }
+
@AllArgsConstructor
@Getter
@Setter
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
new file mode 100644
index 000000000..e6d32ab67
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
@@ -0,0 +1,759 @@
+/**
+ * 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.assertFalse;
+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.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+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.PaymentAllocationOrder;
+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.client.models.PostPaymentTypesRequest;
+import org.apache.fineract.client.models.PostPaymentTypesResponse;
+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.LoanTestLifecycleExtension;
+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.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(LoanTestLifecycleExtension.class)
+public class LoanAccountChargeOffWithAdvancedPaymentAllocationTest {
+
+ private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private ClientHelper clientHelper;
+ private LoanTransactionHelper loanTransactionHelper;
+ private JournalEntryHelper journalEntryHelper;
+ private AccountHelper accountHelper;
+ private LoanProductHelper loanProductHelper;
+ private PaymentTypeHelper paymentTypeHelper;
+ // asset
+ private Account loansReceivable;
+ private Account interestFeeReceivable;
+ private Account suspenseAccount;
+ private Account fundReceivables;
+ // liability
+ private Account suspenseClearingAccount;
+ private Account overpaymentAccount;
+ // income
+ 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;
+
+ @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();
+ this.paymentTypeHelper = new PaymentTypeHelper();
+
+ // Asset
+ this.loansReceivable = this.accountHelper.createAssetAccount();
+ this.interestFeeReceivable = this.accountHelper.createAssetAccount();
+ this.suspenseAccount = this.accountHelper.createAssetAccount();
+ this.fundReceivables = this.accountHelper.createAssetAccount();
+
+ // Liability
+ this.suspenseClearingAccount = this.accountHelper.createLiabilityAccount();
+ this.overpaymentAccount = this.accountHelper.createLiabilityAccount();
+
+ // income
+ 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);
+ }
+
+ // Charge-off accounting and balances
+ @Test
+ public void loanChargeOffWithAdvancedPaymentStrategyTest() {
+ String loanExternalIdStr = UUID.randomUUID().toString();
+ final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy();
+ 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 = DATE_FORMATTER.format(targetDate);
+ Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
+
+ // apply penalty
+ Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true));
+
+ final String penaltyCharge1AddedDate = DATE_FORMATTER.format(targetDate);
+
+ Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
+
+ // make Repayment
+ final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+ new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("9 September 2022").locale("en")
+ .transactionAmount(10.0));
+
+ GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+
+ // 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 = this.loanTransactionHelper.chargeOffLoan((long) loanId,
+ new PostLoansLoanIdTransactionsRequest().transactionDate("10 September 2022").locale("en").dateFormat("dd MMMM yyyy")
+ .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
+
+ loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertTrue(loanDetails.getChargedOff());
+
+ // verify amounts for charge-off transaction
+ verifyTransaction(LocalDate.of(2022, 9, 10), 1010.0f, 1000.0f, 0.0f, 10.0f, 0.0f, loanId, "chargeoff");
+ // verify journal entries
+ GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper
+ .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString());
+
+ assertNotNull(journalEntriesForChargeOff);
+
+ List<JournalEntryTransactionItem> journalEntries = journalEntriesForChargeOff.getPageItems();
+ assertEquals(4, journalEntries.size());
+ verifyJournalEntry(journalEntries.get(3), 1000.0, LocalDate.of(2022, 9, 10), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(2), 10.0, LocalDate.of(2022, 9, 10), interestFeeReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(1), 1000.0, LocalDate.of(2022, 9, 10), creditLossBadDebt, "DEBIT");
+ verifyJournalEntry(journalEntries.get(0), 10.0, LocalDate.of(2022, 9, 10), feeChargeOff, "DEBIT");
+
+ }
+
+ // Reverse Replay of Charge-Off
+ @Test
+ public void loanChargeOffReverseReplayWithAdvancedPaymentStrategyTest() {
+ String loanExternalIdStr = UUID.randomUUID().toString();
+ final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy();
+ 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 = DATE_FORMATTER.format(targetDate);
+ Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
+
+ // apply penalty
+ Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true));
+
+ final String penaltyCharge1AddedDate = DATE_FORMATTER.format(targetDate);
+
+ Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId,
+ LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
+
+ // make Repayment
+ final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+ new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("9 September 2022").locale("en")
+ .transactionAmount(10.0));
+
+ GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+
+ // 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 = this.loanTransactionHelper.chargeOffLoan((long) loanId,
+ new PostLoansLoanIdTransactionsRequest().transactionDate("10 September 2022").locale("en").dateFormat("dd MMMM yyyy")
+ .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
+
+ loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertTrue(loanDetails.getChargedOff());
+
+ // verify amounts for charge-off transaction
+ verifyTransaction(LocalDate.of(2022, 9, 10), 1010.0f, 1000.0f, 0.0f, 10.0f, 0.0f, loanId, "chargeoff");
+
+ Long reversedAndReplayedTransactionId = chargeOffTransaction.getResourceId();
+
+ // reverse Repayment
+ loanTransactionHelper.reverseRepayment(loanId, repaymentTransaction.getResourceId().intValue(), "11 September 2022");
+
+ // verify chargeOffTransaction gets reverse replayed
+
+ GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper
+ .getLoanTransactionDetails((long) loanId, transactionExternalId);
+ assertNotNull(getLoansTransactionResponse);
+ assertNotNull(getLoansTransactionResponse.getTransactionRelations());
+
+ // test replayed relationship
+ GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next();
+ assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction());
+ assertEquals("REPLAYED", transactionRelation.getRelationType());
+
+ // verify amounts for charge-off transaction
+ verifyTransaction(LocalDate.of(2022, 9, 10), 1020.0f, 1000.0f, 0.0f, 10.0f, 10.0f, loanId, "chargeoff");
+
+ }
+
+ // undo Charge-Off
+ @Test
+ public void loanUndoChargeOffTest() {
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy();
+ final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr);
+
+ // make Repayment
+ final PostLoansLoanIdTransactionsResponse repaymentTransaction = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+ new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("6 September 2022").locale("en")
+ .transactionAmount(100.0));
+
+ GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+
+ // 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();
+ 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());
+
+ // undo charge-off
+ String reverseTransactionExternalId = UUID.randomUUID().toString();
+ PostLoansLoanIdTransactionsResponse undoChargeOffTxResponse = loanTransactionHelper.undoChargeOffLoan((long) loanId,
+ new PostLoansLoanIdTransactionsRequest().reversalExternalId(reverseTransactionExternalId));
+ assertNotNull(undoChargeOffTxResponse);
+
+ loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertFalse(loanDetails.getChargedOff());
+
+ GetLoansLoanIdTransactionsTransactionIdResponse chargeOffTransactionDetails = loanTransactionHelper
+ .getLoanTransactionDetails((long) loanId, transactionExternalId);
+ assertNotNull(chargeOffTransactionDetails);
+ assertTrue(chargeOffTransactionDetails.getManuallyReversed());
+ assertEquals(reverseTransactionExternalId, chargeOffTransactionDetails.getReversalExternalId());
+ }
+
+ // Backdated repayment transaction, Reverse replay of charge off
+ @Test
+ public void postChargeOffAddBackdatedTransactionAndReverseReplayTest() {
+ String loanExternalIdStr = UUID.randomUUID().toString();
+ final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy();
+ 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 = DATE_FORMATTER.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());
+
+ Long reversedAndReplayedTransactionId = chargeOffTransaction.getResourceId();
+
+ // verify Journal Entries For ChargeOff Transaction
+ GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper
+ .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString());
+
+ assertNotNull(journalEntriesForChargeOff);
+ List<JournalEntryTransactionItem> journalEntries = journalEntriesForChargeOff.getPageItems();
+ assertEquals(4, journalEntries.size());
+
+ verifyJournalEntry(journalEntries.get(3), 1000.0, LocalDate.of(2022, 9, 14), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(2), 10.0, LocalDate.of(2022, 9, 14), interestFeeReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(1), 1000.0, LocalDate.of(2022, 9, 14), creditLossBadDebt, "DEBIT");
+ verifyJournalEntry(journalEntries.get(0), 10.0, LocalDate.of(2022, 9, 14), feeChargeOff, "DEBIT");
+
+ // 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());
+
+ verifyJournalEntry(journalEntries.get(2), 90.0, LocalDate.of(2022, 9, 7), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(1), 10.0, LocalDate.of(2022, 9, 7), interestFeeReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(0), 100.0, LocalDate.of(2022, 9, 7), suspenseClearingAccount, "DEBIT");
+
+ // verify reverse replay of Charge-Off
+
+ GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper
+ .getLoanTransactionDetails((long) loanId, transactionExternalId);
+ assertNotNull(getLoansTransactionResponse);
+ assertNotNull(getLoansTransactionResponse.getTransactionRelations());
+
+ // test replayed relationship
+ GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next();
+ assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction());
+ assertEquals("REPLAYED", transactionRelation.getRelationType());
+
+ // verify amounts for charge-off transaction
+ verifyTransaction(LocalDate.of(2022, 9, 14), 910.0f, 910.0f, 0.0f, 0.0f, 0.0f, loanId, "chargeoff");
+
+ // 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());
+
+ verifyJournalEntry(journalEntries.get(1), 100.0, LocalDate.of(2022, 9, 15), recoveries, "CREDIT");
+ verifyJournalEntry(journalEntries.get(0), 100.0, LocalDate.of(2022, 9, 15), suspenseClearingAccount, "DEBIT");
+ }
+
+ // Repayment before charge off on charge off date, reverse replay of charge off
+ @Test
+ public void transactionOnChargeOffDateReverseTest() {
+ String loanExternalIdStr = UUID.randomUUID().toString();
+ final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy();
+ 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 = DATE_FORMATTER.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<JournalEntryTransactionItem> journalEntries = journalEntriesForRepayment.getPageItems();
+ assertEquals(3, journalEntries.size());
+
+ verifyJournalEntry(journalEntries.get(2), 90.0, LocalDate.of(2022, 9, 7), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(1), 10.0, LocalDate.of(2022, 9, 7), interestFeeReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(0), 100.0, LocalDate.of(2022, 9, 7), suspenseClearingAccount, "DEBIT");
+
+ // 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());
+
+ Long reversedAndReplayedTransactionId = chargeOffTransaction.getResourceId();
+
+ // verify Journal Entries For ChargeOff Transaction
+ GetJournalEntriesTransactionIdResponse journalEntriesForChargeOff = journalEntryHelper
+ .getJournalEntries("L" + chargeOffTransaction.getResourceId().toString());
+
+ assertNotNull(journalEntriesForChargeOff);
+ journalEntries = journalEntriesForChargeOff.getPageItems();
+ assertEquals(2, journalEntries.size());
+
+ verifyJournalEntry(journalEntries.get(1), 910.0, LocalDate.of(2022, 9, 7), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(0), 910.0, LocalDate.of(2022, 9, 7), creditLossBadDebt, "DEBIT");
+
+ // 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());
+
+ verifyJournalEntry(journalEntries.get(5), 90.0, LocalDate.of(2022, 9, 7), loansReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(4), 10.0, LocalDate.of(2022, 9, 7), interestFeeReceivable, "CREDIT");
+ verifyJournalEntry(journalEntries.get(3), 100.0, LocalDate.of(2022, 9, 7), suspenseClearingAccount, "DEBIT");
+ verifyJournalEntry(journalEntries.get(2), 90.0, LocalDate.of(2022, 9, 7), loansReceivable, "DEBIT");
+ verifyJournalEntry(journalEntries.get(1), 10.0, LocalDate.of(2022, 9, 7), interestFeeReceivable, "DEBIT");
+ verifyJournalEntry(journalEntries.get(0), 100.0, LocalDate.of(2022, 9, 7), suspenseClearingAccount, "CREDIT");
+
+ // verify reverse replay of Charge-Off
+
+ GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper
+ .getLoanTransactionDetails((long) loanId, transactionExternalId);
+ assertNotNull(getLoansTransactionResponse);
+ assertNotNull(getLoansTransactionResponse.getTransactionRelations());
+
+ // test replayed relationship
+ GetLoanTransactionRelation transactionRelation = getLoansTransactionResponse.getTransactionRelations().iterator().next();
+ assertEquals(reversedAndReplayedTransactionId, transactionRelation.getToLoanTransaction());
+ assertEquals("REPLAYED", transactionRelation.getRelationType());
+
+ // verify amounts for charge-off transaction
+ verifyTransaction(LocalDate.of(2022, 9, 7), 1010.0f, 1000.0f, 0.0f, 10.0f, 0.0f, loanId, "chargeoff");
+
+ }
+
+ private void verifyJournalEntry(JournalEntryTransactionItem journalEntryTransactionItem, Double amount, LocalDate entryDate,
+ Account account, String type) {
+ assertEquals(amount, journalEntryTransactionItem.getAmount());
+ assertEquals(entryDate, journalEntryTransactionItem.getTransactionDate());
+ assertEquals(account.getAccountID().longValue(), journalEntryTransactionItem.getGlAccountId().longValue());
+ assertEquals(type, journalEntryTransactionItem.getEntryType().getValue());
+ }
+
+ private void verifyTransaction(final LocalDate transactionDate, final Float transactionAmount, final Float principalPortion,
+ final Float interestPortion, final Float feePortion, final Float penaltyPortion, final Integer loanID,
+ final String transactionOfType) {
+ ArrayList<HashMap> transactions = (ArrayList<HashMap>) loanTransactionHelper.getLoanTransactions(this.requestSpec,
+ this.responseSpec, loanID);
+ boolean isTransactionFound = false;
+ for (int i = 0; i < transactions.size(); i++) {
+ HashMap transactionType = (HashMap) transactions.get(i).get("type");
+ boolean isTransaction = (Boolean) transactionType.get(transactionOfType);
+
+ if (isTransaction) {
+ ArrayList<Integer> transactionDateAsArray = (ArrayList<Integer>) transactions.get(i).get("date");
+ LocalDate transactionEntryDate = LocalDate.of(transactionDateAsArray.get(0), transactionDateAsArray.get(1),
+ transactionDateAsArray.get(2));
+
+ if (transactionDate.isEqual(transactionEntryDate)) {
+ isTransactionFound = true;
+ assertEquals(transactionAmount, Float.valueOf(String.valueOf(transactions.get(i).get("amount"))),
+ "Mismatch in transaction amounts");
+ assertEquals(principalPortion, Float.valueOf(String.valueOf(transactions.get(i).get("principalPortion"))),
+ "Mismatch in transaction amounts");
+ assertEquals(interestPortion, Float.valueOf(String.valueOf(transactions.get(i).get("interestPortion"))),
+ "Mismatch in transaction amounts");
+ assertEquals(feePortion, Float.valueOf(String.valueOf(transactions.get(i).get("feeChargesPortion"))),
+ "Mismatch in transaction amounts");
+ assertEquals(penaltyPortion, Float.valueOf(String.valueOf(transactions.get(i).get("penaltyChargesPortion"))),
+ "Mismatch in transaction amounts");
+ break;
+ }
+ }
+ }
+ assertTrue(isTransactionFound, "No Transaction entries are posted");
+ }
+
+ 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)
+ .withRepaymentStrategy("advanced-payment-allocation-strategy").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 createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy() {
+
+ String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
+ String shortName = Utils.uniqueRandomStringGenerator("", 4);
+
+ List<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
+ List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
+ List<Integer> interestRateVariationsForBorrowerCycle = new ArrayList<>();
+ List<ChargeData> charges = new ArrayList<>();
+ List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>();
+ List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = new ArrayList<>();
+
+ String paymentTypeName = PaymentTypeHelper.randomNameGenerator("P_T", 5);
+ String description = PaymentTypeHelper.randomNameGenerator("PT_Desc", 15);
+ Boolean isCashPayment = false;
+ Integer position = 1;
+
+ PostPaymentTypesResponse paymentTypesResponse = paymentTypeHelper.createPaymentType(new PostPaymentTypesRequest()
+ .name(paymentTypeName).description(description).isCashPayment(isCashPayment).position(position));
+ Long paymentTypeIdOne = paymentTypesResponse.getResourceId();
+ Assertions.assertNotNull(paymentTypeIdOne);
+
+ List<GetLoanPaymentChannelToFundSourceMappings> 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);
+
+ String futureInstallmentAllocationRule = "NEXT_INSTALLMENT";
+ AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule);
+
+ 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("advanced-payment-allocation-strategy")//
+ .addPaymentAllocationItem(defaultAllocation)//
+ .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);
+ }
+
+ private AdvancedPaymentData createDefaultPaymentAllocation(String futureInstallmentAllocationRule) {
+ AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+ advancedPaymentData.setTransactionType("DEFAULT");
+ advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
+
+ List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+ PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
+ PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+ PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+ PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+ advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+ return advancedPaymentData;
+ }
+
+ private List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
+ AtomicInteger integer = new AtomicInteger(1);
+ return Arrays.stream(paymentAllocationTypes).map(pat -> {
+ PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder();
+ paymentAllocationOrder.setPaymentAllocationRule(pat.name());
+ paymentAllocationOrder.setOrder(integer.getAndIncrement());
+ return paymentAllocationOrder;
+ }).toList();
+ }
+}