mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 08:58:48 +00:00
añadidos margenes presupuesto
This commit is contained in:
@ -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<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@ -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<NoRangeOverlap, Object> {
|
||||
|
||||
@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<Long> 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<Number> eMin = root.get(minField);
|
||||
Expression<Number> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user