mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
150 lines
5.3 KiB
Java
150 lines
5.3 KiB
Java
package com.imprimelibros.erp.datatables;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.springframework.data.domain.*;
|
|
import org.springframework.data.jpa.domain.Specification;
|
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|
|
|
import java.util.*;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.function.BiFunction;
|
|
import java.util.function.Function;
|
|
|
|
public class DataTable<T> {
|
|
|
|
public interface FilterHook<T> extends BiConsumer<SpecBuilder<T>, DataTablesRequest> {
|
|
}
|
|
|
|
public interface SpecBuilder<T> {
|
|
void add(Specification<T> extra);
|
|
}
|
|
|
|
private final JpaSpecificationExecutor<T> repo;
|
|
private final Class<T> entityClass;
|
|
private final DataTablesRequest dt;
|
|
private final List<String> searchable;
|
|
private final List<Function<T, Map<String, Object>>> adders = new ArrayList<>();
|
|
private final List<Function<Map<String, Object>, Map<String, Object>>> editors = new ArrayList<>();
|
|
private final List<FilterHook<T>> filters = new ArrayList<>();
|
|
private Specification<T> baseSpec = (root, q, cb) -> cb.conjunction();
|
|
private final ObjectMapper om = new ObjectMapper();
|
|
|
|
private DataTable(JpaSpecificationExecutor<T> repo, Class<T> entityClass, DataTablesRequest dt,
|
|
List<String> searchable) {
|
|
this.repo = repo;
|
|
this.entityClass = entityClass;
|
|
this.dt = dt;
|
|
this.searchable = searchable;
|
|
}
|
|
|
|
public static <T> DataTable<T> of(JpaSpecificationExecutor<T> repo, Class<T> entityClass, DataTablesRequest dt,
|
|
List<String> searchable) {
|
|
return new DataTable<>(repo, entityClass, dt, searchable);
|
|
}
|
|
|
|
/** Equivalente a tu $q->where(...): establece condición base */
|
|
public DataTable<T> where(Specification<T> spec) {
|
|
this.baseSpec = this.baseSpec.and(spec);
|
|
return this;
|
|
}
|
|
|
|
/** add("campo", fn(entity)->valor|Map) */
|
|
public DataTable<T> add(String field, Function<T, Object> fn) {
|
|
adders.add(entity -> {
|
|
Map<String, Object> m = new HashMap<>();
|
|
m.put(field, fn.apply(entity));
|
|
return m;
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* add(fn(entity)->Map<String,Object>) para devolver objetos anidados como tu
|
|
* "logo"
|
|
*/
|
|
public DataTable<T> add(Function<T, Map<String, Object>> fn) {
|
|
adders.add(fn);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* edit("campo", fn(entity)->valor) sobreescribe un campo existente o lo crea si
|
|
* no existe
|
|
*/
|
|
public DataTable<T> edit(String field, Function<T, Object> fn) {
|
|
editors.add(row -> {
|
|
row.put(field, fn.apply((T) row.get("__entity")));
|
|
return row;
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/** filter((builder, req) -> builder.add(miExtraSpec(req))) */
|
|
public DataTable<T> filter(FilterHook<T> hook) {
|
|
filters.add(hook);
|
|
return this;
|
|
}
|
|
|
|
public DataTablesResponse<Map<String, Object>> toJson(long totalCount) {
|
|
// Construye spec con búsqueda global + base + filtros custom
|
|
Specification<T> spec = baseSpec.and(DataTablesSpecification.build(dt, searchable));
|
|
final Specification<T>[] holder = new Specification[] { spec };
|
|
filters.forEach(h -> h.accept(extra -> holder[0] = holder[0].and(extra), dt));
|
|
spec = holder[0];
|
|
|
|
// Sort
|
|
// Sort
|
|
Sort sort = Sort.unsorted();
|
|
if (!dt.order.isEmpty() && !dt.columns.isEmpty()) {
|
|
List<Sort.Order> orders = new ArrayList<>();
|
|
for (var o : dt.order) {
|
|
var col = dt.columns.get(o.column);
|
|
String field = col != null ? col.name : null;
|
|
|
|
if (field == null || field.isBlank())
|
|
continue;
|
|
if (!col.orderable)
|
|
continue;
|
|
if (!searchable.contains(field))
|
|
continue; // << usa tu whitelist
|
|
|
|
orders.add(new Sort.Order(
|
|
"desc".equalsIgnoreCase(o.dir) ? Sort.Direction.DESC : Sort.Direction.ASC,
|
|
field));
|
|
}
|
|
if (!orders.isEmpty()) {
|
|
sort = Sort.by(orders);
|
|
} else {
|
|
for (var c : dt.columns) {
|
|
if (c != null && c.orderable && c.name != null && !c.name.isBlank()
|
|
&& searchable.contains(c.name)) {
|
|
sort = Sort.by(c.name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Page
|
|
int page = dt.length > 0 ? dt.start / dt.length : 0;
|
|
Pageable pageable = dt.length > 0 ? PageRequest.of(page, dt.length, sort) : Pageable.unpaged();
|
|
|
|
var p = repo.findAll(holder[0], pageable);
|
|
long filtered = p.getTotalElements();
|
|
|
|
// Mapear entidad -> Map base (via Jackson) + add/edit
|
|
List<Map<String, Object>> data = new ArrayList<>();
|
|
for (T e : p.getContent()) {
|
|
Map<String, Object> row = om.convertValue(e, Map.class);
|
|
row.put("__entity", e); // para editores que necesiten la entidad
|
|
for (var ad : adders)
|
|
row.putAll(ad.apply(e));
|
|
for (var ed : editors)
|
|
ed.apply(row);
|
|
row.remove("__entity");
|
|
data.add(row);
|
|
}
|
|
return new DataTablesResponse<>(dt.draw, totalCount, filtered, data);
|
|
}
|
|
}
|