Files
erp-imprimelibros/src/main/java/com/imprimelibros/erp/pdf/PdfService.java

244 lines
9.5 KiB
Java

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<String, String> 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<String, Object> 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<String, Object> specs = utils.getTextoPresupuesto(presupuesto, locale);
model.put("specs", specs);
Map<String, Object> pricing = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
// Si quieres parsear directamente a un Map:
Map<Integer, PrecioTirada> snapshot = mapper.readValue(presupuesto.getPricingSnapshotJson(),
mapper.getTypeFactory().constructMapType(Map.class, Integer.class, PrecioTirada.class));
List<Integer> 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)</head>", "<style>\n" + css + "\n</style>\n</head>");
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<String, Object> 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)</head>", "<style>\n" + css + "\n</style>\n</head>");
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);
}
}
}