package com.imprimelibros.erp.presupuesto; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; 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; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas; import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups; import jakarta.validation.Valid; @Controller @RequestMapping("/presupuesto") public class PresupuestoController { @Autowired protected PresupuestoService presupuestoService; @Autowired protected skApiClient apiClient; @Autowired protected MessageSource messageSource; private final ObjectMapper objectMapper; public PresupuestoController(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @PostMapping("/public/validar/datos-generales") public ResponseEntity validarDatosGenerales( @Validated(PresupuestoValidationGroups.DatosGenerales.class) Presupuesto presupuesto, BindingResult result, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); // errores globales (@ConsistentTiradas...) result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } // opciones color Map resultado = presupuestoService.obtenerOpcionesColor(presupuesto, locale); // opciones papel interior resultado.putAll(presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale)); // opciones gramaje interior resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto)); return ResponseEntity.ok(resultado); } @PostMapping("/public/validar/interior") public ResponseEntity validarInterior( @Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto, BindingResult result, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); // errores globales (@ConsistentTiradas...) result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } Map resultado = new HashMap<>(); resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto))); resultado.putAll(presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale)); return ResponseEntity.ok(resultado); } @PostMapping("/public/validar/cubierta") public ResponseEntity validarCubierta( @Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto, BindingResult result, @RequestParam(name = "calcular", defaultValue = "true") boolean calcular, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); // errores globales (@ConsistentTiradas...) result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } if (calcular) { HashMap price = presupuestoService.calcularPresupuesto(presupuesto, locale); if (!price.containsKey("data")) { return ResponseEntity.badRequest() .body(messageSource.getMessage("presupuesto.error-obtener-precio", null, locale)); } return ResponseEntity.ok(price.get("data")); } return ResponseEntity.ok().build(); } @PostMapping("/public/validar/seleccion-tirada") public ResponseEntity validarSeleccionTirada( Presupuesto presupuesto, BindingResult result, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); // errores globales (@ConsistentTiradas...) result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } Map resultado = new HashMap<>(); // servicios extra resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale)); Map language = new HashMap<>(); language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale)); resultado.put("language", language); return ResponseEntity.ok(resultado); } @PostMapping("/public/get-papel-interior") public ResponseEntity getPapelInterior( @Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto, BindingResult result, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } // opciones color Map resultado = presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale); // opciones gramaje interior resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto)); List opciones = new ObjectMapper().convertValue(resultado.get("opciones_papel_interior"), new TypeReference>() { }); if (opciones != null && !opciones.isEmpty()) { String gramajeActual = presupuesto.getGramajeInterior().toString(); if (!opciones.contains(gramajeActual)) { presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción } } resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto))); return ResponseEntity.ok(resultado); } @PostMapping("/public/get-gramaje-interior") public ResponseEntity getGramajeInterior( @Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto, BindingResult result) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } Map resultado = presupuestoService.obtenerOpcionesGramajeInterior(presupuesto); List opciones = new ObjectMapper().convertValue(resultado.get("opciones_gramaje_interior"), new TypeReference>() { }); if (opciones != null && !opciones.isEmpty()) { String gramajeActual = presupuesto.getGramajeInterior().toString(); if (!opciones.contains(gramajeActual)) { presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción } } resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto))); return ResponseEntity.ok(resultado); } @PostMapping("/public/get-max-solapas") public ResponseEntity getMaxSolapas( @Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto, BindingResult result) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } Map resultado = new HashMap<>(); resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto))); return ResponseEntity.ok(resultado); } @PostMapping("/public/get-papel-cubierta") public ResponseEntity getPapelCubierta( Presupuesto presupuesto, BindingResult result, Locale locale) { Map resultado = new HashMap<>(); Map papelesCubierta = presupuestoService.obtenerOpcionesPapelCubierta(presupuesto, locale); List opciones = new ObjectMapper().convertValue( presupuestoService .obtenerOpcionesPapelCubierta(presupuesto, locale) .get("opciones_papel_cubierta"), new TypeReference>() { }); if (opciones != null && opciones.stream().noneMatch( o -> o.getExtra_data().get("sk-id").equals(String.valueOf(presupuesto.getPapelCubiertaId())))) { presupuesto.setPapelCubiertaId(Integer.valueOf(opciones.get(0).getExtra_data().get("sk-id"))); } resultado.putAll(papelesCubierta); resultado.putAll(presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto)); List gramajesCubierta = new ObjectMapper().convertValue( resultado.get("opciones_gramaje_cubierta"), new TypeReference>() { }); if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) { String gramajeActual = presupuesto.getGramajeCubierta().toString(); if (!gramajesCubierta.contains(gramajeActual)) { presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción } } return ResponseEntity.ok(resultado); } @PostMapping("/public/get-gramaje-cubierta") public ResponseEntity getGramajeCubierta( Presupuesto presupuesto, BindingResult result) { Map resultado = presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto); List gramajesCubierta = (List) resultado.get("opciones_gramaje_cubierta"); if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) { String gramajeActual = presupuesto.getGramajeCubierta().toString(); if (!gramajesCubierta.contains(gramajeActual)) { presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción } } return ResponseEntity.ok(resultado); } @PostMapping("/public/get-acabados-cubierta") public ResponseEntity getAcabadosCubierta( Presupuesto presupuesto, BindingResult result, Locale locale) { Map resultado = presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale); return ResponseEntity.ok(resultado); } @PostMapping("/public/get-price") public ResponseEntity getPrice( Presupuesto presupuesto, BindingResult result, Locale locale) { Map errores = new HashMap<>(); // errores de campos individuales result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage())); // errores globales (@ConsistentTiradas...) result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage())); if (!errores.isEmpty()) { return ResponseEntity.badRequest().body(errores); } 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."); } return ResponseEntity.ok(price); } @GetMapping(value = "/public/maquetacion/form", produces = MediaType.TEXT_HTML_VALUE) public String getMaquetacionForm(Model model) { model.addAttribute("presupuestoMaquetacion", new PresupuestoMaquetacion()); return "imprimelibros/presupuestos/presupuesto-maquetacion-form :: maquetacionForm"; } @GetMapping("/public/maquetacion") public ResponseEntity getPresupuestoMaquetacion( @Valid @ModelAttribute PresupuestoMaquetacion presupuestoMaquetacion, BindingResult result, Locale locale) { if (result.hasErrors()) { // Construimos un mapa field -> mensaje para tu AJAX Map errores = result.getFieldErrors().stream() .collect(java.util.stream.Collectors.toMap( fe -> fe.getField(), fe -> fe.getDefaultMessage(), (a, b) -> a)); return ResponseEntity.badRequest().body(errores); } Map resultado = presupuestoService.getPrecioMaquetacion(presupuestoMaquetacion, locale); if ((Double) resultado.get("precio") == 0.0 && (Integer) resultado.get("numPaginasEstimadas") == 0 && (Double) resultado.get("precioPaginaEstimado") == 0.0) { return ResponseEntity.badRequest() .body(messageSource.getMessage("presupuesto.errores.presupuesto-maquetacion", null, locale)); } return ResponseEntity.ok(resultado); } @GetMapping(value = "/public/marcapaginas/form", produces = MediaType.TEXT_HTML_VALUE) public String getMarcapaginasForm(Model model) { model.addAttribute("presupuestoMarcapaginas", new PresupuestoMarcapaginas()); return "imprimelibros/presupuestos/presupuesto-marcapaginas-form :: marcapaginasForm"; } @GetMapping("/public/marcapaginas") public ResponseEntity getPresupuestoMarcapaginas( @Valid @ModelAttribute PresupuestoMarcapaginas presupuestoMarcapaginas, BindingResult result, Locale locale) { if (result.hasErrors()) { // Construimos un mapa field -> mensaje para tu AJAX Map errores = result.getFieldErrors().stream() .collect(java.util.stream.Collectors.toMap( fe -> fe.getField(), fe -> fe.getDefaultMessage(), (a, b) -> a)); return ResponseEntity.badRequest().body(errores); } Map resultado = presupuestoService.getPrecioMarcapaginas(presupuestoMarcapaginas, locale); if ((Double) resultado.get("precio_total") == 0.0 && (Double) resultado.get("precio_unitario") == 0.0) { return ResponseEntity.badRequest() .body(messageSource.getMessage("presupuesto.errores.presupuesto-marcapaginas", null, locale)); } return ResponseEntity.ok(resultado); } // Se hace un post para no tener problemas con la longitud de la URL @PostMapping("/public/resumen") public ResponseEntity getResumen(@RequestBody Map body, Locale locale) { Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class); @SuppressWarnings("unchecked") List> serviciosList = (List>) body.getOrDefault("servicios", List.of()); return ResponseEntity.ok(presupuestoService.getResumen(p, serviciosList, locale)); } }