From 6feb10a87e119a6a43b6c35770274fc4490ad038 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Wed, 6 Nov 2024 10:20:03 +0100 Subject: [PATCH] FINERACT-2070: e2e test framework update --- .../data/AdvancePaymentsAdjustmentType.java | 5 +- .../fineract/test/data/CobBusinessStep.java | 43 ++ .../test/data/LoanRescheduleErrorMessage.java | 25 +- .../apache/fineract/test/data/LoanStatus.java | 11 +- .../fineract/test/data/TransactionType.java | 16 +- .../fineract/test/data/job/DefaultJob.java | 2 +- .../data/loanproduct/DefaultLoanProduct.java | 34 +- .../factory/LoanProductsRequestFactory.java | 271 +++++----- .../test/factory/LoanRequestFactory.java | 25 +- .../factory/SavingsAccountRequestFactory.java | 91 ++++ .../factory/SavingsProductRequestFactory.java | 63 +++ .../test/helper/BusinessDateHelper.java | 4 +- .../test/helper/ErrorMessageHelper.java | 43 +- .../apache/fineract/test/helper/Utils.java | 6 + .../test/helper/WorkFlowJobHelper.java | 49 ++ .../BaseFineractInitializerConfiguration.java | 6 +- .../initializer/base/FineractInitializer.java | 3 + .../CobBusinessStepInitializerStep.java | 59 +++ .../LoanProductGlobalInitializerStep.java | 500 ++++++++++++++++-- .../SavingsProductGlobalInitializer.java | 53 ++ .../test/messaging/EmptyEventMessage.java | 2 +- .../test/messaging/EventAssertion.java | 12 +- .../fineract/test/messaging/EventMessage.java | 1 + .../messaging/event/EventCheckHelper.java | 147 +++-- .../event/loan/LoanRejectedEvent.java | 27 + .../event/loan/LoanUndoApprovalEvent.java | 27 + ...anTransactionAccrualActivityPostEvent.java | 27 + ...sactionInterestPaymentWaiverPostEvent.java | 27 + .../test/messaging/store/EventStore.java | 11 +- .../test/messaging/store/LoggedEvent.java | 4 +- .../test/stepdef/common/BatchApiStepDef.java | 12 +- .../stepdef/common/BusinessStepStepDef.java | 137 ++--- .../common/GlobalConfigurationStepDef.java | 9 + .../stepdef/common/JournalEntriesStepDef.java | 1 - .../test/stepdef/common/SchedulerStepDef.java | 5 + .../stepdef/hook/TestRailLifecycleHook.java | 45 ++ .../loan/LoanChargeAdjustmentStepDef.java | 20 +- .../stepdef/loan/LoanChargeBackStepDef.java | 13 +- .../test/stepdef/loan/LoanChargeStepDef.java | 10 + .../stepdef/loan/LoanDelinquencyStepDef.java | 8 +- .../test/stepdef/loan/LoanReAgingStepDef.java | 16 +- .../stepdef/loan/LoanRepaymentStepDef.java | 54 +- .../stepdef/loan/LoanRescheduleStepDef.java | 43 +- .../test/stepdef/loan/LoanStepDef.java | 298 ++++++++++- .../stepdef/saving/SavingsAccountStepDef.java | 184 +++++++ .../fineract/test/support/TestContext.java | 3 +- .../fineract/test/support/TestContextKey.java | 31 +- .../testrail/AddResultForCaseRequest.java | 37 ++ .../test/testrail/TestRailApiClient.java | 32 ++ .../test/testrail/TestRailClient.java | 141 +++++ .../test/testrail/TestRailConfiguration.java | 80 +++ .../testrail/TestRailEnabledCondition.java | 34 ++ .../test/testrail/TestRailProperties.java | 39 ++ .../test/testrail/TestRailStatus.java | 34 ++ .../src/test/resources/features/Loan.feature | 22 +- .../resources/features/LoanCharge.feature | 10 +- .../resources/features/LoanRepayment.feature | 6 +- 57 files changed, 2456 insertions(+), 462 deletions(-) create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/CobBusinessStep.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsAccountRequestFactory.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsProductRequestFactory.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CobBusinessStepInitializerStep.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/SavingsProductGlobalInitializer.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanRejectedEvent.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanUndoApprovalEvent.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionAccrualActivityPostEvent.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestPaymentWaiverPostEvent.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/hook/TestRailLifecycleHook.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/AddResultForCaseRequest.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailApiClient.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailClient.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailConfiguration.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailEnabledCondition.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailProperties.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailStatus.java diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java index e236191a4..1c73e94d6 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java @@ -20,7 +20,10 @@ package org.apache.fineract.test.data; public enum AdvancePaymentsAdjustmentType { - RESCHEDULE_NEXT_REPAYMENTS(1), REDUCE_NUMBER_OF_INSTALLMENTS(2), REDUCE_EMI_AMOUNT(3), ADJUST_LAST_UNPAID_PERIOD(4); + RESCHEDULE_NEXT_REPAYMENTS(1), // + REDUCE_NUMBER_OF_INSTALLMENTS(2), // + REDUCE_EMI_AMOUNT(3), // + ADJUST_LAST_UNPAID_PERIOD(4);// public final Integer value; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/CobBusinessStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/CobBusinessStep.java new file mode 100644 index 000000000..37f52c4b7 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/CobBusinessStep.java @@ -0,0 +1,43 @@ +/** + * 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.test.data; + +public enum CobBusinessStep { + + APPLY_CHARGE_TO_OVERDUE_LOANS("APPLY_CHARGE_TO_OVERDUE_LOANS"), // + LOAN_DELINQUENCY_CLASSIFICATION("LOAN_DELINQUENCY_CLASSIFICATION"), // + CHECK_LOAN_REPAYMENT_DUE("CHECK_LOAN_REPAYMENT_DUE"), // + CHECK_LOAN_REPAYMENT_OVERDUE("CHECK_LOAN_REPAYMENT_OVERDUE"), // + UPDATE_LOAN_ARREARS_AGING("UPDATE_LOAN_ARREARS_AGING"), // + ADD_PERIODIC_ACCRUAL_ENTRIES("ADD_PERIODIC_ACCRUAL_ENTRIES"), // + EXTERNAL_ASSET_OWNER_TRANSFER("EXTERNAL_ASSET_OWNER_TRANSFER"), // + CHECK_DUE_INSTALLMENTS("CHECK_DUE_INSTALLMENTS"), // + ACCRUAL_ACTIVITY_POSTING("ACCRUAL_ACTIVITY_POSTING"), // + LOAN_INTEREST_RECALCULATION("LOAN_INTEREST_RECALCULATION");// + + public final String value; + + CobBusinessStep(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanRescheduleErrorMessage.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanRescheduleErrorMessage.java index 148cd2c83..bf2655d67 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanRescheduleErrorMessage.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanRescheduleErrorMessage.java @@ -20,16 +20,29 @@ package org.apache.fineract.test.data; public enum LoanRescheduleErrorMessage { - LOAN_CHARGED_OFF("Loan: %s reschedule installment is not allowed. Loan Account is Charged-off"), LOAN_LOCKED_BY_COB( - "Loan is locked by the COB job. Loan ID: %s"); + LOAN_CHARGED_OFF("Loan: %s reschedule installment is not allowed. Loan Account is Charged-off"), // + LOAN_RESCHEDULE_DATE_NOT_IN_FUTURE("Loan Reschedule From date (%s) for Loan: %s should be in the future."), // + LOAN_LOCKED_BY_COB("Loan is locked by the COB job. Loan ID: %s");// - public final String value; + private final String messageTemplate; - LoanRescheduleErrorMessage(String value) { - this.value = value; + LoanRescheduleErrorMessage(String messageTemplate) { + this.messageTemplate = messageTemplate; } public String getValue(Object... params) { - return String.format(this.value, params); + if (params.length != getExpectedParameterCount()) { + throw new IllegalArgumentException("Expected " + getExpectedParameterCount() + " parameters, but got " + params.length); + } + return String.format(this.messageTemplate, params); + } + + public int getExpectedParameterCount() { + // Count the number of placeholders (%s) in the message template + return (int) messageTemplate.chars().filter(ch -> ch == '%').count(); + } + + public String getMessageTemplate() { + return messageTemplate; } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java index a90e101a9..5a02805d7 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java @@ -23,8 +23,15 @@ import java.util.Map; public enum LoanStatus { - NONE(0), SUBMITTED_AND_PENDING_APPROVAL(100), APPROVED(200), ACTIVE(300), CLOSED_OBLIGATIONS_MET(600), OVERPAID( - 700), CLOSED_WRITTEN_OFF(601); + NONE(0), // + SUBMITTED_AND_PENDING_APPROVAL(100), // + APPROVED(200), // + ACTIVE(300), // + WITHDRAWN(400), // + REJECTED(500), // + CLOSED_OBLIGATIONS_MET(600), // + OVERPAID(700), // + CLOSED_WRITTEN_OFF(601);// public final Integer value; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java index 127833e40..b493da518 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java @@ -20,9 +20,19 @@ package org.apache.fineract.test.data; public enum TransactionType { - DISBURSEMENT("disbursement"), REPAYMENT("repayment"), DOWN_PAYMENT("downPayment"), GOODWILL_CREDIT("goodwillCredit"), PAYOUT_REFUND( - "payoutRefund"), REFUND_BY_CASH("refundByCash"), MERCHANT_ISSUED_REFUND("merchantIssuedRefund"), CREDIT_BALANCE_REFUND( - "creditBalanceRefund"), CHARGEBACK("chargeback"), ACCRUAL("accrual"), CHARGE_OFF("chargeOff"); + DISBURSEMENT("disbursement"), // + REPAYMENT("repayment"), // + DOWN_PAYMENT("downPayment"), // + GOODWILL_CREDIT("goodwillCredit"), // + PAYOUT_REFUND("payoutRefund"), // + REFUND_BY_CASH("refundByCash"), // + MERCHANT_ISSUED_REFUND("merchantIssuedRefund"), // + CREDIT_BALANCE_REFUND("creditBalanceRefund"), // + CHARGEBACK("chargeback"), // + ACCRUAL("accrual"), // + CHARGE_OFF("chargeOff"), // + CHARGE_ADJUSTMENT("chargeAdjustment"), // + INTEREST_PAYMENT_WAIVER("interestPaymentWaiver");// public final String value; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java index 58d2a9100..1798e8783 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java @@ -25,7 +25,7 @@ public enum DefaultJob implements Job { INCREASE_BUSINESS_DAY("Increase Business Date by 1 day", "BDT_INC1"), // LOAN_DELINQUENCY_CLASSIFICATION("Loan Delinquency Classification", "LA_DECL"), // LOAN_COB("Loan COB", "LA_ECOB"), // - ; + ACCRUAL_ACTIVITY_POSTING("Accrual Activity Posting", "ACC_ACPO"); private final String customName; private final String shortName; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index 122ff7af0..82972057d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -25,6 +25,8 @@ public enum DefaultLoanProduct implements LoanProduct { LP1_INTEREST_FLAT, // LP1_INTEREST_DECLINING_BALANCE_PERIOD_SAME_AS_PAYMENT, // LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY, // + LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY_ACCRUAL_ACTIVITY, // + LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_ACCRUAL_ACTIVITY, // LP1_1MONTH_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY, // LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE, // LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_REDUCE_NR_INST, // @@ -37,21 +39,37 @@ public enum DefaultLoanProduct implements LoanProduct { LP1_PAYMENT_STRATEGY_DUE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_INTEREST_FLAT, // LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT, // LP1_INTEREST_FLAT_OVERDUE_FROM_AMOUNT_INTEREST, // - LP2_DOWNPAYMENT, LP2_DOWNPAYMENT_AUTO, // + LP2_DOWNPAYMENT, // + LP2_DOWNPAYMENT_AUTO, // LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION, // LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION, // - LP2_DOWNPAYMENT_INTEREST, LP2_DOWNPAYMENT_INTEREST_AUTO, // - LP2_ADV_CUST_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // + LP2_DOWNPAYMENT_INTEREST, // + LP2_DOWNPAYMENT_INTEREST_AUTO, // LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL, // LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY, // LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION, // LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH, // - LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE, // - LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY, // - LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY, // - LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // - ; + LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION_REPAYMENT_START_SUBMITTED, // + LP2_DOWNPAYMENT_INTEREST_FLAT_ADV_PMT_ALLOC, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_PMT_ALLOC_1, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_PRECLOSE, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_REST_FREQUENCY_DATE, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND, // + LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // + LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM, // + LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY;// @Override public String getName() { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java index 376813d58..beb83efd5 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java @@ -62,29 +62,30 @@ public class LoanProductsRequestFactory { public static final String NAME_PREFIX = "LP1-"; public static final String NAME_PREFIX_LP2 = "LP2-"; + public static final String NAME_PREFIX_LP2_EMI = "LP2Emi-"; public static final String NAME_PREFIX_INTEREST_FLAT = "LP1InterestFlat-"; public static final String NAME_PREFIX_INTEREST_FLAT_LP2 = "LP2InterestFlat-"; public static final String NAME_PREFIX_INTEREST_DECLINING = "LP1InterestDeclining-"; public static final String NAME_PREFIX_INTEREST_DECLINING_RECALCULATION = "LP1InterestDecliningRecalculation-"; - public static final String NAME_PREFIX_INTEREST_RECALCULATION_WITH_DOWN_PAYMENT_LP3 = "LP3InterestRecalculationWithDownPayment-"; - public static final String NAME_PREFIX_LP2_EMI = "LP2Emi-"; + public static final String NAME_PREFIX_INTEREST_RECALCULATION = "LP2InterestRecalculation-"; public static final String SHORT_NAME_PREFIX = "p"; public static final String SHORT_NAME_PREFIX_INTEREST = "i"; public static final String SHORT_NAME_PREFIX_EMI = "e"; public static final String DATE_FORMAT = "dd MMMM yyyy"; public static final String LOCALE_EN = "en"; - public static final String DESCRIPTION = "LP1 product"; - public static final String DESCRIPTION_LP2 = "LP2 product"; - public static final String DESCRIPTION_INTEREST_FLAT = "LP1 product with 12% interest - FLAT"; - public static final String DESCRIPTION_INTEREST_FLAT_LP2 = "LP2 product with 12% interest - FLAT"; - public static final String DESCRIPTION_INTEREST_DECLINING = "LP1 product with 12% interest - DECLINING BALANCE"; + public static final String DESCRIPTION = "30 days repayment"; + public static final String DESCRIPTION_LP2 = "4 installments repayment"; + public static final String DESCRIPTION_LP2_EMI = "4 installments repayment with EMI"; + public static final String DESCRIPTION_INTEREST_FLAT = "30 days repayment with 12% interest - FLAT"; + public static final String DESCRIPTION_INTEREST_FLAT_LP2 = "4 installments repayment with 12% interest - FLAT"; + public static final String DESCRIPTION_INTEREST_DECLINING = "30 days repayment with 12% interest - DECLINING BALANCE"; public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY = "LP1-1MONTH with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Monthly, Compounding:Interest"; public static final String DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE = "LP1 with 12% DECLINING BALANCE interest, interest period: Daily, Interest recalculation-Daily, Compounding:none"; - public static final String DESCRIPTION_LP2_EMI = "LP2 product with EMI"; public static final Long FUND_ID = FundId.LENDER_A.value; public static final String CURRENCY_CODE = "EUR"; public static final Integer INTEREST_RATE_FREQUENCY_TYPE_MONTH = InterestRateFrequencyType.MONTH.value; public static final Integer INTEREST_RATE_FREQUENCY_TYPE_YEAR = InterestRateFrequencyType.YEAR.value; + public static final Integer INTEREST_RATE_FREQUENCY_TYPE_WHOLE_TERM = InterestRateFrequencyType.WHOLE_TERM.value; public static final Long REPAYMENT_FREQUENCY_TYPE_DAYS = RepaymentFrequencyType.DAYS.value.longValue(); public static final Long REPAYMENT_FREQUENCY_TYPE_MONTHS = RepaymentFrequencyType.MONTHS.value.longValue(); public static final Integer AMORTIZATION_TYPE = AmortizationType.EQUAL_INSTALLMENTS.value; @@ -94,10 +95,10 @@ public class LoanProductsRequestFactory { public static final Integer INTEREST_CALCULATION_PERIOD_TYPE_DAILY = InterestCalculationPeriodTime.DAILY.value; public static final String TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER.value; public static final String TRANSACTION_PROCESSING_STRATEGY_CODE_ADVANCED = TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION.value; - public static final Integer DAYS_IN_YEAR_TYPE = DaysInYearType.ACTUAL.value; public static final Integer DAYS_IN_YEAR_TYPE_360 = DaysInYearType.DAYS360.value; - public static final Integer DAYS_IN_MONTH_TYPE = DaysInMonthType.ACTUAL.value; + public static final Integer DAYS_IN_YEAR_TYPE = DaysInYearType.ACTUAL.value; public static final Integer DAYS_IN_MONTH_TYPE_30 = DaysInMonthType.DAYS30.value; + public static final Integer DAYS_IN_MONTH_TYPE = DaysInMonthType.ACTUAL.value; public static final Integer LOAN_ACCOUNTING_RULE = AccountingRule.ACCRUAL_PERIODIC.value; public static final Integer LOAN_ACCOUNTING_RULE_NONE = AccountingRule.NONE.value; public static final String OVER_APPLIED_CALCULATION_TYPE = "percentage"; @@ -661,36 +662,119 @@ public class LoanProductsRequestFactory { .recalculationRestFrequencyInterval(1);// } - public PostLoanProductsRequest defaultLoanProductsRequestLP2InterestDailyRecalculationWithDownPayment() { - final String name = Utils.randomNameGenerator(NAME_PREFIX_INTEREST_RECALCULATION_WITH_DOWN_PAYMENT_LP3, 4); + public PostLoanProductsRequest defaultLoanProductsRequestLP2InterestDailyRecalculation() { + final String name = Utils.randomNameGenerator(NAME_PREFIX_INTEREST_RECALCULATION, 4); final String shortName = Utils.randomNameGenerator(SHORT_NAME_PREFIX_INTEREST, 3); - return new PostLoanProductsRequest().name(name).shortName(shortName) - .description(DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE).startDate(null).closeDate(null) - .accountMovesOutOfNPAOnlyOnArrearsCompletion(false).accountingRule(LOAN_ACCOUNTING_RULE_NONE) - .allowApprovedDisbursedAmountsOverApplied(true) - .allowAttributeOverrides(new AllowAttributeOverrides().amortizationType(true).interestType(true) - .transactionProcessingStrategyCode(true).interestCalculationPeriodType(true).inArrearsTolerance(true) - .repaymentEvery(true).graceOnPrincipalAndInterestPayment(true).graceOnArrearsAgeing(true)) - .allowPartialPeriodInterestCalcualtion(false).allowVariableInstallments(false).amortizationType(AMORTIZATION_TYPE) - .canDefineInstallmentAmount(true).canUseForTopup(false).charges(new ArrayList<>()).creditAllocation(new ArrayList<>()) - .currencyCode(CURRENCY_CODE).dateFormat(DATE_FORMAT).daysInMonthType(DAYS_IN_MONTH_TYPE_30) - .daysInYearType(DAYS_IN_YEAR_TYPE_360).delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue()).digitsAfterDecimal(2) - .disallowExpectedDisbursements(true).dueDaysForRepaymentEvent(1).enableDownPayment(false) - .enableInstallmentLevelDelinquency(true).fixedLength(null).holdGuaranteeFunds(false).inMultiplesOf(0) - .includeInBorrowerCycle(false).interestCalculationPeriodType(0).interestRateFrequencyType(3).interestRatePerPeriod(9.99) - .interestRateVariationsForBorrowerCycle(new ArrayList<>()).interestRecalculationCompoundingMethod(0).interestType(0) - .isArrearsBasedOnOriginalSchedule(false).isEqualAmortization(false).isInterestRecalculationEnabled(true) - .isLinkedToFloatingInterestRates(false).loanScheduleProcessingType("HORIZONTAL").loanScheduleType("PROGRESSIVE") - .locale(LOCALE_EN).maxInterestRatePerPeriod((double) 50).maxNumberOfRepayments(48).maxPrincipal((double) 10000) - .maxTrancheCount(10).minInterestRatePerPeriod((double) 0).minNumberOfRepayments(1).minPrincipal((double) 1) - .multiDisburseLoan(true).numberOfRepaymentVariationsForBorrowerCycle(new ArrayList<>()).numberOfRepayments(3) - .outstandingLoanBalance((double) 10000).overAppliedCalculationType("flat").overAppliedNumber(10000) - .overDueDaysForRepaymentEvent(2).preClosureInterestCalculationStrategy(1).principal((double) 40) - .principalThresholdForLastInstallment(50).principalVariationsForBorrowerCycle(new ArrayList<>()) - .recalculationRestFrequencyInterval(1).recalculationRestFrequencyType(2).repaymentEvery(1).repaymentFrequencyType(2L) - .repaymentStartDateType(2).rescheduleStrategyMethod(4).supportedInterestRefundTypes(new ArrayList<>()) - .transactionProcessingStrategyCode(TRANSACTION_PROCESSING_STRATEGY_CODE_ADVANCED).useBorrowerCycle(false); + List penaltyToIncomeAccountMappings = new ArrayList<>(); + List feeToIncomeAccountMappings = new ArrayList<>(); + + List paymentChannelToFundSourceMappings = new ArrayList<>(); + GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings(); + loanPaymentChannelToFundSourceMappings.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.FUND_RECEIVABLES)); + loanPaymentChannelToFundSourceMappings.paymentTypeId(paymentTypeResolver.resolve(DefaultPaymentType.MONEY_TRANSFER)); + paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings); + + return new PostLoanProductsRequest().name(name)// + .shortName(shortName)// + .description(DESCRIPTION_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE)// + .startDate(null)// + .closeDate(null)// + .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)// + .allowApprovedDisbursedAmountsOverApplied(true)// + .allowAttributeOverrides(new AllowAttributeOverrides()// + .amortizationType(true)// + .interestType(true)// + .transactionProcessingStrategyCode(true)// + .interestCalculationPeriodType(true)// + .inArrearsTolerance(true)// + .repaymentEvery(true)// + .graceOnPrincipalAndInterestPayment(true)// + .graceOnArrearsAgeing(true))// + .allowPartialPeriodInterestCalcualtion(false)// + .allowVariableInstallments(false)// + .amortizationType(AMORTIZATION_TYPE)// + .canDefineInstallmentAmount(true)// + .canUseForTopup(false)// + .charges(new ArrayList<>()).creditAllocation(new ArrayList<>())// + .currencyCode(CURRENCY_CODE)// + .dateFormat(DATE_FORMAT)// + .daysInMonthType(DAYS_IN_MONTH_TYPE_30)// + .daysInYearType(DAYS_IN_YEAR_TYPE_360)// + .delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())// + .digitsAfterDecimal(2)// + .disallowExpectedDisbursements(true)// + .dueDaysForRepaymentEvent(1)// + .enableDownPayment(false)// + .enableInstallmentLevelDelinquency(true)// + .fixedLength(null).holdGuaranteeFunds(false)// + .inMultiplesOf(0)// + .includeInBorrowerCycle(false)// + .interestCalculationPeriodType(0)// + .interestRateFrequencyType(3)// + .interestRatePerPeriod(9.99)// + .interestRateVariationsForBorrowerCycle(new ArrayList<>())// + .interestRecalculationCompoundingMethod(0)// + .interestType(0)// + .isArrearsBasedOnOriginalSchedule(false)// + .isEqualAmortization(false)// + .isInterestRecalculationEnabled(true)// + .isLinkedToFloatingInterestRates(false)// + .loanScheduleProcessingType("HORIZONTAL")// + .loanScheduleType("PROGRESSIVE")// + .locale(LOCALE_EN)// + .maxInterestRatePerPeriod((double) 50)// + .maxNumberOfRepayments(48)// + .maxPrincipal((double) 10000)// + .maxTrancheCount(10)// + .minInterestRatePerPeriod((double) 0)// + .minNumberOfRepayments(1)// + .minPrincipal((double) 1)// + .multiDisburseLoan(true)// + .numberOfRepaymentVariationsForBorrowerCycle(new ArrayList<>())// + .numberOfRepayments(3)// + .outstandingLoanBalance((double) 10000)// + .overAppliedCalculationType("flat")// + .overAppliedNumber(10000)// + .overDueDaysForRepaymentEvent(2)// + .preClosureInterestCalculationStrategy(1)// + .principal((double) 40)// + .principalThresholdForLastInstallment(50)// + .principalVariationsForBorrowerCycle(new ArrayList<>())// + .recalculationRestFrequencyInterval(1)// + .recalculationRestFrequencyType(2)// + .repaymentEvery(1)// + .repaymentFrequencyType(2L)// + .repaymentStartDateType(2)// + .rescheduleStrategyMethod(4)// + .supportedInterestRefundTypes(new ArrayList<>())// + .transactionProcessingStrategyCode(TRANSACTION_PROCESSING_STRATEGY_CODE_ADVANCED)// + .useBorrowerCycle(false)// + .accountingRule(LOAN_ACCOUNTING_RULE)// + .fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT))// + .loanPortfolioAccountId(accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE))// + .transfersInSuspenseAccountId(accountTypeResolver.resolve(DefaultAccountType.TRANSFER_IN_SUSPENSE_ACCOUNT))// + .interestOnLoanAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))// + .incomeFromFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))// + .incomeFromPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))// + .incomeFromRecoveryAccountId(accountTypeResolver.resolve(DefaultAccountType.RECOVERIES))// + .writeOffAccountId(accountTypeResolver.resolve(DefaultAccountType.WRITTEN_OFF))// + .overpaymentLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))// + .receivableInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// + .receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// + .receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// + .goodwillCreditAccountId(accountTypeResolver.resolve(DefaultAccountType.GOODWILL_EXPENSE_ACCOUNT))// + .incomeFromGoodwillCreditInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))// + .incomeFromGoodwillCreditFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// + .incomeFromGoodwillCreditPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// + .paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)// + .penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)// + .feeToIncomeAccountMappings(feeToIncomeAccountMappings)// + .incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))// + .incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// + .chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))// + .chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))// + .incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));// } public PostLoanProductsRequest defaultLoanProductsRequestLP2() { @@ -806,116 +890,6 @@ public class LoanProductsRequestFactory { .incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));// } - public PostLoanProductsRequest defaultLoanProductsRequestLP2NoDown() { - String name = Utils.randomNameGenerator(NAME_PREFIX, 4); - String shortName = Utils.randomNameGenerator(SHORT_NAME_PREFIX, 3); - - List principalVariationsForBorrowerCycle = new ArrayList<>(); - List numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>(); - List interestRateVariationsForBorrowerCycle = new ArrayList<>(); - List charges = new ArrayList<>(); - List penaltyToIncomeAccountMappings = new ArrayList<>(); - List feeToIncomeAccountMappings = new ArrayList<>(); - - List paymentChannelToFundSourceMappings = new ArrayList<>(); - GetLoanPaymentChannelToFundSourceMappings loanPaymentChannelToFundSourceMappings = new GetLoanPaymentChannelToFundSourceMappings(); - loanPaymentChannelToFundSourceMappings.fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.FUND_RECEIVABLES)); - loanPaymentChannelToFundSourceMappings.paymentTypeId(paymentTypeResolver.resolve(DefaultPaymentType.MONEY_TRANSFER)); - paymentChannelToFundSourceMappings.add(loanPaymentChannelToFundSourceMappings); - - return new PostLoanProductsRequest()// - .name(name)// - .shortName(shortName)// - .description(DESCRIPTION_LP2)// - .fundId(FUND_ID)// - .startDate(null)// - .closeDate(null)// - .includeInBorrowerCycle(false)// - .currencyCode(CURRENCY_CODE)// - .digitsAfterDecimal(2)// - .inMultiplesOf(0)// - .installmentAmountInMultiplesOf(1)// - .useBorrowerCycle(false)// - .minPrincipal(100.0)// - .principal(1000.0)// - .maxPrincipal(10000.0)// - .minNumberOfRepayments(1)// - .numberOfRepayments(1)// - .maxNumberOfRepayments(30)// - .isLinkedToFloatingInterestRates(false)// - .minInterestRatePerPeriod((double) 0)// - .interestRatePerPeriod((double) 0)// - .maxInterestRatePerPeriod((double) 0)// - .interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_MONTH)// - .repaymentEvery(30)// - .repaymentFrequencyType(REPAYMENT_FREQUENCY_TYPE_DAYS)// - .principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)// - .numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)// - .interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)// - .amortizationType(AMORTIZATION_TYPE)// - .interestType(INTEREST_TYPE_DECLINING_BALANCE)// - .isEqualAmortization(false)// - .interestCalculationPeriodType(INTEREST_CALCULATION_PERIOD_TYPE_SAME_AS_REPAYMENT)// - .transactionProcessingStrategyCode(TRANSACTION_PROCESSING_STRATEGY_CODE)// - .daysInYearType(DAYS_IN_YEAR_TYPE)// - .daysInMonthType(DAYS_IN_MONTH_TYPE)// - .canDefineInstallmentAmount(true)// - .graceOnArrearsAgeing(3)// - .overdueDaysForNPA(179)// - .accountMovesOutOfNPAOnlyOnArrearsCompletion(false)// - .principalThresholdForLastInstallment(50)// - .allowVariableInstallments(false)// - .canUseForTopup(false)// - .isInterestRecalculationEnabled(false)// - .holdGuaranteeFunds(false)// - .multiDisburseLoan(true)// - .allowAttributeOverrides(new AllowAttributeOverrides()// - .amortizationType(true)// - .interestType(true)// - .transactionProcessingStrategyCode(true)// - .interestCalculationPeriodType(true)// - .inArrearsTolerance(true)// - .repaymentEvery(true)// - .graceOnPrincipalAndInterestPayment(true)// - .graceOnArrearsAgeing(true))// - .allowPartialPeriodInterestCalcualtion(true)// - .maxTrancheCount(10)// - .outstandingLoanBalance(10000.0)// - .charges(charges)// - .accountingRule(LOAN_ACCOUNTING_RULE)// - .fundSourceAccountId(accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT))// - .loanPortfolioAccountId(accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE))// - .transfersInSuspenseAccountId(accountTypeResolver.resolve(DefaultAccountType.TRANSFER_IN_SUSPENSE_ACCOUNT))// - .interestOnLoanAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME))// - .incomeFromFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))// - .incomeFromPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_INCOME))// - .incomeFromRecoveryAccountId(accountTypeResolver.resolve(DefaultAccountType.RECOVERIES))// - .writeOffAccountId(accountTypeResolver.resolve(DefaultAccountType.WRITTEN_OFF))// - .overpaymentLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.OVERPAYMENT_ACCOUNT))// - .receivableInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// - .receivableFeeAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// - .receivablePenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_FEE_RECEIVABLE))// - .dateFormat(DATE_FORMAT)// - .locale(LOCALE_EN)// - .disallowExpectedDisbursements(true)// - .allowApprovedDisbursedAmountsOverApplied(true)// - .overAppliedCalculationType(OVER_APPLIED_CALCULATION_TYPE)// - .overAppliedNumber(OVER_APPLIED_NUMBER)// - .delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())// - .goodwillCreditAccountId(accountTypeResolver.resolve(DefaultAccountType.GOODWILL_EXPENSE_ACCOUNT))// - .incomeFromGoodwillCreditInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))// - .incomeFromGoodwillCreditFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// - .incomeFromGoodwillCreditPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// - .paymentChannelToFundSourceMappings(paymentChannelToFundSourceMappings)// - .penaltyToIncomeAccountMappings(penaltyToIncomeAccountMappings)// - .feeToIncomeAccountMappings(feeToIncomeAccountMappings)// - .incomeFromChargeOffInterestAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME_CHARGE_OFF))// - .incomeFromChargeOffFeesAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF))// - .chargeOffExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT))// - .chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))// - .incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));// - } - public PostLoanProductsRequest defaultLoanProductsRequestLP2InterestFlat() { String name = Utils.randomNameGenerator(NAME_PREFIX_INTEREST_FLAT_LP2, 4); String shortName = Utils.randomNameGenerator(SHORT_NAME_PREFIX_INTEREST, 3); @@ -1136,5 +1110,4 @@ public class LoanProductsRequestFactory { .chargeOffFraudExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD))// .incomeFromChargeOffPenaltyAccountId(accountTypeResolver.resolve(DefaultAccountType.FEE_CHARGE_OFF));// } - } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java index 6de4bcb6c..c6e604b58 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java @@ -19,8 +19,6 @@ package org.apache.fineract.test.factory; import java.math.BigDecimal; -import java.time.Clock; -import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import lombok.RequiredArgsConstructor; @@ -39,6 +37,7 @@ import org.apache.fineract.test.data.RepaymentFrequencyType; import org.apache.fineract.test.data.TransactionProcessingStrategyCode; import org.apache.fineract.test.data.loanproduct.DefaultLoanProduct; import org.apache.fineract.test.data.loanproduct.LoanProductResolver; +import org.apache.fineract.test.helper.Utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -79,8 +78,10 @@ public class LoanRequestFactory { public static final String DEFAULT_TRANSACTION_PROCESSING_STRATEGY_CODE = TransactionProcessingStrategyCode.PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER.value; public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT); - public static final String DATE_SUBMIT_STRING = FORMATTER.format(LocalDate.now(Clock.systemUTC()).minusMonths(1L)); - public static final String DEFAULT_TRANSACTION_DATE = FORMATTER.format(LocalDate.now(Clock.systemUTC()).minusMonths(1L)); + public static final String DATE_SUBMIT_STRING = FORMATTER.format(Utils.now().minusMonths(1L)); + public static final String DATE_REJECT_STRING = FORMATTER.format(Utils.now().minusMonths(1L)); + public static final String DATE_WITHDRAWN_STRING = FORMATTER.format(Utils.now().minusMonths(1L)); + public static final String DEFAULT_TRANSACTION_DATE = FORMATTER.format(Utils.now().minusMonths(1L)); public PostLoansRequest defaultLoansRequest(Long clientId) { return new PostLoansRequest()// @@ -108,7 +109,7 @@ public class LoanRequestFactory { public PutLoansLoanIdRequest modifySubmittedOnDateOnLoan(Long clientId, String newSubmittedOnDate) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); - String dateDisburseStr = formatter.format(LocalDate.now(Clock.systemUTC())); + String dateDisburseStr = formatter.format(Utils.now()); return new PutLoansLoanIdRequest()// .productId(loanProductResolver.resolve(DEFAULT_LOAN_PRODUCT))// @@ -162,6 +163,20 @@ public class LoanRequestFactory { .locale(DEFAULT_LOCALE);// } + public static PostLoansLoanIdRequest defaultLoanRejectRequest() { + return new PostLoansLoanIdRequest()// + .rejectedOnDate(DATE_REJECT_STRING)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } + + public static PostLoansLoanIdRequest defaultLoanWithdrawnRequest() { + return new PostLoansLoanIdRequest()// + .withdrawnOnDate(DATE_WITHDRAWN_STRING)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } + public static PostLoansLoanIdRequest defaultLoanDisburseRequest() { return new PostLoansLoanIdRequest().actualDisbursementDate(DATE_SUBMIT_STRING).transactionAmount(DEFAULT_DISBURSED_AMOUNT) .paymentTypeId(Math.toIntExact(DEFAULT_PAYMENT_TYPE_ID)).dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsAccountRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsAccountRequestFactory.java new file mode 100644 index 000000000..bb4564e21 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsAccountRequestFactory.java @@ -0,0 +1,91 @@ +/** + * 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.test.factory; + +import java.math.BigDecimal; +import org.apache.fineract.client.models.PostSavingsAccountTransactionsRequest; +import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest; +import org.apache.fineract.client.models.PostSavingsAccountsRequest; + +public final class SavingsAccountRequestFactory { + + public static final String DATE_FORMAT = "dd MMMM yyyy"; + public static final String DEFAULT_LOCALE = "en"; + public static final String DEFAULT_TRANSACTION_DATE = ""; + public static final Integer DEFAULT_CLIENT_ID = 1; + public static final String DEFAULT_SUBMITTED_ON_DATE = ""; + public static final String DEFAULT_APPROVED_ON_DATE = ""; + public static final String DEFAULT_ACTIVATED_ON_DATE = ""; + public static final BigDecimal DEFAULT_REPAYMENT_TRANSACTION_AMOUNT = new BigDecimal(1); + public static final Integer DEFAULT_PAYMENT_TYPE_ID = 2; + public static final Integer EUR_SAVING_PRODUCT_ID = 1; + public static final Integer USD_SAVING_PRODUCT_ID = 2; + + private SavingsAccountRequestFactory() {} + + public static PostSavingsAccountsRequest defaultEURSavingsAccountRequest() { + return new PostSavingsAccountsRequest()// + .clientId(DEFAULT_CLIENT_ID)// + .dateFormat(DATE_FORMAT)// + .productId(EUR_SAVING_PRODUCT_ID)// + .submittedOnDate(DEFAULT_SUBMITTED_ON_DATE)// + .locale(DEFAULT_LOCALE);// + } + + public static PostSavingsAccountsRequest defaultUSDSavingsAccountRequest() { + return new PostSavingsAccountsRequest()// + .clientId(DEFAULT_CLIENT_ID)// + .dateFormat(DATE_FORMAT)// + .productId(USD_SAVING_PRODUCT_ID)// + .submittedOnDate(DEFAULT_SUBMITTED_ON_DATE)// + .locale(DEFAULT_LOCALE);// + } + + public static PostSavingsAccountsAccountIdRequest defaultApproveRequest() { + return new PostSavingsAccountsAccountIdRequest()// + .approvedOnDate(DEFAULT_APPROVED_ON_DATE)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } + + public static PostSavingsAccountsAccountIdRequest defaultActivateRequest() { + return new PostSavingsAccountsAccountIdRequest()// + .activatedOnDate(DEFAULT_ACTIVATED_ON_DATE)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } + + public static PostSavingsAccountTransactionsRequest defaultDepositRequest() { + return new PostSavingsAccountTransactionsRequest()// + .transactionDate(DEFAULT_TRANSACTION_DATE)// + .transactionAmount(DEFAULT_REPAYMENT_TRANSACTION_AMOUNT)// + .paymentTypeId(DEFAULT_PAYMENT_TYPE_ID)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } + + public static PostSavingsAccountTransactionsRequest defaultWithdrawRequest() { + return new PostSavingsAccountTransactionsRequest()// + .transactionDate(DEFAULT_TRANSACTION_DATE)// + .transactionAmount(DEFAULT_REPAYMENT_TRANSACTION_AMOUNT)// + .paymentTypeId(DEFAULT_PAYMENT_TYPE_ID)// + .dateFormat(DATE_FORMAT)// + .locale(DEFAULT_LOCALE);// + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsProductRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsProductRequestFactory.java new file mode 100644 index 000000000..54baa519b --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/SavingsProductRequestFactory.java @@ -0,0 +1,63 @@ +/** + * 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.test.factory; + +import java.util.HashSet; +import java.util.Set; +import org.apache.fineract.client.models.PostSavingsCharges; +import org.apache.fineract.client.models.PostSavingsProductsRequest; + +public final class SavingsProductRequestFactory { + + public static final String DEFAULT_SAVINGS_PRODUCT_NAME = "CEUR"; + + public static final String DEFAULT_SAVINGS_PRODUCT_SHORT_NAME = "CEU"; + public static final String DEFAULT_SAVINGS_PRODUCT_DESCRIPTION = ""; + public static final String DEFAULT_SAVINGS_PRODUCT_CURRENCY_CODE = "EUR"; + public static final Integer DEFAULT_SAVINGS_PRODUCT_DIGITS_AFTER_DECIMAL = 2; + public static final Integer DEFAULT_SAVINGS_PRODUCT_IN_MULTIPLES_OF = 0; + public static final Double DEFAULT_SAVINGS_PRODUCT_NOMINAL_ANNUAL_INTEREST_RATE = 0.0; + public static final Integer DEFAULT_SAVINGS_PRODUCT_INTEREST_COMPOUNDING_PERIOD_TIME = 1; + public static final Integer DEFAULT_SAVINGS_PRODUCT_INTEREST_POSTING_PERIOD_TIME = 4; + public static final Integer DEFAULT_SAVINGS_PRODUCT_INTEREST_CALCULATION_TYPE = 1; + public static final Integer DEFAULT_SAVINGS_PRODUCT_INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE = 365; + public static final Integer DEFAULT_SAVINGS_PRODUCT_ACCOUNTING_RULE = 1; + public static final String LOCALE_EN = "en"; + + private SavingsProductRequestFactory() {} + + public static PostSavingsProductsRequest defaultSavingsProductRequest() { + Set charges = new HashSet<>(); + + return new PostSavingsProductsRequest().name(DEFAULT_SAVINGS_PRODUCT_NAME)// + .shortName(DEFAULT_SAVINGS_PRODUCT_SHORT_NAME)// + .description(DEFAULT_SAVINGS_PRODUCT_DESCRIPTION)// + .currencyCode(DEFAULT_SAVINGS_PRODUCT_CURRENCY_CODE)// + .digitsAfterDecimal(DEFAULT_SAVINGS_PRODUCT_DIGITS_AFTER_DECIMAL)// + .inMultiplesOf(DEFAULT_SAVINGS_PRODUCT_IN_MULTIPLES_OF)// + .nominalAnnualInterestRate(DEFAULT_SAVINGS_PRODUCT_NOMINAL_ANNUAL_INTEREST_RATE)// + .interestCompoundingPeriodType(DEFAULT_SAVINGS_PRODUCT_INTEREST_COMPOUNDING_PERIOD_TIME)// + .interestPostingPeriodType(DEFAULT_SAVINGS_PRODUCT_INTEREST_POSTING_PERIOD_TIME)// + .interestCalculationType(DEFAULT_SAVINGS_PRODUCT_INTEREST_CALCULATION_TYPE)// + .interestCalculationDaysInYearType(DEFAULT_SAVINGS_PRODUCT_INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE)// + .charges(charges)// + .accountingRule(DEFAULT_SAVINGS_PRODUCT_ACCOUNTING_RULE)// + .locale(LOCALE_EN);// + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/BusinessDateHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/BusinessDateHelper.java index 5553b9a11..c4411ffc1 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/BusinessDateHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/BusinessDateHelper.java @@ -19,8 +19,6 @@ package org.apache.fineract.test.helper; import java.io.IOException; -import java.time.Clock; -import java.time.LocalDate; import java.time.format.DateTimeFormatter; import lombok.RequiredArgsConstructor; import org.apache.fineract.client.models.BusinessDateRequest; @@ -53,7 +51,7 @@ public class BusinessDateHelper { public void setBusinessDateToday() throws IOException { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); - String today = formatter.format(LocalDate.now(Clock.systemUTC())); + String today = formatter.format(Utils.now()); setBusinessDate(today); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index 469c96d9b..0c1033edf 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -24,7 +24,6 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.client.models.BatchResponse; import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; @@ -63,14 +62,14 @@ public final class ErrorMessageHelper { return String.format("The date on which a loan with identifier : %s is disbursed cannot be in the future.", loanIdStr); } - public static String disburseMaxAmountFailure() { - return "Loan disbursal amount can't be greater than maximum applied loan amount calculation. Total disbursed amount: [0-9]* Maximum disbursal amount: [0-9]*"; - } - public static String disbursePastDateFailure(Integer loanId, String actualDisbursementDate) { return String.format("The date on which a loan is disbursed cannot be before its approval date: %s", actualDisbursementDate); } + public static String disburseMaxAmountFailure() { + return "Loan disbursal amount can't be greater than maximum applied loan amount calculation. Total disbursed amount: [0-9]* Maximum disbursal amount: [0-9]*"; + } + public static String loanSubmitDateInFutureFailureMsg() { return "The date on which a loan is submitted cannot be in the future."; } @@ -787,6 +786,26 @@ public final class ErrorMessageHelper { sb.toString(), expectedStr); } + public static String wrongValueInLineInLoanTermVariations(int line, List> actual, List expected) { + String lineStr = String.valueOf(line); + String expectedStr = expected.toString(); + StringBuilder sb = new StringBuilder(); + for (List innerList : actual) { + sb.append(innerList.toString()); + sb.append(System.lineSeparator()); + } + + return String.format("%nWrong value in Loan Term Variations line %s. %nActual values in line: %s %nExpected values in line: %s", + lineStr, sb.toString(), expectedStr); + } + + public static String wrongNumberOfLinesInLoanTermVariations(int actual, int expected) { + String actualStr = String.valueOf(actual); + String expectedStr = String.valueOf(expected); + return String.format("Number of items (lines) in Loan Term Variations is not correct. Actual value is: %s - Expected value is: %s", + actualStr, expectedStr); + } + public static String nrOfLinesWrongInLoanDelinquencyPauseData(int actual, int expected) { String actualStr = String.valueOf(actual); String expectedStr = String.valueOf(expected); @@ -866,17 +885,7 @@ public final class ErrorMessageHelper { expectedToStr); } - public static String wrongValueInLineInLoanTermVariations(final int line, final List> actual, - final List expected) { - final String actualValues = actual.stream().map(List::toString).collect(Collectors.joining(System.lineSeparator())); - - return String.format( - "%nWrong value in loan term variations tab line %d.%nActual values in line (with the same term variation applicable from) are:%n%s%nExpected values in line:%n%s", - line, actualValues, expected); - } - - public static String wrongNumberOfLinesInLoanTermVariations(final int actual, final int expected) { - return String.format("Number of lines in loan term variations is not correct. Actual value is: %d - Expected value is: %d", actual, - expected); + public static String downpaymentDisabledOnProductErrorCodeMsg() { + return "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled"; } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java index f4e882ddc..70cfa1802 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java @@ -19,6 +19,8 @@ package org.apache.fineract.test.helper; import java.security.SecureRandom; +import java.time.Clock; +import java.time.LocalDate; public final class Utils { @@ -42,4 +44,8 @@ public final class Utils { public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) { return randomStringGenerator(prefix, lenOfRandomSuffix); } + + public static LocalDate now() { + return LocalDate.now(Clock.systemUTC()); + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java new file mode 100644 index 000000000..c77b2e921 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java @@ -0,0 +1,49 @@ +/** + * 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.test.helper; + +import java.io.IOException; +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.models.BusinessStep; +import org.apache.fineract.client.models.GetBusinessStepConfigResponse; +import org.apache.fineract.client.services.BusinessStepConfigurationApi; +import org.apache.fineract.test.support.TestContext; +import org.apache.fineract.test.support.TestContextKey; +import org.springframework.stereotype.Component; +import retrofit2.Response; + +@RequiredArgsConstructor +@Component +public class WorkFlowJobHelper { + + private static final String WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS = "LOAN_CLOSE_OF_BUSINESS"; + + private final BusinessStepConfigurationApi businessStepConfigurationApi; + + public void saveOriginalCOBWorkflowJobBusinessStepList() throws IOException { + Response businessStepConfigResponse = businessStepConfigurationApi + .retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute(); + ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse); + List businessSteps = businessStepConfigResponse.body().getBusinessSteps(); + businessSteps.sort(Comparator.comparingLong(BusinessStep::getOrder)); + TestContext.GLOBAL.set(TestContextKey.ORIGINAL_COB_WORKFLOW_JOB_BUSINESS_STEP_LIST, businessSteps); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java index f1a624e97..afb74e3ec 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java @@ -21,6 +21,7 @@ package org.apache.fineract.test.initializer.base; import java.util.List; import org.apache.fineract.test.config.CacheConfiguration; import org.apache.fineract.test.helper.BusinessDateHelper; +import org.apache.fineract.test.helper.WorkFlowJobHelper; import org.apache.fineract.test.initializer.global.FineractGlobalInitializerStep; import org.apache.fineract.test.initializer.scenario.FineractScenarioInitializerStep; import org.apache.fineract.test.initializer.suite.FineractSuiteInitializerStep; @@ -39,7 +40,8 @@ public class BaseFineractInitializerConfiguration { @Bean public FineractInitializer fineractInitializer(List globalInitializerSteps, List suiteInitializerSteps, List scenarioInitializerSteps, - BusinessDateHelper businessDateHelper) { - return new FineractInitializer(globalInitializerSteps, suiteInitializerSteps, scenarioInitializerSteps, businessDateHelper); + BusinessDateHelper businessDateHelper, WorkFlowJobHelper workFlowJobHelper) { + return new FineractInitializer(globalInitializerSteps, suiteInitializerSteps, scenarioInitializerSteps, businessDateHelper, + workFlowJobHelper); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/FineractInitializer.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/FineractInitializer.java index 4e5d680d0..a67ce2125 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/FineractInitializer.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/FineractInitializer.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.test.helper.BusinessDateHelper; +import org.apache.fineract.test.helper.WorkFlowJobHelper; import org.apache.fineract.test.initializer.global.FineractGlobalInitializerStep; import org.apache.fineract.test.initializer.scenario.FineractScenarioInitializerStep; import org.apache.fineract.test.initializer.suite.FineractSuiteInitializerStep; @@ -38,6 +39,7 @@ public class FineractInitializer implements InitializingBean { private final List suiteInitializerSteps; private final List scenarioInitializerSteps; private final BusinessDateHelper businessDateHelper; + private final WorkFlowJobHelper workFlowJobHelper; @Override public void afterPropertiesSet() throws Exception { @@ -71,6 +73,7 @@ public class FineractInitializer implements InitializingBean { } businessDateHelper.setBusinessDateToday(); + workFlowJobHelper.saveOriginalCOBWorkflowJobBusinessStepList(); } public void setupDefaultsForScenario() throws Exception { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CobBusinessStepInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CobBusinessStepInitializerStep.java new file mode 100644 index 000000000..4167d36cc --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CobBusinessStepInitializerStep.java @@ -0,0 +1,59 @@ +/** + * 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.test.initializer.global; + +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.models.BusinessStep; +import org.apache.fineract.client.models.GetBusinessStepConfigResponse; +import org.apache.fineract.client.models.UpdateBusinessStepConfigRequest; +import org.apache.fineract.client.services.BusinessStepConfigurationApi; +import org.apache.fineract.test.helper.ErrorHelper; +import org.springframework.stereotype.Component; +import retrofit2.Response; + +@RequiredArgsConstructor +@Component +public class CobBusinessStepInitializerStep implements FineractGlobalInitializerStep { + + private static final String WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS = "LOAN_CLOSE_OF_BUSINESS"; + private static final String BUSINESS_STEP_NAME_ACCRUAL_ACTIVITY_POSTING = "ACCRUAL_ACTIVITY_POSTING"; + private final BusinessStepConfigurationApi businessStepConfigurationApi; + + @Override + public void initialize() throws Exception { + // --- Adding ACCRUAL_ACTIVITY_POSTING to default COB steps --- + Response businessStepConfigResponse = businessStepConfigurationApi + .retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute(); + ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse); + List businessSteps = businessStepConfigResponse.body().getBusinessSteps(); + businessSteps.sort(Comparator.comparingLong(BusinessStep::getOrder)); + Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder(); + + BusinessStep accrualActivityPosting = new BusinessStep().stepName(BUSINESS_STEP_NAME_ACCRUAL_ACTIVITY_POSTING).order(lastOrder + 1); + businessSteps.add(accrualActivityPosting); + + UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); + + Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) + .execute(); + ErrorHelper.checkSuccessfulApiCall(response); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index 4d873d9dc..0fecd42a6 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -19,7 +19,9 @@ package org.apache.fineract.test.initializer.global; import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.INTEREST_RATE_FREQUENCY_TYPE_WHOLE_TERM; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -479,13 +481,243 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH, responseLoanProductsRequestDownPaymentAdvPmtAllocFixedLength); + // LP2 with Down-payment+autopayment + advanced payment allocation + repayment start date SUBMITTED ON DATE + // (LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION_REPAYMENT_START_SUBMITTED) + String name26 = DefaultLoanProduct.LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION_REPAYMENT_START_SUBMITTED.getName(); + PostLoanProductsRequest loanProductsRequestDownPaymentAutoAdvPaymentAllocationRepaymentStartSubmitted = loanProductsRequestFactory + .defaultLoanProductsRequestLP2()// + .name(name26)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// + .loanScheduleType("PROGRESSIVE") // + .repaymentStartDateType(2)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestDownPaymentAutoAdvPaymentAllocationRepaymentStartSubmitted = loanProductsApi + .createLoanProduct(loanProductsRequestDownPaymentAutoAdvPaymentAllocationRepaymentStartSubmitted).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_AUTO_ADVANCED_REPAYMENT_ALLOCATION_PAYMENT_START_SUBMITTED, + responseLoanProductsRequestDownPaymentAutoAdvPaymentAllocationRepaymentStartSubmitted); + + // LP2 with Down-payment + advanced payment allocation + progressive loan schedule + horizontal + interest Flat + // (LP2_DOWNPAYMENT_INTEREST_FLAT_ADV_PMT_ALLOC) + String name27 = DefaultLoanProduct.LP2_DOWNPAYMENT_INTEREST_FLAT_ADV_PMT_ALLOC.getName(); + PostLoanProductsRequest loanProductsRequestDownPaymentAdvPaymentAllocationInterestFlat = loanProductsRequestFactory + .defaultLoanProductsRequestLP2InterestFlat()// + .name(name27)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// + .loanScheduleType("PROGRESSIVE") // + .loanScheduleProcessingType("HORIZONTAL")// + .enableAutoRepaymentForDownPayment(false)// + .installmentAmountInMultiplesOf(null)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestDownPaymentAdvPaymentAllocationInterestFlat = loanProductsApi + .createLoanProduct(loanProductsRequestDownPaymentAdvPaymentAllocationInterestFlat).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_INTEREST_FLAT_ADV_PMT_ALLOC, + responseLoanProductsRequestDownPaymentAdvPaymentAllocationInterestFlat); + + // LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL) + String name28 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActual = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name28)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActual = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActual).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActual); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30) + String name29 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030 = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name29)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030 = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE) + String name36 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030MultiDisburse = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name36)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))// + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(true)// + .maxTrancheCount(10)// + .outstandingLoanBalance(10000.0);// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030MultiDisburse = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030MultiDisburse).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030MultiDisburse); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement + downpayment + // 25%, auto disabled + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT) + String name37 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030MultiDisburseDownPayment = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name37)// + .enableDownPayment(true)// + .disbursedAmountPercentageForDownPayment(new BigDecimal(25))// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))// + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(true)// + .maxTrancheCount(10)// + .outstandingLoanBalance(10000.0);// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030MultiDisburseDownPayment = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030MultiDisburseDownPayment).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030MultiDisburseDownPayment); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 365/Actual + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL) + String name30 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterest365Actual = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name30)// + .daysInYearType(DaysInYearType.DAYS365.value)// + .daysInMonthType(DaysInMonthType.ACTUAL.value)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmi365Actual = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterest365Actual).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmi365Actual); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + downpayment 25% + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT) + String name31 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterest36030Downpayment = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name31)// + .enableDownPayment(true)// + .disbursedAmountPercentageForDownPayment(new BigDecimal(25))// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030Downpayment = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterest36030Downpayment).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030Downpayment); + + // LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + + // enableAccrualActivityPosting + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY) + String name32 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualAccrualActivity = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name32)// + .enableAccrualActivityPosting(true)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualAccrualActivity = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualAccrualActivity).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualAccrualActivity); + + // LP1 with 12% DECLINING BALANCE interest, interest period: Daily + enableAccrualActivityPosting + // (LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY_ACCRUAL_ACTIVITY) + String name33 = DefaultLoanProduct.LP1_INTEREST_DECLINING_BALANCE_PERIOD_DAILY_ACCRUAL_ACTIVITY.getName(); + PostLoanProductsRequest loanProductsRequestInterestDecliningPeriodDailyAccrualActivity = loanProductsRequestFactory + .defaultLoanProductsRequestLP1InterestDeclining()// + .name(name33)// + .enableAccrualActivityPosting(true)// + .interestCalculationPeriodType(InterestCalculationPeriodTime.DAILY.value)// + .allowPartialPeriodInterestCalcualtion(false);// + Response responseInterestDecliningPeriodDailyAccrualActivity = loanProductsApi + .createLoanProduct(loanProductsRequestInterestDecliningPeriodDailyAccrualActivity).execute(); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_PERIOD_DAILY_ACCRUAL_ACTIVITY, + responseInterestDecliningPeriodDailyAccrualActivity); + + // LP1 with 12% DECLINING BALANCE interest, interest period: Daily, Interest + // recalculation-Daily, Compounding:none + enableAccrualActivityPosting + // (LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_ACCRUAL_ACTIVITY) + String name34 = DefaultLoanProduct.LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_ACCRUAL_ACTIVITY.getName(); + PostLoanProductsRequest loanProductsRequestLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity = loanProductsRequestFactory + .defaultLoanProductsRequestLP1InterestDecliningBalanceDailyRecalculationCompoundingNone()// + .name(name34)// + .enableAccrualActivityPosting(true)// + .interestCalculationPeriodType(InterestCalculationPeriodTime.DAILY.value)// + .allowPartialPeriodInterestCalcualtion(false);// + Response responseLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity = loanProductsApi + .createLoanProduct(loanProductsRequestLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity) + .execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_ACCRUAL_ACTIVITY, + responseLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity); + + // LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + interest refund with + // Merchant issued and Payment refund + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND) + String name35 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND.getName(); + List supportedInterestRefundTypes = Arrays.asList("MERCHANT_ISSUED_REFUND", "PAYOUT_REFUND"); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefund = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name35)// + .supportedInterestRefundTypes(supportedInterestRefundTypes).paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"), // + createPaymentAllocation("INTEREST_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefund = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefund).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefund); + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 // + interest recalculation, preClosureInterestCalculationStrategy= till preclose, // interestRecalculationCompoundingMethod = none // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 - // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE) - String name38 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE.getName(); - PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillPreclose = loanProductsRequestFactory + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE) + String name38 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPreclose = loanProductsRequestFactory .defaultLoanProductsRequestLP2Emi()// .name(name38)// .daysInYearType(DaysInYearType.DAYS360.value)// @@ -497,20 +729,24 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ .recalculationRestFrequencyType(2)// .recalculationRestFrequencyInterval(1)// .paymentAllocation(List.of(// - createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT")));// - Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillPreClose = loanProductsApi - .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillPreclose).execute(); + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillPreCloese = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPreclose).execute(); TestContext.INSTANCE.set( - TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE, - responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillPreClose); + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillPreCloese); // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 - // + interest recalculation, preClosureInterestCalculationStrategy= till rest frequency, + // + interest recalculation, preClosureInterestCalculationStrategy= till rest frequency date, // interestRecalculationCompoundingMethod = none // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 - // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY) - String name39 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY.getName(); - PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillRestFrequency = loanProductsRequestFactory + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE) + String name39 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE + .getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillRestFrequencyDate = loanProductsRequestFactory .defaultLoanProductsRequestLP2Emi()// .name(name39)// .daysInYearType(DaysInYearType.DAYS360.value)// @@ -526,16 +762,78 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// - Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillRestFrequency = loanProductsApi - .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillRestFrequency).execute(); + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillRestFrequencyDate = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillRestFrequencyDate).execute(); TestContext.INSTANCE.set( - TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY, - responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillRestFrequency); + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillRestFrequencyDate); - String name40 = DefaultLoanProduct.LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL.getName(); - PostLoanProductsRequest loanProductsRequestLP2AdvPmtAllocProgressiveLoanScheduleHorizontal = loanProductsRequestFactory - .defaultLoanProductsRequestLP2NoDown()// + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // + interest recalculation, preClosureInterestCalculationStrategy= till preclose, + // interestRecalculationCompoundingMethod = none + // Frequency for recalculate Outstanding Principal: Same as repayment period, Frequency Interval for + // recalculation: 1 + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_PRECLOSE) + String name40 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_PRECLOSE.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcSameAsRepTillPreclose = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// .name(name40)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(1)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcSameAsRepTillPreCloese = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcSameAsRepTillPreclose).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_PRECLOSE, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcSameAsRepTillPreCloese); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // + interest recalculation, preClosureInterestCalculationStrategy= till rest frequency date, + // interestRecalculationCompoundingMethod = none + // Frequency for recalculate Outstanding Principal: Same as repayment period, Frequency Interval for + // recalculation: 1 + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_REST_FREQUENCY_DATE) + String name41 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_REST_FREQUENCY_DATE + .getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcSameAsRepTillRestFrequencyDate = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name41)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(2)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(1)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcSameAsRepTillRestFrequencyDate = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcSameAsRepTillRestFrequencyDate) + .execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_REST_FREQUENCY_DATE, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcSameAsRepTillRestFrequencyDate); + + // LP1 advanced payment allocation + progressive loan schedule + horizontal + // (LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL) + String name42 = DefaultLoanProduct.LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL.getName(); + PostLoanProductsRequest loanProductsRequestLP1AdvPmtAllocProgressiveLoanScheduleHorizontal = loanProductsRequestFactory// + .defaultLoanProductsRequestLP1()// + .name(name42)// .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// .loanScheduleType("PROGRESSIVE") // .loanScheduleProcessingType("HORIZONTAL")// @@ -544,20 +842,22 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ createPaymentAllocation("GOODWILL_CREDIT", "NEXT_INSTALLMENT"), // createPaymentAllocation("MERCHANT_ISSUED_REFUND", "NEXT_INSTALLMENT"), // createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// - Response responseLP2AdvPmtAllocProgressiveLoanScheduleHorizontal = loanProductsApi - .createLoanProduct(loanProductsRequestLP2AdvPmtAllocProgressiveLoanScheduleHorizontal).execute(); - TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, - responseLP2AdvPmtAllocProgressiveLoanScheduleHorizontal); + Response responseLP1AdvPmtAllocProgressiveLoanScheduleHorizontal = loanProductsApi + .createLoanProduct(loanProductsRequestLP1AdvPmtAllocProgressiveLoanScheduleHorizontal).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, + responseLP1AdvPmtAllocProgressiveLoanScheduleHorizontal); - // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 // + interest recalculation, preClosureInterestCalculationStrategy= till preclose, // interestRecalculationCompoundingMethod = none // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 - // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE) - String name41 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY + // Frequency for Interest rate - Whole Year + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM) + String name43 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM .getName(); - PostLoanProductsRequest product41 = loanProductsRequestFactory.defaultLoanProductsRequestLP2Emi()// - .name(name41)// + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseWholeTerm = loanProductsRequestFactory// + .defaultLoanProductsRequestLP2Emi()// + .name(name43)// .daysInYearType(DaysInYearType.DAYS360.value)// .daysInMonthType(DaysInMonthType.DAYS30.value)// .isInterestRecalculationEnabled(true)// @@ -566,19 +866,25 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ .interestRecalculationCompoundingMethod(0)// .recalculationRestFrequencyType(2)// .recalculationRestFrequencyInterval(1)// + .interestRatePerPeriod((double) 4)// + .interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_WHOLE_TERM)// .paymentAllocation(List.of(// - createPaymentAllocation("DEFAULT", "LAST_INSTALLMENT")));// - Response response41 = loanProductsApi.createLoanProduct(product41).execute(); + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseWholeTerm = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseWholeTerm).execute(); TestContext.INSTANCE.set( - TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_LAST, - response41); + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM, + responseLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseWholeTerm); - // LP2 with Down-payment + advanced custom payment allocation + progressive loan schedule + horizontal - // (LP2_DOWNPAYMENT_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL) - String name44 = DefaultLoanProduct.LP2_ADV_CUST_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL.getName(); + // LP2 + interest recalculation + advanced custom payment allocation + progressive loan schedule + horizontal + // (LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL) + String name44 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL.getName(); PostLoanProductsRequest loanProductsRequestAdvCustomPaymentAllocationProgressiveLoanSchedule = loanProductsRequestFactory - .defaultLoanProductsRequestLP2InterestDailyRecalculationWithDownPayment()// + .defaultLoanProductsRequestLP2InterestDailyRecalculation()// .name(name44)// .paymentAllocation(List.of(// createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT", @@ -602,6 +908,88 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ TestContext.INSTANCE.set( TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE, responseLoanProductsRequestAdvCustomPaymentAllocationProgressiveLoanSchedule); + + String name45 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL.getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundFull = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name45)// + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(true)// + .maxTrancheCount(10)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .supportedInterestRefundTypes(supportedInterestRefundTypes).paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "NEXT_INSTALLMENT"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"), // + createPaymentAllocation("INTEREST_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundFull = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundFull).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL, + responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundFull); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // + interest recalculation, preClosureInterestCalculationStrategy= till preclose, + // interestRecalculationCompoundingMethod = none + // payment allocation order: penalty-fee-interest-principal + // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_PMT_ALLOC_1) + String name46 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_PMT_ALLOC_1 + .getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPreclosePmtAlloc1 = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name46)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocationPenFeeIntPrincipal("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocationPenFeeIntPrincipal("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocationPenFeeIntPrincipal("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocationPenFeeIntPrincipal("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + Response responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillPreCloesePmtAlloc1 = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPreclosePmtAlloc1).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_PMT_ALLOC_1, + responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcDailyTillPreCloesePmtAlloc1); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30, LAST INSTALLMENT strategy + // + interest recalculation, preClosureInterestCalculationStrategy= till preclose, + // interestRecalculationCompoundingMethod = none + // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 + // (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY) + String name47 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY + .getName(); + PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseLastInstallment = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name47)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "LAST_INSTALLMENT")));// + Response loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseLastInstallmentResponse = loanProductsApi + .createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseLastInstallment) + .execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT, + loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseLastInstallmentResponse); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, @@ -634,6 +1022,46 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ return advancedPaymentData; } + public static AdvancedPaymentData createPaymentAllocationPenFeeIntPrincipal(String transactionType, + String futureInstallmentAllocationRule, LoanProductPaymentAllocationRule.AllocationTypesEnum... rules) { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType(transactionType); + advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule); + + List paymentAllocationOrders; + if (rules.length == 0) { + paymentAllocationOrders = getPaymentAllocationOrder(// + LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST, // + LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL);// + } else { + paymentAllocationOrders = getPaymentAllocationOrder(rules); + } + + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders); + + return advancedPaymentData; + } + + public static AdvancedPaymentData editPaymentAllocationFutureInstallment(String transactionType, String futureInstallmentAllocationRule, + List paymentAllocationOrder) { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType(transactionType); + advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule); + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrder); + + return advancedPaymentData; + } + private static CreditAllocationData createCreditAllocation(String transactionType, List creditAllocationRules) { CreditAllocationData creditAllocationData = new CreditAllocationData(); creditAllocationData.setTransactionType(transactionType); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/SavingsProductGlobalInitializer.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/SavingsProductGlobalInitializer.java new file mode 100644 index 000000000..812180796 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/SavingsProductGlobalInitializer.java @@ -0,0 +1,53 @@ +/** + * 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.test.initializer.global; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class SavingsProductGlobalInitializer implements FineractGlobalInitializerStep { + + @Override + public void initialize() throws Exception { + /** + * TODO uncomment and check when PS-1088 is done + */ + // //EUR + // PostSavingsProductsRequest savingsProductsRequestEUR = + // SavingsProductRequestFactory.defaultSavingsProductRequest(); + // + // Response savingsProductsResponseEUR = + // savingsProductApi.create13(savingsProductsRequestEUR).execute(); + // testContext().set(TestContextKey.DEFAULT_SAVINGS_PRODUCT_CREATE_RESPONSE_EUR, savingsProductsResponseEUR); + // + // //USD + // PostSavingsProductsRequest savingsProductsRequestUSD = + // SavingsProductRequestFactory.defaultSavingsProductRequest() + // .name("CUSD") + // .shortName("CUS") + // .currencyCode("USD"); + // + // Response savingsProductsResponseUSD = + // savingsProductApi.create13(savingsProductsRequestUSD).execute(); + // testContext().set(TestContextKey.DEFAULT_SAVINGS_PRODUCT_CREATE_RESPONSE_USD, savingsProductsResponseUSD); + + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EmptyEventMessage.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EmptyEventMessage.java index 402261dc5..22601e78f 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EmptyEventMessage.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EmptyEventMessage.java @@ -21,6 +21,6 @@ package org.apache.fineract.test.messaging; public class EmptyEventMessage extends EventMessage { public EmptyEventMessage() { - super(null, null, null); + super(null, null, null, null); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java index 3cc7bd0bb..aac1cd885 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventAssertion.java @@ -21,13 +21,13 @@ package org.apache.fineract.test.messaging; import static java.lang.String.format; import static org.awaitility.Awaitility.await; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDate; import java.util.function.Function; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.test.messaging.config.EventProperties; import org.apache.fineract.test.messaging.event.Event; import org.apache.fineract.test.messaging.event.EventFactory; @@ -39,7 +39,7 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor @Component -@SuppressFBWarnings({ "VA_FORMAT_STRING_USES_NEWLINE" }) +@Slf4j public class EventAssertion { private final EventStore eventStore; @@ -85,11 +85,8 @@ public class EventAssertion { String receivedEventsLogParam = eventStore.getReceivedEvents().stream().map(LoggedEvent::new).map(LoggedEvent::toString) .reduce("", (s, e) -> format("%s%s%n", s, e)); - Assertions.fail(""" - %s has been received, but it was unexpected. - Events received but not verified: - %s - """.formatted(event.getEventName(), receivedEventsLogParam)); + Assertions.fail("%s has been received, but it was unexpected. Events received but not verified: %s", event.getEventName(), + receivedEventsLogParam); } catch (ConditionTimeoutException e) { // This is the expected outcome here! } @@ -104,6 +101,7 @@ public class EventAssertion { } else { eventMessage = (EventMessage) new EmptyEventMessage(); } + log.info("Assert event: {}", eventMessage.getIdempotencyKey()); return new EventAssertionBuilder<>(eventMessage); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventMessage.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventMessage.java index ca039edaa..85939f1a9 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventMessage.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/EventMessage.java @@ -29,4 +29,5 @@ public class EventMessage { private final String type; private final LocalDate businessDate; private final T data; + private final String idempotencyKey; } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java index 83eedb213..eba050c47 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.math.BigDecimal; +import java.math.MathContext; import java.time.format.DateTimeFormatter; import java.util.List; import lombok.RequiredArgsConstructor; @@ -55,15 +56,21 @@ import org.apache.fineract.test.messaging.event.assetexternalization.LoanAccount import org.apache.fineract.test.messaging.event.assetexternalization.LoanOwnershipTransferEvent; import org.apache.fineract.test.messaging.event.client.ClientActivatedEvent; import org.apache.fineract.test.messaging.event.client.ClientCreatedEvent; +import org.apache.fineract.test.messaging.event.loan.AbstractLoanEvent; import org.apache.fineract.test.messaging.event.loan.LoanApprovedEvent; +import org.apache.fineract.test.messaging.event.loan.LoanBalanceChangedEvent; import org.apache.fineract.test.messaging.event.loan.LoanCreatedEvent; import org.apache.fineract.test.messaging.event.loan.LoanDisbursalEvent; +import org.apache.fineract.test.messaging.event.loan.LoanRejectedEvent; +import org.apache.fineract.test.messaging.event.loan.LoanStatusChangedEvent; +import org.apache.fineract.test.messaging.event.loan.LoanUndoApprovalEvent; import org.apache.fineract.test.messaging.event.loan.delinquency.LoanDelinquencyPauseChangedEvent; import org.apache.fineract.test.messaging.event.loan.delinquency.LoanDelinquencyRangeChangeEvent; import org.apache.fineract.test.messaging.event.loan.transaction.AbstractLoanTransactionEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanDisbursalTransactionEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanRefundPostBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionGoodwillCreditPostEvent; +import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionInterestPaymentWaiverPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMakeRepaymentPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMerchantIssuedRefundPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionPayoutRefundPostEvent; @@ -111,22 +118,21 @@ public class EventCheckHelper { eventAssertion.assertEvent(ClientActivatedEvent.class, clientCreationResponse.body().getClientId())// .extractingData(ClientDataV1::getActive).isEqualTo(true)// .extractingData(clientDataV1 -> clientDataV1.getStatus().getId()).isEqualTo(status);// - } public void createLoanEventCheck(Response createLoanResponse) throws IOException { - Response loanDetails = loansApi.retrieveLoan(createLoanResponse.body().getLoanId(), false, "", "", "") + Response loanDetails = loansApi.retrieveLoan(createLoanResponse.body().getLoanId(), false, "all", "", "") .execute(); GetLoansLoanIdResponse body = loanDetails.body(); - eventAssertion.assertEvent(LoanCreatedEvent.class, createLoanResponse.body().getLoanId()).extractingData(LoanAccountDataV1::getId) - .isEqualTo(body.getId()).extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getId()) - .isEqualTo(body.getStatus().getId()).extractingData(LoanAccountDataV1::getClientId) - .isEqualTo(Long.valueOf(body.getClientId())) - .extractingData(loanAccountDataV11 -> loanAccountDataV11.getPrincipal().longValue()) - .isEqualTo(body.getPrincipal().longValue()) + eventAssertion.assertEvent(LoanCreatedEvent.class, createLoanResponse.body().getLoanId())// + .extractingData(LoanAccountDataV1::getId).isEqualTo(body.getId())// + .extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getId()).isEqualTo(body.getStatus().getId())// + .extractingData(LoanAccountDataV1::getClientId).isEqualTo(Long.valueOf(body.getClientId()))// + .extractingData(loanAccountDataV1 -> loanAccountDataV1.getPrincipal().longValue()) + .isEqualTo(body.getPrincipal().longValue())// .extractingData(loanAccountDataV1 -> loanAccountDataV1.getSummary().getCurrency().getCode()) - .isEqualTo(body.getCurrency().getCode()); + .isEqualTo(body.getCurrency().getCode());// } public void approveLoanEventCheck(Response loanApproveResponse) throws IOException { @@ -134,37 +140,111 @@ public class EventCheckHelper { .execute(); GetLoansLoanIdResponse body = loanDetails.body(); - eventAssertion.assertEvent(LoanApprovedEvent.class, loanApproveResponse.body().getLoanId()).extractingData(LoanAccountDataV1::getId) - .isEqualTo(body.getId()).extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getId()) - .isEqualTo(body.getStatus().getId()).extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getCode()) - .isEqualTo(body.getStatus().getCode()).extractingData(LoanAccountDataV1::getClientId) - .isEqualTo(Long.valueOf(body.getClientId())) - .extractingData(loanAccountDataV1 -> loanAccountDataV1.getApprovedPrincipal().longValue()) - .isEqualTo(body.getApprovedPrincipal().longValue()) - .extractingData(loanAccountDataV1 -> loanAccountDataV1.getTimeline().getApprovedOnDate()) - .isEqualTo(FORMATTER_EVENTS.format(body.getTimeline().getApprovedOnDate())) - .extractingData(loanAccountDataV1 -> loanAccountDataV1.getSummary().getCurrency().getCode()) - .isEqualTo(body.getCurrency().getCode()); - } - - public void disburseLoanEventCheck(Response loanDisburseResponse) throws IOException { - Response loanDetails = loansApi.retrieveLoan(loanDisburseResponse.body().getLoanId(), false, "", "", "") - .execute(); - GetLoansLoanIdResponse body = loanDetails.body(); - - eventAssertion.assertEvent(LoanDisbursalEvent.class, loanDisburseResponse.body().getLoanId())// + eventAssertion.assertEvent(LoanApprovedEvent.class, loanApproveResponse.body().getLoanId())// .extractingData(LoanAccountDataV1::getId).isEqualTo(body.getId())// .extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getId()).isEqualTo(body.getStatus().getId())// .extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getCode()).isEqualTo(body.getStatus().getCode())// .extractingData(LoanAccountDataV1::getClientId).isEqualTo(Long.valueOf(body.getClientId()))// - .extractingData(loanAccountDataV1 -> loanAccountDataV1.getSummary().getPrincipalDisbursed().longValue()) - .isEqualTo(body.getSummary().getPrincipalDisbursed().longValue())// - .extractingData(loanAccountDataV1 -> loanAccountDataV1.getTimeline().getActualDisbursementDate()) - .isEqualTo(FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate())) + .extractingData(loanAccountDataV1 -> loanAccountDataV1.getApprovedPrincipal().longValue()) + .isEqualTo(body.getApprovedPrincipal().longValue())// + .extractingData(loanAccountDataV1 -> loanAccountDataV1.getTimeline().getApprovedOnDate())// + .isEqualTo(FORMATTER_EVENTS.format(body.getTimeline().getApprovedOnDate()))// .extractingData(loanAccountDataV1 -> loanAccountDataV1.getSummary().getCurrency().getCode()) .isEqualTo(body.getCurrency().getCode());// } + public void undoApproveLoanEventCheck(Response loanUndoApproveResponse) throws IOException { + Response loanDetails = loansApi.retrieveLoan(loanUndoApproveResponse.body().getLoanId(), false, "", "", "") + .execute(); + GetLoansLoanIdResponse body = loanDetails.body(); + + eventAssertion.assertEventRaised(LoanUndoApprovalEvent.class, body.getId()); + } + + public void loanRejectedEventCheck(Response loanRejectedResponse) throws IOException { + Response loanDetails = loansApi.retrieveLoan(loanRejectedResponse.body().getLoanId(), false, "", "", "") + .execute(); + GetLoansLoanIdResponse body = loanDetails.body(); + + eventAssertion.assertEventRaised(LoanRejectedEvent.class, body.getId()); + } + + public void disburseLoanEventCheck(Long loanId) throws IOException { + loanAccountDataV1Check(LoanDisbursalEvent.class, loanId); + } + + public void loanBalanceChangedEventCheck(Long loanId) throws IOException { + loanAccountDataV1Check(LoanBalanceChangedEvent.class, loanId); + } + + public void loanStatusChangedEventCheck(Long loanId) throws IOException { + loanAccountDataV1Check(LoanStatusChangedEvent.class, loanId); + } + + private void loanAccountDataV1Check(Class eventClazz, Long loanId) throws IOException { + Response loanDetails = loansApi.retrieveLoan(loanId, false, "all", "", "").execute(); + GetLoansLoanIdResponse body = loanDetails.body(); + + eventAssertion.assertEvent(eventClazz, loanId)// + .extractingData(loanAccountDataV1 -> { + Long idActual = loanAccountDataV1.getId(); + Long idExpected = body.getId(); + Integer statusIdActual = loanAccountDataV1.getStatus().getId(); + Integer statusIdExpected = body.getStatus().getId(); + String statusCodeActual = loanAccountDataV1.getStatus().getCode(); + String statusCodeExpected = body.getStatus().getCode(); + Long clientIdActual = loanAccountDataV1.getClientId(); + Long clientIdExpected = body.getClientId(); + BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed(); + Double principalDisbursedExpectedDouble = body.getSummary().getPrincipalDisbursed(); + BigDecimal principalDisbursedExpected = new BigDecimal(principalDisbursedExpectedDouble, MathContext.DECIMAL64); + String actualDisbursementDateActual = loanAccountDataV1.getTimeline().getActualDisbursementDate(); + String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate()); + String currencyCodeActual = loanAccountDataV1.getSummary().getCurrency().getCode(); + String currencyCodeExpected = body.getSummary().getCurrency().getCode(); + BigDecimal totalUnpaidPayableDueInterestActual = loanAccountDataV1.getSummary().getTotalUnpaidPayableDueInterest(); + BigDecimal totalUnpaidPayableDueInterestExpected = body.getSummary().getTotalUnpaidPayableDueInterest(); + BigDecimal totalUnpaidPayableNotDueInterestActual = loanAccountDataV1.getSummary() + .getTotalUnpaidPayableNotDueInterest(); + BigDecimal totalUnpaidPayableNotDueInterestExpected = body.getSummary().getTotalUnpaidPayableNotDueInterest(); + BigDecimal totalInterestPaymentWaiverActual = loanAccountDataV1.getSummary().getTotalInterestPaymentWaiver(); + Double totalInterestPaymentWaiverExpectedDouble = body.getSummary().getTotalInterestPaymentWaiver(); + BigDecimal totalInterestPaymentWaiverExpected = new BigDecimal(totalInterestPaymentWaiverExpectedDouble, + MathContext.DECIMAL64); + BigDecimal delinquentInterestActual = loanAccountDataV1.getDelinquent().getDelinquentInterest(); + BigDecimal delinquentInterestExpected = body.getDelinquent().getDelinquentInterest(); + BigDecimal delinquentFeeActual = loanAccountDataV1.getDelinquent().getDelinquentFee(); + BigDecimal delinquentFeeExpected = body.getDelinquent().getDelinquentFee(); + BigDecimal delinquentPenaltyActual = loanAccountDataV1.getDelinquent().getDelinquentPenalty(); + BigDecimal delinquentPenaltyExpected = body.getDelinquent().getDelinquentPenalty(); + + assertThat(idActual).isEqualTo(idExpected); + assertThat(statusIdActual).isEqualTo(statusIdExpected); + assertThat(statusCodeActual).isEqualTo(statusCodeExpected); + assertThat(clientIdActual).isEqualTo(clientIdExpected); + assertThat(areBigDecimalValuesEqual(principalDisbursedActual, principalDisbursedExpected)).isTrue(); + assertThat(actualDisbursementDateActual).isEqualTo(actualDisbursementDateExpected); + assertThat(currencyCodeActual).isEqualTo(currencyCodeExpected); + assertThat(areBigDecimalValuesEqual(totalUnpaidPayableDueInterestActual, totalUnpaidPayableDueInterestExpected)) + .isTrue(); + assertThat(areBigDecimalValuesEqual(totalUnpaidPayableNotDueInterestActual, totalUnpaidPayableNotDueInterestExpected)) + .isTrue(); + assertThat(areBigDecimalValuesEqual(totalInterestPaymentWaiverActual, totalInterestPaymentWaiverExpected)).isTrue(); + assertThat(areBigDecimalValuesEqual(delinquentInterestActual, delinquentInterestExpected)).isTrue(); + assertThat(areBigDecimalValuesEqual(delinquentFeeActual, delinquentFeeExpected)).isTrue(); + assertThat(areBigDecimalValuesEqual(delinquentPenaltyActual, delinquentPenaltyExpected)).isTrue(); + + return null; + }); + } + + private boolean areBigDecimalValuesEqual(BigDecimal actual, BigDecimal expected) { + log.info("--- Checking BigDecimal values.... ---"); + log.info("Actual: {}", actual); + log.info("Expected: {}", expected); + return actual.compareTo(expected) == 0; + } + public void loanDisbursalTransactionEventCheck(Response loanDisburseResponse) throws IOException { Long disbursementTransactionId = loanDisburseResponse.body().getSubResourceId(); @@ -204,6 +284,7 @@ public class EventCheckHelper { case PAYOUT_REFUND -> LoanTransactionPayoutRefundPostEvent.class; case MERCHANT_ISSUED_REFUND -> LoanTransactionMerchantIssuedRefundPostEvent.class; case REFUND_BY_CASH -> LoanRefundPostBusinessEvent.class; + case INTEREST_PAYMENT_WAIVER -> LoanTransactionInterestPaymentWaiverPostEvent.class; default -> throw new IllegalStateException(String.format("transaction type %s cannot be found", transactionType.getValue())); }; @@ -404,6 +485,6 @@ public class EventCheckHelper { } private BigDecimal zeroConversion(BigDecimal input) { - return input.compareTo(BigDecimal.ZERO) == 0 ? new BigDecimal(input.toEngineeringString()) : input.setScale(8); + return input.compareTo(new BigDecimal("0.000000")) == 0 ? new BigDecimal(input.toEngineeringString()) : input.setScale(8); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanRejectedEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanRejectedEvent.java new file mode 100644 index 000000000..1c66b54d9 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanRejectedEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan; + +public class LoanRejectedEvent extends AbstractLoanEvent { + + @Override + public String getEventName() { + return "LoanRejectedBusinessEvent"; + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanUndoApprovalEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanUndoApprovalEvent.java new file mode 100644 index 000000000..fbbf04c70 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/LoanUndoApprovalEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan; + +public class LoanUndoApprovalEvent extends AbstractLoanEvent { + + @Override + public String getEventName() { + return "LoanUndoApprovalBusinessEvent"; + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionAccrualActivityPostEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionAccrualActivityPostEvent.java new file mode 100644 index 000000000..71aaa2c85 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionAccrualActivityPostEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan.transaction; + +public class LoanTransactionAccrualActivityPostEvent extends AbstractLoanTransactionEvent { + + @Override + public String getEventName() { + return "LoanTransactionAccrualActivityPostBusinessEvent"; + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestPaymentWaiverPostEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestPaymentWaiverPostEvent.java new file mode 100644 index 000000000..38145b5f2 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestPaymentWaiverPostEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan.transaction; + +public class LoanTransactionInterestPaymentWaiverPostEvent extends AbstractLoanTransactionEvent { + + @Override + public String getEventName() { + return "LoanTransactionInterestPaymentWaiverPostBusinessEvent"; + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/EventStore.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/EventStore.java index 91b4051bc..53745f184 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/EventStore.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/EventStore.java @@ -71,20 +71,21 @@ public class EventStore { void receive(byte[] message) throws Exception { MessageV1 msgObject = MessageV1.fromByteBuffer(ByteBuffer.wrap(message)); String type = msgObject.getType(); + String idempotencyKey = msgObject.getIdempotencyKey(); LocalDate businessDate = LocalDate.parse(msgObject.getBusinessDate(), DateTimeFormatter.ISO_LOCAL_DATE); Object dataObject = getDataObject(msgObject); if (BULK_BUSINESS_EVENT_TYPE.equals(type)) { BulkMessagePayloadV1 bulkPayload = (BulkMessagePayloadV1) dataObject; List> bulkEvents = bulkPayload.getDatas().stream() - .map((BulkMessageItemV1 item) -> getEventMessageFromBulkItem(item, businessDate)).toList(); + .map((BulkMessageItemV1 item) -> getEventMessageFromBulkItem(item, businessDate, idempotencyKey)).toList(); if (log.isDebugEnabled()) { bulkEvents.forEach(msg -> { - log.debug("Received event {}", new LoggedEvent(msg)); + log.debug("Received Bulk event {}", new LoggedEvent(msg)); }); } receivedEvents.addAll(bulkEvents); } else { - EventMessage msg = new EventMessage<>(type, businessDate, dataObject); + EventMessage msg = new EventMessage<>(type, businessDate, dataObject, idempotencyKey); if (log.isDebugEnabled()) { log.debug("Received event {}", new LoggedEvent(msg)); } @@ -93,12 +94,12 @@ public class EventStore { log.trace("Data object within event {}", dataObject); } - private EventMessage getEventMessageFromBulkItem(BulkMessageItemV1 item, LocalDate businessDate) { + private EventMessage getEventMessageFromBulkItem(BulkMessageItemV1 item, LocalDate businessDate, String idempotencyKey) { try { String dataschema = item.getDataschema(); ByteBuffer data = item.getData(); Object deserialized = deserialize(dataschema, data); - return new EventMessage<>(item.getType(), businessDate, deserialized); + return new EventMessage<>(item.getType(), businessDate, deserialized, idempotencyKey); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/LoggedEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/LoggedEvent.java index 869ccf44b..dc32560cd 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/LoggedEvent.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/store/LoggedEvent.java @@ -25,15 +25,17 @@ public class LoggedEvent { private final String type; private final String businessDate; + private final String idempotencyKey; public LoggedEvent(EventMessage message) { this.type = message.getType(); this.businessDate = DateTimeFormatter.ISO_LOCAL_DATE.format(message.getBusinessDate()); + this.idempotencyKey = message.getIdempotencyKey(); } // Don't use Lombok @ToString since the class name isn't included in the msg @Override public String toString() { - return "{" + "type='" + type + '\'' + ", businessDate='" + businessDate + '\'' + '}'; + return "{" + "idempotencyKey='" + idempotencyKey + '\'' + ", type='" + type + "', businessDate='" + businessDate + '\'' + '}'; } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java index 92d5dd3d9..7fdd1414b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java @@ -27,7 +27,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.time.Clock; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -75,6 +74,7 @@ import org.apache.fineract.test.factory.LoanRequestFactory; import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; +import org.apache.fineract.test.helper.Utils; import org.apache.fineract.test.messaging.EventAssertion; import org.apache.fineract.test.messaging.event.loan.LoanRescheduledDueAdjustScheduleEvent; import org.apache.fineract.test.stepdef.AbstractStepDef; @@ -175,7 +175,7 @@ public class BatchApiStepDef extends AbstractStepDef { // request 3 - charge Loan DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); - String dateOfCharge = formatter.format(LocalDate.now(Clock.systemUTC()).minusMonths(1L).plusDays(1L)); + String dateOfCharge = formatter.format(Utils.now().minusMonths(1L).plusDays(1L)); PostLoansLoanIdChargesRequest loanIdChargesRequest = new PostLoansLoanIdChargesRequest(); loanIdChargesRequest.chargeId(CHARGE_ID_NFS_FEE); @@ -448,11 +448,11 @@ public class BatchApiStepDef extends AbstractStepDef { testContext().set(TestContextKey.BATCH_API_CALL_RESPONSE, batchResponseList); testContext().set(TestContextKey.BATCH_API_CALL_IDEMPOTENCY_KEY, idempotencyKey); eventAssertion.assertEvent(LoanRescheduledDueAdjustScheduleEvent.class, loanId).extractingData(loanAccountDataV1 -> { - Optional period = loanAccountDataV1.getRepaymentSchedule().getPeriods().stream() - .filter(p -> formatter.format(LocalDate.parse(p.getDueDate())).equals(toDateStr)).findFirst(); + LoanSchedulePeriodDataV1 period = loanAccountDataV1.getRepaymentSchedule().getPeriods().stream() + .filter(p -> formatter.format(LocalDate.parse(p.getDueDate())).equals(toDateStr)).findFirst().orElse(null); String dueDate = ""; - if (period.isPresent()) { - dueDate = formatter.format(LocalDate.parse(period.get().getDueDate())); + if (period != null) { + dueDate = formatter.format(LocalDate.parse(period.getDueDate())); } assertThat(dueDate).as(ErrorMessageHelper.wrongDataInLastPaymentAmount(dueDate, toDateStr)).isEqualTo(toDateStr); return null; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepStepDef.java index 9e32c1374..1752fcc3c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepStepDef.java @@ -30,8 +30,11 @@ import org.apache.fineract.client.models.BusinessStep; import org.apache.fineract.client.models.GetBusinessStepConfigResponse; import org.apache.fineract.client.models.UpdateBusinessStepConfigRequest; import org.apache.fineract.client.services.BusinessStepConfigurationApi; +import org.apache.fineract.test.data.CobBusinessStep; import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContext; +import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; import retrofit2.Response; @@ -47,28 +50,20 @@ public class BusinessStepStepDef extends AbstractStepDef { private static final String BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES = "ADD_PERIODIC_ACCRUAL_ENTRIES"; private static final String BUSINESS_STEP_NAME_EXTERNAL_ASSET_OWNER_TRANSFER = "EXTERNAL_ASSET_OWNER_TRANSFER"; private static final String BUSINESS_STEP_NAME_CHECK_DUE_INSTALLMENTS = "CHECK_DUE_INSTALLMENTS"; + private static final String BUSINESS_STEP_NAME_ACCRUAL_ACTIVITY_POSTING = "ACCRUAL_ACTIVITY_POSTING"; + private static final List ORIGINAL_COB_BUSINESS_STEPS = TestContext.GLOBAL + .get(TestContextKey.ORIGINAL_COB_WORKFLOW_JOB_BUSINESS_STEP_LIST); @Autowired private BusinessStepConfigurationApi businessStepConfigurationApi; @Given("Admin puts EXTERNAL_ASSET_OWNER_TRANSFER job into LOAN_CLOSE_OF_BUSINESS workflow") public void putExternalAssetOwnerTransferJobInCOB() throws IOException { - BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L); - BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION) - .order(2L); - BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L); - BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L); - BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L); - BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L); - BusinessStep externalAssetOwnerTransfer = new BusinessStep().stepName(BUSINESS_STEP_NAME_EXTERNAL_ASSET_OWNER_TRANSFER).order(7L); + List businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS); + Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder(); - List businessSteps = new ArrayList<>(); - businessSteps.add(applyChargeToOverdueLoans); - businessSteps.add(loanDelinquencyClassification); - businessSteps.add(checkLoanRepaymentDue); - businessSteps.add(checkLoanRepaymentOverdue); - businessSteps.add(updateLoanArrearsAging); - businessSteps.add(addPeriodicAccrualEntries); + BusinessStep externalAssetOwnerTransfer = new BusinessStep().stepName(BUSINESS_STEP_NAME_EXTERNAL_ASSET_OWNER_TRANSFER) + .order(lastOrder + 1); businessSteps.add(externalAssetOwnerTransfer); UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); @@ -76,51 +71,22 @@ public class BusinessStepStepDef extends AbstractStepDef { Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) .execute(); ErrorHelper.checkSuccessfulApiCall(response); + + // --- log changes --- + logChanges(); } @Then("Admin removes EXTERNAL_ASSET_OWNER_TRANSFER job from LOAN_CLOSE_OF_BUSINESS workflow") public void removeExternalAssetOwnerTransferJobInCOB() throws IOException { - BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L); - BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION) - .order(2L); - BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L); - BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L); - BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L); - BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L); - - List businessSteps = new ArrayList<>(); - businessSteps.add(applyChargeToOverdueLoans); - businessSteps.add(loanDelinquencyClassification); - businessSteps.add(checkLoanRepaymentDue); - businessSteps.add(checkLoanRepaymentOverdue); - businessSteps.add(updateLoanArrearsAging); - businessSteps.add(addPeriodicAccrualEntries); - - UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); - - Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) - .execute(); - ErrorHelper.checkSuccessfulApiCall(response); + setBackBusinessStepsToOriginal(); } @Given("Admin puts CHECK_DUE_INSTALLMENTS job into LOAN_CLOSE_OF_BUSINESS workflow") public void putCheckDueInstallmentsJobInCOB() throws IOException { - BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L); - BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION) - .order(2L); - BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L); - BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L); - BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L); - BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L); - BusinessStep checkDueInstallments = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_DUE_INSTALLMENTS).order(7L); + List businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS); + Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder(); - List businessSteps = new ArrayList<>(); - businessSteps.add(applyChargeToOverdueLoans); - businessSteps.add(loanDelinquencyClassification); - businessSteps.add(checkLoanRepaymentDue); - businessSteps.add(checkLoanRepaymentOverdue); - businessSteps.add(updateLoanArrearsAging); - businessSteps.add(addPeriodicAccrualEntries); + BusinessStep checkDueInstallments = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_DUE_INSTALLMENTS).order(lastOrder + 1); businessSteps.add(checkDueInstallments); UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); @@ -128,68 +94,71 @@ public class BusinessStepStepDef extends AbstractStepDef { Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) .execute(); ErrorHelper.checkSuccessfulApiCall(response); + + // --- log changes --- + logChanges(); } @Then("Admin removes CHECK_DUE_INSTALLMENTS job from LOAN_CLOSE_OF_BUSINESS workflow") public void removeCheckDueInstallmentsJobInCOB() throws IOException { - BusinessStep applyChargeToOverdueLoans = new BusinessStep().stepName(BUSINESS_STEP_NAME_APPLY_CHARGE_TO_OVERDUE_LOANS).order(1L); - BusinessStep loanDelinquencyClassification = new BusinessStep().stepName(BUSINESS_STEP_NAME_LOAN_DELINQUENCY_CLASSIFICATION) - .order(2L); - BusinessStep checkLoanRepaymentDue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_DUE).order(3L); - BusinessStep checkLoanRepaymentOverdue = new BusinessStep().stepName(BUSINESS_STEP_NAME_CHECK_LOAN_REPAYMENT_OVERDUE).order(4L); - BusinessStep updateLoanArrearsAging = new BusinessStep().stepName(BUSINESS_STEP_NAME_UPDATE_LOAN_ARREARS_AGING).order(5L); - BusinessStep addPeriodicAccrualEntries = new BusinessStep().stepName(BUSINESS_STEP_NAME_ADD_PERIODIC_ACCRUAL_ENTRIES).order(6L); - - List businessSteps = new ArrayList<>(); - businessSteps.add(applyChargeToOverdueLoans); - businessSteps.add(loanDelinquencyClassification); - businessSteps.add(checkLoanRepaymentDue); - businessSteps.add(checkLoanRepaymentOverdue); - businessSteps.add(updateLoanArrearsAging); - businessSteps.add(addPeriodicAccrualEntries); - - UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); - - Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) - .execute(); - ErrorHelper.checkSuccessfulApiCall(response); + setBackBusinessStepsToOriginal(); } @Given("Admin puts {string} business step into LOAN_CLOSE_OF_BUSINESS workflow") public void putGivenJobInCOB(String businessStepName) throws IOException { - List businessSteps = retrieveLoanCOBJobSteps(); - if (businessSteps.stream().anyMatch(businessStep -> businessStep.getStepName().equals(businessStepName))) { - return; - } + List businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS); + Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder(); - businessSteps.add(new BusinessStep().stepName(businessStepName).order((long) (1 + businessSteps.size()))); + CobBusinessStep cobBusinessStep = CobBusinessStep.valueOf(businessStepName); + String stepName = cobBusinessStep.getValue(); + + BusinessStep businessStepToAdd = new BusinessStep().stepName(stepName).order(lastOrder + 1); + businessSteps.add(businessStepToAdd); UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); + Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) .execute(); ErrorHelper.checkSuccessfulApiCall(response); + // --- log changes --- logChanges(); } + @Then("Admin sets back LOAN_CLOSE_OF_BUSINESS workflow to its initial state") + public void setBackCOBToInitialState() throws IOException { + setBackBusinessStepsToOriginal(); + } + @Then("Admin removes {string} business step into LOAN_CLOSE_OF_BUSINESS workflow") public void removeGivenJobInCOB(String businessStepName) throws IOException { - List businessSteps = retrieveLoanCOBJobSteps(); - businessSteps.removeIf(businessStep -> businessStep.getStepName().equals(businessStepName)); + List businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS); + + CobBusinessStep cobBusinessStep = CobBusinessStep.valueOf(businessStepName); + String stepName = cobBusinessStep.getValue(); + + businessSteps.removeIf(businessStep -> businessStep.getStepName().equals(stepName)); UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps); + Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) .execute(); ErrorHelper.checkSuccessfulApiCall(response); + // --- log changes --- logChanges(); } - private List retrieveLoanCOBJobSteps() throws IOException { - Response businessStepConfigResponse = businessStepConfigurationApi - .retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute(); - ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse); - return businessStepConfigResponse.body().getBusinessSteps(); + private void setBackBusinessStepsToOriginal() throws IOException { + log.info("Setting back Business steps to original..."); + UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(ORIGINAL_COB_BUSINESS_STEPS); + + Response response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request) + .execute(); + ErrorHelper.checkSuccessfulApiCall(response); + + // --- log changes --- + logChanges(); } private void logChanges() throws IOException { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java index 4631bf2b4..d692a7b2b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java @@ -21,6 +21,7 @@ package org.apache.fineract.test.stepdef.common; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import java.io.IOException; +import org.apache.fineract.client.services.DefaultApi; import org.apache.fineract.test.helper.GlobalConfigurationHelper; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +29,8 @@ public class GlobalConfigurationStepDef { @Autowired private GlobalConfigurationHelper globalConfigurationHelper; + @Autowired + private DefaultApi defaultApi; @Given("Global configuration {string} is disabled") public void disableGlobalConfiguration(String configKey) throws IOException { @@ -42,6 +45,12 @@ public class GlobalConfigurationStepDef { @When("Global config {string} value set to {string}") public void setGlobalConfigValueString(String configKey, String configValue) throws IOException { globalConfigurationHelper.setGlobalConfigValueString(configKey, configValue); + } + + @When("Global config {string} value set to {string} through DefaultApi") + public void setGlobalConfigValueStringDefaultApi(String configKey, String configValue) throws IOException { + Long configValueLong = Long.valueOf(configValue); + defaultApi.updateGlobalConfiguration(configKey, configValueLong); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java index 0e81930db..97191a40a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java @@ -67,7 +67,6 @@ public class JournalEntriesStepDef extends AbstractStepDef { String transactionTypeExpected = transactionType1.getValue(); List transactions = loanDetailsResponse.body().getTransactions(); - List transactionsMatch = transactions.stream() .filter(t -> transactionDate.equals(formatter.format(t.getDate())) && transactionTypeExpected.equals(t.getType().getCode().substring(20))) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java index ac562f9e2..e0db1f459 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java @@ -56,4 +56,9 @@ public class SchedulerStepDef extends AbstractStepDef { public void runCOB() { jobService.executeAndWait(DefaultJob.LOAN_COB); } + + @When("Admin runs the Accrual Activity Posting job") + public void runAccrualActivityPosting() { + jobService.executeAndWait(DefaultJob.ACCRUAL_ACTIVITY_POSTING); + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/hook/TestRailLifecycleHook.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/hook/TestRailLifecycleHook.java new file mode 100644 index 000000000..ab39f3bb7 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/hook/TestRailLifecycleHook.java @@ -0,0 +1,45 @@ +/** + * 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.test.stepdef.hook; + +import io.cucumber.java.After; +import io.cucumber.java.Scenario; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.test.testrail.TestRailClient; +import org.apache.fineract.test.testrail.TestRailProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +@Slf4j +public class TestRailLifecycleHook { + + @Autowired + private TestRailProperties testRailProperties; + + @Autowired + private ApplicationContext applicationContext; + + @After + public void tearDown(Scenario scenario) { + if (testRailProperties.isEnabled()) { + TestRailClient testRailClient = applicationContext.getBean(TestRailClient.class); + testRailClient.saveScenarioResult(scenario); + } + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java index 9e3aeaa14..72cd37afd 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java @@ -21,12 +21,14 @@ package org.apache.fineract.test.stepdef.loan; import static org.assertj.core.api.Assertions.assertThat; import com.google.gson.Gson; +import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import java.io.IOException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.BusinessDateResponse; import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData; import org.apache.fineract.client.models.GetLoansLoanIdResponse; @@ -46,11 +48,14 @@ import org.apache.fineract.test.factory.LoanRequestFactory; import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; +import org.apache.fineract.test.messaging.event.EventCheckHelper; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; import retrofit2.Response; +@Slf4j public class LoanChargeAdjustmentStepDef extends AbstractStepDef { public static final String DATE_FORMAT = "dd MMMM yyyy"; @@ -65,6 +70,10 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { private LoanTransactionsApi loanTransactionsApi; @Autowired private BusinessDateManagementApi businessDateManagementApi; + @Autowired + private EventCheckHelper eventCheckHelper; + @Autowired + private EventStore eventStore; @When("Admin makes a charge adjustment for the last {string} type charge which is due on {string} with {double} EUR transaction amount and externalId {string}") public void makeLoanChargeAdjustment(String chargeTypeEnum, String date, Double transactionAmount, String externalId) @@ -79,8 +88,8 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { makeChargeAdjustmentCall(loanId, transactionId, externalId, transactionAmount); } - @When("Admin makes a charge adjustment for the last {string} type charge which is due on {string} with transaction amount higher than the available charge amount") - public void loanChargeAdjustmentFailedOnWrongAmount(String chargeTypeEnum, String date) throws IOException { + @Then("Charge adjustment for the last {string} type charge which is due on {string} with transaction amount {double} which is higher than the available charge amount results an ERROR") + public void loanChargeAdjustmentFailedOnWrongAmount(String chargeTypeEnum, String date, double amount) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -88,7 +97,7 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); Long transactionId = getTransactionIdForLastChargeMetConditions(chargeTypeEnum, date, loanDetailsResponse); - PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest().amount(8.0) + PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest().amount(amount) .externalId(""); Response chargeAdjustmentResponseFail = loanChargesApi @@ -108,6 +117,9 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { assertThat(developerMessageActual) .as(ErrorMessageHelper.wrongErrorMessageInFailedChargeAdjustment(developerMessageActual, developerMessageExpected)) .isEqualTo(developerMessageExpected); + + log.info("Error code: {}", httpStatusCodeActual); + log.info("Error message: {}", developerMessageActual); } @When("Admin reverts the charge adjustment which was raised on {string} with {double} EUR transaction amount") @@ -153,6 +165,7 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { } private void makeChargeAdjustmentCall(Long loanId, Long transactionId, String externalId, double transactionAmount) throws IOException { + eventStore.reset(); PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest() .amount(transactionAmount).externalId(externalId); @@ -160,6 +173,7 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef { .executeLoanCharge2(loanId, transactionId, chargeAdjustmentRequest, "adjustment").execute(); testContext().set(TestContextKey.LOAN_CHARGE_ADJUSTMENT_RESPONSE, chargeAdjustmentResponse); ErrorHelper.checkSuccessfulApiCall(chargeAdjustmentResponse); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } private Long getTransactionIdForLastChargeMetConditions(String chargeTypeEnum, String date, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeBackStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeBackStepDef.java index 9eaf7c1ed..458cd9aa9 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeBackStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeBackStepDef.java @@ -40,8 +40,9 @@ import org.apache.fineract.test.data.paymenttype.PaymentTypeResolver; import org.apache.fineract.test.factory.LoanRequestFactory; import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.messaging.EventAssertion; -import org.apache.fineract.test.messaging.event.loan.LoanBalanceChangedEvent; +import org.apache.fineract.test.messaging.event.EventCheckHelper; import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargebackTransactionEvent; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -61,6 +62,12 @@ public class LoanChargeBackStepDef extends AbstractStepDef { @Autowired private PaymentTypeResolver paymentTypeResolver; + @Autowired + private EventCheckHelper eventCheckHelper; + + @Autowired + EventStore eventStore; + @When("Admin makes {string} chargeback with {double} EUR transaction amount") public void makeLoanChargeback(String repaymentType, double transactionAmount) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -116,6 +123,7 @@ public class LoanChargeBackStepDef extends AbstractStepDef { } private void makeChargebackCall(Long loanId, Long transactionId, String repaymentType, double transactionAmount) throws IOException { + eventStore.reset(); DefaultPaymentType paymentType = DefaultPaymentType.valueOf(repaymentType); Long paymentTypeValue = paymentTypeResolver.resolve(paymentType); @@ -134,8 +142,7 @@ public class LoanChargeBackStepDef extends AbstractStepDef { PostLoansLoanIdTransactionsResponse responseBody = chargebackResponse.body(); Long loanId = responseBody.getLoanId(); - eventAssertion.assertEvent(LoanBalanceChangedEvent.class, loanId).extractingData(data -> data.getId()).isEqualTo(loanId); - + eventCheckHelper.loanBalanceChangedEventCheck(loanId); checkLoanChargebackTransactionEvent(responseBody); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java index 51bd48e27..9a2bccac2 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java @@ -51,7 +51,9 @@ import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; import org.apache.fineract.test.messaging.EventAssertion; +import org.apache.fineract.test.messaging.event.EventCheckHelper; import org.apache.fineract.test.messaging.event.loan.charge.LoanAddChargeEvent; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -73,6 +75,10 @@ public class LoanChargeStepDef extends AbstractStepDef { private LoansApi loansApi; @Autowired private EventAssertion eventAssertion; + @Autowired + private EventCheckHelper eventCheckHelper; + @Autowired + private EventStore eventStore; @When("Admin adds {string} due date charge with {string} due date and {double} EUR transaction amount") public void addChargeDueDate(String chargeType, String transactionDate, double transactionAmount) throws IOException { @@ -142,6 +148,7 @@ public class LoanChargeStepDef extends AbstractStepDef { @And("Admin adds a {double} % Processing charge to the loan with {string} locale on date: {string}") public void addProcessingFee(double chargeAmount, String locale, String date) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest() @@ -152,10 +159,12 @@ public class LoanChargeStepDef extends AbstractStepDef { .execute(); ErrorHelper.checkSuccessfulApiCall(loanChargeResponse); testContext().set(TestContextKey.ADD_PROCESSING_FEE_RESPONSE, loanChargeResponse); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Admin adds an NSF fee because of payment bounce with {string} transaction date") public void addNSFfee(String date) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest() @@ -166,6 +175,7 @@ public class LoanChargeStepDef extends AbstractStepDef { .execute(); ErrorHelper.checkSuccessfulApiCall(loanChargeResponse); testContext().set(TestContextKey.ADD_NSF_FEE_RESPONSE, loanChargeResponse); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Admin waives charge") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java index e55519460..d584e3a0b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java @@ -21,7 +21,6 @@ package org.apache.fineract.test.stepdef.loan; import static org.assertj.core.api.Assertions.assertThat; import com.google.gson.Gson; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -629,7 +628,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue(); eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)// - .extractingData(loanAccountDelinquencyRangeDataV1 -> { // + .extractingData(loanAccountDelinquencyRangeDataV1 -> { String actualDelinquencyRangeValue = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getClassification();// assertThat(actualDelinquencyRangeValue)// .as(ErrorMessageHelper.delinquencyRangeError(actualDelinquencyRangeValue, expectedDelinquencyRangeValue))// @@ -648,7 +647,8 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { GetLoansLoanIdDelinquencySummary delinquent = loanDetails.body().getDelinquent(); eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId)// - .extractingData(loanAccountDelinquencyRangeDataV1 -> { // + .extractingData(loanAccountDelinquencyRangeDataV1 -> { + Long loanLevelDelinquencyRangeId = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getId(); String loanLevelDelinquencyRange = loanAccountDelinquencyRangeDataV1.getDelinquencyRange().getClassification(); String loanLevelDelinquentDate = loanAccountDelinquencyRangeDataV1.getDelinquentDate(); @@ -732,7 +732,6 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { log.info("loanDetails.delinquent.lastRepaymentDate: {}", actualLastRepaymentDate); } - @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") private List fetchValuesOfDelinquencyPausePeriods(List header, GetLoansLoanIdDelinquencyPausePeriod t) { List actualValues = new ArrayList<>(); for (String headerName : header) { @@ -741,6 +740,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { case "pausePeriodStart" -> actualValues.add(t.getPausePeriodStart() == null ? null : FORMATTER.format(t.getPausePeriodStart())); case "pausePeriodEnd" -> actualValues.add(t.getPausePeriodEnd() == null ? null : FORMATTER.format(t.getPausePeriodEnd())); + default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName)); } } return actualValues; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java index 5f2f0a7e9..327225d08 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java @@ -57,8 +57,12 @@ public class LoanReAgingStepDef extends AbstractStepDef { String startDate = data.get(2); int numberOfInstallments = Integer.parseInt(data.get(3)); - PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory.defaultReAgingRequest().frequencyNumber(frequencyNumber) - .frequencyType(frequencyType).startDate(startDate).numberOfInstallments(numberOfInstallments); + PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory// + .defaultReAgingRequest()// + .frequencyNumber(frequencyNumber)// + .frequencyType(frequencyType)// + .startDate(startDate)// + .numberOfInstallments(numberOfInstallments);// Response response = loanTransactionsApi.executeLoanTransaction(loanId, reAgingRequest, "reAge") .execute(); @@ -77,8 +81,12 @@ public class LoanReAgingStepDef extends AbstractStepDef { String startDate = data.get(2); int numberOfInstallments = Integer.parseInt(data.get(3)); - PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory.defaultReAgingRequest().frequencyNumber(frequencyNumber) - .frequencyType(frequencyType).startDate(startDate).numberOfInstallments(numberOfInstallments); + PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory// + .defaultReAgingRequest()// + .frequencyNumber(frequencyNumber)// + .frequencyType(frequencyType)// + .startDate(startDate)// + .numberOfInstallments(numberOfInstallments);// Response response = loanTransactionsApi .executeLoanTransaction1(loanExternalId, reAgingRequest, "reAge").execute(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java index 206abd1a0..992c4fd07 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java @@ -61,8 +61,8 @@ import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; import org.apache.fineract.test.messaging.EventAssertion; import org.apache.fineract.test.messaging.event.EventCheckHelper; -import org.apache.fineract.test.messaging.event.loan.LoanBalanceChangedEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -77,6 +77,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { public static final String DEFAULT_CHECK_NB = "1234567890"; public static final String DEFAULT_RECEIPT_NB = "1234567890"; public static final String DEFAULT_BANK_NB = "1234567890"; + public static final String DEFAULT_REPAYMENT_TYPE = "AUTOPAY"; @Autowired private LoanTransactionsApi loanTransactionsApi; @@ -98,6 +99,9 @@ public class LoanRepaymentStepDef extends AbstractStepDef { @Autowired private EventCheckHelper eventCheckHelper; + @Autowired + private EventStore eventStore; + @And("Customer makes {string} repayment on {string} with {double} EUR transaction amount") public void makeLoanRepayment(String repaymentType, String transactionDate, double transactionAmount) throws IOException { makeRepayment(repaymentType, transactionDate, transactionAmount, null); @@ -111,6 +115,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { private void makeRepayment(String repaymentType, String transactionDate, double transactionAmount, String transferExternalOwnerId) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -132,11 +137,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { EventAssertion.EventAssertionBuilder transactionEvent = eventCheckHelper .transactionEventCheck(repaymentResponse, TransactionType.REPAYMENT, transferExternalOwnerId); testContext().set(TestContextKey.TRANSACTION_EVENT, transactionEvent); - eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Created user makes {string} repayment on {string} with {double} EUR transaction amount") public void makeRepaymentWithGivenUser(String repaymentType, String transactionDate, double transactionAmount) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -164,11 +170,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { .executeLoanTransaction(loanId, repaymentRequest, "repayment", headerMap).execute(); testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse); ErrorHelper.checkSuccessfulApiCall(repaymentResponse); - eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Customer makes externalID controlled {string} repayment on {string} with {double} EUR transaction amount") public void makeRepaymentByExternalId(String repaymentType, String transactionDate, double transactionAmount) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); String resourceExternalId = loanResponse.body().getResourceExternalId(); @@ -189,12 +196,13 @@ public class LoanRepaymentStepDef extends AbstractStepDef { testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse); ErrorHelper.checkSuccessfulApiCall(repaymentResponse); - eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Created user makes externalID controlled {string} repayment on {string} with {double} EUR transaction amount") public void makeRepaymentWithGivenUserByExternalId(String repaymentType, String transactionDate, double transactionAmount) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); String resourceExternalId = loanResponse.body().getResourceExternalId(); @@ -223,7 +231,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { .executeLoanTransaction1(resourceExternalId, repaymentRequest, "repayment", headerMap).execute(); testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse); ErrorHelper.checkSuccessfulApiCall(repaymentResponse); - eventAssertion.assertEventRaised(LoanBalanceChangedEvent.class, loanId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @And("Customer not able to make {string} repayment on {string} with {double} EUR transaction amount") @@ -282,6 +290,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { @When("Refund happens on {string} with {double} EUR transaction amount") public void makeRefund(String transactionDate, double transactionAmount) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); PostLoansLoanIdTransactionsRequest refundRequest = LoanRequestFactory.defaultRefundRequest().transactionDate(transactionDate) @@ -293,10 +302,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { .executeLoanTransaction(loanId, refundRequest, "payoutRefund").execute(); ErrorHelper.checkSuccessfulApiCall(refundResponse); testContext().set(TestContextKey.LOAN_REFUND_RESPONSE, refundResponse); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Refund undo happens on {string}") public void makeRefundUndo(String transactionDate) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); Response refundResponse = testContext().get(TestContextKey.LOAN_REFUND_RESPONSE); @@ -318,10 +329,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed()) .isEqualTo(Boolean.TRUE); eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Customer makes a repayment undo on {string}") public void makeLoanRepaymentUndo(String transactionDate) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); Response repaymentResponse = testContext().get(TestContextKey.LOAN_REPAYMENT_RESPONSE); @@ -343,6 +356,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed()) .isEqualTo(Boolean.TRUE); eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @Then("Loan {string} transaction adjust amount {double} must return {int} code") @@ -361,6 +375,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { @When("Customer undo {string}th repayment on {string}") public void undoNthRepayment(String nthItemStr, String transactionDate) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); List transactions = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute().body() @@ -387,10 +402,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed()) .isEqualTo(Boolean.TRUE); eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Customer undo {string}th transaction made on {string}") public void undoNthTransaction(String nthItemStr, String transactionDate) throws IOException { + eventStore.reset(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -418,10 +435,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef { loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed()) .isEqualTo(Boolean.TRUE); eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Customer undo {string}th {string} transaction made on {string}") public void undoNthTransactionType(String nthItemStr, String transactionType, String transactionDate) throws IOException { + eventStore.reset(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -452,6 +471,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef { loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed()) .isEqualTo(Boolean.TRUE); eventAssertionBuilder.extractingData(LoanTransactionAdjustmentDataV1::getNewTransactionDetail).isEqualTo(null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @Then("Repayment transaction is created with {double} amount and {string} type") @@ -516,6 +536,18 @@ public class LoanRepaymentStepDef extends AbstractStepDef { adjustNthRepaymentWithExternalOwnerCheck(nthItemStr, transactionDate, amount, null); } + @When("Loan Pay-off is made on {string}") + public void makeLoanPayOff(String transactionDate) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId1 = loanResponse.body().getLoanId(); + Response response = loanTransactionsApi + .retrieveTransactionTemplate(loanId1, "prepayLoan", DATE_FORMAT, transactionDate, DEFAULT_LOCALE).execute(); + Double transactionAmount = response.body().getAmount(); + + log.info("%n--- Loan Pay-off with amount: {} ---", transactionAmount); + makeRepayment(DEFAULT_REPAYMENT_TYPE, transactionDate, transactionAmount, null); + } + private void adjustNthRepaymentWithExternalOwnerCheck(String nthItemStr, String transactionDate, String amount, String externalOwnerId) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -564,16 +596,4 @@ public class LoanRepaymentStepDef extends AbstractStepDef { } } - - @When("Loan Pay-off is made on {string}") - public void makeLoanPayOff(String transactionDate) throws IOException { - Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId1 = loanResponse.body().getLoanId(); - Response response = loanTransactionsApi - .retrieveTransactionTemplate(loanId1, "prepayLoan", DATE_FORMAT, transactionDate, DEFAULT_LOCALE).execute(); - Double transactionAmount = response.body().getAmount(); - - log.info("--- Loan Pay-off with amount: {} ---", transactionAmount); - makeRepayment("AUTOPAY", transactionDate, transactionAmount, null); - } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java index 8ae467cf9..31c64d90c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java @@ -26,6 +26,8 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import java.io.IOException; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest; @@ -48,6 +50,10 @@ import retrofit2.Response; public class LoanRescheduleStepDef extends AbstractStepDef { private static final Gson GSON = new JSON().getGson(); + public static final String DATE_FORMAT_HU = "yyyy-MM-dd"; + public static final String DATE_FORMAT_EN = "dd MMMM yyyy"; + public static final DateTimeFormatter FORMATTER_HU = DateTimeFormatter.ofPattern(DATE_FORMAT_HU); + public static final DateTimeFormatter FORMATTER_EN = DateTimeFormatter.ofPattern(DATE_FORMAT_EN); @Autowired private RescheduleLoansApi rescheduleLoansApi; @@ -59,13 +65,18 @@ public class LoanRescheduleStepDef extends AbstractStepDef { List> data = table.asLists(); List rescheduleData = data.get(1); + String rescheduleFromDate = rescheduleData.get(0); String submittedOnDate = rescheduleData.get(1); String adjustedDueDate = rescheduleData.get(2); - Integer graceOfPrincipal = rescheduleData.get(3) != null ? Integer.parseInt(rescheduleData.get(3)) : null; - Integer graceOnInterest = rescheduleData.get(4) != null ? Integer.parseInt(rescheduleData.get(4)) : null; - Integer extraTerms = rescheduleData.get(5) != null ? Integer.parseInt(rescheduleData.get(5)) : null; - BigDecimal newInterestRate = rescheduleData.get(6) != null ? new BigDecimal(rescheduleData.get(6)) : null; + Integer graceOfPrincipal = (rescheduleData.get(3) == null || "0".equals(rescheduleData.get(3))) ? null + : Integer.valueOf(rescheduleData.get(3)); + Integer graceOnInterest = (rescheduleData.get(4) == null || "0".equals(rescheduleData.get(4))) ? null + : Integer.valueOf(rescheduleData.get(4)); + Integer extraTerms = (rescheduleData.get(5) == null || "0".equals(rescheduleData.get(5))) ? null + : Integer.valueOf(rescheduleData.get(5)); + BigDecimal newInterestRate = (rescheduleData.get(6) == null || "0".equals(rescheduleData.get(6))) ? null + : new BigDecimal(rescheduleData.get(6)); PostCreateRescheduleLoansRequest request = new PostCreateRescheduleLoansRequest()// .loanId(loanId)// @@ -105,10 +116,13 @@ public class LoanRescheduleStepDef extends AbstractStepDef { String rescheduleFromDate = rescheduleData.get(0); String submittedOnDate = rescheduleData.get(1); String adjustedDueDate = rescheduleData.get(2); - Integer graceOfPrincipal = rescheduleData.get(3) != null ? Integer.parseInt(rescheduleData.get(3)) : null; - Integer graceOnInterest = rescheduleData.get(4) != null ? Integer.parseInt(rescheduleData.get(4)) : null; - Integer extraTerms = rescheduleData.get(5) != null ? Integer.parseInt(rescheduleData.get(5)) : null; - BigDecimal newInterestRate = rescheduleData.get(6) != null ? new BigDecimal(rescheduleData.get(6)) : null; + Integer graceOfPrincipal = (rescheduleData.get(3) == null || "0".equals(rescheduleData.get(3))) ? null + : Integer.valueOf(rescheduleData.get(3)); + Integer graceOnInterest = (rescheduleData.get(4) == null || "0".equals(rescheduleData.get(4))) ? null + : Integer.valueOf(rescheduleData.get(4)); + Integer extraTerms = (rescheduleData.get(5) == null || "0".equals(rescheduleData.get(5))) ? null + : Integer.valueOf(rescheduleData.get(5)); + BigDecimal newInterestRate = rescheduleData.get(6) == null ? null : new BigDecimal(rescheduleData.get(6)); PostCreateRescheduleLoansRequest request = new PostCreateRescheduleLoansRequest()// .loanId(loanId)// @@ -127,7 +141,18 @@ public class LoanRescheduleStepDef extends AbstractStepDef { Response createResponse = rescheduleLoansApi.createLoanRescheduleRequest(request).execute(); LoanRescheduleErrorMessage loanRescheduleErrorMessage = LoanRescheduleErrorMessage.valueOf(errorMessageType); - String errorMessageExpected = loanRescheduleErrorMessage.getValue(loanId); + + LocalDate localDate = LocalDate.parse(rescheduleFromDate, FORMATTER_EN); + String rescheduleFromDateFormatted = localDate.format(FORMATTER_HU); + String errorMessageExpected = ""; + int expectedParameterCount = loanRescheduleErrorMessage.getExpectedParameterCount(); + if (expectedParameterCount == 1) { + errorMessageExpected = loanRescheduleErrorMessage.getValue(loanId); + } else if (expectedParameterCount == 2) { + errorMessageExpected = loanRescheduleErrorMessage.getValue(rescheduleFromDateFormatted, loanId); + } else { + throw new IllegalStateException("Parameter count in Error message does not met the criteria"); + } String errorToString = createResponse.errorBody().string(); ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index b460e117e..6fa3f649d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -21,7 +21,7 @@ package org.apache.fineract.test.stepdef.loan; import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.Assert.assertNotNull; import com.google.gson.Gson; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -63,6 +63,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTimeline; import org.apache.fineract.client.models.GetLoansLoanIdTransactions; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; import org.apache.fineract.client.models.IsCatchUpRunningResponse; +import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdResponse; @@ -100,10 +101,13 @@ import org.apache.fineract.test.helper.Utils; import org.apache.fineract.test.initializer.global.LoanProductGlobalInitializerStep; import org.apache.fineract.test.messaging.EventAssertion; import org.apache.fineract.test.messaging.event.EventCheckHelper; +import org.apache.fineract.test.messaging.event.loan.LoanRescheduledDueAdjustScheduleEvent; import org.apache.fineract.test.messaging.event.loan.LoanStatusChangedEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffUndoEvent; +import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionAccrualActivityPostEvent; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -117,6 +121,8 @@ public class LoanStepDef extends AbstractStepDef { public static final String DEFAULT_LOCALE = "en"; public static final String LOAN_STATE_SUBMITTED_AND_PENDING = "Submitted and pending approval"; public static final String LOAN_STATE_APPROVED = "Approved"; + public static final String LOAN_STATE_REJECTED = "Rejected"; + public static final String LOAN_STATE_WITHDRAWN = "Withdrawn by applicant"; public static final String LOAN_STATE_ACTIVE = "Active"; private static final Gson GSON = new JSON().getGson(); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT); @@ -149,6 +155,9 @@ public class LoanStepDef extends AbstractStepDef { @Autowired private LoanProductsApi loanProductsApi; + @Autowired + private EventStore eventStore; + @When("Admin creates a new Loan") public void createLoan() throws IOException { Response clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); @@ -240,6 +249,7 @@ public class LoanStepDef extends AbstractStepDef { private void createTransactionWithIdempotencyKeyAndExternalOwnerCheck(String transactionTypeInput, String transactionPaymentType, String transactionDate, double transactionAmount, String externalOwnerId) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -262,11 +272,13 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(paymentTransactionResponse); eventCheckHelper.transactionEventCheck(paymentTransactionResponse, transactionType, externalOwnerId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Admin makes {string} transaction with {string} payment type on {string} with {double} EUR transaction amount") public void createTransactionForRefund(String transactionTypeInput, String transactionPaymentType, String transactionDate, double transactionAmount) throws IOException, InterruptedException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -284,6 +296,7 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(paymentTransactionResponse); eventCheckHelper.transactionEventCheck(paymentTransactionResponse, transactionType, null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Customer makes {string} transaction with {string} payment type on {string} with {double} EUR transaction amount and system-generated Idempotency key") @@ -303,6 +316,7 @@ public class LoanStepDef extends AbstractStepDef { private void createTransactionWithAutoIdempotencyKeyAndWithExternalOwner(String transactionTypeInput, String transactionPaymentType, String transactionDate, double transactionAmount, String externalOwnerId) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -321,10 +335,12 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(paymentTransactionResponse); eventCheckHelper.transactionEventCheck(paymentTransactionResponse, transactionType, externalOwnerId); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @When("Admin makes Credit Balance Refund transaction on {string} with {double} EUR transaction amount") public void createCBR(String transactionDate, double transactionAmount) throws IOException { + eventStore.reset(); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); @@ -337,6 +353,7 @@ public class LoanStepDef extends AbstractStepDef { .executeLoanTransaction(loanId, paymentTransactionRequest, transactionTypeValue).execute(); testContext().set(TestContextKey.LOAN_PAYMENT_TRANSACTION_RESPONSE, paymentTransactionResponse); ErrorHelper.checkSuccessfulApiCall(paymentTransactionResponse); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); } @Then("Credit Balance Refund transaction on future date {string} with {double} EUR transaction amount will result an error") @@ -444,7 +461,8 @@ public class LoanStepDef extends AbstractStepDef { .expectedDisbursementDate(submitDate)// .graceOnPrincipalPayment(graceOnPrincipalPayment)// .graceOnInterestPayment(graceOnInterestPayment)// - .graceOnInterestPayment(graceOnInterestCharged).transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue)// + .graceOnInterestPayment(graceOnInterestCharged)// + .transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue)// .fixedLength(fixedLength);// Response response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute(); @@ -454,6 +472,84 @@ public class LoanStepDef extends AbstractStepDef { eventCheckHelper.createLoanEventCheck(response); } + @When("Trying to create a fully customized loan with fixed length {int} and with the following data will result a {int} ERROR:") + public void createFullyCustomizedLoanFixedLengthError(int fixedLength, int errorCodeExpected, DataTable table) throws IOException { + List> data = table.asLists(); + List loanData = data.get(1); + String loanProduct = loanData.get(0); + String submitDate = loanData.get(1); + String principal = loanData.get(2); + BigDecimal interestRate = new BigDecimal(loanData.get(3)); + String interestType = loanData.get(4); + String interestCalculationPeriod = loanData.get(5); + String amortizationType = loanData.get(6); + Integer loanTermFrequency = Integer.valueOf(loanData.get(7)); + String loanTermFrequencyType = loanData.get(8); + Integer repaymentFrequency = Integer.valueOf(loanData.get(9)); + String repaymentFrequencyType = loanData.get(10); + Integer numberOfRepayments = Integer.valueOf(loanData.get(11)); + Integer graceOnPrincipalPayment = Integer.valueOf(loanData.get(12)); + Integer graceOnInterestPayment = Integer.valueOf(loanData.get(13)); + Integer graceOnInterestCharged = Integer.valueOf(loanData.get(14)); + String transactionProcessingStrategyCode = loanData.get(15); + + Response clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); + Long clientId = clientResponse.body().getClientId(); + + DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProduct); + Long loanProductId = loanProductResolver.resolve(product); + + LoanTermFrequencyType termFrequencyType = LoanTermFrequencyType.valueOf(loanTermFrequencyType); + Integer loanTermFrequencyTypeValue = termFrequencyType.getValue(); + + RepaymentFrequencyType repaymentFrequencyType1 = RepaymentFrequencyType.valueOf(repaymentFrequencyType); + Integer repaymentFrequencyTypeValue = repaymentFrequencyType1.getValue(); + + InterestType interestType1 = InterestType.valueOf(interestType); + Integer interestTypeValue = interestType1.getValue(); + + InterestCalculationPeriodTime interestCalculationPeriod1 = InterestCalculationPeriodTime.valueOf(interestCalculationPeriod); + Integer interestCalculationPeriodValue = interestCalculationPeriod1.getValue(); + + AmortizationType amortizationType1 = AmortizationType.valueOf(amortizationType); + Integer amortizationTypeValue = amortizationType1.getValue(); + + TransactionProcessingStrategyCode processingStrategyCode = TransactionProcessingStrategyCode + .valueOf(transactionProcessingStrategyCode); + String transactionProcessingStrategyCodeValue = processingStrategyCode.getValue(); + + PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId)// + .productId(loanProductId)// + .principal(new BigDecimal(principal))// + .interestRatePerPeriod(interestRate)// + .interestType(interestTypeValue)// + .interestCalculationPeriodType(interestCalculationPeriodValue)// + .amortizationType(amortizationTypeValue)// + .loanTermFrequency(loanTermFrequency)// + .loanTermFrequencyType(loanTermFrequencyTypeValue)// + .numberOfRepayments(numberOfRepayments)// + .repaymentEvery(repaymentFrequency)// + .repaymentFrequencyType(repaymentFrequencyTypeValue)// + .submittedOnDate(submitDate)// + .expectedDisbursementDate(submitDate)// + .graceOnPrincipalPayment(graceOnPrincipalPayment)// + .graceOnInterestPayment(graceOnInterestPayment)// + .graceOnInterestPayment(graceOnInterestCharged)// + .transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue)// + .fixedLength(fixedLength);// + + Response response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute(); + String errorToString = response.errorBody().string(); + ErrorResponse errorResponse = GSON.fromJson(errorToString, ErrorResponse.class); + String errorMessageActual = errorResponse.getErrors().get(0).getDeveloperMessage(); + int errorCodeActual = response.code(); + + assertThat(errorCodeActual).as(ErrorMessageHelper.wrongErrorCode(errorCodeActual, errorCodeExpected)).isEqualTo(errorCodeExpected); + + log.info("ERROR CODE: {}", errorCodeActual); + log.info("ERROR MESSAGE: {}", errorMessageActual); + } + @When("Admin creates a fully customized loan with Advanced payment allocation and with product no Advanced payment allocation set results an error:") public void createFullyCustomizedLoanNoAdvancedPaymentError(DataTable table) throws IOException { int errorCodeExpected = 403; @@ -519,7 +615,8 @@ public class LoanStepDef extends AbstractStepDef { .expectedDisbursementDate(submitDate)// .graceOnPrincipalPayment(graceOnPrincipalPayment)// .graceOnInterestPayment(graceOnInterestPayment)// - .graceOnInterestPayment(graceOnInterestCharged).transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue);// + .graceOnInterestPayment(graceOnInterestCharged)// + .transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue);// Response response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute(); int errorCodeActual = response.code(); @@ -535,6 +632,79 @@ public class LoanStepDef extends AbstractStepDef { log.info("ERROR MESSAGE: {}", errorMessageActual); } + @When("Admin creates a fully customized loan with installment level delinquency and with the following data:") + public void createFullyCustomizedLoanWithInstallmentLvlDelinquency(DataTable table) throws IOException { + List> data = table.asLists(); + List loanData = data.get(1); + String loanProduct = loanData.get(0); + String submitDate = loanData.get(1); + String principal = loanData.get(2); + BigDecimal interestRate = new BigDecimal(loanData.get(3)); + String interestType = loanData.get(4); + String interestCalculationPeriod = loanData.get(5); + String amortizationType = loanData.get(6); + Integer loanTermFrequency = Integer.valueOf(loanData.get(7)); + String loanTermFrequencyType = loanData.get(8); + Integer repaymentFrequency = Integer.valueOf(loanData.get(9)); + String repaymentFrequencyType = loanData.get(10); + Integer numberOfRepayments = Integer.valueOf(loanData.get(11)); + Integer graceOnPrincipalPayment = Integer.valueOf(loanData.get(12)); + Integer graceOnInterestPayment = Integer.valueOf(loanData.get(13)); + Integer graceOnInterestCharged = Integer.valueOf(loanData.get(14)); + String transactionProcessingStrategyCode = loanData.get(15); + + Response clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); + Long clientId = clientResponse.body().getClientId(); + + DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProduct); + Long loanProductId = loanProductResolver.resolve(product); + + LoanTermFrequencyType termFrequencyType = LoanTermFrequencyType.valueOf(loanTermFrequencyType); + Integer loanTermFrequencyTypeValue = termFrequencyType.getValue(); + + RepaymentFrequencyType repaymentFrequencyType1 = RepaymentFrequencyType.valueOf(repaymentFrequencyType); + Integer repaymentFrequencyTypeValue = repaymentFrequencyType1.getValue(); + + InterestType interestType1 = InterestType.valueOf(interestType); + Integer interestTypeValue = interestType1.getValue(); + + InterestCalculationPeriodTime interestCalculationPeriod1 = InterestCalculationPeriodTime.valueOf(interestCalculationPeriod); + Integer interestCalculationPeriodValue = interestCalculationPeriod1.getValue(); + + AmortizationType amortizationType1 = AmortizationType.valueOf(amortizationType); + Integer amortizationTypeValue = amortizationType1.getValue(); + + TransactionProcessingStrategyCode processingStrategyCode = TransactionProcessingStrategyCode + .valueOf(transactionProcessingStrategyCode); + String transactionProcessingStrategyCodeValue = processingStrategyCode.getValue(); + + PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId)// + .productId(loanProductId)// + .principal(new BigDecimal(principal))// + .interestRatePerPeriod(interestRate)// + .interestType(interestTypeValue)// + .interestCalculationPeriodType(interestCalculationPeriodValue)// + .amortizationType(amortizationTypeValue)// + .loanTermFrequency(loanTermFrequency)// + .loanTermFrequencyType(loanTermFrequencyTypeValue)// + .numberOfRepayments(numberOfRepayments)// + .repaymentEvery(repaymentFrequency)// + .repaymentFrequencyType(repaymentFrequencyTypeValue)// + .submittedOnDate(submitDate)// + .expectedDisbursementDate(submitDate)// + .graceOnPrincipalPayment(graceOnPrincipalPayment)// + .graceOnInterestPayment(graceOnInterestPayment)// + .graceOnInterestPayment(graceOnInterestCharged)// + .transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue)// + .enableInstallmentLevelDelinquency(true);// + + Response response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute(); + testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response); + ErrorHelper.checkSuccessfulApiCall(response); + + eventCheckHelper.createLoanEventCheck(response); + } + @Then("Loan details has the following last payment related data:") public void checkLastPaymentData(DataTable table) throws IOException { List> data = table.asLists(); @@ -730,6 +900,37 @@ public class LoanStepDef extends AbstractStepDef { eventCheckHelper.approveLoanEventCheck(loanApproveResponse); } + @And("Admin successfully rejects the loan on {string}") + public void rejectLoan(String rejectDate) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + PostLoansLoanIdRequest rejectRequest = LoanRequestFactory.defaultLoanRejectRequest().rejectedOnDate(rejectDate); + + Response loanRejectResponse = loansApi.stateTransitions(loanId, rejectRequest, "reject").execute(); + testContext().set(TestContextKey.LOAN_REJECT_RESPONSE, loanRejectResponse); + ErrorHelper.checkSuccessfulApiCall(loanRejectResponse); + assertThat(loanRejectResponse.body().getChanges().getStatus().getValue()).isEqualTo(LOAN_STATE_REJECTED); + assertThat(loanRejectResponse.body().getChanges().getStatus().getValue()).isEqualTo(LOAN_STATE_REJECTED); + + eventCheckHelper.loanRejectedEventCheck(loanRejectResponse); + } + + @And("Admin successfully withdrawn the loan on {string}") + public void withdrawnLoan(String withdrawnDate) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + PostLoansLoanIdRequest withdawnRequest = LoanRequestFactory.defaultLoanWithdrawnRequest().withdrawnOnDate(withdrawnDate); + + Response loanWithdrawnResponse = loansApi.stateTransitions(loanId, withdawnRequest, "withdrawnByApplicant") + .execute(); + testContext().set(TestContextKey.LOAN_WITHDRAWN_RESPONSE, loanWithdrawnResponse); + ErrorHelper.checkSuccessfulApiCall(loanWithdrawnResponse); + assertThat(loanWithdrawnResponse.body().getChanges().getStatus().getValue()).isEqualTo(LOAN_STATE_WITHDRAWN); + assertThat(loanWithdrawnResponse.body().getChanges().getStatus().getValue()).isEqualTo(LOAN_STATE_WITHDRAWN); + + eventCheckHelper.undoApproveLoanEventCheck(loanWithdrawnResponse); + } + @And("Admin successfully approves the second loan on {string} with {string} amount and expected disbursement date on {string}") public void approveSecondLoan(String approveDate, String approvedAmount, String expectedDisbursementDate) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_SECOND_LOAN_RESPONSE); @@ -785,10 +986,31 @@ public class LoanStepDef extends AbstractStepDef { @And("Admin successfully disburse the loan on {string} with {string} EUR transaction amount") public void disburseLoan(String actualDisbursementDate, String transactionAmount) throws IOException { - final Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - assertNotNull(loanResponse.body()); - final long loanId = loanResponse.body().getLoanId(); - final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() + .actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount)); + + Response loanDisburseResponse = loansApi.stateTransitions(loanId, disburseRequest, "disburse").execute(); + testContext().set(TestContextKey.LOAN_DISBURSE_RESPONSE, loanDisburseResponse); + ErrorHelper.checkSuccessfulApiCall(loanDisburseResponse); + Long statusActual = loanDisburseResponse.body().getChanges().getStatus().getId(); + + Response loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute(); + Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId()); + + assertThat(statusActual)// + .as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))// + .isEqualTo(statusExpected);// + eventCheckHelper.disburseLoanEventCheck(loanId); + eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); + } + + @And("Admin successfully disburse the loan without auto downpayment on {string} with {string} EUR transaction amount") + public void disburseLoanWithoutAutoDownpayment(String actualDisbursementDate, String transactionAmount) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() .actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount)); performLoanDisbursementAndVerifyStatus(loanId, disburseRequest); } @@ -829,7 +1051,7 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(loanDisburseResponse); assertThat(loanDisburseResponse.body().getChanges().getStatus().getValue()).isEqualTo(LOAN_STATE_ACTIVE); - eventCheckHelper.disburseLoanEventCheck(loanDisburseResponse); + eventCheckHelper.disburseLoanEventCheck(loanId); eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); } @@ -1141,10 +1363,13 @@ public class LoanStepDef extends AbstractStepDef { List> data = table.asLists(); for (int i = 1; i < data.size(); i++) { List expectedValues = data.get(i); + String transactionDateExpected = expectedValues.get(0); List> actualValuesList = transactions.stream()// + .filter(t -> transactionDateExpected.equals(FORMATTER.format(t.getDate())))// .map(t -> fetchValuesOfTransaction(table.row(0), t))// .collect(Collectors.toList());// - boolean containsExpectedValues = actualValuesList.get(i - 1).equals(expectedValues);// + boolean containsExpectedValues = actualValuesList.stream()// + .anyMatch(actualValues -> actualValues.equals(expectedValues));// assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInTransactionsTab(i, actualValuesList, expectedValues)) .isTrue(); } @@ -1319,7 +1544,7 @@ public class LoanStepDef extends AbstractStepDef { assertThat(allInstallmentsObligationsMet).isTrue(); } - @Then("Loan closedon_date is {}") + @Then("Loan closedon_date is {string}") public void loanClosedonDate(String date) throws IOException { Response loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanCreateResponse.body().getLoanId(); @@ -1327,10 +1552,10 @@ public class LoanStepDef extends AbstractStepDef { Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse); - if (date == null || "null".equals(date)) { + if ("null".equals(date)) { assertThat(loanDetailsResponse.body().getTimeline().getClosedOnDate()).isNull(); } else { - assertThat(loanDetailsResponse.body().getTimeline().getClosedOnDate()).isEqualTo(date); + assertThat(FORMATTER.format(loanDetailsResponse.body().getTimeline().getClosedOnDate())).isEqualTo(date); } } @@ -1555,7 +1780,7 @@ public class LoanStepDef extends AbstractStepDef { IsCatchUpRunningResponse isCatchUpRunning = isCatchUpRunningResponse.body(); return isCatchUpRunning.getIsCatchUpRunning(); }); - await().pollInterval(2, TimeUnit.SECONDS).atMost(Duration.ofSeconds(240)).until(() -> { + await().pollInterval(2, TimeUnit.SECONDS).atMost(Duration.ofMinutes(4)).until(() -> { Response isCatchUpRunningResponse = loanCobCatchUpApi.isCatchUpRunning().execute(); ErrorHelper.checkSuccessfulApiCall(isCatchUpRunningResponse); IsCatchUpRunningResponse isCatchUpRunning = isCatchUpRunningResponse.body(); @@ -1596,6 +1821,31 @@ public class LoanStepDef extends AbstractStepDef { eventAssertion.assertEventRaised(LoanAccrualTransactionCreatedBusinessEvent.class, accrualTransactionId); } + @Then("LoanTransactionAccrualActivityPostBusinessEvent is raised on {string}") + public void checkLoanTransactionAccrualActivityPostBusinessEvent(String date) throws IOException { + Response loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanCreateResponse.body().getLoanId(); + + Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + List transactions = loanDetailsResponse.body().getTransactions(); + GetLoansLoanIdTransactions accrualTransaction = transactions.stream() + .filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())).findFirst() + .orElseThrow(() -> new IllegalStateException(String.format("No Accrual activity transaction found on %s", date))); + Long accrualTransactionId = accrualTransaction.getId(); + + eventAssertion.assertEventRaised(LoanTransactionAccrualActivityPostEvent.class, accrualTransactionId); + } + + @Then("LoanRescheduledDueAdjustScheduleBusinessEvent is raised on {string}") + public void checkLoanRescheduledDueAdjustScheduleBusinessEvent(String date) throws IOException { + Response loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanCreateResponse.body().getLoanId(); + + eventAssertion.assertEventRaised(LoanRescheduledDueAdjustScheduleEvent.class, loanId); + } + @Then("Loan details and event has the following last repayment related data:") public void checkLastRepaymentData(DataTable table) throws IOException { List> data = table.asLists(); @@ -1649,7 +1899,6 @@ public class LoanStepDef extends AbstractStepDef { return null; }); - } @And("Admin does a charge-off undo the loan with reversal external Id") @@ -1741,7 +1990,7 @@ public class LoanStepDef extends AbstractStepDef { String futureInstallmentAllocationRuleNew) throws IOException { DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProductName); Long loanProductId = loanProductResolver.resolve(product); - log.info("loanProductId {}", loanProductId); + log.info("loanProductId: {}", loanProductId); Response loanProductDetails = loanProductsApi.retrieveLoanProductDetails(loanProductId).execute(); ErrorHelper.checkSuccessfulApiCall(loanProductDetails); @@ -1751,11 +2000,12 @@ public class LoanStepDef extends AbstractStepDef { paymentAllocation.forEach(e -> { String transactionTypeOriginal = e.getTransactionType(); String futureInstallmentAllocationRule = e.getFutureInstallmentAllocationRule(); + List paymentAllocationOrder = e.getPaymentAllocationOrder(); if (transactionTypeToChange.equals(transactionTypeOriginal)) { futureInstallmentAllocationRule = futureInstallmentAllocationRuleNew; } - newPaymentAllocation.add( - LoanProductGlobalInitializerStep.createPaymentAllocation(transactionTypeOriginal, futureInstallmentAllocationRule)); + newPaymentAllocation.add(LoanProductGlobalInitializerStep.editPaymentAllocationFutureInstallment(transactionTypeOriginal, + futureInstallmentAllocationRule, paymentAllocationOrder)); }); PutLoanProductsProductIdRequest putLoanProductsProductIdRequest = new PutLoanProductsProductIdRequest() @@ -1770,7 +2020,7 @@ public class LoanStepDef extends AbstractStepDef { public void editRepaymentStartDateType(String loanProductName, String repaymentStartDateType) throws IOException { DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProductName); Long loanProductId = loanProductResolver.resolve(product); - log.info("loanProductId {}", loanProductId); + log.info("loanProductId: {}", loanProductId); Map repaymentStartDateTypeMap = Map.of("DISBURSEMENT_DATE", 1, "SUBMITTED_ON_DATE", 2); @@ -1822,7 +2072,6 @@ public class LoanStepDef extends AbstractStepDef { .adjustLoanTransaction(loanId, targetTransaction.getId(), transactionUndoRequest, "").execute(); ErrorResponse errorDetails = ErrorResponse.from(transactionUndoResponse); assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(503); - } @Then("Loan {string} repayment transaction on {string} with {double} EUR transaction amount results in error") @@ -1843,7 +2092,6 @@ public class LoanStepDef extends AbstractStepDef { ErrorResponse errorDetails = ErrorResponse.from(repaymentResponse); assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(400); - } @Then("Loan details has the downpayment amount {string} in summary.totalRepaymentTransaction") @@ -2036,7 +2284,7 @@ public class LoanStepDef extends AbstractStepDef { assertThat(statusActual).as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected))) .isEqualTo(statusExpected); - eventCheckHelper.disburseLoanEventCheck(loanDisburseResponse); + eventCheckHelper.disburseLoanEventCheck(loanId); eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); } @@ -2114,7 +2362,6 @@ public class LoanStepDef extends AbstractStepDef { return result; } - @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") private List fetchValuesOfTransaction(List header, GetLoansLoanIdTransactions t) { List actualValues = new ArrayList<>(); for (String headerName : header) { @@ -2132,12 +2379,16 @@ public class LoanStepDef extends AbstractStepDef { case "Overpayment" -> actualValues.add(t.getOverpaymentPortion() == null ? null : String.valueOf(t.getOverpaymentPortion())); case "Reverted" -> actualValues.add(t.getManuallyReversed() == null ? null : String.valueOf(t.getManuallyReversed())); + case "Replayed" -> { + boolean hasReplayed = t.getTransactionRelations().stream().anyMatch(e -> "REPLAYED".equals(e.getRelationType())); + actualValues.add(hasReplayed ? "true" : "false"); + } + default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName)); } } return actualValues; } - @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") private List fetchValuesOfRepaymentSchedule(List header, GetLoansLoanIdRepaymentPeriod repaymentPeriod) { List actualValues = new ArrayList<>(); for (String headerName : header) { @@ -2171,6 +2422,7 @@ public class LoanStepDef extends AbstractStepDef { : String.valueOf(repaymentPeriod.getTotalWaivedForPeriod())); case "Outstanding" -> actualValues.add(repaymentPeriod.getTotalOutstandingForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalOutstandingForPeriod())); + default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName)); } } return actualValues; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java new file mode 100644 index 000000000..6a66a162e --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java @@ -0,0 +1,184 @@ +/** + * 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.test.stepdef.saving; + +import io.cucumber.java.en.And; +import java.io.IOException; +import java.math.BigDecimal; +import org.apache.fineract.client.models.PostClientsResponse; +import org.apache.fineract.client.models.PostSavingsAccountTransactionsRequest; +import org.apache.fineract.client.models.PostSavingsAccountTransactionsResponse; +import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest; +import org.apache.fineract.client.models.PostSavingsAccountsAccountIdResponse; +import org.apache.fineract.client.models.PostSavingsAccountsRequest; +import org.apache.fineract.client.models.PostSavingsAccountsResponse; +import org.apache.fineract.client.services.SavingsAccountApi; +import org.apache.fineract.client.services.SavingsAccountTransactionsApi; +import org.apache.fineract.test.factory.SavingsAccountRequestFactory; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; +import org.springframework.beans.factory.annotation.Autowired; +import retrofit2.Response; + +public class SavingsAccountStepDef extends AbstractStepDef { + + @Autowired + private SavingsAccountTransactionsApi savingsAccountTransactionsApi; + + @Autowired + private SavingsAccountApi savingsAccountApi; + + @And("Client creates a new EUR savings account with {string} submitted on date") + public void createSavingsAccountEUR(String submittedOnDate) throws IOException { + Response clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); + long clientId = clientResponse.body().getClientId(); + + PostSavingsAccountsRequest createSavingsAccountRequest = SavingsAccountRequestFactory.defaultEURSavingsAccountRequest() + .clientId((int) clientId).submittedOnDate(submittedOnDate); + + Response createSavingsAccountResponse = savingsAccountApi + .submitApplication2(createSavingsAccountRequest).execute(); + testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE, createSavingsAccountResponse); + } + + @And("Client creates a new USD savings account with {string} submitted on date") + public void createSavingsAccountUSD(String submittedOnDate) throws IOException { + Response clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); + long clientId = clientResponse.body().getClientId(); + + PostSavingsAccountsRequest createSavingsAccountRequest = SavingsAccountRequestFactory.defaultUSDSavingsAccountRequest() + .clientId((int) clientId).submittedOnDate(submittedOnDate); + + Response createSavingsAccountResponse = savingsAccountApi + .submitApplication2(createSavingsAccountRequest).execute(); + testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE, createSavingsAccountResponse); + } + + @And("Approve EUR savings account on {string} date") + public void approveEurSavingsAccount(String approvedOnDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountsAccountIdRequest approveSavingsAccountRequest = SavingsAccountRequestFactory.defaultApproveRequest() + .approvedOnDate(approvedOnDate); + + Response approveSavingsAccountResponse = savingsAccountApi + .handleCommands6(savingsAccountID, approveSavingsAccountRequest, "approve").execute(); + testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_APPROVE_RESPONSE, approveSavingsAccountResponse); + } + + @And("Approve USD savings account on {string} date") + public void approveUsdSavingsAccount(String approvedOnDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountsAccountIdRequest approveSavingsAccountRequest = SavingsAccountRequestFactory.defaultApproveRequest() + .approvedOnDate(approvedOnDate); + + Response approveSavingsAccountResponse = savingsAccountApi + .handleCommands6(savingsAccountID, approveSavingsAccountRequest, "approve").execute(); + testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_APPROVE_RESPONSE, approveSavingsAccountResponse); + } + + @And("Activate EUR savings account on {string} date") + public void activateSavingsAccount(String activatedOnDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountsAccountIdRequest activateSavingsAccountRequest = SavingsAccountRequestFactory.defaultActivateRequest() + .activatedOnDate(activatedOnDate); + + Response activateSavingsAccountResponse = savingsAccountApi + .handleCommands6(savingsAccountID, activateSavingsAccountRequest, "activate").execute(); + testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_ACTIVATED_RESPONSE, activateSavingsAccountResponse); + } + + @And("Activate USD savings account on {string} date") + public void activateUsdSavingsAccount(String activatedOnDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountsAccountIdRequest activateSavingsAccountRequest = SavingsAccountRequestFactory.defaultActivateRequest() + .activatedOnDate(activatedOnDate); + + Response activateSavingsAccountResponse = savingsAccountApi + .handleCommands6(savingsAccountID, activateSavingsAccountRequest, "activate").execute(); + testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_ACTIVATED_RESPONSE, activateSavingsAccountResponse); + } + + @And("Client successfully deposits {double} EUR to the savings account on {string} date") + public void createEurDeposit(double depositAmount, String depositDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountTransactionsRequest depositRequest = SavingsAccountRequestFactory.defaultDepositRequest() + .transactionDate(depositDate).transactionAmount(BigDecimal.valueOf(depositAmount)); + + Response depositResponse = savingsAccountTransactionsApi + .transaction2(savingsAccountID, depositRequest, "deposit").execute(); + testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_DEPOSIT_RESPONSE, depositResponse); + } + + @And("Client successfully deposits {double} USD to the savings account on {string} date") + public void createUsdDeposit(double depositAmount, String depositDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountTransactionsRequest depositRequest = SavingsAccountRequestFactory.defaultDepositRequest() + .transactionDate(depositDate).transactionAmount(BigDecimal.valueOf(depositAmount)); + + Response depositResponse = savingsAccountTransactionsApi + .transaction2(savingsAccountID, depositRequest, "deposit").execute(); + testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_DEPOSIT_RESPONSE, depositResponse); + } + + @And("Client successfully withdraw {double} EUR from the savings account on {string} date") + public void createEurWithdraw(double withdrawAmount, String transcationDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountTransactionsRequest withdrawRequest = SavingsAccountRequestFactory.defaultWithdrawRequest() + .transactionDate(transcationDate).transactionAmount(BigDecimal.valueOf(withdrawAmount)); + + Response withdrawalResponse = savingsAccountTransactionsApi + .transaction2(savingsAccountID, withdrawRequest, "withdrawal").execute(); + testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_WITHDRAW_RESPONSE, withdrawalResponse); + } + + @And("Client successfully withdraw {double} USD from the savings account on {string} date") + public void createUsdWithdraw(double withdrawAmount, String transcationDate) throws IOException { + Response savingsAccountResponse = testContext() + .get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE); + long savingsAccountID = savingsAccountResponse.body().getSavingsId(); + + PostSavingsAccountTransactionsRequest withdrawRequest = SavingsAccountRequestFactory.defaultWithdrawRequest() + .transactionDate(transcationDate).transactionAmount(BigDecimal.valueOf(withdrawAmount)); + + Response withdrawalResponse = savingsAccountTransactionsApi + .transaction2(savingsAccountID, withdrawRequest, "withdrawal").execute(); + testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_WITHDRAW_RESPONSE, withdrawalResponse); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContext.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContext.java index f9beda2a5..91ec17a93 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContext.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContext.java @@ -25,7 +25,8 @@ import java.util.Map; public enum TestContext { - INSTANCE; + INSTANCE, // + GLOBAL;// @SuppressWarnings("ImmutableEnumChecker") private final ThreadLocal> testContexts = withInitial(HashMap::new); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index 211947497..0502844ce 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -24,6 +24,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) public abstract class TestContextKey { + public static final String ORIGINAL_COB_WORKFLOW_JOB_BUSINESS_STEP_LIST = "originalCOBWorkflowJobBusinessStepList"; public static final String CLIENT_CREATE_RESPONSE = "clientCreateResponse"; public static final String CLIENT_CREATE_SECOND_CLIENT_RESPONSE = "clientCreateSecondClientResponse"; public static final String LOAN_CREATE_RESPONSE = "loanCreateResponse"; @@ -35,6 +36,8 @@ public abstract class TestContextKey { public static final String WAIVE_CHARGE_RESPONSE = "waiveChargeResponse"; public static final String UNDO_WAIVE_RESPONSE = "waiveNsfFeeResponse"; public static final String LOAN_APPROVAL_RESPONSE = "loanApprovalResponse"; + public static final String LOAN_REJECT_RESPONSE = "loanRejectResponse"; + public static final String LOAN_WITHDRAWN_RESPONSE = "loanWithdrawnResponse"; public static final String LOAN_APPROVAL_SECOND_LOAN_RESPONSE = "loanApprovalSecondLoanResponse"; public static final String LOAN_UNDO_APPROVAL_RESPONSE = "loanUndoApprovalResponse"; public static final String LOAN_DISBURSE_RESPONSE = "loanDisburseResponse"; @@ -61,27 +64,44 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_PERIOD_SAME_AS_PAYMENT = "loanProductCreateResponseLP1InterestDecliningPeriodSameAsPayment"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_1MONTH_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_MONTHLY = "loanProductCreateResponseLP1InterestDecliningPeriodSameAsPaymentRecalculation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE = "loanProductCreateResponseLP1InterestDecliningBalanceDailyRecalculationCompoundingNone"; - public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE = "loanProductCreateResponseLP2ProgressiveLoanScheduleCustomPaymentAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE = "loanProductCreateResponseLP1InterestDecliningBalanceDailyRecalculationSameAsRepaymentCompoundingNone"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTI_DISBURSEMENT = "loanProductCreateResponseLP1InterestDecliningBalanceDailyRecalculationSameAsRepaymentCompoundingNoneMultiDisbursement"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_REDUCE_NR_INSTALLMENTS = "loanProductCreateResponseLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneRescheduleReduceNrInstallments"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_RESCHEDULE_NEXT_REPAYMENTS = "loanProductCreateResponseLP1InterestDecliningBalanceDailyRecalculationCompoundingNoneRescheduleNextRepayments"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_PERIOD_DAILY = "loanProductCreateResponseLP1InterestDecliningPeriodDaily"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_PERIOD_DAILY_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP1InterestDecliningPeriodDailyAccrualActivity"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_INTEREST_DECLINING_BALANCE_DAILY_RECALCULATION_COMPOUNDING_NONE_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP1InterestDecliningBalanceRecalculationCompoundingNoneAccrualActivity"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION = "loanProductCreateResponseLP2DownPaymentAutoAdvancedPaymentAllocation"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_AUTO_ADVANCED_REPAYMENT_ALLOCATION_PAYMENT_START_SUBMITTED = "loanProductCreateResponseLP2DownPaymentAutoAdvancedPaymentAllocationPaymentStartSubmitted"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION = "loanProductCreateResponseLP2DownPaymentAdvancedPaymentAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT = "loanProductCreateResponseLP2DownPayment"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_INTEREST = "loanProductCreateResponseLP2DownPaymentInterest"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_INTEREST_AUTO = "loanProductCreateResponseLP2DownPaymentInterestAuto"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_AUTO = "loanProductCreateResponseLP2DownPaymentAuto"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE = "loanProductCreateResponseLP2DownPaymentProgressiveLoanSchedule"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE = "loanProductCreateResponseLP2ProgressiveLoanScheduleCustomPaymentAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleVertical"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_INSTALLMENT_LEVEL_DELINQUENCY = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleInstallmentLevelDelinquency"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROG_SCHEDULE_HOR_INST_LVL_DELINQUENCY_CREDIT_ALLOCATION = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleHorizontalInstallmentLevelDelinquencyCreditAllocation"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH = "loanProductCreateResponseLP2DownPaymentProgressiveLoanScheduleFixedLength"; - public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationTillPreClose"; - public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationTillRestFrequency"; - public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL = "loanProductCreateResponseLP2ProgressiveLoanScheduleHorizontal"; - public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_LAST = "loanProductCreateResponseLP2ProgressiveLoanScheduleHorizontal"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_INTEREST_FLAT_ADV_PMT_ALLOC = "loanProductCreateResponseLP2DownPaymentInterestFlatAdvancedPaymentAllocation"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActual"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualAccrualActivity"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30 = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyTillPreClose"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyTillPreCloseLastInstallment"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_PMT_ALLOC_1 = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyTillPreClosePmtAlloc1"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_PRECLOSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillPreClose"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyTillRestFrequencyDate"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_REST_FREQUENCY_DATE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillRestFrequencyDate"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefundFull"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburse"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburseDownPayment"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi365Actual"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030DownPayment"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefund"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL = "loanProductCreateResponseLP1ProgressiveLoanScheduleHorizontal"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillPreCloseWholeTerm"; public static final String CHARGE_FOR_LOAN_PERCENT_LATE_CREATE_RESPONSE = "ChargeForLoanPercentLateCreateResponse"; public static final String CHARGE_FOR_LOAN_PERCENT_LATE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanPercentLateAmountPlusInterestCreateResponse"; public static final String CHARGE_FOR_LOAN_PERCENT_PROCESSING_CREATE_RESPONSE = "ChargeForLoanPercentProcessingCreateResponse"; @@ -132,5 +152,4 @@ public abstract class TestContextKey { public static final String TRANSACTION_EVENT = "transactionEvent"; public static final String LOAN_WRITE_OFF_RESPONSE = "loanWriteOffResponse"; public static final String LOAN_DELINQUENCY_ACTION_RESPONSE = "loanDelinquencyActionResponse"; - } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/AddResultForCaseRequest.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/AddResultForCaseRequest.java new file mode 100644 index 000000000..67b4453e4 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/AddResultForCaseRequest.java @@ -0,0 +1,37 @@ +/** + * 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.test.testrail; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +class AddResultForCaseRequest { + + @SerializedName("status_id") + private TestRailStatus statusId; + + private String comment; +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailApiClient.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailApiClient.java new file mode 100644 index 000000000..29e943a18 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailApiClient.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.testrail; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; + +interface TestRailApiClient { + + @Headers({ "Content-Type:application/json" }) + @POST("/api/v2/add_result_for_case/{runId}/{caseId}") + Call addResultForCase(@Path("runId") int runId, @Path("caseId") int caseId, @Body AddResultForCaseRequest request); +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailClient.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailClient.java new file mode 100644 index 000000000..ce0f605a3 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailClient.java @@ -0,0 +1,141 @@ +/** + * 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.test.testrail; + +import io.cucumber.java.Scenario; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.ResponseBody; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.runner.Result; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; +import retrofit2.Response; + +@Slf4j +@Component +@RequiredArgsConstructor +@Conditional(TestRailEnabledCondition.class) +public class TestRailClient implements InitializingBean { + + public static final Pattern TESTRAIL_TAG_PATTERN = Pattern.compile("@TestRailId:C([0-9]+)"); + private static final String SUCCESS_COMMENT = "Test passed"; + private static final String FAILED_COMMENT = "Test failed"; + + private final TestRailProperties testRailProperties; + private final TestRailApiClient apiClient; + + @Override + public void afterPropertiesSet() { + log.warn("Configured TestRail Run ID: '{}'", testRailProperties.getRunId()); + } + + public void saveScenarioResult(Scenario scenario) { + Integer caseId = getScenarioCaseId(scenario); + + AddResultForCaseRequest request; + if (scenario.isFailed()) { + request = createFailedRequest(scenario); + } else { + request = createSuccessRequest(scenario); + } + + if (caseId != null) { + try { + Response response = apiClient.addResultForCase(testRailProperties.getRunId(), caseId, request).execute(); + if (response.code() != HttpStatus.SC_OK) { + handleApiError(response); + } + } catch (IOException e) { + throw new RuntimeException("Error while reporting to TestRail", e); + } + } + } + + private Integer getScenarioCaseId(Scenario scenario) { + Integer caseId = null; + for (String s : scenario.getSourceTagNames()) { + if (s.contains("TestRail")) { + Matcher matcher = TESTRAIL_TAG_PATTERN.matcher(s); + if (matcher.matches()) { + caseId = Integer.parseInt(matcher.group(1)); + } + } + } + return caseId; + } + + private void handleApiError(Response response) throws IOException { + String exceptionMsg = "Error while reporting to TestRail"; + ResponseBody errorBody = response.errorBody(); + if (errorBody != null) { + exceptionMsg += " " + errorBody.string(); + } + throw new RuntimeException(exceptionMsg); + } + + private AddResultForCaseRequest createFailedRequest(Scenario scenario) { + return new AddResultForCaseRequest.AddResultForCaseRequestBuilder().statusId(TestRailStatus.FAILED) + .comment(createFailedComment(scenario)).build(); + } + + private AddResultForCaseRequest createSuccessRequest(Scenario scenario) { + return new AddResultForCaseRequest.AddResultForCaseRequestBuilder().statusId(TestRailStatus.PASSED) + .comment(createSuccessComment(scenario)).build(); + } + + private String createSuccessComment(Scenario scenario) { + return SUCCESS_COMMENT; + } + + private String createFailedComment(Scenario scenario) { + try { + Class c = ClassUtils.getClass("cucumber.runtime.java.JavaHookDefinition$ScenarioAdaptor"); + Field fieldScenario = FieldUtils.getField(c, "scenario", true); + if (fieldScenario != null) { + + fieldScenario.setAccessible(true); + Object objectScenario = fieldScenario.get(scenario); + + Field fieldStepResults = objectScenario.getClass().getDeclaredField("stepResults"); + fieldStepResults.setAccessible(true); + + ArrayList results = (ArrayList) fieldStepResults.get(objectScenario); + for (Result result : results) { + if (result.getFailures() != null) { + return FAILED_COMMENT + "%n" + result.getFailures(); + } + } + } + + return FAILED_COMMENT; + + } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { + return FAILED_COMMENT; + } + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailConfiguration.java new file mode 100644 index 000000000..838e463be --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailConfiguration.java @@ -0,0 +1,80 @@ +/** + * 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.test.testrail; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +import java.io.IOException; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.fineract.client.auth.HttpBasicAuth; +import org.apache.fineract.client.util.JSON; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import retrofit2.Retrofit; + +@Configuration +@Conditional(TestRailEnabledCondition.class) +public class TestRailConfiguration { + + @Autowired + private TestRailProperties testRailProperties; + + @Bean + public TestRailApiClient testRailApiClient() { + String testRailBaseUrl = testRailProperties.getBaseUrl(); + String testRailUsername = testRailProperties.getUsername(); + String testRailPassword = testRailProperties.getPassword(); + + if (isBlank(testRailBaseUrl)) { + throw new IllegalStateException("TestRail base URL has not been set"); + } + if (isBlank(testRailUsername)) { + throw new IllegalStateException("TestRail username has not been set"); + } + if (isBlank(testRailPassword)) { + throw new IllegalStateException("TestRail password has not been set"); + } + + HttpBasicAuth httpBasicAuth = new HttpBasicAuth(); + httpBasicAuth.setCredentials(testRailUsername, testRailPassword); + OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(httpBasicAuth).addInterceptor(new TestRailIndexPhpInterceptor()) + .build(); + + Retrofit retrofit = new Retrofit.Builder().addConverterFactory(JSON.GsonCustomConverterFactory.create(new JSON().getGson())) + .client(httpClient).baseUrl(testRailBaseUrl).build(); + + return retrofit.create(TestRailApiClient.class); + } + + // Needed otherwise Retrofit 2 will be mad on the URL query/path parameters + private static final class TestRailIndexPhpInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + String finalUrl = request.url().toString().replace("api/v2", "index.php?/api/v2"); + return chain.proceed(request.newBuilder().url(finalUrl).build()); + } + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailEnabledCondition.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailEnabledCondition.java new file mode 100644 index 000000000..161e47e5d --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailEnabledCondition.java @@ -0,0 +1,34 @@ +/** + * 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.test.testrail; + +import org.apache.fineract.test.support.PropertiesCondition; + +public class TestRailEnabledCondition extends PropertiesCondition { + + @Override + protected Class getPropertiesClass() { + return TestRailProperties.class; + } + + @Override + protected boolean matches(TestRailProperties properties) { + return properties.isEnabled(); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailProperties.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailProperties.java new file mode 100644 index 000000000..472182fbf --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailProperties.java @@ -0,0 +1,39 @@ +/** + * 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.test.testrail; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class TestRailProperties { + + @Value("${fineract-test.testrail.enabled}") + private boolean enabled; + @Value("${fineract-test.testrail.base-url}") + private String baseUrl; + @Value("${fineract-test.testrail.username}") + private String username; + @Value("${fineract-test.testrail.password}") + private String password; + @Value("${fineract-test.testrail.run-id}") + private int runId; +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailStatus.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailStatus.java new file mode 100644 index 000000000..2dc659179 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/testrail/TestRailStatus.java @@ -0,0 +1,34 @@ +/** + * 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.test.testrail; + +import com.google.gson.annotations.SerializedName; + +enum TestRailStatus { + @SerializedName("1") // + PASSED, // + @SerializedName("2") // + BLOCKED, // + @SerializedName("3") // + UNTESTED, // + @SerializedName("4") // + RETEST, // + @SerializedName("5") // + FAILED// +} diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature index 747fb3c1c..87dce825a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature @@ -1257,7 +1257,7 @@ Feature: Loan When Admin sets the business date to "20 June 2023" When Customer undo "1"th "Repayment" transaction made on "20 June 2023" Then Loan status will be "ACTIVE" - Then Loan closedon_date is null + Then Loan closedon_date is "null" Scenario: As an admin I would like to delete a loan using external id When Admin sets the business date to the actual date @@ -5541,7 +5541,7 @@ Feature: Loan When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount Then Loan Repayment schedule has 6 periods, with the following data for periods: @@ -5598,10 +5598,10 @@ Feature: Loan Scenario: Early pay-off loan with interest, TILL_REST_FREQUENCY_DATE product When Admin sets the business date to "01 January 2024" When Admin creates a client with random data - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount Then Loan Repayment schedule has 6 periods, with the following data for periods: @@ -5654,7 +5654,7 @@ Feature: Loan | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | | 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false | Then Loan's all installments have obligations met - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule Scenario: Interest recalculation - S1 daily for overdue loan Given Global configuration "enable-business-date" is enabled @@ -5662,7 +5662,7 @@ Feature: Loan When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount When Admin sets the business date to "15 July 2024" @@ -5685,7 +5685,7 @@ Feature: Loan When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount When Admin sets the business date to "10 March 2024" @@ -5708,7 +5708,7 @@ Feature: Loan When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount When Admin sets the business date to "1 February 2024" @@ -5736,7 +5736,7 @@ Feature: Loan | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount and "50" fixed emi amount + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount and "50" fixed emi amount Then Loan emi amount variations has 1 variation, with the following data: | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | @@ -5750,7 +5750,7 @@ Feature: Loan | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount, "50" EUR fixed emi amount and adjust repayment date on "15 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount, "50" EUR fixed emi amount and adjust repayment date on "15 January 2023" Then Loan term variations has 2 variation, with the following data: | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | @@ -5838,4 +5838,4 @@ Feature: Loan | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | | 15 January 2024 | Repayment | 83.76 | 83.5 | 0.26 | 0.0 | 0.0 | 0.0 | | 15 January 2024 | Accrual | 0.26 | 0.0 | 0.26 | 0.0 | 0.0 | 0.0 | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" \ No newline at end of file + Then Loan status will be "CLOSED_OBLIGATIONS_MET" diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature index b3b86e030..690fc317e 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -173,8 +173,7 @@ Feature: LoanCharge | NSF fee | true | Specified due date | 23 October 2022 | Flat | 10.0 | 10.0 | 0.0 | 0.0 | # ## charge adjustment with 8 will fail - When Admin makes a charge adjustment for the last "LOAN_NSF_FEE" type charge which is due on "23 October 2022" with transaction amount higher than the available charge amount - + When Charge adjustment for the last "LOAN_NSF_FEE" type charge which is due on "23 October 2022" with transaction amount 8 which is higher than the available charge amount results an ERROR ## revert last charge adjustment (was amount 3) When Admin reverts the charge adjustment which was raised on "04 November 2022" with 3 EUR transaction amount Then Loan has 1002 outstanding amount @@ -1678,7 +1677,7 @@ Feature: LoanCharge When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUST_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 27 Sep 2024 | 100 | 9.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 27 Sep 2024 | 100 | 9.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "27 September 2024" with "40" amount and expected disbursement date on "27 September 2024" When Admin successfully disburse the loan on "27 September 2024" with "40" EUR transaction amount Then Loan Repayment schedule has 1 periods, with the following data for periods: @@ -1691,7 +1690,6 @@ Feature: LoanCharge Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 27 September 2024 | Disbursement | 40.0 | 0.0 | 0.0 | 0.0 | 0.0 | 40.0 | - | 27 September 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | When Admin adds "LOAN_NSF_FEE" due date charge with "27 September 2024" due date and 1 EUR transaction amount When Admin makes a charge adjustment for the last "LOAN_NSF_FEE" type charge which is due on "27 September 2024" with 1 EUR transaction amount and externalId "" Then Loan has 40.32 outstanding amount @@ -1705,6 +1703,4 @@ Feature: LoanCharge Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 27 September 2024 | Disbursement | 40.0 | 0.0 | 0.0 | 0.0 | 0.0 | 40.0 | - | 27 September 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | - | 27 September 2024 | Accrual | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | - | 27 September 2024 | Charge Adjustment | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 39.0 | \ No newline at end of file + | 27 September 2024 | Charge Adjustment | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 39.0 | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature index 722bf3f82..e4f439412 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -3250,7 +3250,7 @@ Feature: LoanRepayment When Admin creates a client with random data When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 23 July 2024 | 150 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 23 July 2024 | 150 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | # --- 7/23 - Disbursement for 111.92 EUR --- And Admin successfully approves the loan on "23 July 2024" with "111.92" amount and expected disbursement date on "23 July 2024" When Admin successfully disburse the loan on "23 July 2024" with "111.92" EUR transaction amount @@ -3386,8 +3386,8 @@ Feature: LoanRepayment When Admin sets the business date to "23 June 2024" When Admin creates a client with random data When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 23 June 2024 | 400 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 23 June 2024 | 400 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "23 June 2024" with "400" amount and expected disbursement date on "23 June 2024" When Admin successfully disburse the loan on "23 June 2024" with "400" EUR transaction amount When Admin sets the business date to "24 June 2024"