mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
acabando presupuesto
This commit is contained in:
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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<byte[]> generate(
|
||||
@PathVariable("type") DocumentType type,
|
||||
@PathVariable String templateId,
|
||||
@RequestBody Map<String,Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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) {
|
||||
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<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().getFullName()));
|
||||
|
||||
model.put("titulo", presupuesto.getTitulo());
|
||||
|
||||
/*
|
||||
* Map<String, Object> 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<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 -> 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)</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);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatCurrency(double value, Locale locale) {
|
||||
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale);
|
||||
return currencyFormatter.format(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<String, Object>) 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 += "<br><ul><li>";
|
||||
descripcion += Marcapaginas.Tamanios.valueOf(datosMarcapaginas.get("tamanio").toString()).getLabel() + ", ";
|
||||
descripcion += Marcapaginas.Caras_Impresion.valueOf(datosMarcapaginas.get("carasImpresion").toString()).getMessageKey() + ", ";
|
||||
descripcion += messageSource.getMessage(Marcapaginas.Papeles.valueOf(datosMarcapaginas.get("papel").toString()).getMessageKey(), null, locale) + " - " +
|
||||
descripcion += "<br><ul><li>";
|
||||
descripcion += Marcapaginas.Tamanios.valueOf(datosMarcapaginas.get("tamanio").toString()).getLabel()
|
||||
+ ", ";
|
||||
descripcion += Marcapaginas.Caras_Impresion
|
||||
.valueOf(datosMarcapaginas.get("carasImpresion").toString()).getMessageKey() + ", ";
|
||||
descripcion += messageSource
|
||||
.getMessage(Marcapaginas.Papeles.valueOf(datosMarcapaginas.get("papel").toString())
|
||||
.getMessageKey(), null, locale)
|
||||
+ " - " +
|
||||
datosMarcapaginas.get("gramaje").toString() + " gr, ";
|
||||
descripcion += messageSource.getMessage(Marcapaginas.Acabado.valueOf(datosMarcapaginas.get("acabado").toString()).getMessageKey(), null, locale);
|
||||
descripcion += messageSource.getMessage(
|
||||
Marcapaginas.Acabado.valueOf(datosMarcapaginas.get("acabado").toString()).getMessageKey(),
|
||||
null, locale);
|
||||
descripcion += "</li></ul>";
|
||||
servicioData.put("descripcion", descripcion);
|
||||
|
||||
} else if(servicio.get("id").equals("maquetacion")) {
|
||||
} else if (servicio.get("id").equals("maquetacion")) {
|
||||
String descripcion = servicio.get("label").toString();
|
||||
descripcion += "<br><ul><li>";
|
||||
descripcion += (datosMaquetacion.get("num_caracteres") + " "
|
||||
+ messageSource.getMessage("presupuesto.maquetacion.caracteres", null, locale)) + ", ";
|
||||
descripcion += MaquetacionMatrices.Formato
|
||||
.valueOf(datosMaquetacion.get("formato_maquetacion").toString()).getLabel() + ", ";
|
||||
descripcion += messageSource.getMessage(MaquetacionMatrices.FontSize
|
||||
.valueOf(datosMaquetacion.get("cuerpo_texto").toString()).getMessageKey(), null, locale)
|
||||
+ ", ";
|
||||
descripcion += messageSource.getMessage("presupuesto.maquetacion.num-columnas", null, locale) + ": "
|
||||
+ datosMaquetacion.get("num_columnas").toString() + ", ";
|
||||
descripcion += messageSource.getMessage("presupuesto.maquetacion.num-tablas", null, locale) + ": "
|
||||
+ datosMaquetacion.get("num_tablas").toString() + ", ";
|
||||
descripcion += messageSource.getMessage("presupuesto.maquetacion.num-fotos", null, locale) + ": "
|
||||
+ datosMaquetacion.get("num_fotos").toString();
|
||||
if ((boolean) datosMaquetacion.get("correccion_ortotipografica")) {
|
||||
descripcion += ", " + messageSource
|
||||
.getMessage("presupuesto.maquetacion.correccion-ortotipografica", null, locale);
|
||||
}
|
||||
if ((boolean) datosMaquetacion.get("texto_mecanografiado")) {
|
||||
descripcion += ", " + messageSource.getMessage("presupuesto.maquetacion.texto-mecanografiado",
|
||||
null, locale);
|
||||
}
|
||||
if ((boolean) datosMaquetacion.get("disenio_portada")) {
|
||||
descripcion += ", "
|
||||
+ messageSource.getMessage("presupuesto.maquetacion.diseno-portada", null, locale);
|
||||
}
|
||||
if ((boolean) datosMaquetacion.get("epub")) {
|
||||
descripcion += ", " + messageSource.getMessage("presupuesto.maquetacion.epub", null, locale);
|
||||
}
|
||||
descripcion += "</li></ul>";
|
||||
servicioData.put("descripcion", descripcion);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
servicioData.put("descripcion", servicio.get("label"));
|
||||
}
|
||||
|
||||
|
||||
servicioData.put("precio", servicio.get("id").equals("marcapaginas")
|
||||
? Double.parseDouble(servicio.get("price").toString())
|
||||
/ Double.parseDouble(servicio.get("units").toString())
|
||||
@ -881,7 +904,8 @@ public class PresupuestoService {
|
||||
System.out.println("Error guardando datos adicionales: " + e.getMessage());
|
||||
}
|
||||
|
||||
Map<String, Object> resumen = getTextosResumen(presupuesto, servicios, datosMaquetacion, datosMarcapaginas, locale);
|
||||
Map<String, Object> resumen = getTextosResumen(presupuesto, servicios, datosMaquetacion, datosMarcapaginas,
|
||||
locale);
|
||||
if (resumen.containsKey("error"))
|
||||
return resumen;
|
||||
|
||||
@ -1035,21 +1059,7 @@ public class PresupuestoService {
|
||||
else if (s.get("id").equals("ejemplar-prueba")) {
|
||||
serviciosIva4 = BigDecimal.valueOf(
|
||||
s.get("price") != null ? Double.parseDouble(String.valueOf(s.get("price"))) : 0.0);
|
||||
} else if (s.get("id").equals("marcapaginas")) {
|
||||
PresupuestoMarcapaginas pm = presupuesto.getDatosMarcapaginasJson() != null
|
||||
? new ObjectMapper().readValue(presupuesto.getDatosMarcapaginasJson(),
|
||||
PresupuestoMarcapaginas.class)
|
||||
: null;
|
||||
Map<String, Object> precio_marcapaginas = this.getPrecioMarcapaginas(pm, locale);
|
||||
s.put("price", precio_marcapaginas.getOrDefault("precio_total", 0.0));
|
||||
} else if (s.get("id").equals("maquetacion")) {
|
||||
PresupuestoMaquetacion pm = presupuesto.getDatosMaquetacionJson() != null
|
||||
? new ObjectMapper().readValue(presupuesto.getDatosMaquetacionJson(),
|
||||
PresupuestoMaquetacion.class)
|
||||
: null;
|
||||
Map<String, Object> precio_maquetacion = this.getPrecioMaquetacion(pm, locale);
|
||||
s.put("price", precio_maquetacion.getOrDefault("precio", 0.0));
|
||||
}
|
||||
}
|
||||
double unidades = Double.parseDouble(String.valueOf(s.getOrDefault("units", 0)));
|
||||
double precio = Double.parseDouble(String.valueOf(
|
||||
s.get("id").equals("marcapaginas")
|
||||
@ -1146,7 +1156,8 @@ public class PresupuestoService {
|
||||
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
|
||||
presupuesto.setDatosMarcapaginasJson(
|
||||
datosMarcapaginas != null ? new ObjectMapper().writeValueAsString(datosMarcapaginas) : null);
|
||||
var resumen = this.getTextosResumen(presupuesto, serviciosList, datosMaquetacion, datosMarcapaginas, locale);
|
||||
var resumen = this.getTextosResumen(presupuesto, serviciosList, datosMaquetacion, datosMarcapaginas,
|
||||
locale);
|
||||
|
||||
Object serviciosObj = resumen.get("servicios");
|
||||
|
||||
|
||||
@ -15,6 +15,13 @@ pdf.presupuesto.date=FECHA:
|
||||
|
||||
pdf.presupuesto.titulo=Título:
|
||||
|
||||
pdf.table.tirada=TIRADA
|
||||
pdf.table.impresion=IMPRESIÓN
|
||||
pdf.table.servicios=SERVICIOS
|
||||
pdf.table.iva-4=IVA 4%
|
||||
pdf.table.iva-21=IVA 21%
|
||||
pdf.table.precio-total=PRECIO TOTAL
|
||||
|
||||
pdf.politica-privacidad=Política de privacidad
|
||||
pdf.politica-privacidad.responsable=Responsable: Impresión Imprime Libros - CIF: B04998886 - Teléfono de contacto: 910052574
|
||||
pdf.politica-privacidad.correo-direccion=Correo electrónico: info@imprimelibros.com - Dirección postal: Calle José Picón, Nº 28 Local A, 28028, Madrid
|
||||
|
||||
@ -242,6 +242,7 @@ presupuesto.papel-gramaje=Papel y gramaje
|
||||
# Presupuesto de maquetación
|
||||
presupuesto.maquetacion=Presupuesto de maquetación
|
||||
presupuesto.maquetacion.num-caracteres=Número de caracteres
|
||||
presupuesto.maquetacion.caracteres=caracteres
|
||||
presupuesto.maquetacion.num-caracteres-descripcion=Caracteres con espacios (obtenidos desde Word)
|
||||
presupuesto.maquetacion.formato=Formato
|
||||
presupuesto.maquetacion.formato-descripcion=Seleccione el tamaño que más se aproxime
|
||||
|
||||
@ -3,7 +3,6 @@ import { formateaMoneda } from "../utils.js";
|
||||
|
||||
|
||||
$(document).on('change', '#maquetacion', function (e) {
|
||||
|
||||
e.preventDefault();
|
||||
if ($('#maquetacion').is(':checked')) {
|
||||
$.get("/presupuesto/public/maquetacion/form", function (data) {
|
||||
|
||||
@ -211,6 +211,7 @@ export default class PresupuestoWizard {
|
||||
this.#initDatosGenerales();
|
||||
|
||||
if (presupuestoId && mode !== 'public') {
|
||||
|
||||
await fetch(`/presupuesto/api/get?id=${encodeURIComponent(presupuestoId)}`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -218,12 +219,16 @@ export default class PresupuestoWizard {
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(dto => {
|
||||
sessionStorage.removeItem("formData");
|
||||
this.formData = dto;
|
||||
this.#cacheFormData();
|
||||
this.#loadDatosGeneralesData();
|
||||
});
|
||||
} else {
|
||||
if (stored) {
|
||||
sessionStorage.removeItem("formData");
|
||||
this.formData = JSON.parse(stored);
|
||||
this.#cacheFormData();
|
||||
this.#loadDatosGeneralesData();
|
||||
}
|
||||
}
|
||||
@ -290,8 +295,29 @@ export default class PresupuestoWizard {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Usa function() para que `this` sea el botón
|
||||
$('.btn-imprimir').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// obtén el id de donde lo tengas (data-attr o variable global)
|
||||
const id = this.opts.presupuestoId;
|
||||
|
||||
const url = `/api/pdf/presupuesto/${id}?mode=download`;
|
||||
|
||||
// Truco: crear <a> y hacer click
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_self'; // descarga en la misma pestaña
|
||||
// a.download = `presupuesto-${id}.pdf`; // opcional, tu server ya pone filename
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
async #guardarPresupuesto() {
|
||||
@ -1742,7 +1768,7 @@ export default class PresupuestoWizard {
|
||||
this.divExtras.append(item.render());
|
||||
}
|
||||
|
||||
if (!this.formData.servicios.servicios.includes(s => s.id === "ferro-digital")) {
|
||||
if (!this.formData.servicios.servicios.some(s => s.id === "ferro-digital")) {
|
||||
this.formData.servicios.servicios.push({
|
||||
id: "ferro-digital",
|
||||
label: "Ferro Digital",
|
||||
@ -1776,6 +1802,15 @@ export default class PresupuestoWizard {
|
||||
...result,
|
||||
};
|
||||
|
||||
if (!this.formData.servicios.servicios.some(s => s.id === "maquetacion") && result.precio > 0) {
|
||||
this.formData.servicios.servicios.push({
|
||||
id: "maquetacion",
|
||||
label: $(`label[for="maquetacion"] .service-title`).text().trim(),
|
||||
units: 1,
|
||||
price: result.precio,
|
||||
});
|
||||
}
|
||||
|
||||
this.#cacheFormData();
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,72 +71,8 @@
|
||||
<!-- DATOS TÉCNICOS EN 2 COLUMNAS -->
|
||||
<div class="specs-wrapper align-with-text ">
|
||||
<div class="specs">
|
||||
<div class="col">
|
||||
<div class="block-title">Encuadernación</div>
|
||||
<div class="kv"><span>Encuadernación:</span><b th:text="${encuadernacion} ?: 'Fresado'">Fresado</b></div>
|
||||
<div class="kv"><span>Formato:</span><b>
|
||||
<span th:text="${ancho}">148</span>x<span th:text="${alto}">210</span> mm
|
||||
</b></div>
|
||||
<div class="kv"><span>Páginas:</span><b th:text="${paginasTotales} ?: 132">132</b></div>
|
||||
<div class="kv"><span>Páginas Negro:</span><b th:text="${paginasNegro} ?: 100">100</b></div>
|
||||
<div class="kv"><span>Páginas Color:</span><b th:text="${paginasColor} ?: 32">32</b></div>
|
||||
|
||||
<div class="subblock">
|
||||
<div class="block-title">Interior</div>
|
||||
<div class="kv"><span>Tipo de impresión:</span><b
|
||||
th:text="${interior?.tipoImpresion} ?: 'Color Premium'">Color
|
||||
Premium</b></div>
|
||||
<div class="kv"><span>Papel interior:</span><b th:text="${interior?.papel} ?: 'Estucado Mate'">Estucado
|
||||
Mate</b>
|
||||
</div>
|
||||
<div class="kv"><span>Gramaje interior:</span><b th:text="${interior?.gramaje} ?: 115">115</b></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="subblock">
|
||||
<div class="block-title">Cubierta</div>
|
||||
<div class="kv"><span>Tipo de cubierta:</span><b th:text="${cubierta?.tipo} ?: 'Tapa blanda'">Tapa
|
||||
blanda</b>
|
||||
</div>
|
||||
<div class="kv"><span>Solapas:</span><b th:text="${cubierta?.solapas} ?: 'Sí'">Sí</b></div>
|
||||
<div class="kv"><span>Tamaño solapas:</span><b th:text="${cubierta?.tamSolapas} ?: '80 mm'">80 mm</b></div>
|
||||
<div class="kv"><span>Impresión:</span><b th:text="${cubierta?.impresion} ?: 'Una cara'">Una cara</b></div>
|
||||
<div class="kv"><span>Papel cubierta:</span><b th:text="${cubierta?.papel} ?: 'Estucado mate'">Estucado
|
||||
mate</b>
|
||||
</div>
|
||||
<div class="kv"><span>Gramaje cubierta:</span><b th:text="${cubierta?.gramaje} ?: 250">250</b></div>
|
||||
<div class="kv"><span>Acabado:</span><b
|
||||
th:text="${cubierta?.acabado} ?: 'Plastificado Brillo 1/C'">Plastificado
|
||||
Brillo 1/C</b></div>
|
||||
</div>
|
||||
|
||||
<div class="subblock">
|
||||
<div class="block-title">Servicios Extras</div>
|
||||
<!-- Ejemplos específicos -->
|
||||
<div class="kv" th:if="${servicios != null}">
|
||||
<ul class="services">
|
||||
<li th:each="s : ${servicios}">
|
||||
<span th:text="${s.descripcion}">Ferro Digital</span>
|
||||
<span th:if="${s.precio != null}"
|
||||
th:text="${#numbers.formatDecimal(s.precio,1,'POINT',2,'COMMA')} + ' €'">0,00 €</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Bloque marcapáginas (si existe en servicios) -->
|
||||
<div class="bookmark" th:if="${marcapaginas != null}">
|
||||
<div class="bk-title">Marcapáginas</div>
|
||||
<div class="kv"><span>Tamaño:</span><b th:text="${marcapaginas.tamano} ?: '50x210'">50x210</b></div>
|
||||
<div class="kv"><span>Papel:</span><b th:text="${marcapaginas.papel} ?: 'Estucado mate 300 g'">Estucado
|
||||
mate
|
||||
300 g</b></div>
|
||||
<div class="kv"><span>Impresión:</span><b th:text="${marcapaginas.impresion} ?: 'Una cara'">Una cara</b>
|
||||
</div>
|
||||
<div class="kv"><span>Plastificado:</span><b th:text="${marcapaginas.plastificado} ?: 'Brillo 1/C'">Brillo
|
||||
1/C</b></div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:utext="${especificaciones} ?: '<em>Sin especificaciones técnicas.</em>'">
|
||||
<em>Sin especificaciones técnicas.</em>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- .specs-wrapper -->
|
||||
@ -145,20 +81,22 @@
|
||||
<table class="prices">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-tirada">TIRADA</th>
|
||||
<th>IMPRESIÓN</th>
|
||||
<th>IVA</th>
|
||||
<th>TOTAL</th>
|
||||
<th>UNIDAD</th>
|
||||
<th class="text-center col-tirada" th:text="#{pdf.table.tirada}">TIRADA</th>
|
||||
<th class="text-center" th:text="#{pdf.table.impresion}">IMPRESIÓN</th>
|
||||
<th class="text-center" th:text="#{pdf.table.servicios}">SERVICIOS</th>
|
||||
<th class="text-center" th:if="${pricing.show_iva_4}" th:text="#{pdf.table.iva-4}">IVA 4%</th>
|
||||
<th class="text-center" th:if="${pricing.show_iva_21}" th:text="#{pdf.table.iva-21}">IVA 21%</th>
|
||||
<th class="text-center" th:text="#{pdf.table.precio-total}">PRECIO TOTAL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody th:if="${pricing != null and pricing.tiradas != null}">
|
||||
<tr th:each="t, st : ${pricing.tiradas}">
|
||||
<td class="col-tirada" th:text="${t} + ' uds.'">100 uds.</td>
|
||||
<td th:text="${#numbers.formatDecimal(pricing.impresion[st.index],1,'POINT',2,'COMMA')} + '€'">152,15€</td>
|
||||
<td th:text="${#numbers.formatDecimal(pricing.iva[st.index],1,'POINT',2,'COMMA')} + '€'">7,68€</td>
|
||||
<td th:text="${#numbers.formatDecimal(pricing.total[st.index],1,'POINT',2,'COMMA')} + '€'">159,99€</td>
|
||||
<td th:text="${#numbers.formatDecimal(pricing.unidad[st.index],1,'POINT',2,'COMMA')} + '€'">1,52€</td>
|
||||
<td class="text-center col-tirada" th:text="${t}">100 uds.</td>
|
||||
<td class="text-center" th:text="${pricing.impresion[st.index]}">152,15€</td>
|
||||
<td class="text-center" th:text="${pricing.servicios[st.index]}">7,68€</td>
|
||||
<td class="text-center" th:if="${pricing.show_iva_4}" th:text="${pricing.iva_4[st.index]}">7,68€</td>
|
||||
<td class="text-center" th:if="${pricing.show_iva_21}" th:text="${pricing.iva_21[st.index]}">7,68€</td>
|
||||
<td class="text-center" th:text="${pricing.total[st.index]}">159,99€</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -177,7 +115,8 @@
|
||||
<div class="footer">
|
||||
<div class="privacy">
|
||||
<div class="pv-title" th:text="#{pdf.politica-privacidad}">Política de privacidad</div>
|
||||
<div class="pv-text" th:text="#{pdf.politica-privacidad.responsable}">Responsable: Impresión Imprime Libros - CIF:
|
||||
<div class="pv-text" th:text="#{pdf.politica-privacidad.responsable}">Responsable: Impresión Imprime Libros -
|
||||
CIF:
|
||||
B04998886 - Teléfono de contacto: 910052574</div>
|
||||
<div class="pv-text" th:text="#{pdf.politica-privacidad.correo-direccion}">Correo electrónico:
|
||||
info@imprimelibros.com - Dirección postal: Calle José Picón, Nº 28 Local A, 28028, Madrid</div>
|
||||
|
||||
@ -29,7 +29,7 @@ class PdfSmokeTest {
|
||||
|
||||
@Test
|
||||
void generaPresupuesto() throws Exception {
|
||||
|
||||
/*
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("numero", "2025-00123");
|
||||
model.put("fecha", LocalDate.of(2025, 10, 12));
|
||||
@ -129,5 +129,6 @@ class PdfSmokeTest {
|
||||
Files.write(out, pdf);
|
||||
|
||||
System.out.println("✅ PDF generado en: " + out.toAbsolutePath());
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user