From c2c64855e352a92fe3d14f577749c49e7420d6df Mon Sep 17 00:00:00 2001 From: mdmytriv Date: Tue, 18 Feb 2025 18:42:00 +0200 Subject: [PATCH 1/2] FINERACT-2268: added automation tests for repayment schedules with large charge amounts --- .../apache/fineract/test/helper/Utils.java | 14 +++ .../test/stepdef/loan/LoanStepDef.java | 20 ++-- .../resources/features/LoanCharge.feature | 92 ++++++++++++++++++- 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java index 70cfa180275..d850b7e136d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java @@ -48,4 +48,18 @@ public static String randomNameGenerator(final String prefix, final int lenOfRan public static LocalDate now() { return LocalDate.now(Clock.systemUTC()); } + + public record DoubleFormatter(double value) { + + public String format() { + boolean isWholeNumber = (value % 1.0 == 0); + + String result = isWholeNumber ? String.format("%.1f", value) : String.format("%.2f", value); + + if (!isWholeNumber && result.endsWith("0")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 9e452146796..9b838e14d91 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -2003,10 +2003,11 @@ private List> getActualValuesList(List fetchValuesOfTransaction(List header, GetLoansLoanI private List fetchValuesOfRepaymentSchedule(List header, GetLoansLoanIdRepaymentPeriod repaymentPeriod) { List actualValues = new ArrayList<>(); + for (String headerName : header) { switch (headerName) { case "Nr" -> actualValues.add(repaymentPeriod.getPeriod() == null ? null : String.valueOf(repaymentPeriod.getPeriod())); @@ -3196,12 +3198,12 @@ private List fetchValuesOfRepaymentSchedule(List header, GetLoan actualValues.add(repaymentPeriod.getPrincipalDue() == null ? null : String.valueOf(repaymentPeriod.getPrincipalDue())); case "Interest" -> actualValues.add(repaymentPeriod.getInterestDue() == null ? null : String.valueOf(repaymentPeriod.getInterestDue())); - case "Fees" -> actualValues - .add(repaymentPeriod.getFeeChargesDue() == null ? null : String.valueOf(repaymentPeriod.getFeeChargesDue())); - case "Penalties" -> actualValues.add( - repaymentPeriod.getPenaltyChargesDue() == null ? null : String.valueOf(repaymentPeriod.getPenaltyChargesDue())); - case "Due" -> actualValues.add( - repaymentPeriod.getTotalDueForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalDueForPeriod())); + case "Fees" -> actualValues.add(repaymentPeriod.getFeeChargesDue() == null ? null + : String.valueOf(new Utils.DoubleFormatter(repaymentPeriod.getFeeChargesDue()).format())); + case "Penalties" -> actualValues.add(repaymentPeriod.getPenaltyChargesDue() == null ? null + : String.valueOf(new Utils.DoubleFormatter(repaymentPeriod.getPenaltyChargesDue()).format())); + case "Due" -> actualValues.add(repaymentPeriod.getTotalDueForPeriod() == null ? null + : String.valueOf(new Utils.DoubleFormatter(repaymentPeriod.getTotalDueForPeriod()).format())); case "Paid" -> actualValues.add( repaymentPeriod.getTotalPaidForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalPaidForPeriod())); case "In advance" -> actualValues.add(repaymentPeriod.getTotalPaidInAdvanceForPeriod() == null ? null @@ -3211,7 +3213,7 @@ private List fetchValuesOfRepaymentSchedule(List header, GetLoan case "Waived" -> actualValues.add(repaymentPeriod.getTotalWaivedForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalWaivedForPeriod())); case "Outstanding" -> actualValues.add(repaymentPeriod.getTotalOutstandingForPeriod() == null ? null - : String.valueOf(repaymentPeriod.getTotalOutstandingForPeriod())); + : String.valueOf(new Utils.DoubleFormatter(repaymentPeriod.getTotalOutstandingForPeriod()).format())); default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName)); } } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature index ffb16601174..78c57d81f79 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -2546,7 +2546,7 @@ Feature: LoanCharge | 01 January 2024 | Repayment | 60.0 | 60.0 | 0.0 | 0.0 | 0.0 | 40.0 | Then Loan Charges tab has a given charge with the following data: | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 01 February 2024 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 01 February 2024 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | When Admin makes a charge adjustment for the last "LOAN_NSF_FEE" type charge which is due on "01 February 2024" with 20 EUR transaction amount and externalId "" Then Loan Repayment schedule has 4 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -2564,3 +2564,93 @@ Feature: LoanCharge | 01 January 2024 | Repayment | 60.0 | 60.0 | 0.0 | 0.0 | 0.0 | 40.0 | | 01 February 2024 | Charge Adjustment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 40.0 | And LoanChargeAdjustmentPostBusinessEvent is raised on "01 February 2024" + + @TestRailId:C3501 + Scenario: Verify repayment schedule amounts after large charge amount added - UC1 + When Admin sets the business date to "20 February 2025" + And 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 20 December 2024 | 800 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "20 December 2024" with "800" amount and expected disbursement date on "20 December 2024" + When Admin successfully disburse the loan on "20 December 2024" with "800" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 20 December 2024 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 20 January 2025 | | 600.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 2 | 31 | 20 February 2025 | | 400.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 3 | 28 | 20 March 2025 | | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 4 | 31 | 20 April 2025 | | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 0.0 | 0.0 | 0.0 | 800.0 | 0.0 | 0.0 | 0.0 | 800.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 20 December 2024 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + And Admin adds "LOAN_NSF_FEE" due date charge with "25 December 2024" due date and 123456789012.12 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 20 December 2024 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 20 January 2025 | | 600.0 | 200.0 | 0.0 | 0.0 | 123456789012.12 | 123456789212.12 | 0.0 | 0.0 | 0.0 | 123456789212.12 | + | 2 | 31 | 20 February 2025 | | 400.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 3 | 28 | 20 March 2025 | | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 4 | 31 | 20 April 2025 | | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 0.0 | 0.0 | 123456789012.12 | 123456789812.12 | 0.0 | 0.0 | 0.0 | 123456789812.12 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 20 December 2024 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + Then Loan Charges tab has a given charge with the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 25 December 2024 | Flat | 123456789012.12 | 0.0 | 0.0 | 123456789012.12 | + + @TestRailId:C3502 + Scenario: Verify repayment schedule amounts after a few large charges amount added - UC2 + When Admin sets the business date to "20 February 2025" + And 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 20 December 2024 | 800 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "20 December 2024" with "800" amount and expected disbursement date on "20 December 2024" + When Admin successfully disburse the loan on "20 December 2024" with "800" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 20 December 2024 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 20 January 2025 | | 600.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 2 | 31 | 20 February 2025 | | 400.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 3 | 28 | 20 March 2025 | | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 4 | 31 | 20 April 2025 | | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 0.0 | 0.0 | 0.0 | 800.0 | 0.0 | 0.0 | 0.0 | 800.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 20 December 2024 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + And Admin adds "LOAN_NSF_FEE" due date charge with "25 December 2024" due date and 123456789012.12 EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "28 December 2024" due date and 1003456789012.12 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "31 January 2025" due date and 5503456789012.12 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "23 February 2025" due date and 1003456789012.12 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "03 April 2025" due date and 1503456789012.12 EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "09 April 2025" due date and 103456789037.12 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 20 December 2024 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 20 January 2025 | | 600.0 | 200.0 | 0.0 | 1003456789012.12 | 123456789012.12 | 1126913578224.24 | 0.0 | 0.0 | 0.0 | 1126913578224.24 | + | 2 | 31 | 20 February 2025 | | 400.0 | 200.0 | 0.0 | 0.0 | 5503456789012.12 | 5503456789212.12 | 0.0 | 0.0 | 0.0 | 5503456789212.12 | + | 3 | 28 | 20 March 2025 | | 200.0 | 200.0 | 0.0 | 0.0 | 1003456789012.12 | 1003456789212.12 | 0.0 | 0.0 | 0.0 | 1003456789212.12 | + | 4 | 31 | 20 April 2025 | | 0.0 | 200.0 | 0.0 | 103456789037.12 | 1503456789012.12 | 1606913578249.24 | 0.0 | 0.0 | 0.0 | 1606913578249.24 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 0.0 | 1106913578049.24 | 8133827156048.48 | 9240740734897.72 | 0.0 | 0.0 | 0.0 | 9240740734897.72 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 20 December 2024 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + Then Loan Charges tab has a given charge with the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 25 December 2024 | Flat | 123456789012.12 | 0.0 | 0.0 | 123456789012.12 | + | Snooze fee | false | Specified due date | 28 December 2024 | Flat | 1003456789012.12 | 0.0 | 0.0 | 1003456789012.12 | + | NSF fee | true | Specified due date | 31 January 2025 | Flat | 5503456789012.12 | 0.0 | 0.0 | 5503456789012.12 | + | NSF fee | true | Specified due date | 23 February 2025 | Flat | 1003456789012.12 | 0.0 | 0.0 | 1003456789012.12 | + | NSF fee | true | Specified due date | 03 April 2025 | Flat | 1503456789012.12 | 0.0 | 0.0 | 1503456789012.12 | + | Snooze fee | false | Specified due date | 09 April 2025 | Flat | 103456789037.12 | 0.0 | 0.0 | 103456789037.12 | From 1402fb389fcc2d963c0c3d5d8aa0d95861711dc5 Mon Sep 17 00:00:00 2001 From: Rustam Zeinalov Date: Fri, 21 Feb 2025 18:08:39 +0100 Subject: [PATCH 2/2] FINERACT-1971: added automation tests for repayment schedules with large charge amounts --- .../test/java/org/apache/fineract/test/helper/Utils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java index d850b7e136d..25c2eeac376 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java @@ -49,6 +49,13 @@ public static LocalDate now() { return LocalDate.now(Clock.systemUTC()); } + /** + * A record that formats a double value based on whether it's a whole number or not. + *

+ * If the value is a whole number, the output will have one decimal place (e.g., 16.0). Otherwise, it will have two + * decimal places (e.g., 16.90), but if the second decimal place is zero, it will be removed (so 16.90 becomes + * 16.9). + */ public record DoubleFormatter(double value) { public String format() { @@ -56,6 +63,7 @@ public String format() { String result = isWholeNumber ? String.format("%.1f", value) : String.format("%.2f", value); + // For non-whole numbers, remove trailing '0' if it exists if (!isWholeNumber && result.endsWith("0")) { result = result.substring(0, result.length() - 1); }