diff --git a/src/main/java/com/imprimelibros/erp/pdf/DocumentType.java b/src/main/java/com/imprimelibros/erp/pdf/DocumentType.java index 4f27aef..b73de7a 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/DocumentType.java +++ b/src/main/java/com/imprimelibros/erp/pdf/DocumentType.java @@ -1,5 +1,10 @@ package com.imprimelibros.erp.pdf; public enum DocumentType { - PRESUPUESTO, PEDIDO, FACTURA -} + PRESUPUESTO, PEDIDO, FACTURA; + + @Override + public String toString() { + return name().toLowerCase(); + } +} \ No newline at end of file diff --git a/src/main/java/com/imprimelibros/erp/pdf/PdfController.java b/src/main/java/com/imprimelibros/erp/pdf/PdfController.java index 6c4f5c6..1d1a4a3 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/PdfController.java +++ b/src/main/java/com/imprimelibros/erp/pdf/PdfController.java @@ -1,32 +1,47 @@ package com.imprimelibros.erp.pdf; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ContentDisposition; 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; } + public PdfController(PdfService pdfService) { + this.pdfService = pdfService; + } - @PostMapping("/{type}/{templateId}") + @GetMapping(value = "/{type}/{id}", produces = "application/pdf") public ResponseEntity generate( - @PathVariable("type") DocumentType type, - @PathVariable String templateId, - @RequestBody Map model, + @PathVariable("type") String type, + @PathVariable String id, + @RequestParam(defaultValue = "inline") String mode, Locale locale) { - var spec = new DocumentSpec(type, templateId, locale, model); - var pdf = pdfService.generate(spec); + if (type.equals(DocumentType.PRESUPUESTO.toString()) && id == null) { + throw new IllegalArgumentException("Falta el ID del presupuesto para generar el PDF"); + } + if (type.equals(DocumentType.PRESUPUESTO.toString())) { + Long presupuestoId = Long.valueOf(id); + byte[] pdf = pdfService.generaPresupuesto(presupuestoId, locale); + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentDisposition( + ("download".equals(mode) + ? ContentDisposition.attachment() + : ContentDisposition.inline()).filename("presupuesto-" + id + ".pdf").build()); + + return new ResponseEntity<>(pdf, headers, HttpStatus.OK); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } - var fileName = type.name().toLowerCase() + "-" + templateId + ".pdf"; - return ResponseEntity.ok() - .header("Content-Type", "application/pdf") - .header("Content-Disposition", "inline; filename=\"" + fileName + "\"") - .body(pdf); } } diff --git a/src/main/java/com/imprimelibros/erp/pdf/PdfService.java b/src/main/java/com/imprimelibros/erp/pdf/PdfService.java index 246d0f5..4ab1688 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/PdfService.java +++ b/src/main/java/com/imprimelibros/erp/pdf/PdfService.java @@ -1,20 +1,99 @@ package com.imprimelibros.erp.pdf; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.NumberFormat; +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; + @Service public class PdfService { private final TemplateRegistry registry; private final PdfTemplateEngine engine; private final PdfRenderer renderer; + private final PresupuestoRepository presupuestoRepository; - public PdfService(TemplateRegistry registry, PdfTemplateEngine engine, PdfRenderer renderer) { + 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) { this.registry = registry; this.engine = engine; this.renderer = renderer; + this.presupuestoRepository = presupuestoRepository; } - public byte[] generate(DocumentSpec spec) { + 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()); @@ -22,4 +101,104 @@ public class PdfService { 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().getFullName())); + + model.put("titulo", presupuesto.getTitulo()); + + /* + * Map resumen = presupuestoService.getTextosResumen( + * presupuesto, null, model, model, null) + */ + model.put("lineas", List.of( + Map.of("descripcion", "Impresión interior B/N offset 80 g", + "meta", "300 páginas · tinta negra · papel 80 g", + "uds", 1000, + "precio", 2.15, + "dto", 0, + "importe", 2150.0), + Map.of("descripcion", "Cubierta color 300 g laminado mate", + "meta", "Lomo 15 mm · 4/0 · laminado mate", + "uds", 1000, + "precio", 0.38, + "dto", 5.0, + "importe", 361.0))); + + model.put("servicios", List.of( + Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00))); + + 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 -> formatCurrency(p.getPrecioTotalTirada(), locale)) + .toList()); + pricing.put("servicios", snapshot.values().stream() + .map(p -> formatCurrency(p.getServiciosTotal(), locale)) + .toList()); + pricing.put("peso", snapshot.values().stream() + .map(p -> formatCurrency(p.getPeso(), locale)) + .toList()); + pricing.put("iva_4", snapshot.values().stream() + .map(p -> formatCurrency(p.getIvaImporte4(), locale)) + .toList()); + pricing.put("iva_21", snapshot.values().stream() + .map(p -> formatCurrency(p.getIvaImporte21(), locale)) + .toList()); + pricing.put("total", snapshot.values().stream() + .map(p -> 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); + } + } + + private static String formatCurrency(double value, Locale locale) { + NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale); + return currencyFormatter.format(value); + } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/maquetacion/MaquetacionMatrices.java b/src/main/java/com/imprimelibros/erp/presupuesto/maquetacion/MaquetacionMatrices.java index e826b08..e0bc7da 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/maquetacion/MaquetacionMatrices.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/maquetacion/MaquetacionMatrices.java @@ -7,11 +7,29 @@ import jakarta.persistence.*; public class MaquetacionMatrices { public enum Formato{ - A5, _17x24_, A4 + A5, _17x24_, A4; + private final String label; + Formato() { + this.label = this.name().indexOf('_') > -1 ? this.name().replace("_", "") + " mm" : this.name(); + } + + public String getLabel() { + return label; + } } public enum FontSize{ - small, medium, big + small("presupuesto.maquetacion.cuerpo-texto-pequeño"), + medium("presupuesto.maquetacion.cuerpo-texto-medio"), + big("presupuesto.maquetacion.cuerpo-texto-grande"); + + private final String messageKey; + FontSize(String messageKey) { + this.messageKey = messageKey; + } + public String getMessageKey() { + return messageKey; + } } @Id diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java index e9fbdec..53524de 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/service/PresupuestoService.java @@ -11,8 +11,6 @@ import java.util.Locale; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,12 +36,11 @@ import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta; +import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices; import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatricesRepository; 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; @@ -815,33 +812,59 @@ public class PresupuestoService { servicioData.put("id", servicio.get("id")); if (servicio.get("id").equals("marcapaginas")) { String descripcion = servicio.get("label").toString(); - /*String papel_marcapaginas = datosMarcapaginas != null - ? ((Map) datosMarcapaginas).get("papel").toString() - : ""; - if (papel_marcapaginas.equals("cartulina_grafica")) { - papel_marcapaginas = messageSource.getMessage("presupuesto.marcapaginas.papel.cartulina-grafica", null, locale); - } else if (papel_marcapaginas.equals("estucado_mate")) { - papel_marcapaginas = messageSource.getMessage("presupuesto.marcapaginas.papel.estucado-mate", null, locale); - } else { - papel_marcapaginas = ""; - }*/ - descripcion += "