From 460d2cfc01d30fbc4354ba76ef043ecb395003ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Jim=C3=A9nez?= Date: Thu, 2 Oct 2025 00:07:42 +0200 Subject: [PATCH] =?UTF-8?q?a=C3=B1adidos=20margenes=20presupuesto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../erp/config/SecurityConfig.java | 4 - .../MargenPresupuesto.java | 182 ++++++++++++++++++ .../MargenPresupuestoController.java | 123 ++++++++++++ .../MargenPresupuestoDao.java | 28 +++ .../MargenPresupuestoService.java | 43 +++++ .../erp/datatables/DataTable.java | 6 +- .../erp/shared/validation/NoRangeOverlap.java | 35 ++++ .../validation/NoRangeOverlapValidator.java | 127 ++++++++++++ .../erp/users/UserController.java | 1 - src/main/resources/application.properties | 5 + .../i18n/margenesPresupuesto_en.properties | 1 + .../i18n/margenesPresupuesto_es.properties | 14 ++ .../resources/i18n/presupuesto_es.properties | 3 + .../resources/i18n/validation_es.properties | 3 +- .../margenes-presupuesto/list.js | 48 +++++ .../margenes-presupuesto-list.html | 108 +++++++++++ .../imprimelibros/partials/sidebar.html | 12 +- 17 files changed, 733 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java create mode 100644 src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java create mode 100644 src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java create mode 100644 src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java create mode 100644 src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlap.java create mode 100644 src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlapValidator.java create mode 100644 src/main/resources/i18n/margenesPresupuesto_en.properties create mode 100644 src/main/resources/i18n/margenesPresupuesto_es.properties create mode 100644 src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js create mode 100644 src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html diff --git a/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java b/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java index 9a0b3c0..b8e1cab 100644 --- a/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java +++ b/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java @@ -4,7 +4,6 @@ import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.servlet.PathRequest; -import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; @@ -24,9 +23,6 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; -import com.imprimelibros.erp.users.User; -import com.imprimelibros.erp.users.UserDao; -import com.imprimelibros.erp.users.UserDetailsImpl; import com.imprimelibros.erp.users.UserServiceImpl; import jakarta.servlet.http.HttpServletRequest; 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 new file mode 100644 index 0000000..c64dc3b --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuesto.java @@ -0,0 +1,182 @@ +package com.imprimelibros.erp.configuracion.margenes_presupuestos; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta; +import com.imprimelibros.erp.shared.validation.NoRangeOverlap; + + +@NoRangeOverlap( + min = "tiradaMin", + max = "tiradaMax", + id = "id", + partitionBy = {"tipoEncuadernacion","tipoCubierta"}, + 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) + private Long id; + + @Column(name="tipo_encuadernacion", nullable = false, length = 50) + @NotNull(message="{validation.required}") + @Enumerated(EnumType.STRING) + private TipoEncuadernacion tipoEncuadernacion; + + @Column(name="tipo_cubierta", nullable = false, length = 50) + @NotNull(message="{validation.required}") + @Enumerated(EnumType.STRING) + private TipoCubierta tipoCubierta; + + @Column(name="tirada_min", nullable = false) + @NotBlank(message="{validation.required}") + @Min(value=1, message="{validation.min}") + private Integer tiradaMin; + + @Column(name="tirada_max", nullable = false) + @NotBlank(message="{validation.required}") + @Min(value=1, message="{validation.min}") + private Integer tiradaMax; + + @Column(name="margen_max", nullable = false) + @NotBlank(message="{validation.required}") + @Min(value = 0, message="{validation.min}") + @Max(value = 200, message="{validation.max}") + private Integer margenMax; + + @Column(name = "margen_min", nullable = false) + @NotBlank(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) + private LocalDateTime createdAt; + + @Column(name="updated_at") + private LocalDateTime updatedAt; + + @Column(nullable = false) + private boolean deleted = false; + + @Column(name="deleted_at") + private LocalDateTime deletedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public TipoEncuadernacion getTipoEncuadernacion() { + return tipoEncuadernacion; + } + + public void setTipoEncuadernacion(TipoEncuadernacion tipoEncuadernacion) { + this.tipoEncuadernacion = tipoEncuadernacion; + } + + public TipoCubierta getTipoCubierta() { + return tipoCubierta; + } + + public void setTipoCubierta(TipoCubierta tipoCubierta) { + this.tipoCubierta = tipoCubierta; + } + + 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() { + return margenMin; + } + + public void setMargenMin(Integer margenMin) { + this.margenMin = margenMin; + } + + 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 boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + 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 new file mode 100644 index 0000000..f0d47a4 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoController.java @@ -0,0 +1,123 @@ +package com.imprimelibros.erp.configuracion.margenes_presupuestos; + + +import java.util.List; +import java.util.Map; + +import org.springframework.context.MessageSource; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +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 jakarta.servlet.http.HttpServletRequest; + +import java.util.Locale; + +import org.springframework.ui.Model; + +@Controller +@RequestMapping("/configuracion/margenes-presupuestos") +@PreAuthorize("hasRole('SUPERADMIN')") +public class MargenPresupuestoController { + + private final MargenPresupuestoDao repo; + private final TranslationService translationService; + private final MessageSource messageSource; + + public MargenPresupuestoController(MargenPresupuestoDao repo, TranslationService translationService + , MessageSource messageSource) { + this.repo = repo; + this.translationService = translationService; + this.messageSource = messageSource; + } + + @GetMapping() + public String listView(Model model, Authentication authentication, Locale locale) { + + List keys = List.of(); + + Map translations = translationService.getTranslations(locale, keys); + model.addAttribute("languageBundle", translations); + + return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list"; + } + + @GetMapping(value = "/datatable", produces = "application/json") + @ResponseBody + public DataTablesResponse> datatable(HttpServletRequest request, Authentication authentication, + Locale locale) { + + DataTablesRequest dt = DataTablesParser.from(request); // + + List searchable = List.of( + "tipoEncuadernacion", + "tipoCubierta", + "tiradaMin", "tiradaMax", + "margenMin", "margenMax"); + + List orderable = List.of( + "id", + "tipoEncuadernacion", + "tipoCubierta", + "tiradaMin", + "tiradaMax", + "margenMin", + "margenMax"); + + + Specification base = (root, query, cb) -> cb.conjunction(); + long total = repo.count(); + + return DataTable + .of(repo, MargenPresupuesto.class, dt, searchable) // 'searchable' en DataTable.java + // edita columnas "reales": + .orderable(orderable) + .add("actions", (margen) -> { + return "
\n" + + " \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) -> { + // f_enabled: 'true' | 'false' | '' + /*String fEnabled = Optional.ofNullable(req.raw.get("f_enabled")).orElse("").trim(); + if (!fEnabled.isEmpty()) { + boolean enabledVal = Boolean.parseBoolean(fEnabled); + builder.add((root, q, cb) -> cb.equal(root.get("enabled"), enabledVal)); + } + + // f_role: 'USER' | 'ADMIN' | 'SUPERADMIN' | '' + String fRole = Optional.ofNullable(req.raw.get("f_role")).orElse("").trim(); + if (!fRole.isEmpty()) { + builder.add((root, q, cb) -> { + // join a roles; marca la query como distinct para evitar duplicados + var r = root.join("roles", jakarta.persistence.criteria.JoinType.LEFT); + q.distinct(true); + return cb.equal(r.get("name"), fRole); + }); + }*/ + }) + .toJson(total); + } + +} 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 new file mode 100644 index 0000000..a9d19cc --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoDao.java @@ -0,0 +1,28 @@ +package com.imprimelibros.erp.configuracion.margenes_presupuestos; + +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.Presupuesto.TipoEncuadernacion; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta; + +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) + """) + long countOverlaps( + @Param("enc") TipoEncuadernacion enc, + @Param("cub") TipoCubierta cub, + @Param("min") Integer min, + @Param("max") Integer max, + @Param("id") Long id + ); +} 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 new file mode 100644 index 0000000..b87f4c1 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/configuracion/margenes_presupuestos/MargenPresupuestoService.java @@ -0,0 +1,43 @@ +package com.imprimelibros.erp.configuracion.margenes_presupuestos; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta; + +@Service +@Transactional +public class MargenPresupuestoService { + + private final MargenPresupuestoDao dao; + + public MargenPresupuestoService(MargenPresupuestoDao dao) { + this.dao = dao; + } + + public List findAll() { + return dao.findAll(); + } + + public Optional findById(Long id) { + return dao.findById(id); + } + + public MargenPresupuesto save(MargenPresupuesto entity) { + return dao.save(entity); + } + + 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; + } +} diff --git a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java index c620ced..6af6ba3 100644 --- a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java +++ b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java @@ -1,5 +1,7 @@ package com.imprimelibros.erp.datatables; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.data.domain.*; import org.springframework.data.jpa.domain.Specification; @@ -26,7 +28,9 @@ public class DataTable { 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(); + private final ObjectMapper om = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); private List orderable = null; private DataTable(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt, diff --git a/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlap.java b/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlap.java new file mode 100644 index 0000000..e1adf5c --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlap.java @@ -0,0 +1,35 @@ +package com.imprimelibros.erp.shared.validation; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.*; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@Constraint(validatedBy = NoRangeOverlapValidator.class) +public @interface NoRangeOverlap { + + // Campos obligatorios + String min(); // nombre del campo min (Integer/Long/etc.) + String max(); // nombre del campo max + + // Campos opcionales + String id() default "id"; // nombre del campo ID (para excluir self en update) + String[] partitionBy() default {}; // ej. {"tipoEncuadernacion","tipoCubierta"} + + // Soft delete opcional + String deletedFlag() default ""; // ej. "deleted" (si vacío, no se aplica filtro) + boolean deletedActiveValue() default false; // qué valor significa "activo" (normalmente false) + + // Mensajes + String message() default "{validation.range.overlaps}"; + String invalidRangeMessage() default "{validation.range.invalid}"; + + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlapValidator.java b/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlapValidator.java new file mode 100644 index 0000000..daa87d7 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/shared/validation/NoRangeOverlapValidator.java @@ -0,0 +1,127 @@ +package com.imprimelibros.erp.shared.validation; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.criteria.*; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class NoRangeOverlapValidator implements ConstraintValidator { + + @PersistenceContext + private EntityManager em; + + private String minField; + private String maxField; + private String idField; + private String[] partitionFields; + private String deletedFlag; + private boolean deletedActiveValue; + private String message; + private String invalidRangeMessage; + + @Override + public void initialize(NoRangeOverlap ann) { + this.minField = ann.min(); + this.maxField = ann.max(); + this.idField = ann.id(); + this.partitionFields = ann.partitionBy(); + this.deletedFlag = ann.deletedFlag(); + this.deletedActiveValue = ann.deletedActiveValue(); + this.message = ann.message(); + this.invalidRangeMessage = ann.invalidRangeMessage(); + } + + @Override + public boolean isValid(Object bean, ConstraintValidatorContext ctx) { + if (bean == null) return true; + + try { + Class entityClass = bean.getClass(); + + Number min = (Number) read(bean, minField); + Number max = (Number) read(bean, maxField); + Object id = safeRead(bean, idField); // puede ser null en INSERT + + if (min == null || max == null) return true; + + if (min.longValue() > max.longValue()) { + ctx.disableDefaultConstraintViolation(); + ctx.buildConstraintViolationWithTemplate(invalidRangeMessage) + .addPropertyNode(maxField) + .addConstraintViolation(); + return false; + } + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root root = cq.from(entityClass); + + cq.select(cb.count(root)); + + Predicate pred = cb.conjunction(); + + if (id != null) { + pred = cb.and(pred, cb.notEqual(root.get(idField), id)); + } + + for (String pf : partitionFields) { + Object val = read(bean, pf); + pred = cb.and(pred, cb.equal(root.get(pf), val)); + } + + if (!deletedFlag.isEmpty()) { + pred = cb.and(pred, cb.equal(root.get(deletedFlag), deletedActiveValue)); + } + + Expression eMin = root.get(minField); + Expression eMax = root.get(maxField); + + Predicate noOverlap = cb.or( + cb.lt(eMax.as(Long.class), min.longValue()), + cb.gt(eMin.as(Long.class), max.longValue()) + ); + Predicate overlap = cb.not(noOverlap); + + cq.where(cb.and(pred, overlap)); + + Long count = em.createQuery(cq).getSingleResult(); + if (count != null && count > 0) { + ctx.disableDefaultConstraintViolation(); + ctx.buildConstraintViolationWithTemplate(message) + .addPropertyNode(minField).addConstraintViolation(); + ctx.buildConstraintViolationWithTemplate(message) + .addPropertyNode(maxField).addConstraintViolation(); + return false; + } + + return true; + + } catch (Exception ex) { + // En caso de error inesperado, puedes loguear aquí + return true; + } + } + + private Object read(Object bean, String name) throws Exception { + PropertyDescriptor pd = getPropertyDescriptor(bean.getClass(), name); + Method getter = pd.getReadMethod(); + return getter.invoke(bean); + } + + private Object safeRead(Object bean, String name) { + try { + return read(bean, name); + } catch (Exception ignore) { + return null; + } + } + + private PropertyDescriptor getPropertyDescriptor(Class clazz, String name) throws IntrospectionException { + return new PropertyDescriptor(name, clazz); + } +} diff --git a/src/main/java/com/imprimelibros/erp/users/UserController.java b/src/main/java/com/imprimelibros/erp/users/UserController.java index b4ab09e..0b56747 100644 --- a/src/main/java/com/imprimelibros/erp/users/UserController.java +++ b/src/main/java/com/imprimelibros/erp/users/UserController.java @@ -8,7 +8,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.MessageSource; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 49b563b..058e8ac 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -69,3 +69,8 @@ spring.mail.username=no-reply@imprimelibros.com spring.mail.password=%j4Su*#ZcjRDYsa$ spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true + +# +# Remove JSESSIONID from URL +# +server.servlet.session.persistent=false \ No newline at end of file diff --git a/src/main/resources/i18n/margenesPresupuesto_en.properties b/src/main/resources/i18n/margenesPresupuesto_en.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/i18n/margenesPresupuesto_en.properties @@ -0,0 +1 @@ + diff --git a/src/main/resources/i18n/margenesPresupuesto_es.properties b/src/main/resources/i18n/margenesPresupuesto_es.properties new file mode 100644 index 0000000..7ef7148 --- /dev/null +++ b/src/main/resources/i18n/margenesPresupuesto_es.properties @@ -0,0 +1,14 @@ +margenes-presupuesto.titulo=Márgenes de presupuesto +margenes-presupuesto.breadcrumb=Márgenes de presupuesto +margenes-presupuesto.add=Añadir + +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.margen_minimo=Margen Mín. +margenes-presupuesto.tabla.margen_maximo=Margen Máx. +margenes-presupuesto.tabla.acciones=Acciones + +margenes-presupuesto.todos=Todos \ No newline at end of file diff --git a/src/main/resources/i18n/presupuesto_es.properties b/src/main/resources/i18n/presupuesto_es.properties index 72ec2d2..ff2dd67 100644 --- a/src/main/resources/i18n/presupuesto_es.properties +++ b/src/main/resources/i18n/presupuesto_es.properties @@ -71,8 +71,11 @@ presupuesto.plantilla-cubierta-text=Recuerde que la cubierta es el conjunto form presupuesto.tipo-cubierta=Tipo de cubierta presupuesto.tipo-cubierta-descripcion=Seleccione el tipo de cubierta y sus opciones presupuesto.tapa-blanda=Tapa blanda +presupuesto.tapaBlanda=Tapa blanda presupuesto.tapa-dura=Tapa dura +presupuesto.tapaDura=Tapa dura presupuesto.tapa-dura-lomo-redondo=Tapa dura lomo redondo +presupuesto.tapaDuraLomoRedondo=Tapa dura lomo redondo presupuesto.sin-solapas=Sin solapas presupuesto.con-solapas=Con solapas presupuesto.impresion-cubierta=Impresión de cubierta diff --git a/src/main/resources/i18n/validation_es.properties b/src/main/resources/i18n/validation_es.properties index b120b54..35a5d87 100644 --- a/src/main/resources/i18n/validation_es.properties +++ b/src/main/resources/i18n/validation_es.properties @@ -6,5 +6,6 @@ validation.typeMismatchMsg=Tipo de dato no válido validation.patternMsg=El formato no es válido validation.unique=El valor ya existe y debe ser único validation.email=El correo electrónico no es válido - +validation.range.overlaps=El rango se solapa con otro existente. +validation.range.invalid=El valor máximo debe ser mayor o igual que el mínimo. 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 new file mode 100644 index 0000000..7e3119a --- /dev/null +++ b/src/main/resources/static/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js @@ -0,0 +1,48 @@ +(() => { + // si jQuery está cargado, añade CSRF a AJAX + const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content'); + if (window.$ && csrfToken && csrfHeader) { + $.ajaxSetup({ + beforeSend: function (xhr) { + xhr.setRequestHeader(csrfHeader, csrfToken); + } + }); + } + + const language = document.documentElement.lang || 'es-ES'; + + // Comprueba dependencias antes de iniciar + if (!window.DataTable) { + console.error('DataTables no está cargado aún'); + return; + } + + new DataTable('#margenes-datatable', { + processing: true, + serverSide: true, + orderCellsTop: true, + pageLength: 50, + language: { url: '/assets/libs/datatables/i18n/' + language + '.json' }, + responsive: true, + ajax: { + url: '/configuracion/margenes-presupuestos/datatable', + method: 'GET', + data: function (d) { + // filtros si los necesitas + } + }, + 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: 'margenMax', name: 'margenMax', orderable: true }, + { data: 'margenMin', name: 'margenMin', orderable: true }, + { data: 'actions', name: 'actions' } + ], + columnDefs: [{ targets: -1, orderable: false, searchable: false }] + }); +})(); 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 new file mode 100644 index 0000000..bae6451 --- /dev/null +++ b/src/main/resources/templates/imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-list.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + +
+
+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDTipo encuadernaciónTipo cubiertaTirada Mín.Tirada Máx.Margen Máx.Margen Mín.Acciones
+ + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/partials/sidebar.html b/src/main/resources/templates/imprimelibros/partials/sidebar.html index e6518ff..3ebfb69 100644 --- a/src/main/resources/templates/imprimelibros/partials/sidebar.html +++ b/src/main/resources/templates/imprimelibros/partials/sidebar.html @@ -46,9 +46,15 @@