diff --git a/src/main/java/com/imprimelibros/erp/cart/CartService.java b/src/main/java/com/imprimelibros/erp/cart/CartService.java index cc4400e..d04b349 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartService.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartService.java @@ -5,18 +5,12 @@ import jakarta.transaction.Transactional; import org.springframework.context.MessageSource; import org.springframework.stereotype.Service; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.common.Utils; diff --git a/src/main/java/com/imprimelibros/erp/common/Utils.java b/src/main/java/com/imprimelibros/erp/common/Utils.java index 1ce76c8..b6b2a79 100644 --- a/src/main/java/com/imprimelibros/erp/common/Utils.java +++ b/src/main/java/com/imprimelibros/erp/common/Utils.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; import org.springframework.context.MessageSource; import org.springframework.stereotype.Component; @@ -15,11 +17,17 @@ import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.imprimelibros.erp.datatables.DataTablesRequest; import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices; import com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import java.util.function.Function; + @Component public class Utils { @@ -42,6 +50,55 @@ public class Utils { return currencyFormatter.format(amount); } + public static String formatNumber(BigDecimal amount, Locale locale) { + NumberFormat numberFormatter = NumberFormat.getNumberInstance(locale); + return numberFormatter.format(amount); + } + + public static String formatNumber(Double amount, Locale locale) { + NumberFormat numberFormatter = NumberFormat.getNumberInstance(locale); + return numberFormatter.format(amount); + } + + public static Optional, CriteriaBuilder, Predicate>> parseNumericFilter( + DataTablesRequest dt, String colName, Locale locale) { + String raw = dt.getColumnSearch(colName); // usa el "name" del DataTable (snake_case) + if (raw == null || raw.isBlank()) + return Optional.empty(); + + String s = raw.trim(); + // normaliza número con coma o punto + Function toBig = x -> { + String t = x.replace(".", "").replace(",", "."); // 1.234,56 -> 1234.56 + return new BigDecimal(t); + }; + + try { + if (s.matches("(?i)^>=?\\s*[-\\d.,]+$")) { + BigDecimal v = toBig.apply(s.replace(">=", "").replace(">", "").trim()); + return Optional.of((path, cb) -> cb.greaterThanOrEqualTo(path, v)); + } + if (s.matches("(?i)^<=?\\s*[-\\d.,]+$")) { + BigDecimal v = toBig.apply(s.replace("<=", "").replace("<", "").trim()); + return Optional.of((path, cb) -> cb.lessThanOrEqualTo(path, v)); + } + if (s.contains("-")) { // rango "a-b" + String[] p = s.split("-"); + if (p.length == 2) { + BigDecimal a = toBig.apply(p[0].trim()); + BigDecimal b = toBig.apply(p[1].trim()); + BigDecimal min = a.min(b), max = a.max(b); + return Optional.of((path, cb) -> cb.between(path, min, max)); + } + } + // exacto/like numérico + BigDecimal v = toBig.apply(s); + return Optional.of((path, cb) -> cb.equal(path, v)); + } catch (Exception ignore) { + return Optional.empty(); + } + } + public Map getTextoPresupuesto(Presupuesto presupuesto, Locale locale) { Map resumen = new HashMap<>(); diff --git a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java index 257bcab..430e7a2 100644 --- a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java @@ -1,182 +1,126 @@ package com.imprimelibros.erp.configuracion.margenes_presupuestos; import jakarta.persistence.*; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; import java.time.LocalDateTime; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLRestriction; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoEncuadernacion; import com.imprimelibros.erp.shared.validation.NoRangeOverlap; +@Entity +@Table(name = "margenes_presupuesto") +@SQLDelete(sql = "UPDATE margenes_presupuesto SET deleted = true WHERE id=?") +@SQLRestriction("deleted = false") @NoRangeOverlap( min = "tiradaMin", max = "tiradaMax", id = "id", - partitionBy = {"tipoEncuadernacion","tipoCubierta"}, + partitionBy = {}, deletedFlag = "deleted", // <- si usas soft delete deletedActiveValue = false, // activo cuando deleted == false message = "{validation.range.overlaps}", invalidRangeMessage = "{validation.range.invalid}" ) -@Entity -@Table(name = "margenes_presupuesto") -@SQLDelete(sql = "UPDATE margenes_presupuesto SET deleted = TRUE, deleted_at = NOW() WHERE id = ?") -@SQLRestriction("deleted = false") + public class MargenPresupuesto { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name="tipo_encuadernacion", nullable = false, length = 50) + @Column(name="importe_min", nullable=false, precision=12, scale=2) @NotNull(message="{validation.required}") - @Enumerated(EnumType.STRING) - private TipoEncuadernacion tipoEncuadernacion; + private BigDecimal importeMin; - @Column(name="tipo_cubierta", nullable = false, length = 50) + @Column(name="importe_max", nullable=false, precision=12, scale=2) @NotNull(message="{validation.required}") - @Enumerated(EnumType.STRING) - private TipoCubierta tipoCubierta; + private BigDecimal importeMax; - @Column(name="tirada_min", nullable = false) + @Column(name="margen_min", nullable=false, precision=6, scale=2) @NotNull(message="{validation.required}") - @Min(value=1, message="{validation.min}") - private Integer tiradaMin; + private BigDecimal margenMin; - @Column(name="tirada_max", nullable = false) + @Column(name="margen_max", nullable=false, precision=6, scale=2) @NotNull(message="{validation.required}") - @Min(value=1, message="{validation.min}") - private Integer tiradaMax; + private BigDecimal margenMax; - @Column(name="margen_max", nullable = false) - @NotNull(message="{validation.required}") - @Min(value = 0, message="{validation.min}") - @Max(value = 200, message="{validation.max}") - private Integer margenMax; + @Column(nullable=false) + private boolean deleted = false; - @Column(name = "margen_min", nullable = false) - @NotNull(message="{validation.required}") - @Min(value = 0, message="{validation.min}") - @Max(value = 200, message="{validation.max}") - private Integer margenMin; - - @Column(name="created_at", nullable = false, updatable = false) + @Column(name="created_at", nullable=false) private LocalDateTime createdAt; - @Column(name="updated_at") + @Column(name="updated_at", nullable=false) private LocalDateTime updatedAt; - @Column(nullable = false) - private boolean deleted = false; - @Column(name="deleted_at") private LocalDateTime deletedAt; + @PrePersist + void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = createdAt; + } + @PreUpdate + void onUpdate() { + updatedAt = LocalDateTime.now(); + } public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - - public TipoEncuadernacion getTipoEncuadernacion() { - return tipoEncuadernacion; + public BigDecimal getImporteMin() { + return importeMin; } - - public void setTipoEncuadernacion(TipoEncuadernacion tipoEncuadernacion) { - this.tipoEncuadernacion = tipoEncuadernacion; + public void setImporteMin(BigDecimal importeMin) { + this.importeMin = importeMin; } - - public TipoCubierta getTipoCubierta() { - return tipoCubierta; + public BigDecimal getImporteMax() { + return importeMax; } - - public void setTipoCubierta(TipoCubierta tipoCubierta) { - this.tipoCubierta = tipoCubierta; + public void setImporteMax(BigDecimal importeMax) { + this.importeMax = importeMax; } - - public Integer getTiradaMin() { - return tiradaMin; - } - - public void setTiradaMin(Integer tiradaMin) { - this.tiradaMin = tiradaMin; - } - - public Integer getTiradaMax() { - return tiradaMax; - } - - public void setTiradaMax(Integer tiradaMax) { - this.tiradaMax = tiradaMax; - } - - public Integer getMargenMax() { - return margenMax; - } - - public void setMargenMax(Integer margenMax) { - this.margenMax = margenMax; - } - - public Integer getMargenMin() { + public BigDecimal getMargenMin() { return margenMin; } - - public void setMargenMin(Integer margenMin) { + public void setMargenMin(BigDecimal margenMin) { this.margenMin = margenMin; } - - public LocalDateTime getCreatedAt() { - return createdAt; + public BigDecimal getMargenMax() { + return margenMax; } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; + public void setMargenMax(BigDecimal margenMax) { + this.margenMax = margenMax; } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - public boolean isDeleted() { return deleted; } - public void setDeleted(boolean deleted) { this.deleted = deleted; } - + public LocalDateTime getCreatedAt() { + return createdAt; + } + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } public LocalDateTime getDeletedAt() { return deletedAt; } - public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; } - - @PrePersist - void onCreate() { - this.createdAt = LocalDateTime.now(); - this.updatedAt = this.createdAt; - } - - @PreUpdate - void onUpdate() { - this.updatedAt = LocalDateTime.now(); - } - } - diff --git a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java index 7982295..11a83be 100644 --- a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java @@ -1,10 +1,9 @@ package com.imprimelibros.erp.configuracion.margenes_presupuestos; import java.time.LocalDateTime; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import org.springframework.context.MessageSource; import org.springframework.data.jpa.domain.Specification; @@ -23,13 +22,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.datatables.DataTable; import com.imprimelibros.erp.datatables.DataTablesParser; import com.imprimelibros.erp.datatables.DataTablesRequest; import com.imprimelibros.erp.datatables.DataTablesResponse; import com.imprimelibros.erp.i18n.TranslationService; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoEncuadernacion; +import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -83,25 +82,46 @@ public class MargenPresupuestoController { List searchable = List.of( "id", - "tiradaMin", "tiradaMax", + "importeMin", "importeMax", "margenMin", "margenMax"); List orderable = List.of( "id", - "tipoEncuadernacion", - "tipoCubierta", - "tiradaMin", - "tiradaMax", + "importeMin", + "importeMax", "margenMin", "margenMax"); Specification base = (root, query, cb) -> cb.conjunction(); + + Specification filtros = (root, query, cb) -> { + List ps = new ArrayList<>(); + + Utils.parseNumericFilter(dt, "importe_min", locale) + .ifPresent(f -> ps.add(f.apply(root.get("importeMin"), cb))); + + Utils.parseNumericFilter(dt, "importe_max", locale) + .ifPresent(f -> ps.add(f.apply(root.get("importeMax"), cb))); + + Utils.parseNumericFilter(dt, "margen_min", locale) + .ifPresent(f -> ps.add(f.apply(root.get("margenMin"), cb))); + + Utils.parseNumericFilter(dt, "margen_max", locale) + .ifPresent(f -> ps.add(f.apply(root.get("margenMax"), cb))); + + return ps.isEmpty() ? cb.conjunction() : cb.and(ps.toArray(new Predicate[0])); + }; + long total = repo.count(); return DataTable .of(repo, MargenPresupuesto.class, dt, searchable) // 'searchable' en DataTable.java // edita columnas "reales": .orderable(orderable) + .edit("importeMin", (margen) -> Utils.formatCurrency(margen.getImporteMin(), locale)) + .edit("importeMax", (margen) -> Utils.formatCurrency(margen.getImporteMax(), locale)) + .edit("margenMin", (margen) -> Utils.formatNumber(margen.getMargenMin(), locale)) + .edit("margenMax", (margen) -> Utils.formatNumber(margen.getMargenMax(), locale)) .add("actions", (margen) -> { return "
\n" + " \n" + "
"; }) - .edit("tipoEncuadernacion", (margen) -> { - return messageSource.getMessage("presupuesto." + margen.getTipoEncuadernacion().name(), null, - locale); - }) - .edit("tipoCubierta", (margen) -> { - return messageSource.getMessage("presupuesto." + margen.getTipoCubierta().name(), null, locale); - }) .where(base) // Filtros custom: - .filter((builder, req) -> { - String fEncuadernacion = Optional.ofNullable(req.raw.get("f_encuadernacion")).orElse("").trim(); - if (!fEncuadernacion.isEmpty()) { - boolean added = false; - // 1) Si llega el nombre del enum (p.ej. "fresado", "cosido", ...) - try { - var encEnum = TipoEncuadernacion.valueOf(fEncuadernacion); - builder.add((root, q, cb) -> cb.equal(root.get("tipoEncuadernacion"), encEnum)); - added = true; - } catch (IllegalArgumentException ignored) { - } - // 2) Si llega la clave i18n (p.ej. "presupuesto.fresado", ...) - if (!added) { - Arrays.stream(TipoEncuadernacion.values()) - .filter(e -> e.getMessageKey().equals(fEncuadernacion)) - .findFirst() - .ifPresent(encEnum -> builder - .add((root, q, cb) -> cb.equal(root.get("tipoEncuadernacion"), encEnum))); - } - } - - // --- Cubierta --- - String fCubierta = Optional.ofNullable(req.raw.get("f_cubierta")).orElse("").trim(); - if (!fCubierta.isEmpty()) { - boolean added = false; - // 1) Si llega el nombre del enum (p.ej. "tapaBlanda", "tapaDura", - // "tapaDuraLomoRedondo") - try { - var cubEnum = TipoCubierta.valueOf(fCubierta); - builder.add((root, q, cb) -> cb.equal(root.get("tipoCubierta"), cubEnum)); - added = true; - } catch (IllegalArgumentException ignored) { - } - // 2) Si llega la clave i18n (p.ej. "presupuesto.tapa-blanda", ...) - if (!added) { - Arrays.stream(TipoCubierta.values()) - .filter(e -> e.getMessageKey().equals(fCubierta)) - .findFirst() - .ifPresent(cubEnum -> builder - .add((root, q, cb) -> cb.equal(root.get("tipoCubierta"), cubEnum))); - } - } - }) .toJson(total); } @@ -202,16 +172,14 @@ public class MargenPresupuestoController { Locale locale) { if (binding.hasErrors()) { - response.setStatus(422); + response.setStatus(422); model.addAttribute("action", "/configuracion/margenes-presupuesto"); return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; } MargenPresupuesto data = new MargenPresupuesto(); - data.setTipoEncuadernacion(margenPresupuesto.getTipoEncuadernacion()); - data.setTipoCubierta(margenPresupuesto.getTipoCubierta()); - data.setTiradaMin(margenPresupuesto.getTiradaMin()); - data.setTiradaMax(margenPresupuesto.getTiradaMax()); + data.setImporteMin(margenPresupuesto.getImporteMin()); + data.setImporteMax(margenPresupuesto.getImporteMax()); data.setMargenMax(margenPresupuesto.getMargenMax()); data.setMargenMin(margenPresupuesto.getMargenMin()); @@ -243,7 +211,6 @@ public class MargenPresupuestoController { return null; } - @PutMapping("/{id}") public String edit( @PathVariable Long id, @@ -268,10 +235,8 @@ public class MargenPresupuestoController { var entity = uOpt.get(); // 3) Copiar solamente campos editables - entity.setTipoEncuadernacion(form.getTipoEncuadernacion()); - entity.setTipoCubierta(form.getTipoCubierta()); - entity.setTiradaMin(form.getTiradaMin()); - entity.setTiradaMax(form.getTiradaMax()); + entity.setImporteMin(form.getImporteMin()); + entity.setImporteMax(form.getImporteMax()); entity.setMargenMax(form.getMargenMax()); entity.setMargenMin(form.getMargenMin()); @@ -317,21 +282,23 @@ public class MargenPresupuestoController { @DeleteMapping("/{id}") @Transactional public ResponseEntity delete(@PathVariable Long id, Authentication auth, Locale locale) { - + return repo.findById(id).map(u -> { try { u.setDeleted(true); u.setDeletedAt(LocalDateTime.now()); - + repo.save(u); // ← NO delete(); guardamos el soft delete con deleted_by relleno return ResponseEntity.ok(Map.of("message", messageSource.getMessage("margenes-presupuesto.exito.eliminado", null, locale))); } catch (Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("message", - messageSource.getMessage("margenes-presupuesto.error.delete-internal-error", null, locale))); + messageSource.getMessage("margenes-presupuesto.error.delete-internal-error", null, + locale))); } }).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(Map.of("message", messageSource.getMessage("margenes-presupuesto.error.not-found", null, locale)))); + .body(Map.of("message", + messageSource.getMessage("margenes-presupuesto.error.not-found", null, locale)))); } } diff --git a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java index 309dffc..d0c00f7 100644 --- a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java @@ -1,40 +1,36 @@ package com.imprimelibros.erp.configuracion.margenes_presupuestos; +import java.math.BigDecimal; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoEncuadernacion; - public interface MargenPresupuestoDao extends JpaRepository, JpaSpecificationExecutor { @Query(""" - SELECT COUNT(m) FROM MargenPresupuesto m - WHERE m.deleted = false - AND m.tipoEncuadernacion = :enc - AND m.tipoCubierta = :cub - AND (:id IS NULL OR m.id <> :id) - AND NOT (m.tiradaMax < :min OR m.tiradaMin > :max) - """) + SELECT COUNT(m) FROM MargenPresupuesto m + WHERE m.deleted = false + AND ( ( :min BETWEEN m.importeMin AND m.importeMax ) + OR ( :max BETWEEN m.importeMin AND m.importeMax ) + OR ( m.importeMin BETWEEN :min AND :max ) + OR ( m.importeMax BETWEEN :min AND :max ) ) + AND ( :excludeId IS NULL OR m.id <> :excludeId ) + """) long countOverlaps( - @Param("enc") TipoEncuadernacion enc, - @Param("cub") TipoCubierta cub, - @Param("min") Integer min, - @Param("max") Integer max, - @Param("id") Long id); + @Param("min") BigDecimal min, + @Param("max") BigDecimal max, + @Param("excludeId") Long excludeId); @Query(""" - SELECT m FROM MargenPresupuesto m - WHERE m.deleted = false - AND m.tipoEncuadernacion = :enc - AND m.tipoCubierta = :cub - AND :tirada BETWEEN m.tiradaMin AND m.tiradaMax - """) - MargenPresupuesto findByTipoAndTirada( - @Param("enc") TipoEncuadernacion enc, - @Param("cub") TipoCubierta cub, - @Param("tirada") Integer tirada); + SELECT m FROM MargenPresupuesto m + WHERE m.deleted = false + AND :importe BETWEEN m.importeMin AND m.importeMax + """) + Optional findByImporte(@Param("importe") BigDecimal importe); } + + diff --git a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java index a88e5f6..97fbadc 100644 --- a/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java @@ -1,14 +1,12 @@ package com.imprimelibros.erp.configuracion.margenes_presupuestos; +import java.math.BigDecimal; import java.util.List; import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; -import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoEncuadernacion; - @Service @Transactional public class MargenPresupuestoService { @@ -27,17 +25,19 @@ public class MargenPresupuestoService { return dao.findById(id); } - public MargenPresupuesto save(MargenPresupuesto entity) { - return dao.save(entity); + public Optional findByImporte(BigDecimal importe){ + return dao.findByImporte(importe); + } + + public MargenPresupuesto save(MargenPresupuesto e) { + return dao.save(e); } public void delete(Long id) { dao.deleteById(id); } - - public boolean hasOverlap(TipoEncuadernacion enc, TipoCubierta cub, Integer min, Integer max, Long excludeId) { - long count = dao.countOverlaps(enc, cub, min, max, excludeId); - return count > 0; + public boolean hasOverlap(BigDecimal min, BigDecimal max, Long excludeId) { + return dao.countOverlaps(min, max, excludeId) > 0; } } diff --git a/src/main/java/com/imprimelibros/erp/datatables/DataTablesRequest.java b/src/main/java/com/imprimelibros/erp/datatables/DataTablesRequest.java index 3ce3800..af8d045 100644 --- a/src/main/java/com/imprimelibros/erp/datatables/DataTablesRequest.java +++ b/src/main/java/com/imprimelibros/erp/datatables/DataTablesRequest.java @@ -9,15 +9,34 @@ public class DataTablesRequest { public Search search = new Search(); public List order = new ArrayList<>(); public List columns = new ArrayList<>(); - public Map raw = new HashMap<>(); // <- params extra + public Map raw = new HashMap<>(); // <- params extra + + public static class Search { + public String value = ""; + public boolean regex; + } + + public static class Order { + public int column; + public String dir; + } - public static class Search { public String value=""; public boolean regex; } - public static class Order { public int column; public String dir; } public static class Column { public String data; public String name; - public boolean searchable=true; - public boolean orderable=true; - public Search search=new Search(); + public boolean searchable = true; + public boolean orderable = true; + public Search search = new Search(); + } + + public String getColumnSearch(String columnName) { + if (columnName == null || columns == null) + return null; + for (Column col : columns) { + if (col != null && col.name != null && col.name.equalsIgnoreCase(columnName)) { + return col.search != null ? col.search.value : null; + } + } + return null; } } \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java b/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java index f96363b..4e8fcb3 100644 --- a/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java +++ b/src/main/java/com/imprimelibros/erp/externalApi/skApiClient.java @@ -18,6 +18,8 @@ import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoEncuadernacion; import java.util.Map; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.function.Supplier; @@ -34,7 +36,8 @@ public class skApiClient { private final MargenPresupuestoDao margenPresupuestoDao; private final MessageSource messageSource; - public skApiClient(AuthService authService, MargenPresupuestoDao margenPresupuestoDao, MessageSource messageSource) { + public skApiClient(AuthService authService, MargenPresupuestoDao margenPresupuestoDao, + MessageSource messageSource) { this.authService = authService; this.restTemplate = new RestTemplate(); this.margenPresupuestoDao = margenPresupuestoDao; @@ -80,23 +83,28 @@ public class skApiClient { data.get("precios"), new TypeReference>() { }); - for (int i = 0; i < tiradas.size(); i++) { - int tirada = tiradas.get(i); + for (int i = 0; i < precios.size(); i++) { + BigDecimal importe = new BigDecimal(precios.get(i)); - MargenPresupuesto margen = margenPresupuestoDao.findByTipoAndTirada( - tipoEncuadernacion, tipoCubierta, tirada); + BigDecimal importeTotal = importe.multiply(BigDecimal.valueOf(tiradas.get(i))); + + MargenPresupuesto margen = margenPresupuestoDao + .findByImporte(importeTotal).orElse(null); if (margen != null) { - double margenValue = calcularMargen( - tirada, - margen.getTiradaMin(), - margen.getTiradaMax(), + BigDecimal margenValue = calcularMargen( + importeTotal, + margen.getImporteMin(), + margen.getImporteMax(), margen.getMargenMax(), margen.getMargenMin()); - double nuevoPrecio = precios.get(i) * (1 + margenValue / 100.0); - precios.set(i, Math.round(nuevoPrecio * 10000.0) / 10000.0); // redondear a 2 decimales + BigDecimal nuevoPrecio = new BigDecimal(precios.get(i)).multiply(BigDecimal.ONE + .add(margenValue.divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP))); + precios.set(i, nuevoPrecio.setScale(4, RoundingMode.HALF_UP).doubleValue()); // redondear + // a 4 + // decimales } else { - System.out.println("No se encontró margen para tirada " + tirada); + System.out.println("No se encontró margen para importe " + importe); } } @@ -154,7 +162,8 @@ public class skApiClient { JsonNode root = mapper.readTree(jsonResponse); if (root.get("data") == null || !root.get("data").isInt()) { - throw new RuntimeException(messageSource.getMessage("presupuesto.errores.error-interior", new Object[]{1} , locale)); + throw new RuntimeException( + messageSource.getMessage("presupuesto.errores.error-interior", new Object[] { 1 }, locale)); } return root.get("data").asInt(); @@ -227,13 +236,15 @@ public class skApiClient { } } - private static double calcularMargen( - int tirada, int tiradaMin, int tiradaMax, - double margenMax, double margenMin) { - if (tirada <= tiradaMin) + private static BigDecimal calcularMargen( + BigDecimal importe, BigDecimal importeMin, BigDecimal importeMax, + BigDecimal margenMax, BigDecimal margenMin) { + if (importe.compareTo(importeMin) <= 0) return margenMax; - if (tirada >= tiradaMax) + if (importe.compareTo(importeMax) >= 0) return margenMin; - return margenMax - ((double) (tirada - tiradaMin) / (tiradaMax - tiradaMin)) * (margenMax - margenMin); + return margenMax.subtract(margenMax.subtract(margenMin) + .multiply(importe.subtract(importeMin) + .divide(importeMax.subtract(importeMin), RoundingMode.HALF_UP))); } } \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/validation/TamanioValidator.java b/src/main/java/com/imprimelibros/erp/presupuesto/validation/TamanioValidator.java index 3ca3dcc..85421bb 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/validation/TamanioValidator.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/validation/TamanioValidator.java @@ -26,7 +26,7 @@ public class TamanioValidator implements ConstraintValidator= max) { + if (presupuesto.getAncho() < min || presupuesto.getAncho() > max) { String mensajeInterpolado = messageSource.getMessage( "presupuesto.errores.ancho.min_max", // clave del mensaje diff --git a/src/main/resources/i18n/margenesPresupuesto_es.properties b/src/main/resources/i18n/margenesPresupuesto_es.properties index 9a4799b..5d083fc 100644 --- a/src/main/resources/i18n/margenesPresupuesto_es.properties +++ b/src/main/resources/i18n/margenesPresupuesto_es.properties @@ -6,18 +6,14 @@ margenes-presupuesto.editar=Editar margen margenes-presupuesto.eliminar=Eliminar margenes-presupuesto.tabla.id=ID -margenes-presupuesto.tabla.tipo_encuadernacion=Tipo encuadernación -margenes-presupuesto.tabla.tipo_cubierta=Tipo cubierta -margenes-presupuesto.tabla.tirada_minima=Tirada Mín. -margenes-presupuesto.tabla.tirada_maxima=Tirada Máx. +margenes-presupuesto.tabla.importe_minimo=Importe Mín. +margenes-presupuesto.tabla.importe_maximo=Importe Máx. margenes-presupuesto.tabla.margen_minimo=Margen Mín. margenes-presupuesto.tabla.margen_maximo=Margen Máx. margenes-presupuesto.tabla.acciones=Acciones -margenes-presupuesto.form.tipo_encuadernacion=Tipo de encuadernación -margenes-presupuesto.form.tipo_cubierta=Tipo de cubierta -margenes-presupuesto.form.tirada_minima=Tirada mínima -margenes-presupuesto.form.tirada_maxima=Tirada máxima +margenes-presupuesto.form.importe_minimo=Importe mínimo +margenes-presupuesto.form.importe_maximo=Importe máximo margenes-presupuesto.form.margen_minimo=Margen mínimo (%) margenes-presupuesto.form.margen_maximo=Margen máximo (%) 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 52bfaa2..19e0b25 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 @@ -1,3 +1,5 @@ +import {normalizeNumericFilter} from '../../utils.js'; + (() => { // si jQuery está cargado, añade CSRF a AJAX const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); @@ -22,7 +24,6 @@ processing: true, serverSide: true, orderCellsTop: true, - stateSave: true, pageLength: 50, language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, responsive: true, @@ -45,18 +46,12 @@ ajax: { url: '/configuracion/margenes-presupuesto/datatable', method: 'GET', - data: function (d) { - d.f_encuadernacion = $('#search-encuadernacion').val() || ''; // 'USER' | 'ADMIN' | 'SUPERADMIN' | '' - d.f_cubierta = $('#search-cubierta').val() || ''; // 'true' | 'false' | '' - } }, order: [[0, 'asc']], columns: [ { data: 'id', name: 'id', orderable: true }, - { data: 'tipoEncuadernacion', name: 'tipoEncuadernacion', orderable: true }, - { data: 'tipoCubierta', name: 'tipoCubierta', orderable: true }, - { data: 'tiradaMin', name: 'tiradaMin', orderable: true }, - { data: 'tiradaMax', name: 'tiradaMax', orderable: true }, + { data: 'importeMin', name: 'importeMin', orderable: true }, + { data: 'importeMax', name: 'importeMax', orderable: true }, { data: 'margenMax', name: 'margenMax', orderable: true }, { data: 'margenMin', name: 'margenMin', orderable: true }, { data: 'actions', name: 'actions' } @@ -69,7 +64,7 @@ const colIndex = table.settings()[0].aoColumns.findIndex(c => c.name === colName); if (colIndex >= 0) { - table.column(colIndex).search(this.value).draw(); + table.column(colIndex).search(normalizeNumericFilter(this.value)).draw(); } }); diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/utils.js b/src/main/resources/static/assets/js/pages/imprimelibros/utils.js index 01ace58..b04437c 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/utils.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/utils.js @@ -64,4 +64,17 @@ export function bracketPrefix(obj, prefix) { out[`${prefix}[${k}]`] = v; }); return out; +} + + +export function normalizeNumericFilter(input) { + if (!input) return input; + // Convierte todos los números del string: + // - Quita separadores de miles con punto + // - Cambia coma decimal por punto + // Mantiene operadores (>=, <=, <, >) y rangos con '-' + return input.replace( + /\d{1,3}(?:\.\d{3})*(?:,\d+)?|\d+(?:,\d+)?/g, + (num) => num.replace(/\./g, '').replace(',', '.') + ); } \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form.html b/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form.html index fb05e93..46cec67 100644 --- a/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form.html +++ b/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form.html @@ -9,41 +9,17 @@
- - -
Error
+ + +
Error
- - -
Error
-
- -
- - -
Error
-
- -
- - -
Error
+ + +
Error
diff --git a/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html b/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html index ba7dbcb..91f9e29 100644 --- a/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html +++ b/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html @@ -45,11 +45,8 @@ ID - Tipo - encuadernación - Tipo cubierta - Tirada Mín. - Tirada Máx. + Importe Mín. + Importe Máx. Margen Máx. Margen Mín. Acciones @@ -58,33 +55,12 @@ - - - - + - - - + data-col="importeMax" /> - +