blob: 6ccea902e4e5448aad9e28d58895396f9c94c4d9 [file] [log] [blame]
/**
* 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.service.search;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformServiceImpl;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.apache.fineract.portfolio.search.data.AdvancedQueryRequest;
import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.data.TableQueryData;
import org.apache.fineract.portfolio.search.data.TransactionSearchRequest;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.jetbrains.annotations.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccountTransactionSearchService {
private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final ReadWriteNonCoreDataService datatableService;
private final DataTableValidator dataTableValidator;
private final JdbcTemplate jdbcTemplate;
private final SearchUtil searchUtil;
@Override
public Page<SavingsAccountTransactionData> searchTransactions(@NotNull Long savingsId,
@NotNull TransactionSearchRequest searchParameters) {
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_RESOURCE_NAME);
String apptable = EntityTables.SAVINGS_TRANSACTION.getApptableName();
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(apptable));
PageRequest pageable = searchParameters.getPageable();
PageRequest sortPageable;
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else {
pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", CREATED_DATE_DB_FIELD, "id");
sortPageable = pageable;
}
List<ColumnFilterData> columnFilters = new ArrayList<>();
columnFilters.add(ColumnFilterData.eq("savings_account_id", savingsId.toString()));
columnFilters.add(ColumnFilterData.eq("is_reversal", Boolean.FALSE.toString()));
addFromToFilter("transaction_date", DateUtils.format(searchParameters.getFromDate()),
DateUtils.format(searchParameters.getToDate()), columnFilters);
addFromToFilter("submitted_on_date", DateUtils.format(searchParameters.getFromSubmittedDate()),
DateUtils.format(searchParameters.getToSubmittedDate()), columnFilters);
addFromToFilter("amount", MathUtil.formatToSql(searchParameters.getFromAmount()),
MathUtil.formatToSql(searchParameters.getToAmount()), columnFilters);
Page<SavingsAccountTransactionData> emptyResult = PageableExecutionUtils.getPage(new ArrayList<>(0), pageable, () -> 0);
if (addTransactionTypesFilter(searchParameters, columnFilters) == null) {
return emptyResult;
}
String alias = "tr";
StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>();
searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, null, null, null, false, sqlGenerator);
SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper tm = new SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper();
Object[] args = params.toArray();
String countQuery = "SELECT COUNT(*) " + tm.from() + where;
Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR
if (totalElements == null || totalElements == 0) {
return emptyResult;
}
StringBuilder query = new StringBuilder().append("SELECT ").append(tm.schema()).append(where);
query.append(" ").append(sqlGenerator.buildOrderBy(sortPageable.getSort().toList(), alias, false));
if (pageable.isPaged()) {
query.append(" ").append(sqlGenerator.limit(pageable.getPageSize(), (int) pageable.getOffset()));
}
List<SavingsAccountTransactionData> results = this.jdbcTemplate.query(query.toString(), tm, args);
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
}
private static void addFromToFilter(@NotNull String column, String fromValue, String toValue,
@NotNull List<ColumnFilterData> columnFilters) {
if (fromValue != null) {
columnFilters.add(toValue == null ? ColumnFilterData.create(column, SqlOperator.GTE, fromValue)
: ColumnFilterData.btw(column, fromValue, toValue));
} else if (toValue != null) {
columnFilters.add(ColumnFilterData.create(column, SqlOperator.LTE, toValue));
}
}
@Nullable
private static Boolean addTransactionTypesFilter(@NotNull TransactionSearchRequest searchParameters,
List<ColumnFilterData> columnFilters) {
Predicate<SavingsAccountTransactionType> filter = null;
Boolean credit = searchParameters.getCredit();
Boolean debit = searchParameters.getDebit();
if (credit != null) {
Predicate<SavingsAccountTransactionType> cf = SavingsAccountTransactionType::isCreditEntryType;
filter = credit ? cf : Predicate.not(cf);
}
if (debit != null) {
Predicate<SavingsAccountTransactionType> df = SavingsAccountTransactionType::isDebitEntryType;
if (!debit) {
df = Predicate.not(df);
}
filter = credit == null ? df : (credit && debit ? filter.or(df) : filter.and(df));
}
if (searchParameters.getTypes() != null) {
List<String> types = Arrays.asList(searchParameters.getTypes());
Predicate<SavingsAccountTransactionType> tf = t -> types.contains(String.valueOf(t.getId()));
filter = filter == null ? tf : filter.and(tf);
}
if (filter != null) {
filter = filter.and(SavingsAccountTransactionType::isValid);
List<SavingsAccountTransactionType> filteredTypes = SavingsAccountTransactionType.getFiltered(filter);
if (filteredTypes.isEmpty()) {
return null;
} else {
String[] values = filteredTypes.stream().map(t -> String.valueOf(t.getId())).toArray(String[]::new);
columnFilters.add(ColumnFilterData.create("transaction_type_enum", SqlOperator.IN, values));
return true;
}
}
return false;
}
@Override
public Page<JsonObject> queryAdvanced(@NotNull Long savingsId, @NotNull PagedLocalRequest<AdvancedQueryRequest> pagedRequest) {
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_RESOURCE_NAME);
String apptable = EntityTables.SAVINGS_TRANSACTION.getApptableName();
AdvancedQueryRequest queryRequest = pagedRequest.getRequest().orElseThrow();
dataTableValidator.validateTableSearch(queryRequest);
List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(apptable);
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil.mapHeadersToName(columnHeaders);
String pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
AdvancedQueryData baseQuery = queryRequest.getBaseQuery();
List<TableQueryData> datatableQueries = queryRequest.getDatatableQueries();
List<ColumnFilterData> columnFilters;
List<String> resultColumns;
List<String> selectColumns;
if (baseQuery == null) {
columnFilters = new ArrayList<>();
resultColumns = new ArrayList<>();
selectColumns = new ArrayList<>();
} else {
columnFilters = baseQuery.getNonNullFilters();
columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
resultColumns = baseQuery.getNonNullResultColumns();
selectColumns = new ArrayList<>(searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true));
}
columnFilters.add(0, ColumnFilterData.eq("savings_account_id", savingsId.toString()));
if (resultColumns.isEmpty() && !queryRequest.hasResultColumn()) {
resultColumns.add(pkColumn);
selectColumns.add(pkColumn);
}
PageRequest pageable = pagedRequest.toPageable();
PageRequest sortPageable;
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable;
}
String alias = "main";
String dateFormat = pagedRequest.getDateFormat();
String dateTimeFormat = pagedRequest.getDateTimeFormat();
Locale locale = pagedRequest.getLocaleObject();
StringBuilder select = new StringBuilder(sqlGenerator.buildSelect(selectColumns, alias, false));
StringBuilder from = new StringBuilder(" ").append(sqlGenerator.buildFrom(apptable, alias, false));
StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>();
searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, false,
sqlGenerator);
if (datatableQueries != null) {
StringBuilder dataSelect = new StringBuilder();
StringBuilder dataFrom = new StringBuilder();
StringBuilder dataWhere = new StringBuilder();
ArrayList<Object> dataParams = new ArrayList<>();
for (int i = 0; i < datatableQueries.size(); i++) {
TableQueryData tableQuery = datatableQueries.get(i);
boolean added = datatableService.buildDataQueryEmbedded(EntityTables.SAVINGS_TRANSACTION, tableQuery.getTable(),
tableQuery.getQuery(), selectColumns, dataSelect, dataFrom, dataWhere, dataParams, alias, ("d" + i), dateFormat,
dateTimeFormat, locale);
if (added) {
if (!dataSelect.isEmpty()) {
select.append(select.isEmpty() ? "SELECT " : ", ").append(dataSelect);
}
if (!dataFrom.isEmpty()) {
from.append(" ").append(dataFrom);
}
if (!dataWhere.isEmpty()) {
where.append(where.isEmpty() ? " WHERE " : " AND ").append(dataWhere);
}
params.addAll(dataParams);
dataSelect.setLength(0);
dataFrom.setLength(0);
dataWhere.setLength(0);
dataParams.clear();
}
resultColumns.addAll(tableQuery.getQuery().getNonNullResultColumns());
}
}
List<JsonObject> results = new ArrayList<>();
Object[] args = params.toArray();
// Execute the count Query
String countQuery = "SELECT COUNT(*)" + from + where;
Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR
if (totalElements == null || totalElements == 0) {
return PageableExecutionUtils.getPage(results, pageable, () -> 0);
}
StringBuilder query = new StringBuilder().append(select).append(from).append(where);
query.append(" ").append(sqlGenerator.buildOrderBy(sortPageable.getSort().toList(), null, false));
if (pageable.isPaged()) {
query.append(" ").append(sqlGenerator.limit(pageable.getPageSize(), (int) pageable.getOffset()));
}
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) {
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
}
}