FINERACT-2070: e2e test framework update

This commit is contained in:
Peter Kovacs
2024-11-07 21:49:46 +01:00
committed by Adam Saghy
parent 492ec44cef
commit 6feb10a87e
57 changed files with 2456 additions and 462 deletions
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
@@ -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;
@@ -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;
@@ -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() {
@@ -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<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>();
List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = new ArrayList<>();
List<GetLoanPaymentChannelToFundSourceMappings> 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<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
List<Integer> interestRateVariationsForBorrowerCycle = new ArrayList<>();
List<ChargeData> charges = new ArrayList<>();
List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>();
List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = new ArrayList<>();
List<GetLoanPaymentChannelToFundSourceMappings> 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));//
}
}
@@ -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);
@@ -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);//
}
}
@@ -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<PostSavingsCharges> 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);//
}
}
@@ -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);
}
@@ -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<List<String>> actual, List<String> expected) {
String lineStr = String.valueOf(line);
String expectedStr = expected.toString();
StringBuilder sb = new StringBuilder();
for (List<String> 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<List<String>> actual,
final List<String> 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";
}
}
@@ -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());
}
}
@@ -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<GetBusinessStepConfigResponse> businessStepConfigResponse = businessStepConfigurationApi
.retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute();
ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse);
List<BusinessStep> businessSteps = businessStepConfigResponse.body().getBusinessSteps();
businessSteps.sort(Comparator.comparingLong(BusinessStep::getOrder));
TestContext.GLOBAL.set(TestContextKey.ORIGINAL_COB_WORKFLOW_JOB_BUSINESS_STEP_LIST, businessSteps);
}
}
@@ -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<FineractGlobalInitializerStep> globalInitializerSteps,
List<FineractSuiteInitializerStep> suiteInitializerSteps, List<FineractScenarioInitializerStep> scenarioInitializerSteps,
BusinessDateHelper businessDateHelper) {
return new FineractInitializer(globalInitializerSteps, suiteInitializerSteps, scenarioInitializerSteps, businessDateHelper);
BusinessDateHelper businessDateHelper, WorkFlowJobHelper workFlowJobHelper) {
return new FineractInitializer(globalInitializerSteps, suiteInitializerSteps, scenarioInitializerSteps, businessDateHelper,
workFlowJobHelper);
}
}
@@ -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<FineractSuiteInitializerStep> suiteInitializerSteps;
private final List<FineractScenarioInitializerStep> 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 {
@@ -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<GetBusinessStepConfigResponse> businessStepConfigResponse = businessStepConfigurationApi
.retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute();
ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse);
List<BusinessStep> 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<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
}
}
@@ -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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<String> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvancedpaymentInterest36030InterestRecalcTillRestFrequency = loanProductsApi
.createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcTillRestFrequency).execute();
Response<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> responseLP2AdvPmtAllocProgressiveLoanScheduleHorizontal = loanProductsApi
.createLoanProduct(loanProductsRequestLP2AdvPmtAllocProgressiveLoanScheduleHorizontal).execute();
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL,
responseLP2AdvPmtAllocProgressiveLoanScheduleHorizontal);
Response<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PostLoanProductsResponse> 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<PaymentAllocationOrder> 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> paymentAllocationOrder) {
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
advancedPaymentData.setTransactionType(transactionType);
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrder);
return advancedPaymentData;
}
private static CreditAllocationData createCreditAllocation(String transactionType, List<String> creditAllocationRules) {
CreditAllocationData creditAllocationData = new CreditAllocationData();
creditAllocationData.setTransactionType(transactionType);
@@ -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<PostSavingsProductsResponse> 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<PostSavingsProductsResponse> savingsProductsResponseUSD =
// savingsProductApi.create13(savingsProductsRequestUSD).execute();
// testContext().set(TestContextKey.DEFAULT_SAVINGS_PRODUCT_CREATE_RESPONSE_USD, savingsProductsResponseUSD);
}
}
@@ -21,6 +21,6 @@ package org.apache.fineract.test.messaging;
public class EmptyEventMessage extends EventMessage<Object> {
public EmptyEventMessage() {
super(null, null, null);
super(null, null, null, null);
}
}
@@ -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<R>) new EmptyEventMessage();
}
log.info("Assert event: {}", eventMessage.getIdempotencyKey());
return new EventAssertionBuilder<>(eventMessage);
}
@@ -29,4 +29,5 @@ public class EventMessage<T> {
private final String type;
private final LocalDate businessDate;
private final T data;
private final String idempotencyKey;
}
@@ -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<PostLoansResponse> createLoanResponse) throws IOException {
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(createLoanResponse.body().getLoanId(), false, "", "", "")
Response<GetLoansLoanIdResponse> 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<PostLoansLoanIdResponse> 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<PostLoansLoanIdResponse> loanDisburseResponse) throws IOException {
Response<GetLoansLoanIdResponse> 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<PostLoansLoanIdResponse> loanUndoApproveResponse) throws IOException {
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanUndoApproveResponse.body().getLoanId(), false, "", "", "")
.execute();
GetLoansLoanIdResponse body = loanDetails.body();
eventAssertion.assertEventRaised(LoanUndoApprovalEvent.class, body.getId());
}
public void loanRejectedEventCheck(Response<PostLoansLoanIdResponse> loanRejectedResponse) throws IOException {
Response<GetLoansLoanIdResponse> 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<? extends AbstractLoanEvent> eventClazz, Long loanId) throws IOException {
Response<GetLoansLoanIdResponse> 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<PostLoansLoanIdResponse> 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);
}
}
@@ -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";
}
}
@@ -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";
}
}
@@ -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";
}
}
@@ -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";
}
}
@@ -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<EventMessage<Object>> 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<Object> msg = new EventMessage<>(type, businessDate, dataObject);
EventMessage<Object> 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<Object> getEventMessageFromBulkItem(BulkMessageItemV1 item, LocalDate businessDate) {
private EventMessage<Object> 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);
}
@@ -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 + '\'' + '}';
}
}
@@ -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<LoanSchedulePeriodDataV1> 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;
@@ -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<BusinessStep> 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<BusinessStep> businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS);
Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder();
List<BusinessStep> 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<Void> 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<BusinessStep> 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<Void> 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<BusinessStep> businessSteps = new ArrayList<>(ORIGINAL_COB_BUSINESS_STEPS);
Long lastOrder = businessSteps.get(businessSteps.size() - 1).getOrder();
List<BusinessStep> 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<Void> 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<BusinessStep> 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<Void> 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<BusinessStep> businessSteps = retrieveLoanCOBJobSteps();
if (businessSteps.stream().anyMatch(businessStep -> businessStep.getStepName().equals(businessStepName))) {
return;
}
List<BusinessStep> 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<Void> 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<BusinessStep> businessSteps = retrieveLoanCOBJobSteps();
businessSteps.removeIf(businessStep -> businessStep.getStepName().equals(businessStepName));
List<BusinessStep> 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<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
// --- log changes ---
logChanges();
}
private List<BusinessStep> retrieveLoanCOBJobSteps() throws IOException {
Response<GetBusinessStepConfigResponse> 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<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
.execute();
ErrorHelper.checkSuccessfulApiCall(response);
// --- log changes ---
logChanges();
}
private void logChanges() throws IOException {
@@ -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);
}
}
@@ -67,7 +67,6 @@ public class JournalEntriesStepDef extends AbstractStepDef {
String transactionTypeExpected = transactionType1.getValue();
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
List<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
.filter(t -> transactionDate.equals(formatter.format(t.getDate()))
&& transactionTypeExpected.equals(t.getType().getCode().substring(20)))
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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<PostLoansResponse> 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<PostLoansLoanIdChargesChargeIdResponse> 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,
@@ -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<PostLoansResponse> 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);
}
@@ -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<PostLoansResponse> 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<PostLoansResponse> 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")
@@ -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<String> fetchValuesOfDelinquencyPausePeriods(List<String> header, GetLoansLoanIdDelinquencyPausePeriod t) {
List<String> 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;
@@ -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<PostLoansLoanIdTransactionsResponse> 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<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
.executeLoanTransaction1(loanExternalId, reAgingRequest, "reAge").execute();
@@ -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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
@@ -132,11 +137,12 @@ public class LoanRepaymentStepDef extends AbstractStepDef {
EventAssertion.EventAssertionBuilder<LoanTransactionDataV1> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
Response<PostLoansLoanIdTransactionsResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
List<GetLoansLoanIdTransactions> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId1 = loanResponse.body().getLoanId();
Response<GetLoansLoanIdTransactionsTemplateResponse> 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<PostLoansResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId1 = loanResponse.body().getLoanId();
Response<GetLoansLoanIdTransactionsTemplateResponse> 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);
}
}
@@ -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<List<String>> data = table.asLists();
List<String> 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<PostCreateRescheduleLoansResponse> 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);
@@ -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<PostClientsResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<List<String>> data = table.asLists();
List<String> 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<PostClientsResponse> 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<PostLoansResponse> 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<PostLoansResponse> 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<List<String>> data = table.asLists();
List<String> 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<PostClientsResponse> 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<PostLoansResponse> 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<List<String>> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdRequest rejectRequest = LoanRequestFactory.defaultLoanRejectRequest().rejectedOnDate(rejectDate);
Response<PostLoansLoanIdResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdRequest withdawnRequest = LoanRequestFactory.defaultLoanWithdrawnRequest().withdrawnOnDate(withdrawnDate);
Response<PostLoansLoanIdResponse> 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<PostLoansResponse> 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<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanResponse.body());
final long loanId = loanResponse.body().getLoanId();
final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
Response<PostLoansLoanIdResponse> 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<GetLoansLoanIdResponse> 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<PostLoansResponse> 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<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<String> expectedValues = data.get(i);
String transactionDateExpected = expectedValues.get(0);
List<List<String>> 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<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();
@@ -1327,10 +1552,10 @@ public class LoanStepDef extends AbstractStepDef {
Response<GetLoansLoanIdResponse> 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> 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<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
List<GetLoansLoanIdTransactions> 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<PostLoansResponse> 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<List<String>> 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<GetLoanProductsProductIdResponse> 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> 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<String, Integer> 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<String> fetchValuesOfTransaction(List<String> header, GetLoansLoanIdTransactions t) {
List<String> 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<String> fetchValuesOfRepaymentSchedule(List<String> header, GetLoansLoanIdRepaymentPeriod repaymentPeriod) {
List<String> 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;
@@ -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<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
long clientId = clientResponse.body().getClientId();
PostSavingsAccountsRequest createSavingsAccountRequest = SavingsAccountRequestFactory.defaultEURSavingsAccountRequest()
.clientId((int) clientId).submittedOnDate(submittedOnDate);
Response<PostSavingsAccountsResponse> 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<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
long clientId = clientResponse.body().getClientId();
PostSavingsAccountsRequest createSavingsAccountRequest = SavingsAccountRequestFactory.defaultUSDSavingsAccountRequest()
.clientId((int) clientId).submittedOnDate(submittedOnDate);
Response<PostSavingsAccountsResponse> 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<PostSavingsAccountsResponse> savingsAccountResponse = testContext()
.get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE);
long savingsAccountID = savingsAccountResponse.body().getSavingsId();
PostSavingsAccountsAccountIdRequest approveSavingsAccountRequest = SavingsAccountRequestFactory.defaultApproveRequest()
.approvedOnDate(approvedOnDate);
Response<PostSavingsAccountsAccountIdResponse> 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<PostSavingsAccountsResponse> savingsAccountResponse = testContext()
.get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE);
long savingsAccountID = savingsAccountResponse.body().getSavingsId();
PostSavingsAccountsAccountIdRequest approveSavingsAccountRequest = SavingsAccountRequestFactory.defaultApproveRequest()
.approvedOnDate(approvedOnDate);
Response<PostSavingsAccountsAccountIdResponse> 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<PostSavingsAccountsResponse> savingsAccountResponse = testContext()
.get(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE);
long savingsAccountID = savingsAccountResponse.body().getSavingsId();
PostSavingsAccountsAccountIdRequest activateSavingsAccountRequest = SavingsAccountRequestFactory.defaultActivateRequest()
.activatedOnDate(activatedOnDate);
Response<PostSavingsAccountsAccountIdResponse> 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<PostSavingsAccountsResponse> savingsAccountResponse = testContext()
.get(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE);
long savingsAccountID = savingsAccountResponse.body().getSavingsId();
PostSavingsAccountsAccountIdRequest activateSavingsAccountRequest = SavingsAccountRequestFactory.defaultActivateRequest()
.activatedOnDate(activatedOnDate);
Response<PostSavingsAccountsAccountIdResponse> 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<PostSavingsAccountsResponse> 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<PostSavingsAccountTransactionsResponse> 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<PostSavingsAccountsResponse> 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<PostSavingsAccountTransactionsResponse> 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<PostSavingsAccountsResponse> 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<PostSavingsAccountTransactionsResponse> 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<PostSavingsAccountsResponse> 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<PostSavingsAccountTransactionsResponse> withdrawalResponse = savingsAccountTransactionsApi
.transaction2(savingsAccountID, withdrawRequest, "withdrawal").execute();
testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_WITHDRAW_RESPONSE, withdrawalResponse);
}
}
@@ -25,7 +25,8 @@ import java.util.Map;
public enum TestContext {
INSTANCE;
INSTANCE, //
GLOBAL;//
@SuppressWarnings("ImmutableEnumChecker")
private final ThreadLocal<Map<String, Object>> testContexts = withInitial(HashMap::new);
@@ -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";
}
@@ -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;
}
@@ -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<Void> addResultForCase(@Path("runId") int runId, @Path("caseId") int caseId, @Body AddResultForCaseRequest request);
}
@@ -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<Void> 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<Void> 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<Result> results = (ArrayList<Result>) 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;
}
}
}
@@ -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());
}
}
}
@@ -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<TestRailProperties> {
@Override
protected Class<TestRailProperties> getPropertiesClass() {
return TestRailProperties.class;
}
@Override
protected boolean matches(TestRailProperties properties) {
return properties.isEnabled();
}
}
@@ -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;
}
@@ -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//
}
@@ -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"
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
@@ -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 |
| 27 September 2024 | Charge Adjustment | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 39.0 |
@@ -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"