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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to