package com.imprimelibros.erp.pdf; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.springframework.stereotype.Service; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.imprimelibros.erp.presupuesto.PresupuestoRepository; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.web.HtmlToXhtml; import com.imprimelibros.erp.facturacion.Factura; import com.imprimelibros.erp.facturacion.service.FacturacionService; import com.imprimelibros.erp.pedidos.PedidoDireccion; import com.imprimelibros.erp.pedidos.PedidoService; @Service public class PdfService { private final TemplateRegistry registry; private final PdfTemplateEngine engine; private final PdfRenderer renderer; private final PresupuestoRepository presupuestoRepository; private final Utils utils; private final FacturacionService facturacionService; private final PedidoService pedidoService; private final Map empresa = Map.of( "nombre", "ImprimeLibros ERP", "direccion", "C/ Dirección 123, 28000 Madrid", "telefono", "+34 600 000 000", "email", "info@imprimelibros.com", "cif", "B-12345678", "cp", "28000", "poblacion", "Madrid", "web", "www.imprimelibros.com"); private static class PrecioTirada { private Double peso; @JsonProperty("iva_importe_4") private Double ivaImporte4; @JsonProperty("total_con_iva") private Double totalConIva; @JsonProperty("base_imponible") private Double baseImponible; @JsonProperty("iva_importe_21") private Double ivaImporte21; @JsonProperty("precio_unitario") private Double precioUnitario; @JsonProperty("servicios_total") private Double serviciosTotal; @JsonProperty("precio_total_tirada") private Double precioTotalTirada; public Double getPeso() { return peso; } public Double getIvaImporte4() { return ivaImporte4; } public Double getTotalConIva() { return totalConIva; } public Double getBaseImponible() { return baseImponible; } public Double getIvaImporte21() { return ivaImporte21; } public Double getPrecioUnitario() { return precioUnitario; } public Double getServiciosTotal() { return serviciosTotal; } public Double getPrecioTotalTirada() { return precioTotalTirada; } } public PdfService(TemplateRegistry registry, PdfTemplateEngine engine, PdfRenderer renderer, PresupuestoRepository presupuestoRepository, Utils utils, FacturacionService facturacionService, PedidoService pedidoService) { this.registry = registry; this.engine = engine; this.renderer = renderer; this.presupuestoRepository = presupuestoRepository; this.utils = utils; this.pedidoService = pedidoService; this.facturacionService = facturacionService; } private 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); } public byte[] generaPresupuesto(Long presupuestoId, Locale locale) { try { Presupuesto presupuesto = presupuestoRepository.findById(presupuestoId) .orElseThrow(() -> new IllegalArgumentException("Presupuesto no encontrado: " + presupuestoId)); Map model = new HashMap<>(); model.put("numero", presupuesto.getId()); model.put("fecha", presupuesto.getUpdatedAt()); model.put("empresa", empresa); model.put("cliente", Map.of( "nombre", presupuesto.getUser() != null ? presupuesto.getUser().getFullName() : "")); model.put("titulo", presupuesto.getTitulo()); Map specs = utils.getTextoPresupuesto(presupuesto, locale); model.put("specs", specs); Map pricing = new HashMap<>(); ObjectMapper mapper = new ObjectMapper(); // Si quieres parsear directamente a un Map: Map snapshot = mapper.readValue(presupuesto.getPricingSnapshotJson(), mapper.getTypeFactory().constructMapType(Map.class, Integer.class, PrecioTirada.class)); List tiradas = snapshot.keySet().stream().toList(); pricing.put("tiradas", tiradas); pricing.put("impresion", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getPrecioTotalTirada(), locale)) .toList()); pricing.put("servicios", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getServiciosTotal(), locale)) .toList()); pricing.put("peso", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getPeso(), locale)) .toList()); pricing.put("iva_4", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getIvaImporte4(), locale)) .toList()); pricing.put("iva_21", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getIvaImporte21(), locale)) .toList()); pricing.put("total", snapshot.values().stream() .map(p -> Utils.formatCurrency(p.getTotalConIva(), locale)) .toList()); pricing.put("show_iva_4", presupuesto.getIvaImporte4().floatValue() > 0); pricing.put("show_iva_21", presupuesto.getIvaImporte21().floatValue() > 0); model.put("pricing", pricing); var spec = new DocumentSpec( DocumentType.PRESUPUESTO, "presupuesto-a4", Locale.forLanguageTag("es-ES"), model); byte[] pdf = this.generate(spec); // HTML // (Opcional) generar HTML de depuración con CSS incrustado try { String templateName = registry.resolve(DocumentType.PRESUPUESTO, "presupuesto-a4"); String html = engine.render(templateName, Locale.forLanguageTag("es-ES"), model); String css = Files.readString(Path.of("src/main/resources/static/assets/css/presupuestopdf.css")); String htmlWithCss = html.replaceFirst("(?i)", "\n"); Path htmlPath = Path.of("target/presupuesto-test.html"); Files.writeString(htmlPath, htmlWithCss, StandardCharsets.UTF_8); } catch (Exception ignore) { /* solo para depuración */ } return pdf; } catch (Exception e) { throw new RuntimeException("Error generando presupuesto PDF", e); } } public byte[] generaFactura(Long facturaId, Locale locale) { try { Factura factura = facturacionService.getFactura(facturaId); if (factura == null) { throw new IllegalArgumentException("Factura no encontrada: " + facturaId); } factura.getLineas().forEach(l -> l.setDescripcion(HtmlToXhtml.toXhtml(l.getDescripcion()))); PedidoDireccion direccionFacturacion = pedidoService .getPedidoDireccionFacturacionByPedidoId(factura.getPedidoId()); if (direccionFacturacion == null) { throw new IllegalArgumentException( "Dirección de facturación no encontrada para el pedido: " + factura.getPedidoId()); } Map model = new HashMap<>(); model.put("factura", factura); model.put("direccionFacturacion", direccionFacturacion); var spec = new DocumentSpec( DocumentType.FACTURA, "factura-a4", locale, model); byte[] pdf = this.generate(spec); // HTML // (Opcional) generar HTML de depuración con CSS incrustado try { String templateName = registry.resolve(DocumentType.FACTURA, "factura-a4"); String html = engine.render(templateName, locale, model); String css = Files.readString(Path.of("src/main/resources/static/assets/css/facturapdf.css")); String htmlWithCss = html.replaceFirst("(?i)", "\n"); Path htmlPath = Path.of("target/factura-test.html"); Files.writeString(htmlPath, htmlWithCss, StandardCharsets.UTF_8); } catch (Exception ignore) { /* solo para depuración */ } return pdf; } catch (Exception e) { throw new RuntimeException("Error generando factura PDF", e); } } }