FINERACT-2433/fix-interest-accrual-calculation - E2E test

This commit is contained in:
Peter Kovacs
2026-01-08 12:22:48 +01:00
committed by Adam Saghy
parent 8b3c8280de
commit 9003034b34
5 changed files with 289 additions and 0 deletions
@@ -180,6 +180,7 @@ public enum DefaultLoanProduct implements LoanProduct {
LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES, //
LP2_ADV_PYMNT_360_30_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_PRINCIPAL_FIRST, //
LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_360_30_USD, //
;
@Override
@@ -22,6 +22,7 @@ import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.AD
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -94,6 +95,7 @@ public class LoanProductsRequestFactory {
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 Long FUND_ID = FundId.LENDER_A.value;
public static final String CURRENCY_CODE = "EUR";
public static final String CURRENCY_CODE_USD = "USD";
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;
@@ -1779,6 +1781,110 @@ public class LoanProductsRequestFactory {
.chargeOffReasonToExpenseAccountMappings(chargeOffReasonToExpenseAccountMappings);//
}
public PostLoanProductsRequest defaultLoanProductsRequestLP2EmiUSD() {
String name = Utils.randomNameGenerator(NAME_PREFIX_LP2_EMI, 10);
String shortName = generateShortNameSafely();
List<Integer> principalVariationsForBorrowerCycle = new ArrayList<>();
List<Integer> numberOfRepaymentVariationsForBorrowerCycle = new ArrayList<>();
List<Integer> interestRateVariationsForBorrowerCycle = new ArrayList<>();
return new PostLoanProductsRequest()//
.name(name)//
.shortName(shortName)//
.description(DESCRIPTION_LP2_EMI)//
.includeInBorrowerCycle(false)//
.useBorrowerCycle(false)//
.currencyCode(CURRENCY_CODE_USD)//
.digitsAfterDecimal(2)//
.inMultiplesOf(0)//
.principal(1500.0)//
.minPrincipal(1.0)//
.maxPrincipal(10000.0)//
.numberOfRepayments(3)//
.minNumberOfRepayments(3)//
.maxNumberOfRepayments(48)//
.repaymentEvery(1)//
.repaymentFrequencyType(REPAYMENT_FREQUENCY_TYPE_MONTHS)//
.interestRatePerPeriod(9.99)//
.minInterestRatePerPeriod((double) 0)//
.maxInterestRatePerPeriod((double) 50)//
.interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_YEAR)//
.isLinkedToFloatingInterestRates(false)//
.allowVariableInstallments(false)//
.amortizationType(AmortizationType.EQUAL_INSTALLMENTS.value)//
.interestType(INTEREST_TYPE_DECLINING_BALANCE)//
.interestCalculationPeriodType(INTEREST_CALCULATION_PERIOD_TYPE_DAILY)//
.allowPartialPeriodInterestCalculation(false)//
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
.daysInMonthType(DAYS_IN_MONTH_TYPE_30)//
.daysInYearType(DAYS_IN_YEAR_TYPE_360)//
.isInterestRecalculationEnabled(true)//
.interestRecalculationCompoundingMethod(INTEREST_RECALCULATION_COMPOUND_METHOD_NONE)//
.rescheduleStrategyMethod(AdvancePaymentsAdjustmentType.ADJUST_LAST_UNPAID_PERIOD.value)//
.recalculationRestFrequencyType(FREQUENCY_FOR_RECALCULATE_OUTSTANDING_DAILY)//
.recalculationRestFrequencyInterval(1)//
.isArrearsBasedOnOriginalSchedule(false)//
.preClosureInterestCalculationStrategy(PRE_CLOSURE_INTEREST_CALCULATION_RULE_TILL_PRE_CLOSE_DATE)//
.canDefineInstallmentAmount(true)//
.repaymentStartDateType(1)//
.supportedInterestRefundTypes(Arrays.asList("MERCHANT_ISSUED_REFUND", "PAYOUT_REFUND"))//
.chargeOffBehaviour(ChargeOffBehaviour.ZERO_INTEREST.value)
.principalVariationsForBorrowerCycle(principalVariationsForBorrowerCycle)//
.interestRateVariationsForBorrowerCycle(interestRateVariationsForBorrowerCycle)//
.numberOfRepaymentVariationsForBorrowerCycle(numberOfRepaymentVariationsForBorrowerCycle)//
.accountingRule(AccountingRule.ACCRUAL_PERIODIC.value)//
.canUseForTopup(false)//
.enableAccrualActivityPosting(true)//
.multiDisburseLoan(true)//
.maxTrancheCount(500)//
.outstandingLoanBalance(10000.0)//
.disallowExpectedDisbursements(true)//
.allowApprovedDisbursedAmountsOverApplied(true)//
.overAppliedCalculationType(OverAppliedCalculationType.FIXED_SIZE.value)//
.overAppliedNumber(10000)//
.principalThresholdForLastInstallment(50)//
.holdGuaranteeFunds(false)//
.accountMovesOutOfNPAOnlyOnArrearsCompletion(false)//
.allowAttributeOverrides(new AllowAttributeOverrides()//
.amortizationType(true)//
.interestType(true)//
.transactionProcessingStrategyCode(true)//
.interestCalculationPeriodType(true)//
.inArrearsTolerance(true)//
.repaymentEvery(true)//
.graceOnPrincipalAndInterestPayment(true)//
.graceOnArrearsAgeing(true))//
.isEqualAmortization(false)//
.delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())//
.enableDownPayment(false)//
.enableInstallmentLevelDelinquency(true)//
.loanScheduleType("PROGRESSIVE") //
.loanScheduleProcessingType("HORIZONTAL")//
.enableIncomeCapitalization(false)//
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST).enableBuyDownFee(true)//
.merchantBuyDownFee(true)//
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)//
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)//
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.INTEREST)//
.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))//
.buyDownExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.BUY_DOWN_EXPENSE))//
.incomeFromBuyDownAccountId(accountTypeResolver.resolve(DefaultAccountType.INCOME_FROM_BUY_DOWN)).dateFormat(DATE_FORMAT)//
.locale(LOCALE_EN);//
}
public String generateShortNameSafely() {
String generatedShortName;
int counter = 0;
@@ -4263,6 +4263,40 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_PRINCIPAL_FIRST,
responseLoanProductsRequestAdvCustomPaymentAllocationProgressiveLoanSchedulePrincipalFirst);
// LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_360_30_USD
// Similar to LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL but with 360/30 days and USD
// currency
String name169 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_360_30_USD.getName();
PostLoanProductsRequest loanProductsRequestAdvCustomPaymentAllocationProgressiveLoanScheduleHorizontalUSD = loanProductsRequestFactory
.defaultLoanProductsRequestLP2EmiUSD()//
.name(name169)//
.paymentAllocation(List.of(//
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT", //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST), //
createPaymentAllocation("GOODWILL_CREDIT", "NEXT_INSTALLMENT"), //
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "LAST_INSTALLMENT"), //
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))
.creditAllocation(List.of(//
createCreditAllocation("CHARGEBACK", List.of("PENALTY", "FEE", "PRINCIPAL", "INTEREST"))//
));//
PostLoanProductsResponse responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanScheduleHorizontalUSD = createLoanProductIdempotent(
loanProductsRequestAdvCustomPaymentAllocationProgressiveLoanScheduleHorizontalUSD);
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_360_30_USD,
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanScheduleHorizontalUSD);
}
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
@@ -292,4 +292,5 @@ public abstract class TestContextKey {
public static final String LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES = "loanProductCreateResponseLP1InterestFlatDailyActualActualMultiDisbursementExpectTranches";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_360_30_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffBehaviourAccrualActivity";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_PRINCIPAL_FIRST = "loanProductCreateResponseLP2AdvancedPaymentHorizontalPrincipalFirst";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_360_30_USD = "loanProductCreateResponseLP2AdvancedPaymentHorizontal36030Usd";
}