You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by "galovics (via GitHub)" <gi...@apache.org> on 2023/05/23 06:28:47 UTC

[GitHub] [fineract] galovics commented on a diff in pull request #3192: FINERACT-1912 Support Pagination, sorting and filtering for Savings account transactions

galovics commented on code in PR #3192:
URL: https://github.com/apache/fineract/pull/3192#discussion_r1201573158


##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java:
##########
@@ -132,6 +132,22 @@ public void reverse() {
         this.reversed = true;
     }
 
+    public LocalDate getDate() {

Review Comment:
   Why not Lombok @Getter?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {

Review Comment:
   DTOV2? Where's DTOV1? Can't we get a better name for this that represents the use-case?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {
+
+    private final Long transactionId;
+    private final Integer transactionType;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final Long releaseIdOfHoldAmountTransaction;
+    private final String reasonForBlock;
+    private final LocalDateTime createdDate;
+    private final AppUser appUser;
+    private final String note;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final boolean reversalTransaction;
+    private final Long originalTxnId;
+    private final Boolean lienTransaction;
+    private final boolean isManualTransaction;
+    private final AccountTransferTransaction fromSavingsTransaction;
+    private final AccountTransferTransaction toSavingsTransaction;
+    private final SavingsAccount savingsAccount;
+    private final PaymentDetail paymentDetail;
+    private final ApplicationCurrency currency;
+
+    public static final SavingsAccountTransactionData tosavingsAccountTransactionData(SavingsAccountTransactionDTOV2 dto) {

Review Comment:
   Not conforming the Java naming convention (read: not camelCase)



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {
+
+    private final Long transactionId;
+    private final Integer transactionType;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final Long releaseIdOfHoldAmountTransaction;
+    private final String reasonForBlock;
+    private final LocalDateTime createdDate;
+    private final AppUser appUser;
+    private final String note;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final boolean reversalTransaction;
+    private final Long originalTxnId;
+    private final Boolean lienTransaction;
+    private final boolean isManualTransaction;
+    private final AccountTransferTransaction fromSavingsTransaction;
+    private final AccountTransferTransaction toSavingsTransaction;
+    private final SavingsAccount savingsAccount;
+    private final PaymentDetail paymentDetail;
+    private final ApplicationCurrency currency;
+
+    public static final SavingsAccountTransactionData tosavingsAccountTransactionData(SavingsAccountTransactionDTOV2 dto) {

Review Comment:
   Are you planning to create a unit test for this class? Seems like there's way too much logic here than it should be.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java:
##########
@@ -53,4 +56,28 @@ List<SavingsAccountTransaction> findTransactionRunningBalanceBeforePivotDate(@Pa
     @Query("select sat from SavingsAccountTransaction sat where sat.savingsAccount.id = :savingsId and sat.dateOf <= :transactionDate and sat.reversed=false")
     List<SavingsAccountTransaction> findBySavingsAccountIdAndLessThanDateOfAndReversedIsFalse(@Param("savingsId") Long savingsId,
             @Param("transactionDate") LocalDate transactionDate, Pageable pageable);
+
+    @Query("""
+                    SELECT NEW org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTOV2(tr.id,
+                tr.typeOf, tr.dateOf, tr.amount, tr.releaseIdOfHoldAmountTransaction, tr.reasonForBlock,
+                tr.createdDate, tr.appUser, nt.note, tr.runningBalance, tr.reversed,
+                tr.reversalTransaction, tr.originalTxnId, tr.lienTransaction, tr.isManualTransaction,
+                fromTran, toTran, tr.savingsAccount, tr.paymentDetail, currency
+                )
+                    FROM SavingsAccountTransaction tr
+                    JOIN ApplicationCurrency currency ON (currency.code = tr.savingsAccount.currency.code)
+                    LEFT JOIN AccountTransferTransaction fromtran ON (fromtran.fromSavingsTransaction = tr)
+                    LEFT JOIN AccountTransferTransaction totran ON (totran.toSavingsTransaction = tr)
+                    LEFT JOIN tr.notes nt ON (nt.savingsTransaction = tr)
+                    WHERE tr.savingsAccount.id = :savingsId
+                    AND tr.savingsAccount.depositType = :depositType
+                    AND (:transactionType IS NULL OR tr.typeOf = :transactionType )
+                    AND (:#{#searchParameters.fromDate} IS NULL OR tr.dateOf >= :#{#searchParameters.fromDate} )
+                    AND (:#{#searchParameters.toDate} IS NULL OR tr.dateOf <= :#{#searchParameters.toDate} )
+                    AND (:#{#searchParameters.fromAmount} IS NULL OR tr.amount >= :#{#searchParameters.fromAmount} )
+                    AND (:#{#searchParameters.toAmount} IS NULL OR tr.amount <= :#{#searchParameters.toAmount} )
+            """)
+    Page<SavingsAccountTransactionDTOV2> findAll(@Param("savingsId") Long savingsId, @Param("depositType") Integer depositType,

Review Comment:
   findAll method naming is confusing cause it's not finding all and this is not an entity either. This use-case is for search, name it accordingly.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java:
##########
@@ -1267,6 +1283,136 @@ public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final L
         return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR
     }
 
+    @Override
+    public Page<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType,
+            SearchParameters searchParameters) {
+        // validate the search parameters
+        validateSearchParameters(searchParameters);
+
+        Integer page = 0;
+        Integer size = SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTIONS_DEFAULT_LIMIT;
+        if (searchParameters.isLimited() && searchParameters.isOffset()) {
+            Integer limit = searchParameters.getLimit();
+            page = searchParameters.getOffset() / limit;
+            size = limit;
+        }
+        Pageable pageable = PageRequest.of(page, size);

Review Comment:
   What's the point of creating this PageRequest object if you override it in the following if/else?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java:
##########
@@ -121,4 +121,16 @@ public String getReceiptNumber() {
     public String getRoutingCode() {
         return routingCode;
     }
+
+    public String getAccountNumber() {

Review Comment:
   Why not Lombok @Getter?



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {

Review Comment:
   Don't see a test-case for Asc ordering.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        queryParametersMap.put("sortOrder", "DESC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();

Review Comment:
   I don't like this logic here. Why can't we simply check the 1., 2., 3. items in the list? If the ordering works, it should be in the proper order in the list as well.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        queryParametersMap.put("sortOrder", "DESC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isDescending = isListOrdered(actualList, "DESC");
+
+        assertEquals(true, isDescending);
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDefault() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isAscending = isListOrdered(actualList, null);
+        // Default Sort is ASC if sortOrder is not provided
+        assertEquals(true, isAscending);
+
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByTransactionDateAsc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "dateOf");
+        queryParametersMap.put("sortOrder", "ASC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<LocalDate> actualList = new ArrayList<>();

Review Comment:
   Same as above.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        queryParametersMap.put("sortOrder", "DESC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isDescending = isListOrdered(actualList, "DESC");
+
+        assertEquals(true, isDescending);
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDefault() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isAscending = isListOrdered(actualList, null);
+        // Default Sort is ASC if sortOrder is not provided
+        assertEquals(true, isAscending);
+
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByTransactionDateAsc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "dateOf");
+        queryParametersMap.put("sortOrder", "ASC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<LocalDate> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getDate());
+        });
+        boolean isAscending = isDateListOrdered(actualList, "ASC");
+        assertEquals(true, isAscending);
+
+    }
+
+    @Test
+    public void testSavingsTransactionsFilterByTransactionType() {

Review Comment:
   This test is just half of the pie because you never tested whether without the filtering applied, the post interest transaction is also returned; i.e. you don't know if your filter parameter that you passed has been handled or it's because by default the API filters out non deposit transactions.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        queryParametersMap.put("sortOrder", "DESC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isDescending = isListOrdered(actualList, "DESC");
+
+        assertEquals(true, isDescending);
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDefault() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isAscending = isListOrdered(actualList, null);
+        // Default Sort is ASC if sortOrder is not provided
+        assertEquals(true, isAscending);
+
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByTransactionDateAsc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "dateOf");
+        queryParametersMap.put("sortOrder", "ASC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<LocalDate> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getDate());
+        });
+        boolean isAscending = isDateListOrdered(actualList, "ASC");
+        assertEquals(true, isAscending);
+
+    }
+
+    @Test
+    public void testSavingsTransactionsFilterByTransactionType() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final String withdrawDate = "09 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("transactionType", "deposit");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+
+        this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "400", withdrawDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        for (GetSavingsAccountTransactionsPageItem item : pageItems) {
+            assertEquals(true, item.getTransactionType().getDeposit(), "Transaction Type is not as expected");
+        }
+    }
+
+    @Test
+    public void testSavingsTransactionsFilterByTransactionTypeAndAmount() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final String withdrawDate = "09 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("transactionType", "deposit");
+        queryParametersMap.put("fromAmount", "400");
+        queryParametersMap.put("toAmount", "700");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.postInterestForSavings(savingsId);
+
+        this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "400", withdrawDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(2, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        for (GetSavingsAccountTransactionsPageItem item : pageItems) {
+            assertEquals(true, item.getTransactionType().getDeposit(), "Transaction Type is not as expected");
+            assertEquals(true, item.getAmount().compareTo(BigDecimal.valueOf(700)) <= 0);

Review Comment:
   if you're checking this, why don't you check for 400?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java:
##########
@@ -1267,6 +1283,136 @@ public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final L
         return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR
     }
 
+    @Override
+    public Page<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType,
+            SearchParameters searchParameters) {
+        // validate the search parameters
+        validateSearchParameters(searchParameters);
+
+        Integer page = 0;
+        Integer size = SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTIONS_DEFAULT_LIMIT;
+        if (searchParameters.isLimited() && searchParameters.isOffset()) {
+            Integer limit = searchParameters.getLimit();
+            page = searchParameters.getOffset() / limit;
+            size = limit;
+        }
+        Pageable pageable = PageRequest.of(page, size);
+        if (searchParameters.isOrderByRequested()) {
+            pageable = PageRequest.of(page, size, Sort.by(searchParameters.getOrderBy()));
+            if (searchParameters.isSortOrderProvided()) {
+
+                pageable = PageRequest.of(page, size,
+                        Sort.by(searchParameters.getSortOrder().equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.Direction.ASC
+                                : Sort.Direction.DESC, searchParameters.getOrderBy()));
+            }
+        } else {
+            List<Order> orders = new ArrayList<Order>();
+            Order order1 = new Order(Sort.Direction.DESC, "dateOf");
+            orders.add(order1);
+
+            Order order2 = new Order(Sort.Direction.DESC, "createdDate");
+            orders.add(order2);
+
+            Order order3 = new Order(Sort.Direction.DESC, "id");
+            orders.add(order3);
+            pageable = PageRequest.of(page, size, Sort.by(orders));
+        }
+        Integer transactionTypeEnum = null;
+        if (searchParameters.isTransactionTypeProvided()) {
+            transactionTypeEnum = SavingsAccountTransactionType.fromString(searchParameters.getTransactionType()).getValue();
+        }
+        org.springframework.data.domain.Page<SavingsAccountTransactionDTOV2> resultPage = savingsAccountTransactionsRepository
+                .findAll(savingsId, depositAccountType.getValue(), transactionTypeEnum, searchParameters, pageable);
+        List<SavingsAccountTransactionData> savingsAccountTransactionDataList = resultPage.stream()
+                .map(SavingsAccountTransactionDTOV2::tosavingsAccountTransactionData).collect(Collectors.toList());
+        return new Page<>(savingsAccountTransactionDataList, Long.valueOf(resultPage.getTotalElements()).intValue());
+    }
+
+    private void validateSearchParameters(final SearchParameters searchParameters) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (searchParameters.isFromDateProvided() && searchParameters.isToDateProvided()) {
+            if (searchParameters.getToDate().isBefore(searchParameters.getFromDate())) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                        .append(SavingsApiConstants.toDateParamName).append(".can.not.be.before").append(".")
+                        .append(SavingsApiConstants.fromDateParamName);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toDateParamName)
+                        .append("` must be less than the provided ").append(SavingsApiConstants.fromDateParamName);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), SavingsApiConstants.toDateParamName,
+                        searchParameters.getToDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
+                        searchParameters.getFromDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
+                dataValidationErrors.add(error);
+            }
+        }
+
+        if (searchParameters.isTransactionTypeProvided() && !isValidTransactionType(searchParameters.getTransactionType())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.transactionTypeParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `")
+                    .append(SavingsApiConstants.transactionTypeParamName).append("` provided is not a valid transaction type ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.transactionTypeParamName, searchParameters.getTransactionType());
+            dataValidationErrors.add(error);
+        }
+
+        if (searchParameters.isFromAmountProvided() && searchParameters.isToAmountProvided()
+                && !isValidAmount(searchParameters.getFromAmount(), searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".or").append(SavingsApiConstants.toAmountParamName)
+                    .append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` should be less than ").append(SavingsApiConstants.toAmountParamName);
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isFromAmountProvided() && !isValidAmount(searchParameters.getFromAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isToAmountProvided() && !isValidAmount(searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.toAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.toAmountParamName, searchParameters.getToAmount());
+            dataValidationErrors.add(error);
+        }
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private boolean isValidAmount(BigDecimal amount) {

Review Comment:
   I bet fineract already has code for this, please check.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java:
##########
@@ -126,7 +141,7 @@ public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext conte
             final ChargeReadPlatformService chargeReadPlatformService,
             final EntityDatatableChecksReadService entityDatatableChecksReadService, final ColumnValidator columnValidator,
             final SavingsAccountAssembler savingAccountAssembler, PaginationHelper paginationHelper,
-            DatabaseSpecificSQLGenerator sqlGenerator) {
+            DatabaseSpecificSQLGenerator sqlGenerator, SavingsAccountTransactionRepository savingsAccountTransactionsRepository) {

Review Comment:
   Could've replced this with a lombok annotation.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {
+
+    private final Long transactionId;
+    private final Integer transactionType;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final Long releaseIdOfHoldAmountTransaction;
+    private final String reasonForBlock;
+    private final LocalDateTime createdDate;
+    private final AppUser appUser;
+    private final String note;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final boolean reversalTransaction;
+    private final Long originalTxnId;
+    private final Boolean lienTransaction;
+    private final boolean isManualTransaction;
+    private final AccountTransferTransaction fromSavingsTransaction;
+    private final AccountTransferTransaction toSavingsTransaction;
+    private final SavingsAccount savingsAccount;
+    private final PaymentDetail paymentDetail;
+    private final ApplicationCurrency currency;
+
+    public static final SavingsAccountTransactionData tosavingsAccountTransactionData(SavingsAccountTransactionDTOV2 dto) {
+        final Long id = dto.getTransactionId();
+        final int transactionTypeInt = dto.getTransactionType();
+
+        final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
+
+        final LocalDate date = dto.getTransactionDate();
+        final LocalDate submittedOnDate = Optional.ofNullable(dto.getCreatedDate().toLocalDate()).orElse(null);
+        final BigDecimal amount = dto.getTransactionAmount();
+        final Long releaseTransactionId = Optional.ofNullable(dto.getReleaseIdOfHoldAmountTransaction()).orElse(0L);

Review Comment:
   Is this right? If there's no release transaction, we simply return a 0 as Id? This is misleading in my opinion to clients unless you explicitly mark and document this in the swagger docs.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {
+
+    private final Long transactionId;
+    private final Integer transactionType;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final Long releaseIdOfHoldAmountTransaction;
+    private final String reasonForBlock;
+    private final LocalDateTime createdDate;
+    private final AppUser appUser;
+    private final String note;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final boolean reversalTransaction;
+    private final Long originalTxnId;
+    private final Boolean lienTransaction;
+    private final boolean isManualTransaction;
+    private final AccountTransferTransaction fromSavingsTransaction;
+    private final AccountTransferTransaction toSavingsTransaction;
+    private final SavingsAccount savingsAccount;
+    private final PaymentDetail paymentDetail;
+    private final ApplicationCurrency currency;
+
+    public static final SavingsAccountTransactionData tosavingsAccountTransactionData(SavingsAccountTransactionDTOV2 dto) {
+        final Long id = dto.getTransactionId();
+        final int transactionTypeInt = dto.getTransactionType();
+
+        final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
+
+        final LocalDate date = dto.getTransactionDate();
+        final LocalDate submittedOnDate = Optional.ofNullable(dto.getCreatedDate().toLocalDate()).orElse(null);
+        final BigDecimal amount = dto.getTransactionAmount();
+        final Long releaseTransactionId = Optional.ofNullable(dto.getReleaseIdOfHoldAmountTransaction()).orElse(0L);
+        final String reasonForBlock = dto.getReasonForBlock();
+        final BigDecimal outstandingChargeAmount = null;
+        final BigDecimal runningBalance = dto.getRunningBalance();
+        final boolean reversed = dto.isReversed();
+        final boolean isReversal = dto.isReversalTransaction();
+        final Long originalTransactionId = Optional.ofNullable(dto.getOriginalTxnId()).orElse(0L);

Review Comment:
   Same as above.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTOV2.java:
##########
@@ -0,0 +1,147 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
+import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountTransactionDTOV2 {
+
+    private final Long transactionId;
+    private final Integer transactionType;
+    private final LocalDate transactionDate;
+    private final BigDecimal transactionAmount;
+    private final Long releaseIdOfHoldAmountTransaction;
+    private final String reasonForBlock;
+    private final LocalDateTime createdDate;
+    private final AppUser appUser;
+    private final String note;
+    private final BigDecimal runningBalance;
+    private final boolean reversed;
+    private final boolean reversalTransaction;
+    private final Long originalTxnId;
+    private final Boolean lienTransaction;
+    private final boolean isManualTransaction;
+    private final AccountTransferTransaction fromSavingsTransaction;
+    private final AccountTransferTransaction toSavingsTransaction;
+    private final SavingsAccount savingsAccount;
+    private final PaymentDetail paymentDetail;
+    private final ApplicationCurrency currency;
+
+    public static final SavingsAccountTransactionData tosavingsAccountTransactionData(SavingsAccountTransactionDTOV2 dto) {
+        final Long id = dto.getTransactionId();
+        final int transactionTypeInt = dto.getTransactionType();
+
+        final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
+
+        final LocalDate date = dto.getTransactionDate();
+        final LocalDate submittedOnDate = Optional.ofNullable(dto.getCreatedDate().toLocalDate()).orElse(null);
+        final BigDecimal amount = dto.getTransactionAmount();
+        final Long releaseTransactionId = Optional.ofNullable(dto.getReleaseIdOfHoldAmountTransaction()).orElse(0L);
+        final String reasonForBlock = dto.getReasonForBlock();
+        final BigDecimal outstandingChargeAmount = null;
+        final BigDecimal runningBalance = dto.getRunningBalance();
+        final boolean reversed = dto.isReversed();
+        final boolean isReversal = dto.isReversalTransaction();
+        final Long originalTransactionId = Optional.ofNullable(dto.getOriginalTxnId()).orElse(0L);
+        final Boolean lienTransaction = dto.getLienTransaction();
+
+        final Long savingsId = dto.getSavingsAccount().getId();
+        final String accountNo = dto.getSavingsAccount().getAccountNumber();
+        final boolean postInterestAsOn = dto.isManualTransaction;
+
+        PaymentDetailData paymentDetailData = null;
+        if (transactionType.isDepositOrWithdrawal()) {
+            final Long paymentTypeId = dto.getPaymentDetail().getPaymentType().getId();
+            if (paymentTypeId != null) {
+                final String typeName = dto.getPaymentDetail().getPaymentType().getName();
+                final PaymentTypeData paymentType = PaymentTypeData.instance(paymentTypeId, typeName);
+                final String accountNumber = dto.getPaymentDetail().getAccountNumber();
+                final String checkNumber = dto.getPaymentDetail().getCheckNumber();
+                final String routingCode = dto.getPaymentDetail().getRoutingCode();
+                final String receiptNumber = dto.getPaymentDetail().getReceiptNumber();
+                final String bankNumber = dto.getPaymentDetail().getBankNumber();
+                paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber,
+                        bankNumber);
+            }
+        }
+
+        final String currencyCode = dto.getSavingsAccount().getCurrency().getCode();
+        final String currencyName = dto.getCurrency().getName();
+        final String currencyNameCode = dto.getCurrency().getNameCode();
+        final String currencyDisplaySymbol = dto.getCurrency().getDisplaySymbol();
+        final Integer currencyDigits = dto.getSavingsAccount().getCurrency().getDigitsAfterDecimal();
+        final Integer inMultiplesOf = dto.getSavingsAccount().getCurrency().getCurrencyInMultiplesOf();
+        final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+                currencyNameCode);
+
+        AccountTransferData transfer = null;
+        AccountTransferTransaction transferFrom = dto.getFromSavingsTransaction();
+        AccountTransferTransaction transferTo = dto.getToSavingsTransaction();
+        if (Objects.nonNull(transferFrom)) {
+            final Long fromTransferId = transferFrom.getId();
+            final LocalDate fromTransferDate = transferFrom.getDate();
+            final BigDecimal fromTransferAmount = getOrDefault(transferFrom.getAmount(), BigDecimal.ZERO);
+            final boolean fromTransferReversed = transferFrom.isReversed();
+            final String fromTransferDescription = transferFrom.getDescription();
+
+            transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate,
+                    fromTransferDescription, fromTransferReversed);
+        } else if (Objects.nonNull(transferTo)) {
+            final Long toTransferId = transferTo.getId();
+            final LocalDate toTransferDate = transferTo.getDate();
+            final BigDecimal toTransferAmount = getOrDefault(transferTo.getAmount(), BigDecimal.ZERO);
+            final boolean toTransferReversed = transferTo.isReversed();
+            final String toTransferDescription = transferTo.getDescription();
+
+            transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate,
+                    toTransferDescription, toTransferReversed);
+        }
+        String submittedByUsername = null;
+        if (Objects.nonNull(dto.getAppUser())) {
+            submittedByUsername = getOrDefault(dto.getAppUser().getUsername(), null);
+        }
+        final String note = dto.getNote();
+
+        return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, amount,
+                outstandingChargeAmount, runningBalance, reversed, transfer, submittedOnDate, postInterestAsOn, submittedByUsername, note,
+                isReversal, originalTransactionId, lienTransaction, releaseTransactionId, reasonForBlock);
+    }
+
+    private static <T> T getOrDefault(T input, T defaultValue) {

Review Comment:
   Single responsibility. How come the SavingsAccountTransaction has to deal with getOrDefault logic? You can use Objects.requireNonNull(T object, T defaultObject) instead of this..



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java:
##########
@@ -124,6 +127,20 @@ public static SavingsAccountTransactionType fromInt(final Integer transactionTyp
         return savingsAccountTransactionType;
     }
 
+    public static SavingsAccountTransactionType fromString(String transactionType) {
+        for (SavingsAccountTransactionType savingsAccountTransactionType : SavingsAccountTransactionType.values()) {

Review Comment:
   I really really don't like this splitting logic. Why can't we solve it differently which doesn't involve string magic? this solution is super fragile.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java:
##########
@@ -646,6 +649,27 @@ public HashMap getSavingsTransaction(final Integer savingsID, final Integer savi
         return Utils.performServerGet(requestSpec, responseSpec, URL, "");
     }
 
+    public GetSavingsAccountTransactionsResponse getSavingsTransactionsV2(final Integer savingsID) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions" + "?" + Utils.TENANT_IDENTIFIER;
+        final String response = Utils.performServerGet(requestSpec, responseSpec, URL, null);
+        return GSON.fromJson(response, GetSavingsAccountTransactionsResponse.class);
+    }
+
+    public GetSavingsAccountTransactionsResponse getSavingsTransactionsWithQueryParams(final Integer savingsID,
+            Map<String, String> queryParamMap) {
+        final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions" + "?" + Utils.TENANT_IDENTIFIER;
+        StringBuilder builder = new StringBuilder(URL);
+
+        for (Map.Entry<String, String> entry : queryParamMap.entrySet()) {

Review Comment:
   Why don't we use the generated fineract-client classes? That way you don't even need to do this string magic again on the query params.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());

Review Comment:
   The details of the savings transaction is not checked (amount, etc)



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java:
##########
@@ -1267,6 +1283,136 @@ public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final L
         return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR
     }
 
+    @Override
+    public Page<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType,
+            SearchParameters searchParameters) {
+        // validate the search parameters
+        validateSearchParameters(searchParameters);
+
+        Integer page = 0;
+        Integer size = SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTIONS_DEFAULT_LIMIT;
+        if (searchParameters.isLimited() && searchParameters.isOffset()) {
+            Integer limit = searchParameters.getLimit();
+            page = searchParameters.getOffset() / limit;
+            size = limit;
+        }
+        Pageable pageable = PageRequest.of(page, size);
+        if (searchParameters.isOrderByRequested()) {
+            pageable = PageRequest.of(page, size, Sort.by(searchParameters.getOrderBy()));
+            if (searchParameters.isSortOrderProvided()) {
+
+                pageable = PageRequest.of(page, size,
+                        Sort.by(searchParameters.getSortOrder().equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.Direction.ASC
+                                : Sort.Direction.DESC, searchParameters.getOrderBy()));
+            }
+        } else {
+            List<Order> orders = new ArrayList<Order>();
+            Order order1 = new Order(Sort.Direction.DESC, "dateOf");
+            orders.add(order1);
+
+            Order order2 = new Order(Sort.Direction.DESC, "createdDate");
+            orders.add(order2);
+
+            Order order3 = new Order(Sort.Direction.DESC, "id");
+            orders.add(order3);
+            pageable = PageRequest.of(page, size, Sort.by(orders));
+        }
+        Integer transactionTypeEnum = null;
+        if (searchParameters.isTransactionTypeProvided()) {
+            transactionTypeEnum = SavingsAccountTransactionType.fromString(searchParameters.getTransactionType()).getValue();
+        }
+        org.springframework.data.domain.Page<SavingsAccountTransactionDTOV2> resultPage = savingsAccountTransactionsRepository
+                .findAll(savingsId, depositAccountType.getValue(), transactionTypeEnum, searchParameters, pageable);
+        List<SavingsAccountTransactionData> savingsAccountTransactionDataList = resultPage.stream()
+                .map(SavingsAccountTransactionDTOV2::tosavingsAccountTransactionData).collect(Collectors.toList());
+        return new Page<>(savingsAccountTransactionDataList, Long.valueOf(resultPage.getTotalElements()).intValue());
+    }
+
+    private void validateSearchParameters(final SearchParameters searchParameters) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (searchParameters.isFromDateProvided() && searchParameters.isToDateProvided()) {
+            if (searchParameters.getToDate().isBefore(searchParameters.getFromDate())) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                        .append(SavingsApiConstants.toDateParamName).append(".can.not.be.before").append(".")
+                        .append(SavingsApiConstants.fromDateParamName);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toDateParamName)
+                        .append("` must be less than the provided ").append(SavingsApiConstants.fromDateParamName);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), SavingsApiConstants.toDateParamName,
+                        searchParameters.getToDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
+                        searchParameters.getFromDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
+                dataValidationErrors.add(error);
+            }
+        }
+
+        if (searchParameters.isTransactionTypeProvided() && !isValidTransactionType(searchParameters.getTransactionType())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.transactionTypeParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `")
+                    .append(SavingsApiConstants.transactionTypeParamName).append("` provided is not a valid transaction type ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.transactionTypeParamName, searchParameters.getTransactionType());
+            dataValidationErrors.add(error);
+        }
+
+        if (searchParameters.isFromAmountProvided() && searchParameters.isToAmountProvided()
+                && !isValidAmount(searchParameters.getFromAmount(), searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".or").append(SavingsApiConstants.toAmountParamName)
+                    .append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` should be less than ").append(SavingsApiConstants.toAmountParamName);
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isFromAmountProvided() && !isValidAmount(searchParameters.getFromAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isToAmountProvided() && !isValidAmount(searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.toAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.toAmountParamName, searchParameters.getToAmount());
+            dataValidationErrors.add(error);
+        }
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private boolean isValidAmount(BigDecimal amount) {
+        BigDecimal zero = BigDecimal.ZERO;
+        BigDecimal maxValue = BigDecimal.valueOf(Double.MAX_VALUE);
+
+        return (amount.compareTo(zero) >= 0) && (amount.compareTo(maxValue) <= 0);
+    }
+
+    private boolean isValidAmount(BigDecimal fromAmount, BigDecimal toAmount) {

Review Comment:
   I bet fineract already has code for this, please check.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java:
##########
@@ -1267,6 +1283,136 @@ public Collection<SavingsAccountTransactionData> retrieveAllTransactions(final L
         return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR
     }
 
+    @Override
+    public Page<SavingsAccountTransactionData> retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType,
+            SearchParameters searchParameters) {
+        // validate the search parameters
+        validateSearchParameters(searchParameters);
+
+        Integer page = 0;
+        Integer size = SavingsApiConstants.SAVINGS_ACCOUNT_TRANSACTIONS_DEFAULT_LIMIT;
+        if (searchParameters.isLimited() && searchParameters.isOffset()) {
+            Integer limit = searchParameters.getLimit();
+            page = searchParameters.getOffset() / limit;
+            size = limit;
+        }
+        Pageable pageable = PageRequest.of(page, size);
+        if (searchParameters.isOrderByRequested()) {
+            pageable = PageRequest.of(page, size, Sort.by(searchParameters.getOrderBy()));
+            if (searchParameters.isSortOrderProvided()) {
+
+                pageable = PageRequest.of(page, size,
+                        Sort.by(searchParameters.getSortOrder().equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.Direction.ASC
+                                : Sort.Direction.DESC, searchParameters.getOrderBy()));
+            }
+        } else {
+            List<Order> orders = new ArrayList<Order>();
+            Order order1 = new Order(Sort.Direction.DESC, "dateOf");
+            orders.add(order1);
+
+            Order order2 = new Order(Sort.Direction.DESC, "createdDate");
+            orders.add(order2);
+
+            Order order3 = new Order(Sort.Direction.DESC, "id");
+            orders.add(order3);
+            pageable = PageRequest.of(page, size, Sort.by(orders));
+        }
+        Integer transactionTypeEnum = null;
+        if (searchParameters.isTransactionTypeProvided()) {
+            transactionTypeEnum = SavingsAccountTransactionType.fromString(searchParameters.getTransactionType()).getValue();
+        }
+        org.springframework.data.domain.Page<SavingsAccountTransactionDTOV2> resultPage = savingsAccountTransactionsRepository
+                .findAll(savingsId, depositAccountType.getValue(), transactionTypeEnum, searchParameters, pageable);
+        List<SavingsAccountTransactionData> savingsAccountTransactionDataList = resultPage.stream()
+                .map(SavingsAccountTransactionDTOV2::tosavingsAccountTransactionData).collect(Collectors.toList());
+        return new Page<>(savingsAccountTransactionDataList, Long.valueOf(resultPage.getTotalElements()).intValue());
+    }
+
+    private void validateSearchParameters(final SearchParameters searchParameters) {
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        if (searchParameters.isFromDateProvided() && searchParameters.isToDateProvided()) {
+            if (searchParameters.getToDate().isBefore(searchParameters.getFromDate())) {
+                final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                        .append(SavingsApiConstants.toDateParamName).append(".can.not.be.before").append(".")
+                        .append(SavingsApiConstants.fromDateParamName);
+                final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toDateParamName)
+                        .append("` must be less than the provided ").append(SavingsApiConstants.fromDateParamName);
+                final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                        defaultEnglishMessage.toString(), SavingsApiConstants.toDateParamName,
+                        searchParameters.getToDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
+                        searchParameters.getFromDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
+                dataValidationErrors.add(error);
+            }
+        }
+
+        if (searchParameters.isTransactionTypeProvided() && !isValidTransactionType(searchParameters.getTransactionType())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.transactionTypeParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `")
+                    .append(SavingsApiConstants.transactionTypeParamName).append("` provided is not a valid transaction type ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.transactionTypeParamName, searchParameters.getTransactionType());
+            dataValidationErrors.add(error);
+        }
+
+        if (searchParameters.isFromAmountProvided() && searchParameters.isToAmountProvided()
+                && !isValidAmount(searchParameters.getFromAmount(), searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".or").append(SavingsApiConstants.toAmountParamName)
+                    .append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` should be less than ").append(SavingsApiConstants.toAmountParamName);
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isFromAmountProvided() && !isValidAmount(searchParameters.getFromAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.fromAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.fromAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.fromAmountParamName, searchParameters.getFromAmount());
+            dataValidationErrors.add(error);
+        } else if (searchParameters.isToAmountProvided() && !isValidAmount(searchParameters.getToAmount())) {
+            final StringBuilder validationErrorCode = new StringBuilder("validation.msg").append(".")
+                    .append(SavingsApiConstants.toAmountParamName).append(".invalid");
+            final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(SavingsApiConstants.toAmountParamName)
+                    .append("` provided is not a valid amount ");
+            final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
+                    defaultEnglishMessage.toString(), SavingsApiConstants.toAmountParamName, searchParameters.getToAmount());
+            dataValidationErrors.add(error);
+        }
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private boolean isValidAmount(BigDecimal amount) {
+        BigDecimal zero = BigDecimal.ZERO;
+        BigDecimal maxValue = BigDecimal.valueOf(Double.MAX_VALUE);
+
+        return (amount.compareTo(zero) >= 0) && (amount.compareTo(maxValue) <= 0);
+    }
+
+    private boolean isValidAmount(BigDecimal fromAmount, BigDecimal toAmount) {
+        BigDecimal zero = BigDecimal.ZERO;
+        BigDecimal maxValue = BigDecimal.valueOf(Double.MAX_VALUE);
+
+        boolean isValidFromAmount = isValidAmount(fromAmount);
+        boolean isValidToAmount = isValidAmount(toAmount);
+        boolean isValidFromToAmount = fromAmount.compareTo(toAmount) <= 0;
+
+        return isValidFromAmount && isValidToAmount && isValidFromToAmount;
+    }
+
+    private boolean isValidTransactionType(String transactionType) {

Review Comment:
   I bet fineract already has code for this, please check.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);

Review Comment:
   Please import this statically to have a consistent structure.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java:
##########
@@ -45,6 +45,9 @@ public interface SavingsAccountReadPlatformService {
 
     Collection<SavingsAccountTransactionData> retrieveAllTransactions(Long savingsId, DepositAccountType depositAccountType);
 
+    Page<SavingsAccountTransactionData> retrieveAllTransactions(Long savingsId, DepositAccountType depositAccountType,

Review Comment:
   Same as I said for the repository, search use-case.



##########
integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionsIntegrationTest.java:
##########
@@ -0,0 +1,379 @@
+/**
+ * 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.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsPageItem;
+import org.apache.fineract.client.models.GetSavingsAccountTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class SavingsAccountTransactionsIntegrationTest {
+
+    public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsProductHelper savingsProductHelper;
+    private SavingsAccountHelper savingsAccountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.savingsProductHelper = new SavingsProductHelper();
+    }
+
+    @Test
+    public void testSavingsTransactions() {
+        final String startDate = "01 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
+        Assertions.assertNotNull(clientID);
+
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper.getSavingsTransactionsV2(savingsId);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(1, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+    }
+
+    @Test
+    public void testSavingsTransactionsPagination() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("offset", "0");
+        queryParametersMap.put("limit", "2");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(2, transactionsResponse.getPageItems().size());
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDesc() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        queryParametersMap.put("sortOrder", "DESC");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();
+        pageItems.forEach(data -> {
+            actualList.add(data.getAmount());
+        });
+        boolean isDescending = isListOrdered(actualList, "DESC");
+
+        assertEquals(true, isDescending);
+    }
+
+    @Test
+    public void testSavingsTransactionsSortByAmountDefault() {
+        final String clientCreateDate = "25 Apr 2023";
+        final String startDate1 = "01 May 2023";
+        final String startDate2 = "04 May 2023";
+        final String startDate3 = "07 May 2023";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientCreateDate);
+        Assertions.assertNotNull(clientID);
+        Map<String, String> queryParametersMap = new HashMap<>();
+        queryParametersMap.put("orderBy", "amount");
+        final Integer savingsId = createSavingsAccountDailyPosting(clientID, clientCreateDate);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate1, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "500", startDate2, CommonConstants.RESPONSE_RESOURCE_ID);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsId, "700", startDate3, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        GetSavingsAccountTransactionsResponse transactionsResponse = this.savingsAccountHelper
+                .getSavingsTransactionsWithQueryParams(savingsId, queryParametersMap);
+        Assertions.assertNotNull(transactionsResponse);
+        assertEquals(3, transactionsResponse.getTotalFilteredRecords());
+        Assertions.assertNotNull(transactionsResponse.getPageItems());
+        assertEquals(3, transactionsResponse.getPageItems().size());
+        Set<GetSavingsAccountTransactionsPageItem> pageItems = transactionsResponse.getPageItems();
+        List<BigDecimal> actualList = new ArrayList<>();

Review Comment:
   Same as above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@fineract.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org