mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
terminado margenes presupuesto e incluido en la api
This commit is contained in:
@ -0,0 +1,24 @@
|
||||
package com.imprimelibros.erp.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
|
||||
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
|
||||
@Configuration
|
||||
public class BeanValidationConfig {
|
||||
|
||||
// Asegura que usamos la factory de Spring (con SpringConstraintValidatorFactory)
|
||||
@Bean
|
||||
public LocalValidatorFactoryBean validator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
// Inserta esa factory en Hibernate/JPA
|
||||
@Bean
|
||||
public HibernatePropertiesCustomizer hibernateValidationCustomizer(ValidatorFactory vf) {
|
||||
return props -> props.put("jakarta.persistence.validation.factory", vf);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@ 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;
|
||||
@ -46,23 +45,23 @@ public class MargenPresupuesto {
|
||||
private TipoCubierta tipoCubierta;
|
||||
|
||||
@Column(name="tirada_min", nullable = false)
|
||||
@NotBlank(message="{validation.required}")
|
||||
@NotNull(message="{validation.required}")
|
||||
@Min(value=1, message="{validation.min}")
|
||||
private Integer tiradaMin;
|
||||
|
||||
@Column(name="tirada_max", nullable = false)
|
||||
@NotBlank(message="{validation.required}")
|
||||
@NotNull(message="{validation.required}")
|
||||
@Min(value=1, message="{validation.min}")
|
||||
private Integer tiradaMax;
|
||||
|
||||
@Column(name="margen_max", nullable = false)
|
||||
@NotBlank(message="{validation.required}")
|
||||
@NotNull(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}")
|
||||
@NotNull(message="{validation.required}")
|
||||
@Min(value = 0, message="{validation.min}")
|
||||
@Max(value = 200, message="{validation.max}")
|
||||
private Integer margenMin;
|
||||
|
||||
@ -1,16 +1,27 @@
|
||||
package com.imprimelibros.erp.configuracion.margenes_presupuestos;
|
||||
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
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.datatables.DataTable;
|
||||
@ -18,24 +29,28 @@ 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.Presupuesto.TipoCubierta;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/configuracion/margenes-presupuestos")
|
||||
@RequestMapping("/configuracion/margenes-presupuesto")
|
||||
@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) {
|
||||
public MargenPresupuestoController(MargenPresupuestoDao repo, TranslationService translationService,
|
||||
MessageSource messageSource) {
|
||||
this.repo = repo;
|
||||
this.translationService = translationService;
|
||||
this.messageSource = messageSource;
|
||||
@ -44,7 +59,15 @@ public class MargenPresupuestoController {
|
||||
@GetMapping()
|
||||
public String listView(Model model, Authentication authentication, Locale locale) {
|
||||
|
||||
List<String> keys = List.of();
|
||||
List<String> keys = List.of(
|
||||
"margenes-presupuesto.delete.title",
|
||||
"margenes-presupuesto.delete.text",
|
||||
"margenes-presupuesto.eliminar",
|
||||
"margenes-presupuesto.delete.button",
|
||||
"app.yes",
|
||||
"app.cancelar",
|
||||
"margenes-presupuesto.delete.ok.title",
|
||||
"margenes-presupuesto.delete.ok.text");
|
||||
|
||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||
model.addAttribute("languageBundle", translations);
|
||||
@ -57,23 +80,21 @@ public class MargenPresupuestoController {
|
||||
public DataTablesResponse<Map<String, Object>> datatable(HttpServletRequest request, Authentication authentication,
|
||||
Locale locale) {
|
||||
|
||||
DataTablesRequest dt = DataTablesParser.from(request); //
|
||||
DataTablesRequest dt = DataTablesParser.from(request);
|
||||
|
||||
List<String> searchable = List.of(
|
||||
"tipoEncuadernacion",
|
||||
"tipoCubierta",
|
||||
"id",
|
||||
"tiradaMin", "tiradaMax",
|
||||
"margenMin", "margenMax");
|
||||
|
||||
List<String> orderable = List.of(
|
||||
"id",
|
||||
"tipoEncuadernacion",
|
||||
"tipoCubierta",
|
||||
"tiradaMin",
|
||||
"tiradaMax",
|
||||
"margenMin",
|
||||
"margenMax");
|
||||
|
||||
List<String> orderable = List.of(
|
||||
"id",
|
||||
"tipoEncuadernacion",
|
||||
"tipoCubierta",
|
||||
"tiradaMin",
|
||||
"tiradaMax",
|
||||
"margenMin",
|
||||
"margenMax");
|
||||
|
||||
Specification<MargenPresupuesto> base = (root, query, cb) -> cb.conjunction();
|
||||
long total = repo.count();
|
||||
@ -85,13 +106,14 @@ public class MargenPresupuestoController {
|
||||
.add("actions", (margen) -> {
|
||||
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
|
||||
" <a href=\"javascript:void(0);\" data-id=\"" + margen.getId()
|
||||
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
|
||||
+ "\" class=\"link-success btn-edit-margen fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
|
||||
+ " <a href=\"javascript:void(0);\" data-id=\"" + margen.getId()
|
||||
+ "\" class=\"link-danger btn-delete-user fs-15\"><i class=\"ri-delete-bin-5-line\"></i></a>\n"
|
||||
+ "\" class=\"link-danger btn-delete-margen fs-15\"><i class=\"ri-delete-bin-5-line\"></i></a>\n"
|
||||
+ " </div>";
|
||||
})
|
||||
.edit("tipoEncuadernacion", (margen) -> {
|
||||
return messageSource.getMessage("presupuesto." + margen.getTipoEncuadernacion().name(),null, locale);
|
||||
return messageSource.getMessage("presupuesto." + margen.getTipoEncuadernacion().name(), null,
|
||||
locale);
|
||||
})
|
||||
.edit("tipoCubierta", (margen) -> {
|
||||
return messageSource.getMessage("presupuesto." + margen.getTipoCubierta().name(), null, locale);
|
||||
@ -99,25 +121,220 @@ public class MargenPresupuestoController {
|
||||
.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));
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
}*/
|
||||
// --- 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);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("form")
|
||||
public String getForm(@RequestParam(required = false) Long id,
|
||||
MargenPresupuesto margenPresupuesto,
|
||||
BindingResult binding,
|
||||
Model model,
|
||||
HttpServletResponse response,
|
||||
Locale locale) {
|
||||
|
||||
if (id != null) {
|
||||
var opt = repo.findById(id);
|
||||
if (opt.isEmpty()) {
|
||||
binding.reject("usuarios.error.noEncontrado",
|
||||
messageSource.getMessage("usuarios.error.noEncontrado", null, locale));
|
||||
response.setStatus(404);
|
||||
model.addAttribute("action", "/users/" + id);
|
||||
return "imprimelibros/users/user-form :: userForm";
|
||||
}
|
||||
|
||||
model.addAttribute("margenPresupuesto", opt.get());
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id);
|
||||
} else {
|
||||
// Crear: valores por defecto
|
||||
model.addAttribute("margenPresupuesto", new MargenPresupuesto());
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto");
|
||||
}
|
||||
return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String create(
|
||||
MargenPresupuesto margenPresupuesto,
|
||||
BindingResult binding,
|
||||
Model model,
|
||||
HttpServletResponse response,
|
||||
Locale locale) {
|
||||
|
||||
if (binding.hasErrors()) {
|
||||
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.setMargenMax(margenPresupuesto.getMargenMax());
|
||||
data.setMargenMin(margenPresupuesto.getMargenMin());
|
||||
|
||||
try {
|
||||
repo.save(data);
|
||||
} catch (jakarta.validation.ConstraintViolationException vex) {
|
||||
// Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap)
|
||||
vex.getConstraintViolations().forEach(v -> {
|
||||
// intenta asignar al campo si existe, si no, error global
|
||||
String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null;
|
||||
String code = v.getMessage() != null ? v.getMessage().trim() : "";
|
||||
|
||||
if (code.startsWith("{") && code.endsWith("}")) {
|
||||
code = code.substring(1, code.length() - 1); // -> "validation.required"
|
||||
}
|
||||
|
||||
if (path != null && binding.getFieldError(path) == null) {
|
||||
|
||||
binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale));
|
||||
} else {
|
||||
binding.reject("validation", messageSource.getMessage(code, null, locale));
|
||||
}
|
||||
});
|
||||
response.setStatus(422);
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto");
|
||||
return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm";
|
||||
}
|
||||
response.setStatus(201);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public String edit(
|
||||
@PathVariable Long id,
|
||||
MargenPresupuesto form,
|
||||
BindingResult binding,
|
||||
Model model,
|
||||
HttpServletResponse response,
|
||||
Locale locale) {
|
||||
|
||||
var uOpt = repo.findById(id);
|
||||
if (uOpt.isEmpty()) {
|
||||
binding.reject("usuarios.error.noEncontrado",
|
||||
messageSource.getMessage("usuarios.error.noEncontrado", null, locale));
|
||||
}
|
||||
|
||||
if (binding.hasErrors()) {
|
||||
response.setStatus(422);
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id);
|
||||
return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm";
|
||||
}
|
||||
|
||||
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.setMargenMax(form.getMargenMax());
|
||||
entity.setMargenMin(form.getMargenMin());
|
||||
|
||||
try {
|
||||
repo.saveAndFlush(entity);
|
||||
|
||||
} catch (jakarta.validation.ConstraintViolationException vex) {
|
||||
// Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap)
|
||||
vex.getConstraintViolations().forEach(v -> {
|
||||
// intenta asignar al campo si existe, si no, error global
|
||||
String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null;
|
||||
String code = v.getMessage() != null ? v.getMessage().trim() : "";
|
||||
|
||||
if (code.startsWith("{") && code.endsWith("}")) {
|
||||
code = code.substring(1, code.length() - 1); // -> "validation.required"
|
||||
}
|
||||
|
||||
if (path != null && binding.getFieldError(path) == null) {
|
||||
|
||||
binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale));
|
||||
} else {
|
||||
binding.reject("validation", messageSource.getMessage(code, null, locale));
|
||||
}
|
||||
});
|
||||
response.setStatus(422);
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id);
|
||||
return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm";
|
||||
|
||||
} catch (org.springframework.dao.DataIntegrityViolationException dex) {
|
||||
// Uniques, FKs, checks… mensajes de la BD
|
||||
String msg = dex.getMostSpecificCause() != null ? dex.getMostSpecificCause().getMessage()
|
||||
: dex.getMessage();
|
||||
binding.reject("db.error", messageSource.getMessage(msg, null, locale));
|
||||
response.setStatus(422);
|
||||
model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id);
|
||||
return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm";
|
||||
}
|
||||
|
||||
response.setStatus(204);
|
||||
return null;
|
||||
}
|
||||
|
||||
@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)));
|
||||
}
|
||||
}).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(Map.of("message", messageSource.getMessage("margenes-presupuesto.error.not-found", null, locale))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,21 +8,33 @@ 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<MargenPresupuesto, Long>, JpaSpecificationExecutor<MargenPresupuesto> {
|
||||
public interface MargenPresupuestoDao
|
||||
extends JpaRepository<MargenPresupuesto, Long>, JpaSpecificationExecutor<MargenPresupuesto> {
|
||||
|
||||
@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 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
|
||||
);
|
||||
@Param("enc") TipoEncuadernacion enc,
|
||||
@Param("cub") TipoCubierta cub,
|
||||
@Param("min") Integer min,
|
||||
@Param("max") Integer max,
|
||||
@Param("id") Long id);
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@ -6,12 +6,19 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuesto;
|
||||
import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuestoDao;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
|
||||
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Service
|
||||
@ -22,13 +29,16 @@ public class skApiClient {
|
||||
|
||||
private final AuthService authService;
|
||||
private final RestTemplate restTemplate;
|
||||
private final MargenPresupuestoDao margenPresupuestoDao;
|
||||
|
||||
public skApiClient(AuthService authService) {
|
||||
public skApiClient(AuthService authService, MargenPresupuestoDao margenPresupuestoDao) {
|
||||
this.authService = authService;
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.margenPresupuestoDao = margenPresupuestoDao;
|
||||
}
|
||||
|
||||
public String getPrice(Map<String, Object> requestBody) {
|
||||
public String getPrice(Map<String, Object> requestBody, TipoEncuadernacion tipoEncuadernacion,
|
||||
TipoCubierta tipoCubierta) {
|
||||
return performWithRetry(() -> {
|
||||
String url = this.skApiUrl + "api/calcular";
|
||||
|
||||
@ -45,14 +55,57 @@ public class skApiClient {
|
||||
String.class);
|
||||
|
||||
try {
|
||||
Map<String, Object> responseBody = new ObjectMapper().readValue(response.getBody(), Map.class);
|
||||
Map<String, Object> responseBody = new ObjectMapper().readValue(
|
||||
response.getBody(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
if (responseBody.get("error") == null) {
|
||||
return new ObjectMapper().writeValueAsString(
|
||||
Map.of("data", responseBody.get("data")));
|
||||
Object dataObj = responseBody.get("data");
|
||||
|
||||
if (dataObj instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) dataObj;
|
||||
|
||||
List<Integer> tiradas = mapper.convertValue(
|
||||
data.get("tiradas"), new TypeReference<List<Integer>>() {
|
||||
});
|
||||
List<Double> precios = mapper.convertValue(
|
||||
data.get("precios"), new TypeReference<List<Double>>() {
|
||||
});
|
||||
|
||||
for (int i = 0; i < tiradas.size(); i++) {
|
||||
int tirada = tiradas.get(i);
|
||||
|
||||
MargenPresupuesto margen = margenPresupuestoDao.findByTipoAndTirada(
|
||||
tipoEncuadernacion, tipoCubierta, tirada);
|
||||
|
||||
if (margen != null) {
|
||||
double margenValue = calcularMargen(
|
||||
tirada,
|
||||
margen.getTiradaMin(),
|
||||
margen.getTiradaMax(),
|
||||
margen.getMargenMax(),
|
||||
margen.getMargenMin());
|
||||
double nuevoPrecio = precios.get(i) * (1 + margenValue / 100.0);
|
||||
precios.set(i, nuevoPrecio);
|
||||
} else {
|
||||
System.out.println("No se encontró margen para tirada " + tirada);
|
||||
}
|
||||
}
|
||||
|
||||
// <-- Clave: sustituir la lista en el map que se devuelve
|
||||
data.put("precios", precios);
|
||||
// (tiradas no cambia, pero si la modificases: data.put("tiradas", tiradas);)
|
||||
}
|
||||
|
||||
return mapper.writeValueAsString(Map.of("data", responseBody.get("data")));
|
||||
} else {
|
||||
return "{\"error\": 1}";
|
||||
}
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return "{\"error\": 1}";
|
||||
@ -104,7 +157,11 @@ public class skApiClient {
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
// Fallback al 80% del ancho
|
||||
Map<String, Object> tamanio = (Map<String, Object>) requestBody.get("tamanio");
|
||||
Map<String, Object> tamanio = new ObjectMapper().convertValue(
|
||||
requestBody.get("tamanio"),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
if (tamanio == null || tamanio.get("ancho") == null)
|
||||
throw new RuntimeException("Tamaño no válido en la solicitud: " + requestBody);
|
||||
else {
|
||||
@ -132,7 +189,10 @@ public class skApiClient {
|
||||
String.class);
|
||||
|
||||
try {
|
||||
Map<String, Object> responseBody = new ObjectMapper().readValue(response.getBody(), Map.class);
|
||||
Map<String, Object> responseBody = new ObjectMapper().readValue(
|
||||
response.getBody(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
return responseBody.get("data").toString();
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
@ -162,4 +222,14 @@ public class skApiClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double calcularMargen(
|
||||
int tirada, int tiradaMin, int tiradaMax,
|
||||
double margenMax, double margenMin) {
|
||||
if (tirada <= tiradaMin)
|
||||
return margenMax;
|
||||
if (tirada >= tiradaMax)
|
||||
return margenMin;
|
||||
return margenMax - ((double) (tirada - tiradaMin) / (tiradaMax - tiradaMin)) * (margenMax - margenMin);
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
|
||||
@ -45,7 +46,7 @@ public class PresupuestoController {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PresupuestoController(ObjectMapper objectMapper){
|
||||
public PresupuestoController(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@ -178,7 +179,10 @@ public class PresupuestoController {
|
||||
|
||||
// opciones gramaje interior
|
||||
resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto));
|
||||
List<String> opciones = (List<String>) resultado.get("opciones_gramaje_interior");
|
||||
|
||||
List<String> opciones = new ObjectMapper().convertValue(resultado.get("opciones_papel_interior"),
|
||||
new TypeReference<List<String>>() {
|
||||
});
|
||||
|
||||
if (opciones != null && !opciones.isEmpty()) {
|
||||
String gramajeActual = presupuesto.getGramajeInterior().toString();
|
||||
@ -206,7 +210,9 @@ public class PresupuestoController {
|
||||
}
|
||||
|
||||
Map<String, Object> resultado = presupuestoService.obtenerOpcionesGramajeInterior(presupuesto);
|
||||
List<String> opciones = (List<String>) resultado.get("opciones_gramaje_interior");
|
||||
List<String> opciones = new ObjectMapper().convertValue(resultado.get("opciones_gramaje_interior"),
|
||||
new TypeReference<List<String>>() {
|
||||
});
|
||||
|
||||
if (opciones != null && !opciones.isEmpty()) {
|
||||
String gramajeActual = presupuesto.getGramajeInterior().toString();
|
||||
@ -244,9 +250,12 @@ public class PresupuestoController {
|
||||
|
||||
Map<String, Object> resultado = new HashMap<>();
|
||||
Map<String, Object> papelesCubierta = presupuestoService.obtenerOpcionesPapelCubierta(presupuesto, locale);
|
||||
List<ImagenPresupuesto> opciones = (List<ImagenPresupuesto>) presupuestoService
|
||||
.obtenerOpcionesPapelCubierta(presupuesto, locale)
|
||||
.get("opciones_papel_cubierta");
|
||||
List<ImagenPresupuesto> opciones = new ObjectMapper().convertValue(
|
||||
presupuestoService
|
||||
.obtenerOpcionesPapelCubierta(presupuesto, locale)
|
||||
.get("opciones_papel_cubierta"),
|
||||
new TypeReference<List<ImagenPresupuesto>>() {
|
||||
});
|
||||
|
||||
if (opciones != null && opciones.stream().noneMatch(
|
||||
o -> o.getExtra_data().get("sk-id").equals(String.valueOf(presupuesto.getPapelCubiertaId())))) {
|
||||
@ -255,7 +264,10 @@ public class PresupuestoController {
|
||||
resultado.putAll(papelesCubierta);
|
||||
|
||||
resultado.putAll(presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto));
|
||||
List<String> gramajesCubierta = (List<String>) resultado.get("opciones_gramaje_cubierta");
|
||||
List<String> gramajesCubierta = new ObjectMapper().convertValue(
|
||||
resultado.get("opciones_gramaje_cubierta"),
|
||||
new TypeReference<List<String>>() {
|
||||
});
|
||||
if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) {
|
||||
String gramajeActual = presupuesto.getGramajeCubierta().toString();
|
||||
if (!gramajesCubierta.contains(gramajeActual)) {
|
||||
@ -304,7 +316,8 @@ public class PresupuestoController {
|
||||
if (!errores.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
String price = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto));
|
||||
String price = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto),
|
||||
presupuesto.getTipoEncuadernacion(), presupuesto.getTipoCubierta());
|
||||
if (price == null || price.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body("No se pudo obtener el precio. Intente nuevamente.");
|
||||
}
|
||||
|
||||
@ -575,7 +575,7 @@ public class PresupuestoService {
|
||||
} else if (presupuestoTemp.getTipoImpresion() == Presupuesto.TipoImpresion.negro) {
|
||||
presupuestoTemp.setTipoImpresion(Presupuesto.TipoImpresion.negrohq);
|
||||
}
|
||||
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuestoTemp));
|
||||
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuestoTemp), presupuestoTemp.getTipoEncuadernacion(), presupuestoTemp.getTipoCubierta());
|
||||
Double price_prototipo = 0.0;
|
||||
try {
|
||||
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
|
||||
@ -846,7 +846,7 @@ public class PresupuestoService {
|
||||
public HashMap<String, Object> calcularPresupuesto(Presupuesto presupuesto, Locale locale) {
|
||||
|
||||
HashMap<String, Object> price = new HashMap<>();
|
||||
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuesto));
|
||||
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuesto), presupuesto.getTipoEncuadernacion(), presupuesto.getTipoCubierta());
|
||||
|
||||
try {
|
||||
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
|
||||
|
||||
@ -5,15 +5,21 @@ import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.FlushModeType;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.PersistenceUnit;
|
||||
import jakarta.persistence.criteria.*;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class NoRangeOverlapValidator implements ConstraintValidator<NoRangeOverlap, Object> {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
@PersistenceUnit
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
private String minField;
|
||||
private String maxField;
|
||||
@ -38,22 +44,28 @@ public class NoRangeOverlapValidator implements ConstraintValidator<NoRangeOverl
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object bean, ConstraintValidatorContext ctx) {
|
||||
if (bean == null) return true;
|
||||
|
||||
if (bean == null)
|
||||
return true;
|
||||
EntityManager em = null;
|
||||
try {
|
||||
// EM aislado para evitar auto-flush durante la validación
|
||||
em = emf.createEntityManager();
|
||||
em.setFlushMode(FlushModeType.COMMIT);
|
||||
|
||||
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 == null || max == null)
|
||||
return true;
|
||||
|
||||
if (min.longValue() > max.longValue()) {
|
||||
ctx.disableDefaultConstraintViolation();
|
||||
ctx.buildConstraintViolationWithTemplate(invalidRangeMessage)
|
||||
.addPropertyNode(maxField)
|
||||
.addConstraintViolation();
|
||||
.addPropertyNode(maxField)
|
||||
.addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -82,9 +94,8 @@ public class NoRangeOverlapValidator implements ConstraintValidator<NoRangeOverl
|
||||
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())
|
||||
);
|
||||
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));
|
||||
@ -93,9 +104,9 @@ public class NoRangeOverlapValidator implements ConstraintValidator<NoRangeOverl
|
||||
if (count != null && count > 0) {
|
||||
ctx.disableDefaultConstraintViolation();
|
||||
ctx.buildConstraintViolationWithTemplate(message)
|
||||
.addPropertyNode(minField).addConstraintViolation();
|
||||
.addPropertyNode(minField).addConstraintViolation();
|
||||
ctx.buildConstraintViolationWithTemplate(message)
|
||||
.addPropertyNode(maxField).addConstraintViolation();
|
||||
.addPropertyNode(maxField).addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user