diff --git a/fineract-avro-schemas/src/main/resources/avro-templates/bigdecimal.avsc b/fineract-avro-schemas/src/main/resources/avro-templates/bigdecimal.avsc index 9b1cec773..459854141 100644 --- a/fineract-avro-schemas/src/main/resources/avro-templates/bigdecimal.avsc +++ b/fineract-avro-schemas/src/main/resources/avro-templates/bigdecimal.avsc @@ -1,6 +1,6 @@ { "logicalType": "decimal", - "precision": 20, + "precision": 27, "scale": 8, "type": "bytes" } 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 70cfa1802..25c2eeac3 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,26 @@ public final class Utils { 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() { + boolean isWholeNumber = (value % 1.0 == 0); + + 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); + } + 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 8afb6eaac..fcbc8859f 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 @@ -1989,7 +1989,6 @@ public class LoanStepDef extends AbstractStepDef { private List> getActualValuesList(List charges, String paymentDueAtExpected, String dueAsOfExpected) { List result; - if (dueAsOfExpected != null) { result = charges.stream().filter(t -> { LocalDate dueDate = t.getDueDate(); @@ -1999,7 +1998,6 @@ public class LoanStepDef extends AbstractStepDef { result = charges.stream().filter(t -> paymentDueAtExpected.equals(t.getChargeTimeType().getValue())) .collect(Collectors.toList()); } - return result.stream().map(t -> { List actualValues = new ArrayList<>(); actualValues.add(t.getName() == null ? null : t.getName()); @@ -2007,10 +2005,13 @@ public class LoanStepDef extends AbstractStepDef { actualValues.add(t.getChargeTimeType().getValue() == null ? null : t.getChargeTimeType().getValue()); actualValues.add(t.getDueDate() == null ? null : FORMATTER.format(t.getDueDate())); actualValues.add(t.getChargeCalculationType().getValue() == null ? null : t.getChargeCalculationType().getValue()); - actualValues.add(t.getAmount() == null ? null : String.valueOf(t.getAmount())); - actualValues.add(t.getAmountPaid() == null ? null : String.valueOf(t.getAmountPaid())); - actualValues.add(t.getAmountWaived() == null ? null : String.valueOf(t.getAmountWaived())); - actualValues.add(t.getAmountOutstanding() == null ? null : String.valueOf(t.getAmountOutstanding())); + + actualValues.add(t.getAmount() == null ? null : new Utils.DoubleFormatter(t.getAmount()).format()); + + actualValues.add(t.getAmountPaid() == null ? null : new Utils.DoubleFormatter(t.getAmountPaid()).format()); + actualValues.add(t.getAmountWaived() == null ? null : new Utils.DoubleFormatter(t.getAmountWaived()).format()); + + actualValues.add(t.getAmountOutstanding() == null ? null : new Utils.DoubleFormatter(t.getAmountOutstanding()).format()); return actualValues; }).collect(Collectors.toList()); } @@ -3203,12 +3204,12 @@ public class LoanStepDef extends AbstractStepDef { 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 + : new Utils.DoubleFormatter(repaymentPeriod.getFeeChargesDue()).format()); + case "Penalties" -> actualValues.add(repaymentPeriod.getPenaltyChargesDue() == null ? null + : new Utils.DoubleFormatter(repaymentPeriod.getPenaltyChargesDue()).format()); + case "Due" -> actualValues.add(repaymentPeriod.getTotalDueForPeriod() == null ? null + : 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 @@ -3218,7 +3219,7 @@ public class LoanStepDef extends AbstractStepDef { case "Waived" -> actualValues.add(repaymentPeriod.getTotalWaivedForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalWaivedForPeriod())); case "Outstanding" -> actualValues.add(repaymentPeriod.getTotalOutstandingForPeriod() == null ? null - : String.valueOf(repaymentPeriod.getTotalOutstandingForPeriod())); + : 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 ad34d6efb..07f36010f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -2534,3 +2534,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 |