feat: accounts list — active-only filter, date range & as-on-date search (backend)

- SearchParameters: added fromDate, toDate, asOnDate (LocalDate) fields with hasX() helpers
- SavingsAccountsApiResource: added status, fromDate, toDate, asOnDate query params
- SavingsAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters on activatedon_date, default ORDER BY id DESC
- CurrentAccountsApiResource + service: same filters on ca.status_enum and ca.activatedon_date
- DepositAccountReadPlatformService (interface): new retrieveAllPaged overload with date/status params
- DepositAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters, default ORDER BY id DESC for FD/RD
- FixedDepositAccountsApiResource + RecurringDepositAccountsApiResource: pass new params to service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-21 13:56:31 +05:30
co-authored by Claude Sonnet 4.6
parent ce0fb18eeb
commit 9f627bde61
10 changed files with 223 additions and 13 deletions
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.infrastructure.core.service;
import java.time.LocalDate;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -58,6 +59,21 @@ public class SearchParameters {
@Getter(AccessLevel.NONE)
private Boolean isSelfUser;
private Integer legalForm;
private LocalDate fromDate;
private LocalDate toDate;
private LocalDate asOnDate;
public boolean hasFromDate() {
return this.fromDate != null;
}
public boolean hasToDate() {
return this.toDate != null;
}
public boolean hasAsOnDate() {
return this.asOnDate != null;
}
public Integer getLimit() {
if (limit == null) {
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.currentaccount.service;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@@ -28,4 +29,7 @@ public interface CurrentAccountReadPlatformService {
List<Map<String, Object>> retrieveAll();
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName,
String status, LocalDate fromDate, LocalDate toDate, LocalDate asOnDate);
}
@@ -38,6 +38,7 @@ import jakarta.ws.rs.core.UriInfo;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -103,10 +104,18 @@ public class CurrentAccountsApiResource {
@QueryParam("orderBy") final String orderBy,
@QueryParam("sortOrder") final String sortOrder,
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
@QueryParam("status") @Parameter(description = "status") final String status,
@QueryParam("fromDate") @Parameter(description = "fromDate (YYYY-MM-DD)") final String fromDate,
@QueryParam("toDate") @Parameter(description = "toDate (YYYY-MM-DD)") final String toDate,
@QueryParam("asOnDate") @Parameter(description = "asOnDate (YYYY-MM-DD)") final String asOnDate) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll(offset, limit, accountNo, clientName);
final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll(offset, limit, accountNo, clientName,
status,
fromDate != null ? LocalDate.parse(fromDate) : null,
toDate != null ? LocalDate.parse(toDate) : null,
asOnDate != null ? LocalDate.parse(asOnDate) : null);
return GSON.toJson(accounts);
}
@@ -43,6 +43,7 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -161,7 +162,11 @@ public class FixedDepositAccountsApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
@QueryParam("status") @Parameter(description = "status") final String status,
@QueryParam("fromDate") @Parameter(description = "fromDate (YYYY-MM-DD)") final String fromDate,
@QueryParam("toDate") @Parameter(description = "toDate (YYYY-MM-DD)") final String toDate,
@QueryParam("asOnDate") @Parameter(description = "asOnDate (YYYY-MM-DD)") final String asOnDate) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
sqlValidator.validate(orderBy);
@@ -172,7 +177,11 @@ public class FixedDepositAccountsApiResource {
if (paginationParameters.isPaged()) {
final Page<DepositAccountData> account = this.depositAccountReadPlatformService
.retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters, accountNo, clientName);
.retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters, accountNo, clientName,
status,
fromDate != null ? LocalDate.parse(fromDate) : null,
toDate != null ? LocalDate.parse(toDate) : null,
asOnDate != null ? LocalDate.parse(asOnDate) : null);
return this.toApiJsonSerializer.serialize(settings, account,
DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
}
@@ -43,6 +43,7 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -142,7 +143,11 @@ public class RecurringDepositAccountsApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName) {
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
@QueryParam("status") @Parameter(description = "status") final String status,
@QueryParam("fromDate") @Parameter(description = "fromDate (YYYY-MM-DD)") final String fromDate,
@QueryParam("toDate") @Parameter(description = "toDate (YYYY-MM-DD)") final String toDate,
@QueryParam("asOnDate") @Parameter(description = "asOnDate (YYYY-MM-DD)") final String asOnDate) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
sqlValidator.validate(orderBy);
@@ -153,7 +158,11 @@ public class RecurringDepositAccountsApiResource {
if (paginationParameters.isPaged()) {
final Page<DepositAccountData> account = this.depositAccountReadPlatformService
.retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters, accountNo, clientName);
.retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters, accountNo, clientName,
status,
fromDate != null ? LocalDate.parse(fromDate) : null,
toDate != null ? LocalDate.parse(toDate) : null,
asOnDate != null ? LocalDate.parse(asOnDate) : null);
return this.toApiJsonSerializer.serialize(settings, account,
DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
}
@@ -40,6 +40,7 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
@@ -133,7 +134,11 @@ public class SavingsAccountsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
@QueryParam("depositType") @Parameter(description = "depositType") final Integer depositType) {
@QueryParam("depositType") @Parameter(description = "depositType") final Integer depositType,
@QueryParam("status") @Parameter(description = "status") final String status,
@QueryParam("fromDate") @Parameter(description = "fromDate (YYYY-MM-DD)") final String fromDate,
@QueryParam("toDate") @Parameter(description = "toDate (YYYY-MM-DD)") final String toDate,
@QueryParam("asOnDate") @Parameter(description = "asOnDate (YYYY-MM-DD)") final String asOnDate) {
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
@@ -141,7 +146,12 @@ public class SavingsAccountsApiResource {
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).accountNo(accountNo).name(clientName).depositType(depositType).build();
.orderBy(orderBy).sortOrder(sortOrder).accountNo(accountNo).name(clientName).depositType(depositType)
.status(status)
.fromDate(fromDate != null ? LocalDate.parse(fromDate) : null)
.toDate(toDate != null ? LocalDate.parse(toDate) : null)
.asOnDate(asOnDate != null ? LocalDate.parse(asOnDate) : null)
.build();
final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters);
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -230,6 +231,13 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
@Override
public List<Map<String, Object>> retrieveAll(final Integer offset, final Integer limit, final String accountNo,
final String clientName) {
return retrieveAll(offset, limit, accountNo, clientName, null, null, null, null);
}
@Override
public List<Map<String, Object>> retrieveAll(final Integer offset, final Integer limit, final String accountNo,
final String clientName, final String status, final LocalDate fromDate, final LocalDate toDate,
final LocalDate asOnDate) {
final StringBuilder sqlBuilder = new StringBuilder(BASE_SQL);
final List<Object> params = new ArrayList<>();
boolean hasWhere = false;
@@ -246,6 +254,35 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
params.add("%" + clientName + "%");
hasWhere = true;
}
if (StringUtils.isNotBlank(status)) {
final Integer statusEnum = resolveStatusEnum(status);
if (statusEnum != null) {
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
sqlBuilder.append("ca.status_enum = ?");
params.add(statusEnum);
hasWhere = true;
}
}
if (fromDate != null) {
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
sqlBuilder.append("ca.activatedon_date >= ?");
params.add(fromDate);
hasWhere = true;
}
if (toDate != null) {
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
sqlBuilder.append("ca.activatedon_date <= ?");
params.add(toDate);
hasWhere = true;
}
if (asOnDate != null) {
sqlBuilder.append(hasWhere ? " AND " : " WHERE ");
sqlBuilder.append("ca.activatedon_date <= ?");
params.add(asOnDate);
}
sqlBuilder.append(" ORDER BY ca.id DESC");
if (limit != null && limit > 0) {
sqlBuilder.append(" LIMIT ?");
params.add(limit);
@@ -266,6 +303,27 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
return result;
}
private Integer resolveStatusEnum(final String status) {
if (status == null) {
return null;
}
return switch (status.toLowerCase()) {
case "active" -> 300;
case "approved" -> 200;
case "submitted", "submittedandpendingapproval" -> 100;
case "withdrawn" -> 400;
case "rejected" -> 500;
case "closed" -> 600;
default -> {
try {
yield Integer.parseInt(status);
} catch (NumberFormatException e) {
yield null;
}
}
};
}
private Map<String, Object> buildStructuredResponse(Map<String, Object> row) {
Map<String, Object> out = new LinkedHashMap<>();
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.savings.service;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Map;
import org.apache.fineract.infrastructure.core.data.PaginationParameters;
@@ -36,6 +37,9 @@ public interface DepositAccountReadPlatformService {
Page<DepositAccountData> retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters,
String accountNo, String clientName);
Page<DepositAccountData> retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters,
String accountNo, String clientName, String status, LocalDate fromDate, LocalDate toDate, LocalDate asOnDate);
Collection<DepositAccountData> retrieveAllForLookup(DepositAccountType depositAccountType);
DepositAccountData retrieveOne(DepositAccountType depositAccountType, Long accountId);
@@ -187,6 +187,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
@Override
public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
final PaginationParameters paginationParameters, final String accountNo, final String clientName) {
return retrieveAllPaged(depositAccountType, paginationParameters, accountNo, clientName, null, null, null, null);
}
@Override
public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
final PaginationParameters paginationParameters, final String accountNo, final String clientName,
final String status, final LocalDate fromDate, final LocalDate toDate, final LocalDate asOnDate) {
this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues,
depositAccountType.resourceName());
@@ -203,6 +210,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final List<Object> params = new ArrayList<>();
params.add(depositAccountType.getValue());
if (StringUtils.isNotBlank(accountNo)) {
sqlBuilder.append(" and sa.account_no like ? ");
params.add("%" + accountNo + "%");
@@ -211,11 +219,57 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append(" and c.display_name like ? ");
params.add("%" + clientName + "%");
}
sqlBuilder.append(paginationParameters.paginationSql());
if (StringUtils.isNotBlank(status)) {
final Integer statusEnum = resolveStatusEnum(status);
if (statusEnum != null) {
sqlBuilder.append(" and sa.status_enum = ? ");
params.add(statusEnum);
}
}
if (fromDate != null) {
sqlBuilder.append(" and sa.activatedon_date >= ? ");
params.add(fromDate);
}
if (toDate != null) {
sqlBuilder.append(" and sa.activatedon_date <= ? ");
params.add(toDate);
}
if (asOnDate != null) {
sqlBuilder.append(" and sa.activatedon_date <= ? ");
params.add(asOnDate);
}
if (paginationParameters.hasOrderBy()) {
sqlBuilder.append(paginationParameters.paginationSql());
} else {
sqlBuilder.append(" order by sa.id desc ");
sqlBuilder.append(paginationParameters.limitSql());
}
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), params.toArray(), depositAccountMapper);
}
private Integer resolveStatusEnum(final String status) {
if (status == null) {
return null;
}
return switch (status.toLowerCase()) {
case "active" -> 300;
case "approved" -> 200;
case "submitted", "submittedandpendingapproval" -> 100;
case "withdrawn" -> 400;
case "rejected" -> 500;
case "closed" -> 600;
default -> {
try {
yield Integer.parseInt(status);
} catch (NumberFormatException e) {
yield null;
}
}
};
}
@Override
public Collection<DepositAccountData> retrieveAllForLookup(final DepositAccountType depositAccountType) {
@@ -166,15 +166,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append(" join m_office o on o.id = c.office_id");
sqlBuilder.append(" where o.hierarchy like ?");
final Object[] objectArray = new Object[10];
final Object[] objectArray = new Object[15];
objectArray[0] = hierarchySearchString;
int arrayPos = 1;
if (searchParameters != null) {
if (StringUtils.isNotBlank(searchParameters.getStatus())) {
sqlBuilder.append(" and sa.status_enum = ?");
objectArray[arrayPos] = Integer.parseInt(searchParameters.getStatus());
arrayPos = arrayPos + 1;
final Integer statusEnum = resolveStatusEnum(searchParameters.getStatus());
if (statusEnum != null) {
sqlBuilder.append(" and sa.status_enum = ?");
objectArray[arrayPos++] = statusEnum;
}
}
if (StringUtils.isNotBlank(searchParameters.getExternalId())) {
@@ -198,6 +200,18 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append(" and sa.deposit_type_enum = ?");
objectArray[arrayPos++] = searchParameters.getDepositType();
}
if (searchParameters.hasFromDate()) {
sqlBuilder.append(" and sa.activatedon_date >= ?");
objectArray[arrayPos++] = searchParameters.getFromDate();
}
if (searchParameters.hasToDate()) {
sqlBuilder.append(" and sa.activatedon_date <= ?");
objectArray[arrayPos++] = searchParameters.getToDate();
}
if (searchParameters.hasAsOnDate()) {
sqlBuilder.append(" and sa.activatedon_date <= ?");
objectArray[arrayPos++] = searchParameters.getAsOnDate();
}
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
@@ -206,6 +220,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
} else {
sqlBuilder.append(" order by sa.id desc");
}
if (searchParameters.hasLimit()) {
@@ -221,6 +237,27 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.savingAccountMapper);
}
private Integer resolveStatusEnum(final String status) {
if (status == null) {
return null;
}
return switch (status.toLowerCase()) {
case "active" -> 300;
case "approved" -> 200;
case "submitted", "submittedandpendingapproval" -> 100;
case "withdrawn" -> 400;
case "rejected" -> 500;
case "closed" -> 600;
default -> {
try {
yield Integer.parseInt(status);
} catch (NumberFormatException e) {
yield null;
}
}
};
}
@Override
public SavingsAccountData retrieveOne(final Long accountId) {