From 6641c1f077ade5b0be38717ec1e3276016229fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Jim=C3=A9nez?= Date: Sun, 12 Oct 2025 14:48:20 +0200 Subject: [PATCH] falta presupuesto marcapaginas y maquetacion y revision general en admin. revisar user --- .../erp/datatables/DataTable.java | 191 ++++++--- .../datatables/DataTablesSpecification.java | 8 +- .../PresupuestoDatatableService.java | 371 ++++-------------- .../presupuesto/PresupuestoRepository.java | 2 + src/main/resources/application.properties | 12 +- .../margenes-presupuesto/list.js | 2 +- .../imprimelibros/presupuestador/wizard.js | 2 + .../presupuestos/presupuesto-form.html | 5 + .../tabla-anonimos.html | 53 ++- .../presupuesto-list-items/tabla-cliente.html | 2 +- 10 files changed, 288 insertions(+), 360 deletions(-) diff --git a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java index 05251ca..d63619f 100644 --- a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java +++ b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java @@ -7,12 +7,14 @@ import org.springframework.data.domain.*; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import jakarta.persistence.criteria.*; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; public class DataTable { + /* ===== Tipos funcionales ===== */ public interface FilterHook extends BiConsumer, DataTablesRequest> { } @@ -20,31 +22,55 @@ public class DataTable { void add(Specification extra); } + /** + * Filtro custom por campo virtual: te doy (root, query, cb, value) y me + * devuelves un Predicate + */ + @FunctionalInterface + public interface FieldFilter { + Predicate apply(Root root, CriteriaQuery query, CriteriaBuilder cb, String value); + } + + /** + * Orden custom por campo virtual: te doy (root, query, cb) y me devuelves la + * Expression para orderBy + */ + @FunctionalInterface + public interface FieldOrder { + Expression apply(Root root, CriteriaQuery query, CriteriaBuilder cb); + } + + /* ===== Estado ===== */ private final JpaSpecificationExecutor repo; private final Class entityClass; private final DataTablesRequest dt; private final List searchable; + private final List>> adders = new ArrayList<>(); private final List, Map>> editors = new ArrayList<>(); private final List> filters = new ArrayList<>(); private Specification baseSpec = (root, q, cb) -> cb.conjunction(); + private final ObjectMapper om = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + /** whitelist de campos ordenables “simples” (por nombre) */ private List orderable = null; - private boolean onlyAdded = false; - public DataTable onlyAddedColumns(){ - this.onlyAdded = true; - return this; - } + /** mapas de comportamiento custom por campo */ + private final Map> orderCustom = new HashMap<>(); + private final Map> filterCustom = new HashMap<>(); + private boolean onlyAdded = false; + + /* ===== Ctor / factory ===== */ private DataTable(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt, List searchable) { this.repo = repo; this.entityClass = entityClass; this.dt = dt; - this.searchable = searchable; + this.searchable = searchable != null ? searchable : List.of(); } public static DataTable of(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt, @@ -52,13 +78,19 @@ public class DataTable { return new DataTable<>(repo, entityClass, dt, searchable); } - /** Equivalente a tu $q->where(...): establece condición base */ + /* ===== Fluent API ===== */ + public DataTable onlyAddedColumns() { + this.onlyAdded = true; + return this; + } + + /** WHERE base reusable */ public DataTable where(Specification spec) { this.baseSpec = this.baseSpec.and(spec); return this; } - /** add("campo", fn(entity)->valor|Map) */ + /** Campos renderizados */ public DataTable add(String field, Function fn) { adders.add(entity -> { Map m = new HashMap<>(); @@ -68,19 +100,19 @@ public class DataTable { return this; } - /** - * add(fn(entity)->Map) para devolver objetos anidados como tu - * "logo" - */ + public DataTable addIf(boolean condition, String field, Function fn) { + if (condition) + return add(field, fn); + return this; + } + public DataTable add(Function> fn) { adders.add(fn); return this; } - /** - * edit("campo", fn(entity)->valor) sobreescribe un campo existente o lo crea si - * no existe - */ + /** Edita/inyecta valor usando la entidad original (guardada como __entity) */ + @SuppressWarnings("unchecked") public DataTable edit(String field, Function fn) { editors.add(row -> { row.put(field, fn.apply((T) row.get("__entity"))); @@ -89,73 +121,132 @@ public class DataTable { return this; } + /** Whitelist de campos simples ordenables (por nombre) */ public DataTable orderable(List fields) { this.orderable = fields; return this; } - private List getOrderable() { - return (orderable == null || orderable.isEmpty()) ? this.searchable : this.orderable; + /** Orden custom por campo virtual (expresiones) */ + public DataTable orderable(String field, FieldOrder orderFn) { + this.orderCustom.put(field, orderFn); + return this; } - /** filter((builder, req) -> builder.add(miExtraSpec(req))) */ + /** Filtro custom por campo virtual (LIKE, rangos, etc.) */ + public DataTable filter(String field, FieldFilter filterFn) { + this.filterCustom.put(field, filterFn); + return this; + } + + /** Hook para añadir Specifications extra programáticamente */ public DataTable filter(FilterHook hook) { filters.add(hook); return this; } + /* ===== Helpers ===== */ + private List getOrderable() { + return (orderable == null || orderable.isEmpty()) ? this.searchable : this.orderable; + } + + /* ===== Core ===== */ public DataTablesResponse> toJson(long totalCount) { - // Construye spec con búsqueda global + base + filtros custom + // 1) Spec base + búsqueda (global/columnas) + hooks programáticos Specification spec = baseSpec.and(DataTablesSpecification.build(dt, searchable)); final Specification[] holder = new Specification[] { spec }; - filters.forEach(h -> h.accept(extra -> holder[0] = holder[0].and(extra), dt)); - spec = holder[0]; - // Sort - // Sort + // Hooks externos + filters.forEach(h -> h.accept(extra -> holder[0] = holder[0].and(extra), dt)); + + // 2) Filtros por columna “custom” (virtuales) + for (var col : dt.columns) { + if (col == null || !col.searchable) + continue; + if (col.name == null || col.name.isBlank()) + continue; + if (!filterCustom.containsKey(col.name)) + continue; + if (col.search == null || col.search.value == null || col.search.value.isBlank()) + continue; + + var value = col.search.value; + var filterFn = filterCustom.get(col.name); + holder[0] = holder[0].and((root, query, cb) -> { + Predicate p = filterFn.apply(root, query, cb, value); + return p != null ? p : cb.conjunction(); + }); + } + + // 3) Orden: + // - Para campos “simples” (no custom): con Sort (Spring) + // - Para campos “custom” (virtuales/expresiones): query.orderBy(...) dentro de + // una spec Sort sort = Sort.unsorted(); + List simpleOrders = new ArrayList<>(); + boolean customApplied = false; + if (!dt.order.isEmpty() && !dt.columns.isEmpty()) { - List orders = new ArrayList<>(); for (var o : dt.order) { var col = dt.columns.get(o.column); - String field = col != null ? col.name : null; + if (col == null) + continue; + String field = col.name; if (field == null || field.isBlank()) continue; if (!col.orderable) continue; if (!getOrderable().contains(field)) - continue; // << usa tu whitelist + continue; - 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() - && getOrderable().contains(c.name)) { - sort = Sort.by(c.name); - break; - } + if (orderCustom.containsKey(field)) { + final boolean asc = !"desc".equalsIgnoreCase(o.dir); + final FieldOrder orderFn = orderCustom.get(field); + + // aplica el ORDER BY custom dentro de la Specification (con Criteria) + holder[0] = holder[0].and((root, query, cb) -> { + Expression expr = orderFn.apply(root, query, cb); + if (expr != null) { + query.orderBy(asc ? cb.asc(expr) : cb.desc(expr)); + } + return cb.conjunction(); + }); + customApplied = true; + } else { + // orden simple por nombre de propiedad real + simpleOrders.add(new Sort.Order( + "desc".equalsIgnoreCase(o.dir) ? Sort.Direction.DESC : Sort.Direction.ASC, + field)); } } } - // Page + if (!simpleOrders.isEmpty()) { + sort = Sort.by(simpleOrders); + } + + // 4) Paginación (Sort para simples; custom order ya va dentro de la spec) 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 + // 5) Mapeo a Map + add/edit List> data = new ArrayList<>(); for (T e : p.getContent()) { - Map row = onlyAdded ? new HashMap<>() : om.convertValue(e, Map.class); - row.put("__entity", e); // para editores que necesiten la entidad + Map row; + if (onlyAdded) { + row = new HashMap<>(); + } else { + try { + row = om.convertValue(e, Map.class); + } catch (IllegalArgumentException ex) { + row = new HashMap<>(); + } + } + row.put("__entity", e); for (var ad : adders) row.putAll(ad.apply(e)); for (var ed : editors) @@ -163,6 +254,12 @@ public class DataTable { row.remove("__entity"); data.add(row); } + return new DataTablesResponse<>(dt.draw, totalCount, filtered, data); } -} + + private Predicate nullSafePredicate(CriteriaBuilder cb) { + // Devuelve conjunción para no interferir con los demás predicados + return cb.conjunction(); + } +} \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/datatables/DataTablesSpecification.java b/src/main/java/com/imprimelibros/erp/datatables/DataTablesSpecification.java index e3744aa..1b479d3 100644 --- a/src/main/java/com/imprimelibros/erp/datatables/DataTablesSpecification.java +++ b/src/main/java/com/imprimelibros/erp/datatables/DataTablesSpecification.java @@ -23,9 +23,15 @@ public class DataTablesSpecification { DataTablesRequest.Column col = dt.columns.get(i); if (col.searchable && col.search != null && col.search.value != null && !col.search.value.isEmpty()) { try { - ands.add(like(cb, root.get(col.name), col.search.value)); + Path path = root; + String[] parts = col.name.split("\\."); + for (String part : parts) { + path = path.get(part); + } + ands.add(like(cb, path, col.search.value)); } catch (IllegalArgumentException ex) { // columna no mapeada o relación: la ignoramos + System.out.println("[DT] columna no mapeada o relación: " + col.name); } } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java index d0c89e9..a7690e5 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java @@ -1,21 +1,22 @@ package com.imprimelibros.erp.presupuesto; +import com.imprimelibros.erp.datatables.*; +import com.imprimelibros.erp.presupuesto.dto.Presupuesto; + import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.JoinType; + +import org.springframework.context.MessageSource; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import jakarta.persistence.criteria.Predicate; import java.math.BigDecimal; +import java.text.NumberFormat; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; -import java.util.function.Function; -import org.springframework.context.MessageSource; -import org.springframework.stereotype.Service; -import com.imprimelibros.erp.datatables.*; -import java.text.NumberFormat; - -import com.imprimelibros.erp.presupuesto.dto.Presupuesto; @Service public class PresupuestoDatatableService { @@ -28,301 +29,59 @@ public class PresupuestoDatatableService { this.repo = repo; } - /* ---------- API pública ---------- */ - + @Transactional(readOnly = true) public DataTablesResponse> datatablePublicos(DataTablesRequest dt, Locale locale) { - String term = extractSearch(dt); - Pageable pageable = pageableFrom(dt); - - EnumMatches matches = buildEnumMatches(term, locale); - - Specification spec = baseSpec(term, matches, dt); - - spec = spec.and((root, query, cb) -> cb.equal(root.get("origen"), "publico")); - - Page page = repo.findAll(spec, pageable); - - var rows = page.getContent().stream() - .map(p -> mapPresupuestoPublico(p, locale)) - .toList(); - - return new DataTablesResponse<>(dt.draw, repo.count(), page.getTotalElements(), rows); + return commonDataTable(dt, locale, "publico", true); } + @Transactional(readOnly = true) public DataTablesResponse> datatablePrivados(DataTablesRequest dt, Locale locale) { - String term = extractSearch(dt); - Pageable pageable = pageableFrom(dt); - - EnumMatches matches = buildEnumMatches(term, locale); - - Specification spec = baseSpec(term, matches, dt); - spec = spec.and((root, query, cb) -> cb.equal(root.get("origen"), "privado")); - - Page page = repo.findAll(spec, pageable); - - var rows = page.getContent().stream() - .map(p -> mapPresupuestoPrivado(p, locale)) // 👈 otro mapper con más/otros campos - .toList(); - - return new DataTablesResponse<>(dt.draw, repo.count(), page.getTotalElements(), rows); + return commonDataTable(dt, locale, "privado", false); } - /* ---------- Helpers reutilizables ---------- */ + private DataTablesResponse> commonDataTable(DataTablesRequest dt, Locale locale, String origen, + boolean publico) { + Long count = repo.findAllByOrigen(Presupuesto.Origen.valueOf(origen)).stream().count(); - private String extractSearch(DataTablesRequest dt) { - return (dt.search != null && dt.search.value != null) ? dt.search.value.trim().toLowerCase() : ""; + List orderable = List.of( + "id", "titulo", "user.fullName", "tipoEncuadernacion", "tipoCubierta", "tipoImpresion", + "selectedTirada", "estado", "totalConIva", "paginas", "pais", "region", "ciudad", "updatedAt"); + + return DataTable.of(repo, Presupuesto.class, dt, + List.of("")) // búsqueda global solo por campos simples + .orderable(orderable) + .where((root, query, cb) -> cb.equal(root.get("origen"), Presupuesto.Origen.valueOf(origen))) + .onlyAddedColumns() + .add("id", Presupuesto::getId) + .add("titulo", Presupuesto::getTitulo) + .add("tipoEncuadernacion", p -> msg(p.getTipoEncuadernacion().getMessageKey(), locale)) + .add("tipoCubierta", p -> msg(p.getTipoCubierta().getMessageKey(), locale)) + .add("tipoImpresion", p -> msg(p.getTipoImpresion().getMessageKey(), locale)) + .add("selectedTirada", Presupuesto::getSelectedTirada) + .add("paginas", p -> n(p.getPaginasColor()) + n(p.getPaginasNegro())) + .filter("paginas", (root, q, cb, value) -> { + Expression sum = cb.sum( + cb.coalesce(root.get("paginasColor"), cb.literal(0)), + cb.coalesce(root.get("paginasNegro"), cb.literal(0))); + Expression asStr = cb.function("CONCAT", String.class, cb.literal(""), sum); + return cb.like(asStr, "%" + value.trim() + "%"); + }) + .orderable("paginas", (root, q, cb) -> cb.sum( + cb.coalesce(root.get("paginasColor"), cb.literal(0)), + cb.coalesce(root.get("paginasNegro"), cb.literal(0)))) + + .add("estado", p -> msg(p.getEstado().getMessageKey(), locale)) + .add("totalConIva", p -> formatCurrency(p.getTotalConIva(), locale)) + .addIf(publico, "pais", Presupuesto::getPais) + .addIf(publico, "region", Presupuesto::getRegion) + .addIf(publico, "ciudad", Presupuesto::getCiudad) + .add("updatedAt", p -> formatDate(p.getUpdatedAt(), locale)) + .addIf(!publico, "user", p -> p.getUser() != null ? p.getUser().getFullName() : "") + .add("actions", this::generarBotones) + .toJson(count); } - private Map extractColumnSearches(DataTablesRequest dt) { - Map byColumn = new HashMap<>(); - if (dt.columns == null) - return byColumn; - - for (var col : dt.columns) { - // Importante: en el front usa columns[i][name] con el nombre del campo JPA - String field = col.name; - String value = (col.search != null && col.search.value != null) - ? col.search.value.trim() - : ""; - if (field != null && !field.isBlank() && value != null && !value.isBlank()) { - byColumn.put(field, value.toLowerCase()); - } - } - return byColumn; - } - - private Pageable pageableFrom(DataTablesRequest dt) { - int page = dt.length > 0 ? dt.start / dt.length : 0; - List orders = new ArrayList<>(); - for (var o : dt.order) { - String field = dt.columns.get(o.column).name; // usa columns[i][name] en el front - if (field == null || field.isBlank()) - continue; - orders.add( - new Sort.Order("desc".equalsIgnoreCase(o.dir) ? Sort.Direction.DESC : Sort.Direction.ASC, field)); - } - Sort sort = orders.isEmpty() ? Sort.by(Sort.Order.desc("updatedAt")) : Sort.by(orders); - return dt.length > 0 ? PageRequest.of(page, dt.length, sort) : Pageable.unpaged(); - } - - private EnumMatches buildEnumMatches(String term, Locale locale) { - Function tr = key -> { - try { - return messageSource.getMessage(key, null, locale).toLowerCase(); - } catch (Exception e) { - return key.toLowerCase(); - } - }; - - var enc = Arrays.stream(Presupuesto.TipoEncuadernacion.values()) - .filter(e -> tr.apply(e.getMessageKey()).contains(term)) - .collect(() -> EnumSet.noneOf(Presupuesto.TipoEncuadernacion.class), EnumSet::add, EnumSet::addAll); - - var cub = Arrays.stream(Presupuesto.TipoCubierta.values()) - .filter(e -> tr.apply(e.getMessageKey()).contains(term)) - .collect(() -> EnumSet.noneOf(Presupuesto.TipoCubierta.class), EnumSet::add, EnumSet::addAll); - - var imp = Arrays.stream(Presupuesto.TipoImpresion.values()) - .filter(e -> tr.apply(e.getMessageKey()).contains(term)) - .collect(() -> EnumSet.noneOf(Presupuesto.TipoImpresion.class), EnumSet::add, EnumSet::addAll); - - var est = Arrays.stream(Presupuesto.Estado.values()) - .filter(e -> tr.apply(e.getMessageKey()).contains(term)) - .collect(() -> EnumSet.noneOf(Presupuesto.Estado.class), EnumSet::add, EnumSet::addAll); - - return new EnumMatches(enc, cub, imp, est); - } - - /** - * WHERE + ORDER dinámico (paginas/estado) reutilizable para ambos datatables - */ - private Specification baseSpec(String term, EnumMatches m, DataTablesRequest dt) { - return (root, query, cb) -> { - List ors = new ArrayList<>(); - List ands = new ArrayList<>(); // filtros por columna (AND) - - Expression totalPag = cb.sum( - cb.coalesce(root.get("paginasColor"), cb.literal(0)), - cb.coalesce(root.get("paginasNegro"), cb.literal(0))); - - if (!term.isBlank()) { - String like = "%" + term + "%"; - ors.add(cb.like(cb.lower(root.get("titulo")), like)); - ors.add(cb.like(cb.lower(root.join("user").get("fullName")), like)); - ors.add(cb.like(cb.lower(root.get("ciudad")), like)); - ors.add(cb.like(cb.lower(root.get("region")), like)); - ors.add(cb.like(cb.lower(root.get("pais")), like)); - } - if (!m.enc.isEmpty()) - ors.add(root.get("tipoEncuadernacion").in(m.enc)); - if (!m.cub.isEmpty()) - ors.add(root.get("tipoCubierta").in(m.cub)); - if (!m.imp.isEmpty()) - ors.add(root.get("tipoImpresion").in(m.imp)); - if (!m.est.isEmpty()) - ors.add(root.get("estado").in(m.est)); - - Map byCol = extractColumnSearches(dt); - for (var entry : byCol.entrySet()) { - String field = entry.getKey(); - String value = entry.getValue(); - if (value.isBlank() || field.isBlank() - || field.contains("tipoEncuadernacion") - || field.contains("tipoCubierta") - || field.contains("tipoImpresion") - || field.contains("estado")) { - continue; - } - - // --- CASO ESPECIAL: filtro por nombre del usuario --- - if ("user".equals(field)) { - var userJoin = root.join("user"); - var expr = cb.lower(userJoin.get("fullName")); - ands.add(cb.like(expr, "%" + value.toLowerCase() + "%")); - continue; - } - - // --- CASO ESPECIAL: filtro por total de páginas --- - if ("paginas".equals(field)) { - try { - int paginas = Integer.parseInt(value); - ands.add(cb.equal(totalPag, paginas)); - } catch (NumberFormatException nfe) { - var asString = cb.function("CONCAT", String.class, cb.literal(""), totalPag); - var safe = cb.function("COALESCE", String.class, asString, cb.literal("")); - var strExpr = cb.lower(safe); - ands.add(cb.like(strExpr, "%" + value.toLowerCase() + "%")); - } - continue; - } - - // --- RESTO DE CAMPOS: acceso genérico --- - var path = root.get(field); - Class type = path.getJavaType(); - - Expression strExpr; - if (String.class.isAssignableFrom(type)) { - strExpr = cb.lower(path.as(String.class)); - } else { - var asString = cb.function("CONCAT", String.class, cb.literal(""), path); - var safe = cb.function("COALESCE", String.class, asString, cb.literal("")); - strExpr = cb.lower(safe); - } - - ands.add(cb.like(strExpr, "%" + value.toLowerCase() + "%")); - } - - // ORDER BY especial si en columns[i][name] viene 'paginas' o 'estado' - if (query != null && !query.getOrderList().isEmpty()) { - var jpaOrders = new ArrayList(); - for (var ob : query.getOrderList()) { - String prop = ob.getExpression().toString(); - boolean asc = ob.isAscending(); - if ("paginas".equals(prop)) { - var totalPagOrder = cb.sum( - cb.coalesce(root.get("paginasColor"), cb.literal(0)), - cb.coalesce(root.get("paginasNegro"), cb.literal(0))); - jpaOrders.add(asc ? cb.asc(totalPagOrder) : cb.desc(totalPagOrder)); - } else if ("estado".equals(prop)) { - var estadoStr = cb.function("str", String.class, root.get("estado")); - jpaOrders.add(asc ? cb.asc(estadoStr) : cb.desc(estadoStr)); - } else { - try { - jpaOrders.add(asc ? cb.asc(root.get(prop)) : cb.desc(root.get(prop))); - } catch (IllegalArgumentException e) { - // El campo no existe (como 'paginas'), lo ignoramos - // Opcional: puedes loggear si quieres - // log.warn("Campo no encontrado para ORDER BY: {}", prop); - } - } - } - query.orderBy(jpaOrders); - } - - // === Compose final WHERE === - Predicate where = ors.isEmpty() - ? cb.conjunction() - : cb.or(ors.toArray(new Predicate[0])); - - if (!ands.isEmpty()) { - where = cb.and(where, cb.and(ands.toArray(new Predicate[0]))); - } - - return where; - }; - } - - /* ---------- Mappers de filas (puedes tener tantos como vistas) ---------- */ - - private Map mapPresupuestoPublico(Presupuesto p, Locale locale) { - int paginas = n(p.getPaginasColor()) + n(p.getPaginasNegro()); - Map m = new HashMap<>(); - m.put("id", p.getId()); - m.put("titulo", p.getTitulo()); - m.put("tipoEncuadernacion", msg(p.getTipoEncuadernacion().getMessageKey(), locale)); - m.put("tipoCubierta", msg(p.getTipoCubierta().getMessageKey(), locale)); - m.put("tipoImpresion", msg(p.getTipoImpresion().getMessageKey(), locale)); - m.put("tirada", p.getSelectedTirada()); - m.put("paginas", paginas); - m.put("estado", msg(p.getEstado().getMessageKey(), locale)); - m.put("totalConIva", formatCurrency(p.getTotalConIva(), locale)); - m.put("pais", p.getPais()); - m.put("region", p.getRegion()); - m.put("ciudad", p.getCiudad()); - m.put("updatedAt", formatDate(p.getUpdatedAt(), locale)); - if (p.getEstado().equals(Presupuesto.Estado.borrador)) { - m.put("actions", - "
" + - "" + - "" - + - "
"); - } else { - m.put("actions", - "
" + - "" + - "
"); - } - return m; - } - - private Map mapPresupuestoPrivado(Presupuesto p, Locale locale) { - int paginas = n(p.getPaginasColor()) + n(p.getPaginasNegro()); - Map m = new HashMap<>(); - m.put("id", p.getId()); - m.put("titulo", p.getTitulo()); - m.put("user", p.getUser().getFullName()); - m.put("tipoEncuadernacion", msg(p.getTipoEncuadernacion().getMessageKey(), locale)); - m.put("tipoCubierta", msg(p.getTipoCubierta().getMessageKey(), locale)); - m.put("tipoImpresion", msg(p.getTipoImpresion().getMessageKey(), locale)); - m.put("tirada", p.getSelectedTirada()); - m.put("paginas", paginas); - m.put("estado", msg(p.getEstado().getMessageKey(), locale)); - m.put("totalConIva", formatCurrency(p.getTotalConIva(), locale)); - m.put("updatedAt", formatDate(p.getUpdatedAt(), locale)); - if (p.getEstado().equals(Presupuesto.Estado.borrador)) { - m.put("actions", - "
" + - "" + - "" - + - "
"); - } else { - m.put("actions", - "
" + - "" + - "
"); - } - return m; - } - - /* ---------- utilidades ---------- */ + /* ---------- helpers ---------- */ private String msg(String key, Locale locale) { try { @@ -349,15 +108,21 @@ public class PresupuestoDatatableService { private String formatCurrency(BigDecimal value, Locale locale) { if (value == null) return ""; - NumberFormat nf = NumberFormat.getCurrencyInstance(locale); - return nf.format(value); + return NumberFormat.getCurrencyInstance(locale).format(value); } - /* record para agrupar matches */ - private record EnumMatches( - EnumSet enc, - EnumSet cub, - EnumSet imp, - EnumSet est) { + private String generarBotones(Presupuesto p) { + boolean borrador = p.getEstado() == Presupuesto.Estado.borrador; + String id = String.valueOf(p.getId()); + String editBtn = ""; + + String deleteBtn = borrador ? "" : ""; + + return "
" + editBtn + deleteBtn + "
"; } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoRepository.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoRepository.java index ade0dc5..3eb5732 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoRepository.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoRepository.java @@ -25,4 +25,6 @@ public interface PresupuestoRepository extends JpaRepository, Optional findAnyById(@Param("id") Long id); Optional findTopBySessionIdAndEstadoOrderByCreatedAtDesc(String sessionId, Presupuesto.Estado estado); + + List findAllByOrigen(Presupuesto.Origen origen); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 18a2d3e..4f79442 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,8 +3,8 @@ spring.application.name=erp # # Logging # -logging.level.org.springframework.security=DEBUG -logging.level.root=WARN +logging.level.org.springframework.security=ERROR +logging.level.root=ERROR logging.level.org.springframework=ERROR @@ -18,7 +18,7 @@ spring.datasource.password=om91irrDctd spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true +spring.jpa.show-sql=false # @@ -33,9 +33,9 @@ safekat.api.password=Safekat2024 # # Debug JPA / Hibernate # -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.orm.jdbc.bind=TRACE -spring.jpa.properties.hibernate.format_sql=true +#logging.level.org.hibernate.SQL=DEBUG +#logging.level.org.hibernate.orm.jdbc.bind=TRACE +#spring.jpa.properties.hibernate.format_sql=true # # Resource chain diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js b/src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js index d64e264..1d0ffa6 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js @@ -26,7 +26,7 @@ pageLength: 50, language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, responsive: true, - dom: 'Bfrtip', + dom: 'BlrBtip', buttons: { dom: { button: { diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js index 810a102..4e2ec96 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/wizard.js @@ -341,6 +341,8 @@ export default class PresupuestoWizard { ...this.#getInteriorData(), ...this.#getCubiertaData(), selectedTirada: this.formData.selectedTirada + + }; const sobrecubierta = data.sobrecubierta; diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html index 192736f..59b3876 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-form.html @@ -61,6 +61,9 @@
+
+ +
@@ -69,6 +72,8 @@
+ + diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos.html index 4e70789..25b474f 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos.html @@ -17,7 +17,58 @@ Actualizado el Acciones - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html index 67ef30d..3209586 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente.html @@ -18,7 +18,7 @@ - +