Como implementar filtros e paginação usando @Filterable,
GenericFilterDTO e
GenericSpecificationsBuilder.
@FilterableGenericSpecificationsBuilderGenericSpecificationPageableBuilderGenericFilterDTOpublic class FuncionarioFilterDTO implements GenericFilterDTO {
@Filterable(operation = Filterable.FilterOperation.LIKE)
private String nome;
@Filterable(operation = Filterable.FilterOperation.EQUAL)
private String departamento;
// getters/setters
}public class FuncionarioService extends AbstractBaseCrudService<Funcionario, FuncionarioDTO, Long, FuncionarioFilterDTO> {
public FuncionarioService(BaseCrudRepository<Funcionario, Long> repo, Class<Funcionario> entityClass) {
super(repo, entityClass);
}
}POST /api/human-resources/funcionarios/filter?page=0&size=20
Content-Type: application/json
{ "nome": "ana", "departamento": "RH" }
sort não for informado, aplica-se a
ordenação padrão definida pela entidade
(@DefaultSortColumn).?sort=nome,asc&sort=departamento,desc.size conforme sua política de performancepraxis.filter.range.allow-scalar-payload:
false (padrão): payload escalar de range é inválido e
retorna 400.true: fallback temporário para legado, convertendo
escalar para lista.praxis.filter.range.log-legacy-scalar-payload:
true (padrão): registra uso de payload escalar legado
para plano de migração.400 com
errors[].properties.code = FILTER_PAYLOAD_INVALID.relation="a.b.campo" para navegar
por joins de forma legível.PageableBuilder para requests consistentes.array de enum com muitas opções: o
resolver publica multiSelect como contrato principal.radio/chipInput; médios select;
grandes autoComplete/multiSelect.enum não precisam de endpoint: as
opções são derivadas diretamente do schema OpenAPI (o array
enum).array de enum, o schema é “array de
enum” e a UI renderiza multi‑select automaticamente.endpoint (/options/filter) apenas para
catálogos dinâmicos/relacionais (id/label), não para
enum estático.endpoint é normalizado para
resourcePath, e
displayField/valueField são normalizados para
optionLabelKey/optionValueKey.endpoint=/{resource}/options/filter,
valueField="id" e displayField="label" de
forma explícita.| Operação | Descrição | Exemplo DTO |
|---|---|---|
| EQUAL | Igualdade | @Filterable(EQUAL) |
| NOT_EQUAL | Diferente | @Filterable(NOT_EQUAL) |
| LIKE | Contém (ci) | @Filterable(LIKE) |
| NOT_LIKE | Não contém (ci) | @Filterable(NOT_LIKE) |
| STARTS_WITH | Começa com (ci) | @Filterable(STARTS_WITH) |
| ENDS_WITH | Termina com (ci) | @Filterable(ENDS_WITH) |
| GREATER_THAN | Maior que | @Filterable(GREATER_THAN) |
| GREATER_OR_EQUAL | Maior ou igual | @Filterable(GREATER_OR_EQUAL) |
| LESS_THAN | Menor que | @Filterable(LESS_THAN) |
| LESS_OR_EQUAL | Menor ou igual | @Filterable(LESS_OR_EQUAL) |
| IN | Pertence a uma lista | @Filterable(IN) |
| NOT_IN | Não pertence a uma lista | @Filterable(NOT_IN) |
| BETWEEN | Entre (parcial ou completo) | @Filterable(BETWEEN) |
| IS_NULL | É nulo (usar Boolean TRUE no DTO) | @Filterable(IS_NULL) + Boolean campo |
| IS_NOT_NULL | Não é nulo (usar Boolean TRUE no DTO) | @Filterable(IS_NOT_NULL) + Boolean |
| Operação | Descrição | Exemplo DTO |
|---|---|---|
| BETWEEN_EXCLUSIVE | Entre exclusivo: > a AND < b |
@Filterable(BETWEEN_EXCLUSIVE) |
| NOT_BETWEEN | Negação do between (inclusive) | @Filterable(NOT_BETWEEN) |
| OUTSIDE_RANGE | Fora do intervalo: < min OR > max |
@Filterable(OUTSIDE_RANGE) |
| ON_DATE | Igual à data (parte de data) | @Filterable(ON_DATE) + LocalDate |
| IN_LAST_DAYS | Nos últimos N dias | @Filterable(IN_LAST_DAYS) +
Integer dias |
| IN_NEXT_DAYS | Nos próximos N dias | @Filterable(IN_NEXT_DAYS) +
Integer dias |
| SIZE_EQ | Tamanho de coleção igual a N | @Filterable(SIZE_EQ) + Integer |
| SIZE_GT | Tamanho de coleção maior que N | @Filterable(SIZE_GT) + Integer |
| SIZE_LT | Tamanho de coleção menor que N | @Filterable(SIZE_LT) + Integer |
| IS_TRUE | Campo booleano verdadeiro | @Filterable(IS_TRUE) |
| IS_FALSE | Campo booleano falso | @Filterable(IS_FALSE) |
Notas: - Para ON_DATE, use LocalDate no
DTO. A comparação considera o intervalo [início do dia, início do dia
seguinte). - Para IN_LAST_DAYS/IN_NEXT_DAYS, o valor é
relativo ao horário atual (UTC) e converte para Instant. -
Para SIZE_*, aplique apenas em atributos de coleção
(OneToMany/ManyToMany); o builder usa CriteriaBuilder.size.
- Requisito de coleção em SIZE_*: defina
relation para apontar explicitamente para o atributo de
coleção na entidade. Se o caminho não for coleção, o builder lança erro
informativo. - Para novos componentes compactos de superfície, prefira
controlType canônico com a família
INLINE_*.
ON_DATE, IN_LAST_DAYS e
IN_NEXT_DAYS utilizam normalização com Instant
e horário atual do backend. Em ambientes multi‑região, verifique a zona
padrão da aplicação e do banco. Recomenda‑se padronizar em UTC.Notas: - Operações com ci (case-insensitive) normalizam
usando lower(). - Para IS_NULL/IS_NOT_NULL, sugere-se
modelar no DTO como Boolean campoIsNull; quando
true, o predicado é aplicado. - Para IS_TRUE/IS_FALSE, o
predicado é aplicado quando o campo do DTO está presente (não nulo).
Recomenda‑se enviar true para indicar que o predicado deve
ser considerado.
import org.praxisplatform.uischema.filter.annotation.Filterable;
import java.time.LocalDate;
import java.util.List;
public class VendaFilterDTO implements org.praxisplatform.uischema.filter.dto.GenericFilterDTO {
@Filterable(operation = Filterable.FilterOperation.LIKE)
private String cliente;
@Filterable(operation = Filterable.FilterOperation.GREATER_OR_EQUAL)
private java.math.BigDecimal valorMin;
@Filterable(operation = Filterable.FilterOperation.LESS_OR_EQUAL)
private java.math.BigDecimal valorMax;
@Filterable(operation = Filterable.FilterOperation.BETWEEN)
private List<LocalDate> emissaoEntre; // [de, ate]
@Filterable(operation = Filterable.FilterOperation.IN)
private List<String> canais; // ["ONLINE","LOJA"]
@Filterable(operation = Filterable.FilterOperation.LIKE, relation = "vendedor.nome")
private String vendedorNome;
@Filterable(operation = Filterable.FilterOperation.IS_TRUE)
private Boolean pago;
}POST /api/vendas/filter?page=0&size=20
Content-Type: application/json
{
"cliente": "maria",
"valorMin": 100.00,
"valorMax": 1000.00,
"emissaoEntre": ["2024-01-01", "2024-12-31"],
"canais": ["ONLINE", "LOJA"],
"vendedorNome": "silva",
"pago": true
}
Resultado: menos boilerplate, tempo de entrega menor e APIs/UX mais consistentes — alinhadas com as melhores práticas do ecossistema Spring + OpenAPI.
Sugestoes de combinacao para FilterDTO sem depender de
app externo:
IN e NOT_IN para listas de enums ou
statusON_DATE e IN_LAST_DAYS para datas
operacionaisrelation = "relacao.id" para filtros sobre
relacionamentoBETWEEN para ranges numericos e datasExemplos tipicos:
statusIn com INstatusNotIn com NOT_INdataEventoOnDate com ON_DATEdataEventoInLastDays com IN_LAST_DAYScategoriaId com EQUAL e
relation = "categoria.id"POST /api/human-resources/missoes/filter
Content-Type: application/json
{ "statusIn": ["ABERTA", "EM_ANDAMENTO"] }
POST /api/human-resources/veiculos/filter
Content-Type: application/json
{ "statusNotIn": ["INATIVO"] }
POST /api/human-resources/incidentes/filter
Content-Type: application/json
{ "ocorridoEmOn": "2025-03-01" }
POST /api/human-resources/sinais-socorro/filter
Content-Type: application/json
{ "abertoEmLastDays": 7 }