mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
preparando el imprimir
This commit is contained in:
@ -31,7 +31,7 @@ public class DataTablesSpecification {
|
||||
ands.add(like(cb, path, col.search.value));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// columna no mapeada o relación: la ignoramos
|
||||
System.out.println("[DT] columna no mapeada o relación: " + col.name);
|
||||
//System.out.println("[DT] columna no mapeada o relación: " + col.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ public class skApiClient {
|
||||
margen.getMargenMax(),
|
||||
margen.getMargenMin());
|
||||
double nuevoPrecio = precios.get(i) * (1 + margenValue / 100.0);
|
||||
precios.set(i, nuevoPrecio);
|
||||
precios.set(i, Math.round(nuevoPrecio * 10000.0) / 10000.0); // redondear a 2 decimales
|
||||
} else {
|
||||
System.out.println("No se encontró margen para tirada " + tirada);
|
||||
}
|
||||
|
||||
11
src/main/java/com/imprimelibros/erp/pdf/DocumentSpec.java
Normal file
11
src/main/java/com/imprimelibros/erp/pdf/DocumentSpec.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public record DocumentSpec(
|
||||
DocumentType type,
|
||||
String templateId, // p.ej. "presupuesto-a4"
|
||||
Locale locale,
|
||||
Map<String, Object> model // data del documento
|
||||
) {}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
public enum DocumentType {
|
||||
PRESUPUESTO, PEDIDO, FACTURA
|
||||
}
|
||||
32
src/main/java/com/imprimelibros/erp/pdf/PdfController.java
Normal file
32
src/main/java/com/imprimelibros/erp/pdf/PdfController.java
Normal file
@ -0,0 +1,32 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/pdf")
|
||||
public class PdfController {
|
||||
private final PdfService pdfService;
|
||||
|
||||
public PdfController(PdfService pdfService) { this.pdfService = pdfService; }
|
||||
|
||||
@PostMapping("/{type}/{templateId}")
|
||||
public ResponseEntity<byte[]> generate(
|
||||
@PathVariable("type") DocumentType type,
|
||||
@PathVariable String templateId,
|
||||
@RequestBody Map<String,Object> model,
|
||||
Locale locale) {
|
||||
|
||||
var spec = new DocumentSpec(type, templateId, locale, model);
|
||||
var pdf = pdfService.generate(spec);
|
||||
|
||||
var fileName = type.name().toLowerCase() + "-" + templateId + ".pdf";
|
||||
return ResponseEntity.ok()
|
||||
.header("Content-Type", "application/pdf")
|
||||
.header("Content-Disposition", "inline; filename=\"" + fileName + "\"")
|
||||
.body(pdf);
|
||||
}
|
||||
}
|
||||
18
src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java
Normal file
18
src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "imprimelibros.pdf")
|
||||
public class PdfModuleConfig {
|
||||
/**
|
||||
* Mapa: "TYPE:templateId" -> "ruta thymeleaf" (sin extensión).
|
||||
* Ej: "PRESUPUESTO:presupuesto-a4" -> "pdf/presupuesto-a4"
|
||||
*/
|
||||
private Map<String, String> templates;
|
||||
|
||||
public Map<String, String> getTemplates() { return templates; }
|
||||
public void setTemplates(Map<String, String> templates) { this.templates = templates; }
|
||||
}
|
||||
44
src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java
Normal file
44
src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
@Service
|
||||
public class PdfRenderer {
|
||||
|
||||
@Value("classpath:/static/")
|
||||
private Resource staticRoot;
|
||||
|
||||
public byte[] renderHtmlToPdf(String html) {
|
||||
try (var baos = new ByteArrayOutputStream()) {
|
||||
var builder = new PdfRendererBuilder();
|
||||
|
||||
builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Regular.ttf"), "Open Sans",
|
||||
400, com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true);
|
||||
builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-SemiBold.ttf"), "Open Sans",
|
||||
600, com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true);
|
||||
builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Bold.ttf"), "Open Sans", 700,
|
||||
com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true);
|
||||
|
||||
builder.useFastMode();
|
||||
builder.withHtmlContent(html, baseUrl());
|
||||
builder.toStream(baos);
|
||||
builder.run();
|
||||
return baos.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Error generando PDF", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String baseUrl() {
|
||||
try {
|
||||
return staticRoot.getURL().toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/imprimelibros/erp/pdf/PdfService.java
Normal file
25
src/main/java/com/imprimelibros/erp/pdf/PdfService.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PdfService {
|
||||
private final TemplateRegistry registry;
|
||||
private final PdfTemplateEngine engine;
|
||||
private final PdfRenderer renderer;
|
||||
|
||||
public PdfService(TemplateRegistry registry, PdfTemplateEngine engine, PdfRenderer renderer) {
|
||||
this.registry = registry;
|
||||
this.engine = engine;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public byte[] generate(DocumentSpec spec) {
|
||||
var template = registry.resolve(spec.type(), spec.templateId());
|
||||
if (template == null) {
|
||||
throw new IllegalArgumentException("Plantilla no registrada: " + spec.type() + ":" + spec.templateId());
|
||||
}
|
||||
var html = engine.render(template, spec.locale(), spec.model());
|
||||
return renderer.renderHtmlToPdf(html);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class PdfTemplateEngine {
|
||||
private final SpringTemplateEngine thymeleaf;
|
||||
|
||||
public PdfTemplateEngine(SpringTemplateEngine thymeleaf) {
|
||||
this.thymeleaf = thymeleaf;
|
||||
}
|
||||
|
||||
public String render(String templateName, Locale locale, Map<String,Object> model) {
|
||||
Context ctx = new Context(locale);
|
||||
if (model != null) model.forEach(ctx::setVariable);
|
||||
return thymeleaf.process(templateName, ctx);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
// com.imprimelibros.erp.pdf.TemplateRegistry.java
|
||||
package com.imprimelibros.erp.pdf;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TemplateRegistry {
|
||||
private final PdfModuleConfig config;
|
||||
|
||||
public TemplateRegistry(PdfModuleConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public String resolve(DocumentType type, String templateId) {
|
||||
if (config.getTemplates() == null) return null;
|
||||
return config.getTemplates().get(type.name() + ":" + templateId);
|
||||
}
|
||||
}
|
||||
@ -531,7 +531,9 @@ public class PresupuestoController {
|
||||
"presupuesto.plantilla-cubierta",
|
||||
"presupuesto.plantilla-cubierta-text",
|
||||
"presupuesto.impresion-cubierta",
|
||||
"presupuesto.impresion-cubierta-help");
|
||||
"presupuesto.impresion-cubierta-help",
|
||||
"presupuesto.exito.guardado",
|
||||
"presupuesto.add.error.save.title");
|
||||
|
||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||
model.addAttribute("languageBundle", translations);
|
||||
@ -558,12 +560,14 @@ public class PresupuestoController {
|
||||
return "redirect:/presupuesto";
|
||||
}
|
||||
|
||||
model.addAttribute("presupuesto_id", presupuestoOpt.get().getId());
|
||||
String path = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
|
||||
.getRequest().getRequestURI();
|
||||
String mode = path.contains("/view/") ? "view" : "edit";
|
||||
if (mode.equals("view")) {
|
||||
model.addAttribute("appMode", "view");
|
||||
} else {
|
||||
model.addAttribute("cliente_id", presupuestoOpt.get().getUser().getId());
|
||||
model.addAttribute("appMode", "edit");
|
||||
}
|
||||
model.addAttribute("id", presupuestoOpt.get().getId());
|
||||
@ -594,7 +598,7 @@ public class PresupuestoController {
|
||||
model.addAttribute("ancho_alto_max", variableService.getValorEntero("ancho_alto_max"));
|
||||
|
||||
model.addAttribute("appMode", "add");
|
||||
|
||||
|
||||
if (!mode.equals("public")) {
|
||||
model.addAttribute("cliente_id", clienteId);
|
||||
}
|
||||
@ -603,24 +607,6 @@ public class PresupuestoController {
|
||||
return "imprimelibros/presupuestos/presupuesto-form";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/api/get", produces = "application/json")
|
||||
public ResponseEntity<PresupuestoFormDataDto> getPresupuesto(
|
||||
@RequestParam("id") Long id, Authentication authentication) {
|
||||
|
||||
Optional<Presupuesto> presupuestoOpt = presupuestoRepository.findById(id);
|
||||
|
||||
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
if (presupuestoOpt.isPresent()) {
|
||||
PresupuestoFormDataDto vm = formDataMapper.toFormData(presupuestoOpt.get());
|
||||
return ResponseEntity.ok(vm);
|
||||
} else {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/datatable/{tipo}", produces = "application/json")
|
||||
@ResponseBody
|
||||
public DataTablesResponse<Map<String, Object>> datatable(
|
||||
@ -638,7 +624,6 @@ public class PresupuestoController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ResponseEntity<?> delete(@PathVariable Long id, Authentication auth, Locale locale) {
|
||||
@ -697,7 +682,25 @@ public class PresupuestoController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(path="/save")
|
||||
@GetMapping(value = "/api/get", produces = "application/json")
|
||||
public ResponseEntity<PresupuestoFormDataDto> getPresupuesto(
|
||||
@RequestParam("id") Long id, Authentication authentication) {
|
||||
|
||||
Optional<Presupuesto> presupuestoOpt = presupuestoRepository.findById(id);
|
||||
|
||||
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
if (presupuestoOpt.isPresent()) {
|
||||
PresupuestoFormDataDto vm = formDataMapper.toFormData(presupuestoOpt.get());
|
||||
return ResponseEntity.ok(vm);
|
||||
} else {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(path = "api/save")
|
||||
public ResponseEntity<?> save(
|
||||
@RequestBody Map<String, Object> body,
|
||||
Locale locale, HttpServletRequest request) {
|
||||
@ -707,6 +710,11 @@ public class PresupuestoController {
|
||||
String mode = objectMapper.convertValue(body.get("mode"), String.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
||||
Long cliente_id = objectMapper.convertValue(body.get("cliente_id"), Long.class);
|
||||
Map<String, Object> datosMaquetacion = (Map<String, Object>) objectMapper
|
||||
.convertValue(body.get("datosMaquetacion"), Map.class);
|
||||
Map<String, Object> datosMarcapaginas = (Map<String, Object>) objectMapper
|
||||
.convertValue(body.get("datosMarcapaginas"), Map.class);
|
||||
|
||||
Set<ConstraintViolation<Presupuesto>> violations = validator.validate(presupuesto,
|
||||
PresupuestoValidationGroups.All.class);
|
||||
@ -715,34 +723,26 @@ public class PresupuestoController {
|
||||
Map<String, String> errores = new HashMap<>();
|
||||
for (ConstraintViolation<Presupuesto> v : violations) {
|
||||
String campo = v.getPropertyPath().toString();
|
||||
String mensaje = messageSource.getMessage(v.getMessage().replace("{", "").replace("}", ""), null, locale);
|
||||
String mensaje = messageSource.getMessage(v.getMessage().replace("{", "").replace("}", ""), null,
|
||||
locale);
|
||||
errores.put(campo, mensaje);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
try {
|
||||
var resumen = presupuestoService.getTextosResumen(presupuesto, serviciosList, locale);
|
||||
|
||||
Long cliente_id = objectMapper.convertValue(body.get("cliente_id"), Long.class);
|
||||
if(id == null && cliente_id != null && !mode.equals("public")) {
|
||||
Map<String, Object> saveResult = presupuestoService.guardarPresupuesto(
|
||||
presupuesto,
|
||||
serviciosList,
|
||||
datosMaquetacion,
|
||||
datosMarcapaginas,
|
||||
mode,
|
||||
cliente_id,
|
||||
id,
|
||||
request,
|
||||
locale);
|
||||
|
||||
presupuesto.setUser(userRepo.findById(cliente_id).orElse(null));
|
||||
presupuesto.setOrigen(Presupuesto.Origen.privado);
|
||||
}
|
||||
if (mode.equals("public")) {
|
||||
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||
String sessionId = request.getSession(true).getId();
|
||||
String ip = request.getRemoteAddr();
|
||||
|
||||
presupuesto = presupuestoService.getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||
if (id != null) {
|
||||
presupuesto.setId(id); // para que actualice, no cree uno nuevo
|
||||
}
|
||||
}
|
||||
presupuesto = presupuestoService.generateTotalizadores(presupuesto, serviciosList, resumen, locale);
|
||||
|
||||
Map<String, Object> saveResult = presupuestoService.guardarPresupuesto(presupuesto);
|
||||
return ResponseEntity.ok(Map.of("id", saveResult.get("presupuesto_id"),
|
||||
"message", messageSource.getMessage("presupuesto.exito.guardado", null, locale)));
|
||||
} catch (Exception ex) {
|
||||
|
||||
@ -3,6 +3,9 @@ package com.imprimelibros.erp.presupuesto.service;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder.In;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -84,10 +87,17 @@ public class PresupuestoFormDataMapper {
|
||||
|
||||
// ===== Servicios / Extras =====
|
||||
public static class Servicios {
|
||||
public List<String> servicios = List.of();
|
||||
public List<DatosServicios> servicios = new ArrayList<DatosServicios>();
|
||||
public DatosMarcapaginas datosMarcapaginas = new DatosMarcapaginas();
|
||||
public DatosMaquetacion datosMaquetacion = new DatosMaquetacion();
|
||||
|
||||
public static class DatosServicios {
|
||||
public String id;
|
||||
public String label;
|
||||
public Integer units;
|
||||
public Double price;
|
||||
}
|
||||
|
||||
public static class DatosMarcapaginas {
|
||||
public Integer marcapaginas_tirada = 100;
|
||||
public String tamanio_marcapaginas = "_50x140_";
|
||||
@ -184,8 +194,11 @@ public class PresupuestoFormDataMapper {
|
||||
vm.selectedTirada = p.getSelectedTirada();
|
||||
|
||||
// ===== Servicios desde JSONs
|
||||
// servicios_json: acepta ["maquetacion","marcapaginas"] o [{id:...}, ...]
|
||||
vm.servicios.servicios = parseServiciosIds(p.getServiciosJson());
|
||||
vm.servicios.servicios = parse(p.getServiciosJson(),
|
||||
new TypeReference<List<PresupuestoFormDataDto.Servicios.DatosServicios>>() {
|
||||
});
|
||||
if (vm.servicios.servicios == null)
|
||||
vm.servicios.servicios = new ArrayList<>();
|
||||
|
||||
// datos_maquetacion_json
|
||||
PresupuestoFormDataDto.Servicios.DatosMaquetacion maq = parse(p.getDatosMaquetacionJson(),
|
||||
@ -230,31 +243,13 @@ public class PresupuestoFormDataMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> parseServiciosIds(String json) {
|
||||
if (json == null || json.isBlank())
|
||||
return new ArrayList<>();
|
||||
private <T> T parse(String json, TypeReference<T> typeRef) {
|
||||
try {
|
||||
// 1) intentar como lista de strings
|
||||
List<String> ids = om.readValue(json, new TypeReference<List<String>>() {
|
||||
});
|
||||
return ids != null ? ids : new ArrayList<>();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
try {
|
||||
// 2) intentar como lista de objetos con 'id'
|
||||
List<Map<String, Object>> list = om.readValue(json, new TypeReference<>() {
|
||||
});
|
||||
List<String> ids = new ArrayList<>();
|
||||
for (Map<String, Object> it : list) {
|
||||
Object id = it.get("id");
|
||||
if (id != null)
|
||||
ids.add(String.valueOf(id));
|
||||
}
|
||||
return ids;
|
||||
} catch (
|
||||
|
||||
Exception e) {
|
||||
return new ArrayList<>();
|
||||
if (json == null || json.isBlank())
|
||||
return null;
|
||||
return om.readValue(json, typeRef);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatricesReposito
|
||||
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
|
||||
import com.imprimelibros.erp.users.UserDao;
|
||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder.In;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||
|
||||
@Service
|
||||
@ -448,6 +452,16 @@ public class PresupuestoService {
|
||||
: "0.00";
|
||||
}
|
||||
|
||||
private String obtenerPrecioRetractilado(Integer tirada) {
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("tirada",tirada != null ? tirada : 0);
|
||||
Double precio_retractilado = apiClient.getRetractilado(requestBody);
|
||||
return precio_retractilado != null
|
||||
? String.valueOf(Math.round(precio_retractilado * 100.0) / 100.0)
|
||||
: "0.00";
|
||||
}
|
||||
|
||||
public Map<String, Object> obtenerServiciosExtras(Presupuesto presupuesto, Locale locale) {
|
||||
List<Object> opciones = new ArrayList<>();
|
||||
|
||||
@ -747,13 +761,20 @@ public class PresupuestoService {
|
||||
if (hayDepositoLegal) {
|
||||
pressupuestoTemp.setSelectedTirada(
|
||||
presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() + 4 : 4);
|
||||
for (Integer i = 0; i < pressupuestoTemp.getTiradas().length; i++) {
|
||||
Integer tirada = pressupuestoTemp.getTiradas()[i];
|
||||
if (tirada != null && tirada >= 4) {
|
||||
tirada = tirada + 4;
|
||||
}
|
||||
pressupuestoTemp.getTiradas()[i] = tirada;
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, Object> precios = this.calcularPresupuesto(pressupuestoTemp, locale);
|
||||
if (precios.containsKey("error")) {
|
||||
resumen.put("error", precios.get("error"));
|
||||
return resumen;
|
||||
}
|
||||
resumen.put("precios", precios);
|
||||
|
||||
HashMap<String, Object> linea = new HashMap<>();
|
||||
Double precio_unitario = 0.0;
|
||||
@ -824,7 +845,7 @@ public class PresupuestoService {
|
||||
if (mode.equals("public")) {
|
||||
|
||||
presupuesto = getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||
|
||||
|
||||
} else
|
||||
presupuesto.setOrigen(Presupuesto.Origen.privado);
|
||||
|
||||
@ -847,7 +868,7 @@ public class PresupuestoService {
|
||||
|
||||
if (save != null && save) {
|
||||
// Si NO es para guardar (solo calcular resumen), devolver sin persistir
|
||||
this.guardarPresupuesto(presupuesto);
|
||||
presupuestoRepository.saveAndFlush(presupuesto);
|
||||
}
|
||||
|
||||
// Opcional: devolver el id guardado al frontend para que lo envíe en llamadas
|
||||
@ -865,24 +886,24 @@ public class PresupuestoService {
|
||||
}
|
||||
|
||||
public Presupuesto getDatosLocalizacion(Presupuesto presupuesto, String sessionId, String ip) {
|
||||
|
||||
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||
presupuesto.setSessionId(sessionId);
|
||||
// IP: guarda hash y trunc (si tienes campos). Si no, guarda tal cual en
|
||||
// ip_trunc/ip_hash según tu modelo.
|
||||
String ipTrunc = anonymizeIp(ip);
|
||||
presupuesto.setIpTrunc(ipTrunc);
|
||||
presupuesto.setIpHash(Integer.toHexString(ip.hashCode()));
|
||||
|
||||
// ubicación (si tienes un servicio GeoIP disponible; si no, omite estas tres
|
||||
// líneas)
|
||||
try {
|
||||
GeoIpService.GeoData geo = geoIpService.lookup(ip).orElse(null);
|
||||
presupuesto.setPais(geo.getPais());
|
||||
presupuesto.setRegion(geo.getRegion());
|
||||
presupuesto.setCiudad(geo.getCiudad());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||
presupuesto.setSessionId(sessionId);
|
||||
// IP: guarda hash y trunc (si tienes campos). Si no, guarda tal cual en
|
||||
// ip_trunc/ip_hash según tu modelo.
|
||||
String ipTrunc = anonymizeIp(ip);
|
||||
presupuesto.setIpTrunc(ipTrunc);
|
||||
presupuesto.setIpHash(Integer.toHexString(ip.hashCode()));
|
||||
|
||||
// ubicación (si tienes un servicio GeoIP disponible; si no, omite estas tres
|
||||
// líneas)
|
||||
try {
|
||||
GeoIpService.GeoData geo = geoIpService.lookup(ip).orElse(null);
|
||||
presupuesto.setPais(geo.getPais());
|
||||
presupuesto.setRegion(geo.getRegion());
|
||||
presupuesto.setCiudad(geo.getCiudad());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return presupuesto;
|
||||
}
|
||||
|
||||
@ -892,84 +913,185 @@ public class PresupuestoService {
|
||||
Map<String, Object> resumen,
|
||||
Locale locale) {
|
||||
|
||||
// Genera los totalizadores (precio unitario, total tirada, etc.) sin guardar
|
||||
double precioUnit = 0.0;
|
||||
int cantidad = presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Double> precios = (List<Double>) ((Map<String, Object>) resumen.getOrDefault("precios", Map.of()))
|
||||
.getOrDefault("precios", List.of());
|
||||
if (precios.isEmpty()) {
|
||||
// si no venía en "resumen", recalcúlalo directamente
|
||||
var preciosCalc = this.calcularPresupuesto(presupuesto, locale);
|
||||
precios = (List<Double>) ((Map<String, Object>) preciosCalc.get("data")).get("precios");
|
||||
}
|
||||
precioUnit = precios.get(0);
|
||||
// guarda el snapshot completo de precios para auditoría
|
||||
presupuesto.setPreciosPorTiradaJson(new ObjectMapper().writeValueAsString(precios));
|
||||
} catch (Exception ignore) {
|
||||
Map<Integer, Map<String, Object>> pricing_snapshot = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> preciosNode = (Map<String, Object>) resumen.getOrDefault("precios", Map.of());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) preciosNode.getOrDefault("data", Map.of());
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Integer> tiradas = (List<Integer>) data.getOrDefault("tiradas", List.of());
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Double> precios = (List<Double>) data.getOrDefault("precios", List.of());
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Double> pesos = (List<Double>) data.getOrDefault("peso", List.of());
|
||||
if (precios.isEmpty()) {
|
||||
var preciosCalc = this.calcularPresupuesto(presupuesto, locale);
|
||||
precios = (List<Double>) ((Map<String, Object>) preciosCalc.get("data")).getOrDefault("precios", List.of());
|
||||
}
|
||||
|
||||
BigDecimal precioTotalTirada = BigDecimal.valueOf(precioUnit)
|
||||
.multiply(BigDecimal.valueOf(cantidad))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
// iterate getTiradas with a foreach with not null
|
||||
for (Integer tirada : presupuesto.getTiradas()) {
|
||||
if (tirada == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// servicios_total
|
||||
BigDecimal serviciosTotal = BigDecimal.ZERO;
|
||||
if (servicios != null) {
|
||||
for (Map<String, Object> s : servicios) {
|
||||
// Genera los totalizadores (precio unitario, total tirada, etc.) sin guardar
|
||||
double precioUnit = 0.0;
|
||||
int cantidad = tirada != null ? tirada : 0;
|
||||
int index = tiradas.indexOf(tirada);
|
||||
try {
|
||||
|
||||
if (index >= 0 && index < precios.size()) {
|
||||
precioUnit = precios.get(index);
|
||||
} else if (!precios.isEmpty()) {
|
||||
precioUnit = precios.get(0); // fallback al primero
|
||||
}
|
||||
// guarda el snapshot completo de precios para auditoría
|
||||
presupuesto.setPreciosPorTiradaJson(new ObjectMapper().writeValueAsString(precios));
|
||||
} catch (Exception ignore) {
|
||||
precioUnit = 0.0;
|
||||
}
|
||||
|
||||
BigDecimal precioTotalTirada = BigDecimal.valueOf(precioUnit)
|
||||
.multiply(BigDecimal.valueOf(cantidad))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
// servicios_total
|
||||
BigDecimal serviciosTotal = BigDecimal.ZERO;
|
||||
if (servicios != null) {
|
||||
for (Map<String, Object> s : servicios) {
|
||||
try {
|
||||
// retractilado o ejemplar-prueba: recalcular precio
|
||||
if (s.get("id").equals("retractilado")) {
|
||||
double precio_retractilado = obtenerPrecioRetractilado(cantidad) != null
|
||||
? Double.parseDouble(obtenerPrecioRetractilado(cantidad))
|
||||
: 0.0;
|
||||
s.put("price", precio_retractilado);
|
||||
}
|
||||
double unidades = Double.parseDouble(String.valueOf(s.getOrDefault("units", 0)));
|
||||
double precio = Double.parseDouble(String.valueOf(
|
||||
s.get("id").equals("marcapaginas")
|
||||
? (Double.parseDouble(String.valueOf(s.get("price"))) / unidades) // unidad
|
||||
: s.getOrDefault("price", 0)));
|
||||
serviciosTotal = serviciosTotal.add(
|
||||
BigDecimal.valueOf(precio).multiply(BigDecimal.valueOf(unidades)));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
double unidades = Double.parseDouble(String.valueOf(s.getOrDefault("units", 0)));
|
||||
double precio = Double.parseDouble(String.valueOf(
|
||||
s.get("id").equals("marcapaginas")
|
||||
? (Double.parseDouble(String.valueOf(s.get("price"))) / unidades) // unidad
|
||||
: s.getOrDefault("price", 0)));
|
||||
serviciosTotal = serviciosTotal.add(
|
||||
BigDecimal.valueOf(precio).multiply(BigDecimal.valueOf(unidades)));
|
||||
presupuesto.setServiciosJson(new ObjectMapper().writeValueAsString(servicios));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
// base imponible, IVA y total (si tienes IVA configurable, úsalo; si no, 0)
|
||||
BigDecimal baseImponible = precioTotalTirada.add(serviciosTotal);
|
||||
BigDecimal ivaTipo = BigDecimal.ZERO;
|
||||
try {
|
||||
presupuesto.setServiciosJson(new ObjectMapper().writeValueAsString(servicios));
|
||||
double iva = 4.0; // 0..100
|
||||
ivaTipo = BigDecimal.valueOf(iva);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
BigDecimal ivaImporte = baseImponible.multiply(ivaTipo).divide(BigDecimal.valueOf(100), 2,
|
||||
RoundingMode.HALF_UP);
|
||||
BigDecimal totalConIva = baseImponible.add(ivaImporte);
|
||||
|
||||
// precios y totales
|
||||
if (tirada == (presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0)) {
|
||||
presupuesto.setPrecioUnitario(BigDecimal.valueOf(precioUnit).setScale(6, RoundingMode.HALF_UP));
|
||||
presupuesto.setPrecioTotalTirada(precioTotalTirada);
|
||||
presupuesto.setServiciosTotal(serviciosTotal);
|
||||
presupuesto.setBaseImponible(baseImponible);
|
||||
presupuesto.setIvaTipo(ivaTipo);
|
||||
presupuesto.setIvaImporte(ivaImporte);
|
||||
presupuesto.setTotalConIva(totalConIva);
|
||||
}
|
||||
Map<String, Object> snap = new HashMap<>();
|
||||
snap.put("precio_unitario", BigDecimal.valueOf(precioUnit).setScale(6, RoundingMode.HALF_UP));
|
||||
snap.put("precio_total_tirada", precioTotalTirada);
|
||||
snap.put("servicios_total", serviciosTotal);
|
||||
snap.put("base_imponible", baseImponible);
|
||||
snap.put("iva_tipo", ivaTipo);
|
||||
snap.put("iva_importe", ivaImporte);
|
||||
snap.put("total_con_iva", totalConIva);
|
||||
snap.put("peso", (index >= 0 && index < pesos.size()) ? pesos.get(index) : 0.0);
|
||||
|
||||
pricing_snapshot.put(tirada, snap);
|
||||
|
||||
}
|
||||
|
||||
// base imponible, IVA y total (si tienes IVA configurable, úsalo; si no, 0)
|
||||
BigDecimal baseImponible = precioTotalTirada.add(serviciosTotal);
|
||||
BigDecimal ivaTipo = BigDecimal.ZERO;
|
||||
try {
|
||||
double iva = 4.0; // 0..100
|
||||
ivaTipo = BigDecimal.valueOf(iva);
|
||||
String json = new ObjectMapper()
|
||||
.writer()
|
||||
.withDefaultPrettyPrinter() // opcional
|
||||
.writeValueAsString(pricing_snapshot);
|
||||
presupuesto.setPricingSnapshotJson(pricing_snapshot.isEmpty() ? null : json);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
BigDecimal ivaImporte = baseImponible.multiply(ivaTipo).divide(BigDecimal.valueOf(100), 2,
|
||||
RoundingMode.HALF_UP);
|
||||
BigDecimal totalConIva = baseImponible.add(ivaImporte);
|
||||
|
||||
// precios y totales
|
||||
presupuesto.setPrecioUnitario(BigDecimal.valueOf(precioUnit).setScale(6, RoundingMode.HALF_UP));
|
||||
presupuesto.setPrecioTotalTirada(precioTotalTirada);
|
||||
presupuesto.setServiciosTotal(serviciosTotal);
|
||||
presupuesto.setBaseImponible(baseImponible);
|
||||
presupuesto.setIvaTipo(ivaTipo);
|
||||
presupuesto.setIvaImporte(ivaImporte);
|
||||
presupuesto.setTotalConIva(totalConIva);
|
||||
|
||||
return presupuesto;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public HashMap<String, Object> guardarPresupuesto(Presupuesto presupuesto) {
|
||||
|
||||
public HashMap<String, Object> guardarPresupuesto(
|
||||
Presupuesto presupuesto,
|
||||
List<Map<String, Object>> serviciosList,
|
||||
Map<String, Object> datosMaquetacion,
|
||||
Map<String, Object> datosMarcapaginas,
|
||||
String mode,
|
||||
Long cliente_id,
|
||||
Long id,
|
||||
HttpServletRequest request,
|
||||
Locale locale) {
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
|
||||
Presupuesto p = presupuestoRepository.saveAndFlush(presupuesto);
|
||||
presupuesto.setDatosMaquetacionJson(
|
||||
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
|
||||
presupuesto.setDatosMarcapaginasJson(
|
||||
datosMarcapaginas != null ? new ObjectMapper().writeValueAsString(datosMarcapaginas) : null);
|
||||
var resumen = this.getTextosResumen(presupuesto, serviciosList, locale);
|
||||
|
||||
Object serviciosObj = resumen.get("servicios");
|
||||
|
||||
if (serviciosObj instanceof List<?> servicios && !servicios.isEmpty()) {
|
||||
// serializa a JSON válido
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String json = objectMapper.writeValueAsString(servicios);
|
||||
presupuesto.setServiciosJson(json);
|
||||
} else {
|
||||
// decide tu política: null o "[]"
|
||||
presupuesto.setServiciosJson(null); // o presupuesto.setServiciosJson("[]");
|
||||
}
|
||||
|
||||
if (cliente_id != null && !mode.equals("public")) {
|
||||
|
||||
presupuesto.setUser(userRepo.findById(cliente_id).orElse(null));
|
||||
presupuesto.setOrigen(Presupuesto.Origen.privado);
|
||||
if (id != null) {
|
||||
presupuesto.setId(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.equals("public")) {
|
||||
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||
String sessionId = request.getSession(true).getId();
|
||||
String ip = request.getRemoteAddr();
|
||||
|
||||
presupuesto = this.getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||
if (id != null) {
|
||||
presupuesto.setId(id); // para que actualice, no cree uno nuevo
|
||||
}
|
||||
}
|
||||
presupuesto = this.generateTotalizadores(presupuesto, serviciosList, resumen, locale);
|
||||
|
||||
presupuestoRepository.saveAndFlush(presupuesto);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("presupuesto_id", p.getId());
|
||||
|
||||
result.put("presupuesto_id", presupuesto.getId());
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
@ -1007,7 +1129,7 @@ public class PresupuestoService {
|
||||
if (isUser) {
|
||||
// Si es usuario, solo puede ver sus propios presupuestos
|
||||
String username = authentication.getName();
|
||||
if (!presupuesto.getUser().getUserName().equals(username)) {
|
||||
if (presupuesto.getUser() == null || !presupuesto.getUser().getUserName().equals(username)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user