| /** |
| * 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 java.lang.Boolean.FALSE; |
| import static java.lang.Boolean.TRUE; |
| import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE; |
| import static org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| 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.internal.RequestSpecificationImpl; |
| import io.restassured.specification.RequestSpecification; |
| import io.restassured.specification.ResponseSpecification; |
| import java.math.BigDecimal; |
| import java.time.LocalDate; |
| import java.time.format.DateTimeFormatter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.UUID; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.Consumer; |
| import lombok.AllArgsConstructor; |
| import lombok.RequiredArgsConstructor; |
| import lombok.ToString; |
| import org.apache.fineract.batch.domain.BatchRequest; |
| import org.apache.fineract.batch.domain.BatchResponse; |
| import org.apache.fineract.client.models.AdvancedPaymentData; |
| import org.apache.fineract.client.models.AllowAttributeOverrides; |
| import org.apache.fineract.client.models.BusinessDateRequest; |
| import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; |
| import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; |
| import org.apache.fineract.client.models.GetLoansLoanIdResponse; |
| import org.apache.fineract.client.models.GetLoansLoanIdTransactions; |
| import org.apache.fineract.client.models.PaymentAllocationOrder; |
| import org.apache.fineract.client.models.PostChargesResponse; |
| import org.apache.fineract.client.models.PostLoanProductsRequest; |
| import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse; |
| import org.apache.fineract.client.models.PostLoansLoanIdRequest; |
| import org.apache.fineract.client.models.PostLoansLoanIdResponse; |
| import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; |
| import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; |
| import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; |
| import org.apache.fineract.client.models.PostLoansRequest; |
| import org.apache.fineract.client.models.PostLoansResponse; |
| import org.apache.fineract.client.models.PutLoansLoanIdResponse; |
| import org.apache.fineract.client.util.CallFailedRuntimeException; |
| import org.apache.fineract.integrationtests.common.BatchHelper; |
| import org.apache.fineract.integrationtests.common.BusinessDateHelper; |
| import org.apache.fineract.integrationtests.common.ClientHelper; |
| import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; |
| import org.apache.fineract.integrationtests.common.SchedulerJobHelper; |
| 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.error.ErrorResponse; |
| import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper; |
| import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; |
| import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; |
| import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; |
| import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; |
| import org.apache.fineract.integrationtests.common.system.CodeHelper; |
| import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper; |
| import org.apache.fineract.integrationtests.useradministration.users.UserHelper; |
| import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; |
| import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; |
| import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; |
| import org.hamcrest.Matcher; |
| import org.hamcrest.Matchers; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.extension.ExtendWith; |
| |
| @ExtendWith(LoanTestLifecycleExtension.class) |
| public abstract class BaseLoanIntegrationTest { |
| |
| static { |
| Utils.initializeRESTAssured(); |
| } |
| |
| protected static final String DATETIME_PATTERN = "dd MMMM yyyy"; |
| |
| protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200)); |
| |
| private final String fullAdminAuthKey = getFullAdminAuthKey(); |
| |
| protected final RequestSpecification requestSpec = createRequestSpecification(fullAdminAuthKey); |
| private final String nonByPassUserAuthKey = getNonByPassUserAuthKey(requestSpec, responseSpec); |
| |
| protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec); |
| protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); |
| protected final LoanProductHelper loanProductHelper = new LoanProductHelper(); |
| protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); |
| protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec); |
| protected SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec); |
| protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); |
| |
| protected BusinessDateHelper businessDateHelper = new BusinessDateHelper(); |
| |
| protected final LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(requestSpec, |
| createResponseSpecification(Matchers.is(202))); |
| protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); |
| |
| // asset |
| protected final Account loansReceivableAccount = accountHelper.createAssetAccount("loanPortfolio"); |
| |
| protected final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivable"); |
| protected final Account feeReceivableAccount = accountHelper.createAssetAccount("feeReceivable"); |
| protected final Account penaltyReceivableAccount = accountHelper.createAssetAccount("penaltyReceivable"); |
| protected final Account suspenseAccount = accountHelper.createAssetAccount("suspense"); |
| // liability |
| protected final Account fundSource = accountHelper.createLiabilityAccount("fundSource"); |
| protected final Account overpaymentAccount = accountHelper.createLiabilityAccount("overpayment"); |
| // income |
| protected final Account interestIncomeAccount = accountHelper.createIncomeAccount("interestIncome"); |
| protected final Account feeIncomeAccount = accountHelper.createIncomeAccount("feeIncome"); |
| protected final Account penaltyIncomeAccount = accountHelper.createIncomeAccount("penaltyIncome"); |
| protected final Account feeChargeOffAccount = accountHelper.createIncomeAccount("feeChargeOff"); |
| protected final Account penaltyChargeOffAccount = accountHelper.createIncomeAccount("penaltyChargeOff"); |
| |
| protected final Account recoveriesAccount = accountHelper.createIncomeAccount("recoveries"); |
| protected final Account interestIncomeChargeOffAccount = accountHelper.createIncomeAccount("interestIncomeChargeOff"); |
| // expense |
| protected final Account chargeOffExpenseAccount = accountHelper.createExpenseAccount("chargeOff"); |
| protected final Account chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud"); |
| protected final Account writtenOffAccount = accountHelper.createExpenseAccount(); |
| protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount(); |
| |
| private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) { |
| // creates the user |
| UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec); |
| return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(UserHelper.SIMPLE_USER_NAME, UserHelper.SIMPLE_USER_PASSWORD); |
| } |
| |
| private String getFullAdminAuthKey() { |
| return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(); |
| } |
| |
| // Loan product with proper accounting setup |
| protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() { |
| return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))// |
| .shortName(Utils.uniqueRandomStringGenerator("", 4))// |
| .description("Loan Product Description")// |
| .includeInBorrowerCycle(false)// |
| .currencyCode("USD")// |
| .digitsAfterDecimal(2)// |
| .inMultiplesOf(0)// |
| .installmentAmountInMultiplesOf(1)// |
| .useBorrowerCycle(false)// |
| .minPrincipal(100.0)// |
| .principal(1000.0)// |
| .maxPrincipal(100000.0)// |
| .minNumberOfRepayments(1)// |
| .numberOfRepayments(1)// |
| .maxNumberOfRepayments(30)// |
| .isLinkedToFloatingInterestRates(false)// |
| .minInterestRatePerPeriod((double) 0)// |
| .interestRatePerPeriod((double) 0)// |
| .maxInterestRatePerPeriod((double) 100)// |
| .interestRateFrequencyType(2)// |
| .repaymentEvery(30)// |
| .repaymentFrequencyType(0L)// |
| .amortizationType(1)// |
| .interestType(0)// |
| .isEqualAmortization(false)// |
| .interestCalculationPeriodType(1)// |
| .transactionProcessingStrategyCode( |
| LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY)// |
| .loanScheduleType(LoanScheduleType.CUMULATIVE.toString()) // |
| .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(Collections.emptyList())// |
| .accountingRule(3)// |
| .fundSourceAccountId(fundSource.getAccountID().longValue())// |
| .loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())// |
| .transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())// |
| .interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())// |
| .incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())// |
| .incomeFromPenaltyAccountId(penaltyIncomeAccount.getAccountID().longValue())// |
| .incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())// |
| .writeOffAccountId(writtenOffAccount.getAccountID().longValue())// |
| .overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())// |
| .receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())// |
| .receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())// |
| .receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())// |
| .goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())// |
| .incomeFromGoodwillCreditInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())// |
| .incomeFromGoodwillCreditFeesAccountId(feeChargeOffAccount.getAccountID().longValue())// |
| .incomeFromGoodwillCreditPenaltyAccountId(feeChargeOffAccount.getAccountID().longValue())// |
| .incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())// |
| .incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())// |
| .incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())// |
| .chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())// |
| .chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())// |
| .dateFormat(DATETIME_PATTERN)// |
| .locale("en_GB")// |
| .disallowExpectedDisbursements(true)// |
| .allowApprovedDisbursedAmountsOverApplied(true)// |
| .overAppliedCalculationType("percentage")// |
| .overAppliedNumber(50); |
| } |
| |
| protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() { |
| String futureInstallmentAllocationRule = "NEXT_INSTALLMENT"; |
| AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation(futureInstallmentAllocationRule); |
| |
| return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() // |
| .transactionProcessingStrategyCode("advanced-payment-allocation-strategy")// |
| .loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) // |
| .loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()) // |
| .addPaymentAllocationItem(defaultAllocation); |
| } |
| |
| 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(); |
| } |
| |
| 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; |
| } |
| |
| protected PostLoanProductsRequest create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct( |
| int interestType, int amortizationType) { |
| return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)// |
| .disallowExpectedDisbursements(false)// |
| .allowApprovedDisbursedAmountsOverApplied(false)// |
| .overAppliedCalculationType(null)// |
| .overAppliedNumber(null)// |
| .principal(1250.0)// |
| .numberOfRepayments(4)// |
| .repaymentEvery(1)// |
| .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// |
| .interestType(interestType)// |
| .amortizationType(amortizationType); |
| } |
| |
| private RequestSpecification createRequestSpecification(String authKey) { |
| RequestSpecification requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); |
| requestSpec.header("Authorization", "Basic " + authKey); |
| requestSpec.header("Fineract-Platform-TenantId", "default"); |
| return requestSpec; |
| } |
| |
| protected ResponseSpecification createResponseSpecification(Matcher<Integer> statusCodeMatcher) { |
| return new ResponseSpecBuilder().expectStatusCode(statusCodeMatcher).build(); |
| } |
| |
| protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) { |
| ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); |
| LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse); |
| CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> { |
| validationErrorHelper.undoLastDisbursalLoan(loanId, new PostLoansLoanIdRequest()); |
| }); |
| assertTrue(exception.getMessage().contains(expectedError)); |
| } |
| |
| protected void verifyNoTransactions(Long loanId) { |
| verifyTransactions(loanId, (Transaction[]) null); |
| } |
| |
| protected void verifyTransactions(Long loanId, Transaction... transactions) { |
| GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); |
| if (transactions == null || transactions.length == 0) { |
| assertNull(loanDetails.getTransactions(), "No transaction is expected"); |
| } else { |
| Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size()); |
| Arrays.stream(transactions).forEach(tr -> { |
| Optional<GetLoansLoanIdTransactions> optTx = loanDetails.getTransactions().stream() |
| .filter(item -> Objects.equals(item.getAmount(), tr.amount) // |
| && Objects.equals(item.getType().getValue(), tr.type) // |
| && Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter))) |
| .findFirst(); |
| Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr); |
| |
| GetLoansLoanIdTransactions tx = optTx.get(); |
| |
| if (tr.reversed != null) { |
| Assertions.assertEquals(tr.reversed, tx.getManuallyReversed(), "Transaction is not reversed: " + tr); |
| } |
| }); |
| } |
| } |
| |
| protected void verifyTransactions(Long loanId, TransactionExt... transactions) { |
| GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); |
| if (transactions == null || transactions.length == 0) { |
| assertNull(loanDetails.getTransactions(), "No transaction is expected"); |
| } else { |
| Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size()); |
| Arrays.stream(transactions).forEach(tr -> { |
| boolean found = loanDetails.getTransactions().stream().anyMatch(item -> Objects.equals(item.getAmount(), tr.amount) // |
| && Objects.equals(item.getType().getValue(), tr.type) // |
| && Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) // |
| && Objects.equals(item.getOutstandingLoanBalance(), tr.outstandingPrincipal) // |
| && Objects.equals(item.getPrincipalPortion(), tr.principalPortion) // |
| && Objects.equals(item.getInterestPortion(), tr.interestPortion) // |
| && Objects.equals(item.getFeeChargesPortion(), tr.feePortion) // |
| && Objects.equals(item.getPenaltyChargesPortion(), tr.penaltyPortion) // |
| && Objects.equals(item.getOverpaymentPortion(), tr.overpaymentPortion) // |
| && Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) // |
| ); |
| Assertions.assertTrue(found, "Required transaction not found: " + tr); |
| }); |
| } |
| } |
| |
| protected void placeHardLockOnLoan(Long loanId) { |
| loanAccountLockHelper.placeSoftLockOnLoanAccount(loanId.intValue(), "LOAN_COB_CHUNK_PROCESSING"); |
| } |
| |
| protected void executeInlineCOB(Long loanId) { |
| inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); |
| } |
| |
| protected void reAgeLoan(Long loanId, String frequency, String startDate, Integer numberOfInstallments) { |
| PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest(); |
| request.setDateFormat(DATETIME_PATTERN); |
| request.setLocale("en"); |
| request.setFrequency(frequency); |
| request.setStartDate(startDate); |
| request.setNumberOfInstallments(numberOfInstallments); |
| loanTransactionHelper.reAge(loanId, request); |
| } |
| |
| protected void reAmortizeLoan(Long loanId) { |
| PostLoansLoanIdTransactionsRequest request = new PostLoansLoanIdTransactionsRequest(); |
| request.setDateFormat(DATETIME_PATTERN); |
| request.setLocale("en"); |
| loanTransactionHelper.reAmortize(loanId, request); |
| } |
| |
| protected void undoReAgeLoan(Long loanId) { |
| loanTransactionHelper.undoReAge(loanId, new PostLoansLoanIdTransactionsRequest()); |
| } |
| |
| protected void undoReAmortizeLoan(Long loanId) { |
| loanTransactionHelper.undoReAmortize(loanId, new PostLoansLoanIdTransactionsRequest()); |
| } |
| |
| protected void verifyLastClosedBusinessDate(Long loanId, String lastClosedBusinessDate) { |
| GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); |
| assertNotNull(loanDetails.getLastClosedBusinessDate()); |
| Assertions.assertEquals(lastClosedBusinessDate, loanDetails.getLastClosedBusinessDate().format(dateTimeFormatter)); |
| } |
| |
| protected void disburseLoan(Long loanId, BigDecimal amount, String date) { |
| loanTransactionHelper.disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate(date).dateFormat(DATETIME_PATTERN) |
| .transactionAmount(amount).locale("en")); |
| } |
| |
| protected void undoDisbursement(Integer loanId) { |
| loanTransactionHelper.undoDisbursal(loanId); |
| } |
| |
| protected void verifyJournalEntries(Long loanId, Journal... entries) { |
| GetJournalEntriesTransactionIdResponse journalEntriesForLoan = journalEntryHelper.getJournalEntriesForLoan(loanId); |
| Assertions.assertEquals(entries.length, journalEntriesForLoan.getPageItems().size()); |
| Arrays.stream(entries).forEach(journalEntry -> { |
| boolean found = journalEntriesForLoan.getPageItems().stream() |
| .anyMatch(item -> Objects.equals(item.getAmount(), journalEntry.amount) |
| && Objects.equals(item.getGlAccountId(), journalEntry.account.getAccountID().longValue()) |
| && Objects.requireNonNull(item.getEntryType()).getValue().equals(journalEntry.type)); |
| Assertions.assertTrue(found, "Required journal entry not found: " + journalEntry); |
| }); |
| } |
| |
| protected void verifyTRJournalEntries(Long transactionId, Journal... entries) { |
| GetJournalEntriesTransactionIdResponse journalEntriesForLoan = journalEntryHelper.getJournalEntries("L" + transactionId.toString()); |
| Assertions.assertEquals(entries.length, journalEntriesForLoan.getPageItems().size()); |
| Arrays.stream(entries).forEach(journalEntry -> { |
| boolean found = journalEntriesForLoan.getPageItems().stream() |
| .anyMatch(item -> Objects.equals(item.getAmount(), journalEntry.amount) |
| && Objects.equals(item.getGlAccountId(), journalEntry.account.getAccountID().longValue()) |
| && Objects.requireNonNull(item.getEntryType()).getValue().equals(journalEntry.type)); |
| Assertions.assertTrue(found, "Required journal entry not found: " + journalEntry); |
| }); |
| } |
| |
| protected Long addCharge(Long loanId, boolean isPenalty, double amount, String dueDate) { |
| Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, |
| ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(amount), isPenalty)); |
| assertNotNull(chargeId); |
| Integer loanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId.intValue(), |
| LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(chargeId), dueDate, String.valueOf(amount))); |
| assertNotNull(loanChargeId); |
| return loanChargeId.longValue(); |
| } |
| |
| protected void verifyRepaymentSchedule(Long loanId, Installment... installments) { |
| GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); |
| DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); |
| |
| assertNotNull(loanResponse.getRepaymentSchedule()); |
| assertNotNull(loanResponse.getRepaymentSchedule().getPeriods()); |
| Assertions.assertEquals(installments.length, loanResponse.getRepaymentSchedule().getPeriods().size(), |
| "Expected installments are not matching with the installments configured on the loan"); |
| |
| for (int i = 1; i < installments.length; i++) { |
| GetLoansLoanIdRepaymentPeriod period = loanResponse.getRepaymentSchedule().getPeriods().get(i); |
| Double principalDue = period.getPrincipalDue(); |
| Double amount = installments[i].principalAmount; |
| |
| if (installments[i].completed == null) { // this is for the disbursement |
| Assertions.assertEquals(amount, period.getPrincipalLoanBalanceOutstanding(), |
| "%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount, |
| period.getPrincipalLoanBalanceOutstanding())); |
| } else { |
| Assertions.assertEquals(amount, principalDue, |
| "%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount, principalDue)); |
| |
| Double interestAmount = installments[i].interestAmount; |
| Double interestDue = period.getInterestDue(); |
| if (interestAmount != null) { |
| Assertions.assertEquals(interestAmount, interestDue, |
| "%d. installment's interest due is different, expected: %.2f, actual: %.2f".formatted(i, interestAmount, |
| interestDue)); |
| } |
| |
| Double feeAmount = installments[i].feeAmount; |
| Double feeDue = period.getFeeChargesDue(); |
| if (feeAmount != null) { |
| Assertions.assertEquals(feeAmount, feeDue, |
| "%d. installment's fee charges due is different, expected: %.2f, actual: %.2f".formatted(i, feeAmount, feeDue)); |
| } |
| |
| Double penaltyAmount = installments[i].penaltyAmount; |
| Double penaltyDue = period.getPenaltyChargesDue(); |
| if (penaltyAmount != null) { |
| Assertions.assertEquals(penaltyAmount, penaltyDue, |
| "%d. installment's penalty charges due is different, expected: %.2f, actual: %.2f".formatted(i, penaltyAmount, |
| penaltyDue)); |
| } |
| |
| Double outstandingAmount = installments[i].totalOutstandingAmount; |
| Double totalOutstanding = period.getTotalOutstandingForPeriod(); |
| if (outstandingAmount != null) { |
| Assertions.assertEquals(outstandingAmount, totalOutstanding, |
| "%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i, outstandingAmount, |
| totalOutstanding)); |
| } |
| |
| Double outstandingPrincipalExpected = installments[i].outstandingAmounts != null |
| ? installments[i].outstandingAmounts.principalOutstanding |
| : null; |
| Double outstandingPrincipal = period.getPrincipalOutstanding(); |
| if (outstandingPrincipalExpected != null) { |
| Assertions.assertEquals(outstandingPrincipalExpected, outstandingPrincipal, |
| "%d. installment's outstanding principal is different, expected: %.2f, actual: %.2f".formatted(i, |
| outstandingPrincipalExpected, outstandingPrincipal)); |
| } |
| |
| Double outstandingFeeExpected = installments[i].outstandingAmounts != null |
| ? installments[i].outstandingAmounts.feeOutstanding |
| : null; |
| Double outstandingFee = period.getFeeChargesOutstanding(); |
| if (outstandingFeeExpected != null) { |
| Assertions.assertEquals(outstandingFeeExpected, outstandingFee, |
| "%d. installment's outstanding fee is different, expected: %.2f, actual: %.2f".formatted(i, |
| outstandingFeeExpected, outstandingFee)); |
| } |
| |
| Double outstandingPenaltyExpected = installments[i].outstandingAmounts != null |
| ? installments[i].outstandingAmounts.penaltyOutstanding |
| : null; |
| Double outstandingPenalty = period.getPenaltyChargesOutstanding(); |
| if (outstandingPenaltyExpected != null) { |
| Assertions.assertEquals(outstandingPenaltyExpected, outstandingPenalty, |
| "%d. installment's outstanding penalty is different, expected: %.2f, actual: %.2f".formatted(i, |
| outstandingPenaltyExpected, outstandingPenalty)); |
| } |
| |
| Double outstandingTotalExpected = installments[i].outstandingAmounts != null |
| ? installments[i].outstandingAmounts.totalOutstanding |
| : null; |
| Double outstandingTotal = period.getTotalOutstandingForPeriod(); |
| if (outstandingTotalExpected != null) { |
| Assertions.assertEquals(outstandingTotalExpected, outstandingTotal, |
| "%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i, |
| outstandingTotalExpected, outstandingTotal)); |
| } |
| |
| } |
| Assertions.assertEquals(installments[i].completed, period.getComplete()); |
| Assertions.assertEquals(LocalDate.parse(installments[i].dueDate, dateTimeFormatter), period.getDueDate()); |
| } |
| } |
| |
| protected void runAt(String date, Runnable runnable) { |
| try { |
| GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, true); |
| GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, TRUE); |
| businessDateHelper.updateBusinessDate( |
| new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en")); |
| runnable.run(); |
| } finally { |
| GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, FALSE); |
| GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, false); |
| } |
| } |
| |
| protected void runAsNonByPass(Runnable runnable) { |
| RequestSpecificationImpl requestSpecImpl = (RequestSpecificationImpl) requestSpec; |
| try { |
| requestSpecImpl.replaceHeader("Authorization", "Basic " + nonByPassUserAuthKey); |
| runnable.run(); |
| } finally { |
| requestSpecImpl.replaceHeader("Authorization", "Basic " + fullAdminAuthKey); |
| } |
| } |
| |
| protected PostLoansRequest applyLoanRequest(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, |
| int numberOfRepayments) { |
| return applyLoanRequest(clientId, loanProductId, loanDisbursementDate, amount, numberOfRepayments, null); |
| } |
| |
| protected PostLoansRequest applyLoanRequest(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, |
| int numberOfRepayments, Consumer<PostLoansRequest> customizer) { |
| |
| PostLoansRequest postLoansRequest = new PostLoansRequest().clientId(clientId).productId(loanProductId) |
| .expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN) |
| .transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY) |
| .locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO) |
| .interestCalculationPeriodType(1).interestType(0).repaymentEvery(30).repaymentFrequencyType(0) |
| .numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments * 30).loanTermFrequencyType(0) |
| .maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual"); |
| if (customizer != null) { |
| customizer.accept(postLoansRequest); |
| } |
| return postLoansRequest; |
| } |
| |
| protected PostLoansLoanIdRequest approveLoanRequest(Double amount, String approvalDate) { |
| return new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount)).dateFormat(DATETIME_PATTERN) |
| .approvedOnDate(approvalDate).locale("en"); |
| } |
| |
| protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, |
| int numberOfRepayments) { |
| return applyAndApproveLoan(clientId, loanProductId, loanDisbursementDate, amount, numberOfRepayments, null); |
| } |
| |
| protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, |
| int numberOfRepayments, Consumer<PostLoansRequest> customizer) { |
| PostLoansResponse postLoansResponse = loanTransactionHelper |
| .applyLoan(applyLoanRequest(clientId, loanProductId, loanDisbursementDate, amount, numberOfRepayments, customizer)); |
| |
| PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), |
| approveLoanRequest(amount, loanDisbursementDate)); |
| |
| return approvedLoanResult.getLoanId(); |
| } |
| |
| protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount) { |
| return applyAndApproveLoan(clientId, loanProductId, loanDisbursementDate, amount, 1); |
| } |
| |
| protected Long addRepaymentForLoan(Long loanId, Double amount, String date) { |
| String firstRepaymentUUID = UUID.randomUUID().toString(); |
| PostLoansLoanIdTransactionsResponse response = loanTransactionHelper.makeLoanRepayment(loanId, |
| new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate(date).locale("en") |
| .transactionAmount(amount).externalId(firstRepaymentUUID)); |
| return response.getResourceId(); |
| } |
| |
| protected Long chargeOffLoan(Long loanId, String date) { |
| 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(date).locale("en").dateFormat("dd MMMM yyyy") |
| .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); |
| return chargeOffTransaction.getResourceId(); |
| } |
| |
| protected void changeLoanFraudState(Long loanId, boolean fraudState) { |
| String payload = loanTransactionHelper.getLoanFraudPayloadAsJSON("fraud", fraudState ? "true" : "false"); |
| PutLoansLoanIdResponse response = loanTransactionHelper.modifyLoanCommand(Math.toIntExact(loanId), "markAsFraud", payload, |
| responseSpec); |
| assertNotNull(response); |
| } |
| |
| protected Long addChargebackForLoan(Long loanId, Long transactionId, Double amount) { |
| PostLoansLoanIdTransactionsResponse response = loanTransactionHelper.chargebackLoanTransaction(loanId, transactionId, |
| new PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(amount).paymentTypeId(1L)); |
| return response.getResourceId(); |
| } |
| |
| protected PostChargesResponse createCharge(Double amount) { |
| String payload = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, amount.toString(), false); |
| return ChargesHelper.createLoanCharge(requestSpec, responseSpec, payload); |
| } |
| |
| protected PostLoansLoanIdChargesResponse addLoanCharge(Long loanId, Long chargeId, String date, Double amount) { |
| String payload = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(chargeId.toString(), date, amount.toString()); |
| return loanTransactionHelper.addChargeForLoan(loanId.intValue(), payload, responseSpec); |
| } |
| |
| protected void waiveLoanCharge(Long loanId, Long chargeId, Integer installmentNumber) { |
| String payload = LoanTransactionHelper.getWaiveChargeJSON(installmentNumber.toString()); |
| loanTransactionHelper.waiveChargesForLoan(loanId.intValue(), chargeId.intValue(), payload); |
| } |
| |
| protected void updateBusinessDate(String date) { |
| businessDateHelper.updateBusinessDate( |
| new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en")); |
| } |
| |
| protected Long getTransactionId(Long loanId, String type, String date) { |
| GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); |
| return loan.getTransactions().stream().filter(tr -> Objects.equals(tr.getType().getValue(), type) |
| && Objects.equals(tr.getDate(), LocalDate.parse(date, dateTimeFormatter))).findAny().orElseThrow().getId(); |
| } |
| |
| protected Journal journalEntry(double amount, Account account, String type) { |
| return new Journal(amount, account, type); |
| } |
| |
| protected Journal debit(Account account, double amount) { |
| return new Journal(amount, account, "DEBIT"); |
| } |
| |
| protected Journal credit(Account account, double amount) { |
| return new Journal(amount, account, "CREDIT"); |
| } |
| |
| protected Transaction transaction(double principalAmount, String type, String date) { |
| return new Transaction(principalAmount, type, date, null); |
| } |
| |
| protected Transaction reversedTransaction(double principalAmount, String type, String date) { |
| return new Transaction(principalAmount, type, date, true); |
| } |
| |
| protected TransactionExt transaction(double amount, String type, String date, double outstandingPrincipal, double principalPortion, |
| double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, double overpaymentPortion) { |
| return new TransactionExt(amount, type, date, outstandingPrincipal, principalPortion, interestPortion, feePortion, penaltyPortion, |
| unrecognizedIncomePortion, overpaymentPortion, false); |
| } |
| |
| protected TransactionExt transaction(double amount, String type, String date, double outstandingPrincipal, double principalPortion, |
| double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, double overpaymentPortion, |
| boolean reversed) { |
| return new TransactionExt(amount, type, date, outstandingPrincipal, principalPortion, interestPortion, feePortion, penaltyPortion, |
| unrecognizedIncomePortion, overpaymentPortion, reversed); |
| } |
| |
| protected Installment installment(double principalAmount, Boolean completed, String dueDate) { |
| return new Installment(principalAmount, null, null, null, null, completed, dueDate, null); |
| } |
| |
| protected Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, Boolean completed, |
| String dueDate) { |
| return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate, null); |
| } |
| |
| protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount, |
| Boolean completed, String dueDate) { |
| return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, null); |
| } |
| |
| protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount, |
| double totalOutstandingAmount, Boolean completed, String dueDate) { |
| return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate, null); |
| } |
| |
| protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount, |
| OutstandingAmounts outstandingAmounts, Boolean completed, String dueDate) { |
| return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, null, completed, dueDate, outstandingAmounts); |
| } |
| |
| protected OutstandingAmounts outstanding(double principal, double fee, double penalty, double total) { |
| return new OutstandingAmounts(principal, fee, penalty, total); |
| } |
| |
| protected BatchRequestBuilder batchRequest() { |
| return new BatchRequestBuilder(requestSpec, responseSpec); |
| } |
| |
| protected void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment, |
| Double principalOutstanding, Double principalPaid, Double totalOverpaid) { |
| assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding()); |
| assertEquals(totalRepayment, loanDetails.getSummary().getTotalRepayment()); |
| assertEquals(principalOutstanding, loanDetails.getSummary().getPrincipalOutstanding()); |
| assertEquals(principalPaid, loanDetails.getSummary().getPrincipalPaid()); |
| assertEquals(totalOverpaid, loanDetails.getTotalOverpaid()); |
| } |
| |
| protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue, |
| double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) { |
| GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream() |
| .filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow(); |
| assertEquals(dueDate, period.getDueDate()); |
| assertEquals(principalDue, period.getPrincipalDue()); |
| assertEquals(principalPaid, period.getPrincipalPaid()); |
| assertEquals(principalOutstanding, period.getPrincipalOutstanding()); |
| assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod()); |
| assertEquals(paidLate, period.getTotalPaidLateForPeriod()); |
| } |
| |
| protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, double principalDue, |
| double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) { |
| GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream() |
| .filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow(); |
| assertEquals(principalDue, period.getPrincipalDue()); |
| assertEquals(principalPaid, period.getPrincipalPaid()); |
| assertEquals(principalOutstanding, period.getPrincipalOutstanding()); |
| assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod()); |
| assertEquals(paidLate, period.getTotalPaidLateForPeriod()); |
| } |
| |
| protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue, |
| double principalPaid, double principalOutstanding, double feeDue, double feePaid, double feeOutstanding, double penaltyDue, |
| double penaltyPaid, double penaltyOutstanding, double interestDue, double interestPaid, double interestOutstanding, |
| double paidInAdvance, double paidLate) { |
| GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream() |
| .filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow(); |
| assertEquals(dueDate, period.getDueDate()); |
| assertEquals(principalDue, period.getPrincipalDue()); |
| assertEquals(principalPaid, period.getPrincipalPaid()); |
| assertEquals(principalOutstanding, period.getPrincipalOutstanding()); |
| assertEquals(feeDue, period.getFeeChargesDue()); |
| assertEquals(feePaid, period.getFeeChargesPaid()); |
| assertEquals(feeOutstanding, period.getFeeChargesOutstanding()); |
| assertEquals(penaltyDue, period.getPenaltyChargesDue()); |
| assertEquals(penaltyPaid, period.getPenaltyChargesPaid()); |
| assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding()); |
| assertEquals(interestDue, period.getInterestDue()); |
| assertEquals(interestPaid, period.getInterestPaid()); |
| assertEquals(interestOutstanding, period.getInterestOutstanding()); |
| assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod()); |
| assertEquals(paidLate, period.getTotalPaidLateForPeriod()); |
| } |
| |
| @RequiredArgsConstructor |
| public static class BatchRequestBuilder { |
| |
| private final RequestSpecification requestSpec; |
| private final ResponseSpecification responseSpec; |
| private List<BatchRequest> requests = new ArrayList<>(); |
| |
| public BatchRequestBuilder rescheduleLoan(Long requestId, Long loanId, String submittedOnDate, String rescheduleFromDate, |
| String adjustedDueDate) { |
| BatchRequest bRequest = new BatchRequest(); |
| bRequest.setRequestId(requestId); |
| bRequest.setRelativeUrl("rescheduleloans"); |
| bRequest.setMethod("POST"); |
| |
| bRequest.setBody(""" |
| { |
| "loanId": %d, |
| "rescheduleFromDate": "%s", |
| "rescheduleReasonId": 1, |
| "submittedOnDate": "%s", |
| "rescheduleReasonComment": "", |
| "adjustedDueDate": "%s", |
| "graceOnPrincipal": "", |
| "graceOnInterest": "", |
| "extraTerms": "", |
| "newInterestRate": "", |
| "dateFormat": "%s", |
| "locale": "en" |
| } |
| """.formatted(loanId, rescheduleFromDate, submittedOnDate, adjustedDueDate, DATETIME_PATTERN)); |
| |
| requests.add(bRequest); |
| return this; |
| } |
| |
| public BatchRequestBuilder approveRescheduleLoan(Long requestId, Long rescheduleBatchRequestId, String approvedOnDate) { |
| BatchRequest bRequest = new BatchRequest(); |
| bRequest.setRequestId(requestId); |
| bRequest.setRelativeUrl("rescheduleloans/$.resourceId?command=approve"); |
| bRequest.setMethod("POST"); |
| bRequest.setReference(rescheduleBatchRequestId); |
| |
| bRequest.setBody(""" |
| { |
| "approvedOnDate": "%s", |
| "dateFormat": "%s", |
| "locale": "en" |
| } |
| """.formatted(approvedOnDate, DATETIME_PATTERN)); |
| |
| requests.add(bRequest); |
| return this; |
| } |
| |
| public List<BatchResponse> executeEnclosingTransaction() { |
| return BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, BatchHelper.toJsonString(requests)); |
| } |
| |
| public ErrorResponse executeEnclosingTransactionError(ResponseSpecification responseSpec) { |
| return BatchHelper.postBatchRequestsWithoutEnclosingTransactionError(requestSpec, responseSpec, |
| BatchHelper.toJsonString(requests)); |
| } |
| } |
| |
| @ToString |
| @AllArgsConstructor |
| public static class Transaction { |
| |
| Double amount; |
| String type; |
| String date; |
| Boolean reversed; |
| } |
| |
| @ToString |
| @AllArgsConstructor |
| public static class TransactionExt { |
| |
| Double amount; |
| String type; |
| String date; |
| Double outstandingPrincipal; |
| Double principalPortion; |
| Double interestPortion; |
| Double feePortion; |
| Double penaltyPortion; |
| Double unrecognizedPortion; |
| Double overpaymentPortion; |
| Boolean reversed; |
| } |
| |
| @ToString |
| @AllArgsConstructor |
| public static class Journal { |
| |
| Double amount; |
| Account account; |
| String type; |
| } |
| |
| @ToString |
| @AllArgsConstructor |
| public static class Installment { |
| |
| Double principalAmount; |
| Double interestAmount; |
| Double feeAmount; |
| Double penaltyAmount; |
| Double totalOutstandingAmount; |
| Boolean completed; |
| String dueDate; |
| OutstandingAmounts outstandingAmounts; |
| } |
| |
| @AllArgsConstructor |
| @ToString |
| public static class OutstandingAmounts { |
| |
| Double principalOutstanding; |
| Double feeOutstanding; |
| Double penaltyOutstanding; |
| Double totalOutstanding; |
| } |
| |
| public static class AmortizationType { |
| |
| public static final Integer EQUAL_INSTALLMENTS = 1; |
| } |
| |
| public static class InterestType { |
| |
| public static final Integer DECLINING_BALANCE = 0; |
| public static final Integer FLAT = 1; |
| } |
| |
| public static class RepaymentFrequencyType { |
| |
| public static final Integer MONTHS = 2; |
| public static final String MONTHS_STRING = "MONTHS"; |
| } |
| |
| public static class InterestCalculationPeriodType { |
| |
| public static final Integer SAME_AS_REPAYMENT_PERIOD = 1; |
| } |
| |
| public static class InterestRateFrequencyType { |
| |
| public static final Integer MONTHS = 2; |
| public static final Integer YEARS = 3; |
| } |
| |
| } |