From 3b281dca6c8fb7315c1001bc454c27a60bf7c55e Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Wed, 12 Aug 2026 17:08:04 +0300 Subject: [PATCH 1/2] FINERACT-2455: added e2e test scenario for working capital discount fee amortization on excess repayment --- ...WorkingCapitalDiscountAmortization.feature | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature index 57aca365f2b..46e107c9f99 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature @@ -1074,3 +1074,46 @@ Feature: WorkingCapitalDiscountFeeAmortization | INCOME | 404000 | Interest Income | | 5.14 | | LIABILITY | 240005 | Deferred Interest Revenue | 5.14 | | + Scenario: Verify Discount Fee Amortization when the repayment exceeds the daily payment amount + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 17 | | + Then Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + Then Admin successfully add discount with "1000" amount on Working Capital loan account + When Admin sets the business date to "02 January 2026" + And Customer makes repayment on "02 January 2026" with 50 transaction amount on Working Capital loan + And Admin retrieves the projected amortization schedule + Then The retrieved amortization schedule has the following summary fields: + | discountFeeAmount | netDisbursementAmount | totalPaymentVolume | periodPaymentRate | npvDayCount | expectedPaymentAmount | originalPaymentNumber | + | 1000.00 | 9000.00 | 100000.00 | 17 | 360 | 47.22 | 212 | + And The retrieved amortization schedule has payments with the following details in first "5" lines: + | paymentNo | date | expectedPaymentAmount | expectedBalance | actualBalance | expectedAmortizationAmount | actualPaymentAmount | actualAmortizationAmount | expectedDiscountFeeBalance | actualDiscountFeeBalance | + | 0 | 2026-01-01 | -9000.00 | 9000.00 | 9000.00 | | | | 1000.00 | 1000.00 | + | 1 | 2026-01-02 | 47.22 | 8961.86 | 8959.61 | 9.08 | 50.00 | 9.61 | 990.92 | 990.39 | + | 2 | 2026-01-03 | 47.22 | 8923.68 | | 9.04 | | | 981.88 | | + | 3 | 2026-01-04 | 47.22 | 8885.46 | | 9.00 | | | 972.88 | | + | 4 | 2026-01-05 | 47.22 | 8847.20 | | 8.96 | | | 963.92 | | + And The retrieved amortization schedule has payments with the following details for the listed payment numbers: + | paymentNo | date | expectedPaymentAmount | expectedBalance | expectedAmortizationAmount | expectedDiscountFeeBalance | + | 210 | 2026-07-30 | 47.22 | 83.68 | 0.13 | 0.06 | + | 211 | 2026-07-31 | 47.22 | 36.54 | 0.06 | 0.00 | + | 212 | 2026-08-01 | 36.58 | 0.00 | 0.00 | 0.00 | + When Admin sets the business date to "03 January 2026" + When Admin runs inline COB job for Working Capital Loan + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | totalPaymentVolume | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 50.0 | 100000.0 | 9.61 | 990.39 | 0.0 | + And Working Capital Loan has transactions: + | transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed | + | 01 January 2026 | Disbursement | 9000.0 | 9000.0 | 0.0 | 0.0 | false | + | 01 January 2026 | Discount Fee | 1000.0 | 1000.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Discount Fee Amortization | 9.61 | | | | false | + Then Working Capital Loan Transactions tab has a "DISCOUNT_FEE_AMORTIZATION" transaction with date "02 January 2026" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | INCOME | 404000 | Interest Income | | 9.61 | + | LIABILITY | 240005 | Deferred Interest Revenue | 9.61 | | From f66185eb7fea1a4def65161d9def75191ccba495 Mon Sep 17 00:00:00 2001 From: Rustam Zeinalov Date: Mon, 17 Aug 2026 18:55:05 +0200 Subject: [PATCH 2/2] FINERACT-2455: updated e2e test scenario for working capital discount fee amortization on excess repayment --- ...ingCapitalAmortizationScheduleStepDef.java | 83 +++++++ ...WorkingCapitalAmortizationSchedule.feature | 4 + ...WorkingCapitalDiscountAmortization.feature | 212 ++++++++++++++++++ 3 files changed, 299 insertions(+) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java index 2957a7b4b2d..6e41b8a51c0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java @@ -28,10 +28,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.feign.services.WorkingCapitalLoansApi; +import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdResponse; import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse; import org.apache.fineract.client.models.ProjectedAmortizationScheduleData; import org.apache.fineract.client.models.ProjectedAmortizationScheduleGenerateRequest; @@ -184,6 +186,87 @@ public void verifyRetrievedPaymentDetailsByPaymentNo(final DataTable dataTable) assertions.assertAll(); } + @Then("The retrieved amortization schedule has no negative amounts") + public void verifyRetrievedScheduleHasNoNegativeAmounts() { + final ProjectedAmortizationScheduleData response = TestContext.INSTANCE.get(WC_AMORT_SCHEDULE_KEY); + assertThat(response).as("Amortization schedule response").isNotNull(); + final SoftAssertions assertions = new SoftAssertions(); + for (final ProjectedAmortizationSchedulePaymentData payment : response.getPayments()) { + if (payment.getPaymentNo() == 0) { + continue; // disbursement row carries the negated disbursement by design + } + final String p = "payment[" + payment.getPaymentNo() + "]."; + assertNonNegative(assertions, p + "expectedPaymentAmount", payment.getExpectedPaymentAmount()); + assertNonNegative(assertions, p + "expectedBalance", payment.getExpectedBalance()); + assertNonNegative(assertions, p + "expectedAmortizationAmount", payment.getExpectedAmortizationAmount()); + assertNonNegative(assertions, p + "expectedDiscountFeeBalance", payment.getExpectedDiscountFeeBalance()); + assertNonNegative(assertions, p + "actualPaymentAmount", payment.getActualPaymentAmount()); + assertNonNegative(assertions, p + "actualAmortizationAmount", payment.getActualAmortizationAmount()); + assertNonNegative(assertions, p + "actualDiscountFeeBalance", payment.getActualDiscountFeeBalance()); + } + assertions.assertAll(); + } + + @Then("The retrieved amortization schedule expected amortization sums to the discount fee and both expected balances close to zero") + public void verifyRetrievedScheduleExpectedAmortizationClosesToDiscountFee() { + final ProjectedAmortizationScheduleData response = TestContext.INSTANCE.get(WC_AMORT_SCHEDULE_KEY); + assertThat(response).as("Amortization schedule response").isNotNull(); + final List rows = response.getPayments().stream().filter(p -> p.getPaymentNo() > 0) + .toList(); + assertThat(rows).as("payment rows").isNotEmpty(); + final BigDecimal amortizationSum = sum(rows, ProjectedAmortizationSchedulePaymentData::getExpectedAmortizationAmount); + final BigDecimal paymentSum = sum(rows, ProjectedAmortizationSchedulePaymentData::getExpectedPaymentAmount); + final ProjectedAmortizationSchedulePaymentData last = rows.getLast(); + final SoftAssertions assertions = new SoftAssertions(); + assertions.assertThat(amortizationSum).as("sum(expectedAmortizationAmount) == discountFeeAmount") + .isEqualByComparingTo(response.getDiscountFeeAmount()); + assertions.assertThat(paymentSum).as("sum(expectedPaymentAmount) == netDisbursementAmount + discountFeeAmount") + .isEqualByComparingTo(response.getNetDisbursementAmount().add(response.getDiscountFeeAmount())); + assertions.assertThat(last.getExpectedBalance()).as("last expectedBalance").isEqualByComparingTo(BigDecimal.ZERO); + assertions.assertThat(last.getExpectedDiscountFeeBalance()).as("last expectedDiscountFeeBalance") + .isEqualByComparingTo(BigDecimal.ZERO); + assertions.assertAll(); + } + + @Then("The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income") + public void verifyRetrievedScheduleActualAmortizationMatchesLoanIncome() { + final ProjectedAmortizationScheduleData response = TestContext.INSTANCE.get(WC_AMORT_SCHEDULE_KEY); + assertThat(response).as("Amortization schedule response").isNotNull(); + final List paidRows = response.getPayments().stream() + .filter(p -> p.getPaymentNo() > 0 && p.getActualPaymentAmount() != null).toList(); + assertThat(paidRows).as("paid rows").isNotEmpty(); + final BigDecimal actualAmortizationSum = sum(paidRows, ProjectedAmortizationSchedulePaymentData::getActualAmortizationAmount); + final ProjectedAmortizationSchedulePaymentData lastPaid = paidRows.getLast(); + + final GetWorkingCapitalLoansLoanIdResponse loan = fineractFeignClient.workingCapitalLoans() + .retrieveWorkingCapitalLoanById(extractLoanId()); + assertThat(loan.getBalance()).as("loan balance").isNotNull(); + final BigDecimal realized = loan.getBalance().getRealizedIncomeFromDiscountFee(); + final BigDecimal unrealized = loan.getBalance().getUnrealizedIncomeFromDiscountFee(); + + final SoftAssertions assertions = new SoftAssertions(); + assertions.assertThat(actualAmortizationSum).as("sum(actualAmortizationAmount) == loan realizedIncomeFromDiscountFee") + .isEqualByComparingTo(realized); + assertions.assertThat(lastPaid.getActualDiscountFeeBalance()) + .as("last paid row actualDiscountFeeBalance == loan unrealizedIncomeFromDiscountFee").isEqualByComparingTo(unrealized); + assertions.assertThat(realized.add(unrealized)).as("realized + unrealized == schedule discountFeeAmount") + .isEqualByComparingTo(response.getDiscountFeeAmount()); + assertions.assertThat(realized).as("realized income must never exceed the discount fee") + .isLessThanOrEqualTo(response.getDiscountFeeAmount()); + assertions.assertAll(); + } + + private static BigDecimal sum(final List rows, + final Function getter) { + return rows.stream().map(getter).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private static void assertNonNegative(final SoftAssertions assertions, final String field, final BigDecimal value) { + if (value != null) { + assertions.assertThat(value).as(field + " must not be negative").isGreaterThanOrEqualTo(BigDecimal.ZERO); + } + } + public void checkSummaryFields(final SoftAssertions assertions, ProjectedAmortizationScheduleData response, Map expected) { assertDecimal(assertions, "discountFeeAmount", response.getDiscountFeeAmount(), expected.get("discountFeeAmount")); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature index b8ef83f2ee9..56a5f5de93b 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature @@ -658,6 +658,7 @@ Feature: WorkingCapitalAmortizationSchedule | 210 | 2019-07-30 | 47.22 | 19.84 | 0.07 | | | 0.15 | | | | 211 | 2019-07-31 | 19.86 | 0.00 | 0.15 | | | 0.00 | | | + @TestRailId:C98180 Scenario: Generate a projected amortization schedule whose final payment is a smaller remainder - UC3 When Admin sets the business date to "01 January 2019" And Admin creates a client with random data @@ -680,7 +681,10 @@ Feature: WorkingCapitalAmortizationSchedule | 210 | 2019-07-30 | 47.22 | 83.68 | 0.13 | 0.06 | | 211 | 2019-07-31 | 47.22 | 36.54 | 0.06 | 0.00 | | 212 | 2019-08-01 | 36.58 | 0.00 | 0.00 | 0.00 | + And The retrieved amortization schedule has no negative amounts + And The retrieved amortization schedule expected amortization sums to the discount fee and both expected balances close to zero + @TestRailId:C98181 Scenario: Generate a projected amortization schedule matching reference - UC4 When Admin sets the business date to "01 January 2026" And Admin creates a client with random data diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature index 46e107c9f99..8b1edf6b31e 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDiscountAmortization.feature @@ -1074,6 +1074,218 @@ Feature: WorkingCapitalDiscountFeeAmortization | INCOME | 404000 | Interest Income | | 5.14 | | LIABILITY | 240005 | Deferred Interest Revenue | 5.14 | | + @TestRailId:C98177 + Scenario: Verify that discount fee amortization after a repayment above the daily amount stays consistent with the schedule over the following on-time repayments + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 17 | | + Then Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + Then Admin successfully add discount with "1000" amount on Working Capital loan account + When Admin sets the business date to "02 January 2026" + And Customer makes repayment on "02 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "03 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 50.0 | 9.61 | 990.39 | 0.0 | + And Customer makes repayment on "03 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "04 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 97.22 | 18.65 | 981.35 | 0.0 | + And Customer makes repayment on "04 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "05 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "05 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "06 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 191.66 | 36.61 | 963.39 | 0.0 | + And Customer makes repayment on "06 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "07 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "07 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "08 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 286.1 | 54.41 | 945.59 | 0.0 | + And Working Capital Loan has transactions: + | transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed | + | 01 January 2026 | Disbursement | 9000.0 | 9000.0 | 0.0 | 0.0 | false | + | 01 January 2026 | Discount Fee | 1000.0 | 1000.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Discount Fee Amortization | 9.61 | | | | false | + | 03 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 03 January 2026 | Discount Fee Amortization | 9.04 | | | | false | + | 04 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 04 January 2026 | Discount Fee Amortization | 9.0 | | | | false | + | 05 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 05 January 2026 | Discount Fee Amortization | 8.96 | | | | false | + | 06 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 06 January 2026 | Discount Fee Amortization | 8.91 | | | | false | + | 07 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 07 January 2026 | Discount Fee Amortization | 8.89 | | | | false | + And Admin retrieves the projected amortization schedule + Then The retrieved amortization schedule has payments with the following details for the listed payment numbers: + | paymentNo | date | expectedPaymentAmount | expectedAmortizationAmount | actualPaymentAmount | actualAmortizationAmount | expectedDiscountFeeBalance | actualDiscountFeeBalance | + | 1 | 2026-01-02 | 47.22 | 9.08 | 50.00 | 9.61 | 990.92 | 990.39 | + | 2 | 2026-01-03 | 47.22 | 9.04 | 47.22 | 9.04 | 981.88 | 981.35 | + | 3 | 2026-01-04 | 47.22 | 9.00 | 47.22 | 9.00 | 972.88 | 972.35 | + | 4 | 2026-01-05 | 47.22 | 8.96 | 47.22 | 8.96 | 963.92 | 963.39 | + | 5 | 2026-01-06 | 47.22 | 8.92 | 47.22 | 8.91 | 955.00 | 954.48 | + | 6 | 2026-01-07 | 47.22 | 8.89 | 47.22 | 8.89 | 946.11 | 945.59 | + And The retrieved amortization schedule has no negative amounts + And The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income + + @TestRailId:C98178 + Scenario: Verify that discount fee amortization after a repayment below the daily amount stays consistent with the schedule over the following on-time repayments + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 17 | | + Then Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + Then Admin successfully add discount with "1000" amount on Working Capital loan account + When Admin sets the business date to "02 January 2026" + And Customer makes repayment on "02 January 2026" with 40 transaction amount on Working Capital loan + When Admin sets the business date to "03 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 40.0 | 7.69 | 992.31 | 0.0 | + And Customer makes repayment on "03 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "04 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "04 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "05 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "05 January 2026" with 47.22 transaction amount on Working Capital loan + When Admin sets the business date to "06 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 10000.0 | 181.66 | 34.71 | 965.29 | 0.0 | + And Working Capital Loan has transactions: + | transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed | + | 01 January 2026 | Disbursement | 9000.0 | 9000.0 | 0.0 | 0.0 | false | + | 01 January 2026 | Discount Fee | 1000.0 | 1000.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Repayment | 40.0 | 40.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Discount Fee Amortization | 7.69 | | | | false | + | 03 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 03 January 2026 | Discount Fee Amortization | 9.05 | | | | false | + | 04 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 04 January 2026 | Discount Fee Amortization | 9.0 | | | | false | + | 05 January 2026 | Repayment | 47.22 | 47.22 | 0.0 | 0.0 | false | + | 05 January 2026 | Discount Fee Amortization | 8.97 | | | | false | + And Admin retrieves the projected amortization schedule + Then The retrieved amortization schedule has payments with the following details for the listed payment numbers: + | paymentNo | date | expectedPaymentAmount | expectedAmortizationAmount | actualPaymentAmount | actualAmortizationAmount | actualBalance | actualDiscountFeeBalance | + | 1 | 2026-01-02 | 47.22 | 9.08 | 40.00 | 7.69 | 8967.69 | 992.31 | + | 2 | 2026-01-03 | 47.22 | 9.04 | 47.22 | 9.05 | 8929.52 | 983.26 | + | 3 | 2026-01-04 | 47.22 | 9.00 | 47.22 | 9.00 | 8891.30 | 974.26 | + | 4 | 2026-01-05 | 47.22 | 8.96 | 47.22 | 8.97 | 8853.05 | 965.29 | + And The retrieved amortization schedule has no negative amounts + And The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income + + @TestRailId:C98179 + Scenario: Verify that discount fee amortization after a fractional repayment closes to the discount fee without an amortization adjustment + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP_ADVANCED_ACCOUNTING | 01 January 2026 | 01 January 2026 | 450 | 100000 | 18 | | + Then Admin successfully approves the working capital loan on "01 January 2026" with "450" amount and expected disbursement date on "01 January 2026" + Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "450" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + Then Admin successfully add discount with "50" amount on Working Capital loan account + And Admin retrieves the projected amortization schedule + Then The retrieved amortization schedule has the following summary fields: + | discountFeeAmount | netDisbursementAmount | totalPaymentVolume | periodPaymentRate | npvDayCount | expectedPaymentAmount | originalPaymentNumber | + | 50.00 | 450.00 | 100000.00 | 18 | 360 | 50.00 | 10 | + And The retrieved amortization schedule expected amortization sums to the discount fee and both expected balances close to zero + When Admin sets the business date to "02 January 2026" + And Customer makes repayment on "02 January 2026" with 50.25 transaction amount on Working Capital loan + When Admin sets the business date to "03 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "03 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "04 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 500.0 | 100.25 | 16.9 | 33.1 | 0.0 | + And Admin retrieves the projected amortization schedule + And The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income + And Customer makes repayment on "04 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "05 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "05 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "06 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "06 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "07 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "07 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "08 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "08 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "09 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "09 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "10 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Customer makes repayment on "10 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "11 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 500.0 | 450.25 | 49.03 | 0.97 | 0.0 | + And Admin retrieves the projected amortization schedule + And The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income + And Customer makes repayment on "11 January 2026" with 50 transaction amount on Working Capital loan + When Admin sets the business date to "12 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan status will be "OVERPAID" + And Working capital loan account has the correct data: + | principal | totalPaidPrincipal | realizedIncome | unrealizedIncome | overpaymentAmount | + | 500.0 | 500.0 | 50.0 | 0.0 | 0.25 | + And Working Capital Loan has transactions: + | transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed | + | 01 January 2026 | Disbursement | 450.0 | 450.0 | 0.0 | 0.0 | false | + | 01 January 2026 | Discount Fee | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 02 January 2026 | Repayment | 50.25 | 50.25 | 0.0 | 0.0 | false | + | 02 January 2026 | Discount Fee Amortization | 8.87 | | | | false | + | 03 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 03 January 2026 | Discount Fee Amortization | 8.03 | | | | false | + | 04 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 04 January 2026 | Discount Fee Amortization | 7.19 | | | | false | + | 05 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 05 January 2026 | Discount Fee Amortization | 6.36 | | | | false | + | 06 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 06 January 2026 | Discount Fee Amortization | 5.49 | | | | false | + | 07 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 07 January 2026 | Discount Fee Amortization | 4.63 | | | | false | + | 08 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 08 January 2026 | Discount Fee Amortization | 3.73 | | | | false | + | 09 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 09 January 2026 | Discount Fee Amortization | 2.83 | | | | false | + | 10 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 10 January 2026 | Discount Fee Amortization | 1.90 | | | | false | + | 11 January 2026 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | false | + | 11 January 2026 | Discount Fee Amortization | 0.97 | | | | false | + And Admin retrieves the projected amortization schedule + And The retrieved amortization schedule has no negative amounts + And The retrieved amortization schedule actual amortization is consistent with the loan realized and unrealized income + + @TestRailId:C98183 Scenario: Verify Discount Fee Amortization when the repayment exceeds the daily payment amount When Admin sets the business date to "01 January 2026" And Admin creates a client with random data