mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
draft de impresion presupuesto, añadiendo posibilidades de iva
This commit is contained in:
@ -8,7 +8,6 @@ public class TemplateRegistry {
|
|||||||
|
|
||||||
public TemplateRegistry(PdfModuleConfig config) {
|
public TemplateRegistry(PdfModuleConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
System.out.println("PDF templates registrados => " + config.getTemplates());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String resolve(DocumentType type, String templateId) {
|
public String resolve(DocumentType type, String templateId) {
|
||||||
|
|||||||
0
src/main/resources/i18n/pdf_en.properties
Normal file
0
src/main/resources/i18n/pdf_en.properties
Normal file
21
src/main/resources/i18n/pdf_es.properties
Normal file
21
src/main/resources/i18n/pdf_es.properties
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
pdf.company.name=Impresión ImprimeLibros S.L.
|
||||||
|
pdf.company.address=C/ José Picón, 28 local A
|
||||||
|
pdf.company.postalcode=28028
|
||||||
|
pdf.company.city=Madrid
|
||||||
|
pdf.company.phone=+34 910052574
|
||||||
|
|
||||||
|
pdf.presupuesto=PRESUPUESTO
|
||||||
|
pdf.factura=FACTURA
|
||||||
|
pdf.pedido=PEDIDO
|
||||||
|
|
||||||
|
# Presupuesto
|
||||||
|
pdf.presupuesto.number=PRESUPUESTO Nº:
|
||||||
|
pdf.presupuesto.client=CLIENTE:
|
||||||
|
pdf.presupuesto.date=FECHA:
|
||||||
|
|
||||||
|
pdf.presupuesto.titulo=Título:
|
||||||
|
|
||||||
|
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
|
||||||
|
pdf.politica-privacidad.aviso=Le comunicamos que los datos que usted nos facilite quedarán incorporados en nuestro registro interno de actividades de tratamiento con el fin de llevar a cabo una adecuada gestión fiscal y contable. Los datos proporcionados se conservarán mientras se mantenga la relación comercial o durante los años necesarios para cumplir con las obligaciones legales. Así mismo, los datos no serán cedidos a terceros salvo en aquellos casos en que exista una obligación legal. Tiene derecho a acceder a sus datos personales, rectificar los datos inexactos, solicitar su supresión, limitar alguno de los tratamientos u oponerse a algún uso vía e-mail, personalmente o mediante correo postal.
|
||||||
@ -72,6 +72,15 @@ presupuesto.espiral-descripcion=Espiral (a partir de 20 páginas)
|
|||||||
presupuesto.wire-o=Wire-O
|
presupuesto.wire-o=Wire-O
|
||||||
presupuesto.wireo=Wire-O
|
presupuesto.wireo=Wire-O
|
||||||
presupuesto.wire-o-descripcion=Wire-O (a partir de 20 páginas)
|
presupuesto.wire-o-descripcion=Wire-O (a partir de 20 páginas)
|
||||||
|
presupuesto.informacion-adicional=Información adicional
|
||||||
|
presupuesto.informacion-adicional-descripcion=Datos adicionales
|
||||||
|
presupuesto.iva-reducido=I.V.A reducido
|
||||||
|
presupuesto.iva-reducido-descripcion=Se verificará que el pedido cumpla con los requisitos establecidos en el Artículo 91 de la Ley 37/1992, sobre inserción de publicidad, antes de proceder con su producción, lo que garantiza la aplicación del IVA reducido del 4%.
|
||||||
|
presupuesto.entrega=Entrega
|
||||||
|
presupuesto.entrega.peninsula=Península y Baleares
|
||||||
|
presupuesto.entrega.canarias=Canarias
|
||||||
|
presupuesto.entrega.paises-ue=Países UE
|
||||||
|
|
||||||
presupuesto.encuadernacion-descripcion=Seleccione la encuadernación del libro
|
presupuesto.encuadernacion-descripcion=Seleccione la encuadernación del libro
|
||||||
presupuesto.continuar-interior=Continuar a diseño interior
|
presupuesto.continuar-interior=Continuar a diseño interior
|
||||||
|
|
||||||
|
|||||||
59
src/main/resources/static/assets/css/bootstrap-for-pdf.css
vendored
Normal file
59
src/main/resources/static/assets/css/bootstrap-for-pdf.css
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* =======================
|
||||||
|
Bootstrap for PDF (compatible con OpenHTMLtoPDF)
|
||||||
|
======================= */
|
||||||
|
|
||||||
|
/* -- TEXT ALIGN -- */
|
||||||
|
.text-start { text-align: left !important; }
|
||||||
|
.text-center { text-align: center !important; }
|
||||||
|
.text-end { text-align: right !important; }
|
||||||
|
|
||||||
|
/* -- FONT WEIGHT -- */
|
||||||
|
.fw-normal { font-weight: 400 !important; }
|
||||||
|
.fw-semibold { font-weight: 600 !important; }
|
||||||
|
.fw-bold { font-weight: 700 !important; }
|
||||||
|
|
||||||
|
/* -- SPACING (margin/padding) -- */
|
||||||
|
.mt-1 { margin-top: 0.25rem !important; }
|
||||||
|
.mt-2 { margin-top: 0.5rem !important; }
|
||||||
|
.mt-3 { margin-top: 1rem !important; }
|
||||||
|
.mb-1 { margin-bottom: 0.25rem !important; }
|
||||||
|
.mb-2 { margin-bottom: 0.5rem !important; }
|
||||||
|
.mb-3 { margin-bottom: 1rem !important; }
|
||||||
|
|
||||||
|
.p-1 { padding: 0.25rem !important; }
|
||||||
|
.p-2 { padding: 0.5rem !important; }
|
||||||
|
.p-3 { padding: 1rem !important; }
|
||||||
|
|
||||||
|
/* -- TABLE -- */
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 10.5pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- BORDER -- */
|
||||||
|
.border {
|
||||||
|
border: 1px solid #dee2e6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-0 { border: 0 !important; }
|
||||||
|
|
||||||
|
/* -- BACKGROUND -- */
|
||||||
|
.bg-light { background-color: #f8f9fa !important; }
|
||||||
|
.bg-white { background-color: #fff !important; }
|
||||||
|
|
||||||
|
/* -- DISPLAY UTILS (limited) -- */
|
||||||
|
.d-block { display: block !important; }
|
||||||
|
.d-inline { display: inline !important; }
|
||||||
|
.d-inline-block { display: inline-block !important; }
|
||||||
@ -414,6 +414,7 @@
|
|||||||
|
|
||||||
.form-switch-presupuesto .form-check-input:checked {
|
.form-switch-presupuesto .form-check-input:checked {
|
||||||
border-color: #92b2a7;
|
border-color: #92b2a7;
|
||||||
|
background-color: #cbcecd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-switch-custom.form-switch-presupuesto .form-check-input:checked::before {
|
.form-switch-custom.form-switch-presupuesto .form-check-input:checked::before {
|
||||||
|
|||||||
@ -1,54 +1,169 @@
|
|||||||
|
:root {
|
||||||
|
--verde: #92b2a7;
|
||||||
|
--letterspace: 8px;
|
||||||
|
/* ← puedes ajustar este valor en el root */
|
||||||
|
-ink: #1b1e28;
|
||||||
|
--muted: #5b6472;
|
||||||
|
--accent: #0ea5e9;
|
||||||
|
/* azul tira a cyan */
|
||||||
|
--line: #e6e8ef;
|
||||||
|
--bg-tag: #f4f7fb;
|
||||||
|
}
|
||||||
|
|
||||||
/* Open Sans (rutas relativas desde css → fonts) */
|
/* Open Sans (rutas relativas desde css → fonts) */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Open Sans";
|
font-family: "Open Sans";
|
||||||
src: url("../fonts/OpenSans-Regular.ttf") format("truetype");
|
src: url("../fonts/OpenSans-Regular.ttf") format("truetype");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Open Sans";
|
font-family: "Open Sans";
|
||||||
src: url("../fonts/OpenSans-SemiBold.ttf") format("truetype");
|
src: url("../fonts/OpenSans-SemiBold.ttf") format("truetype");
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Open Sans";
|
font-family: "Open Sans";
|
||||||
src: url("../fonts/OpenSans-Bold.ttf") format("truetype");
|
src: url("../fonts/OpenSans-Bold.ttf") format("truetype");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--ink: #1b1e28;
|
|
||||||
--muted: #5b6472;
|
|
||||||
--accent: #0ea5e9; /* azul tira a cyan */
|
|
||||||
--line: #e6e8ef;
|
|
||||||
--bg-tag: #f4f7fb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 15mm 14mm 18mm 14mm;
|
margin: 0;
|
||||||
@bottom-right { content: "Página " counter(page) " / " counter(pages); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
font-family: "Open Sans", Arial, sans-serif;
|
body {
|
||||||
|
font-family: "Open Sans" !important;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Top band */
|
.page-content {
|
||||||
.topbar {
|
padding: 15mm 14mm 28mm 14mm; /* ↑ deja 10mm extra para no pisar el footer */
|
||||||
display: table;
|
box-sizing: border-box; /* para que el padding no desborde */
|
||||||
width: 100%;
|
|
||||||
border-bottom: 2px solid var(--accent);
|
|
||||||
padding-bottom: 6mm;
|
|
||||||
margin-bottom: 6mm;
|
|
||||||
}
|
}
|
||||||
.brand { display: table-cell; width: 60%; vertical-align: top; }
|
|
||||||
.logo { height: 30px; display: block; margin-bottom: 4px; }
|
|
||||||
.brand-name { font-weight: 700; }
|
body.has-watermark {
|
||||||
.brand-meta { color: var(--muted); font-size: 9.5pt; }
|
background-image: none !important;
|
||||||
.doc-title { display: table-cell; width: 40%; text-align: right; font-weight: 700; letter-spacing: 3px; }
|
}
|
||||||
|
|
||||||
|
/* ====== HEADER (tabla) ====== */
|
||||||
|
.il-header {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0 0 8mm 0;
|
||||||
|
/* ↓ espacio bajo el header */
|
||||||
|
}
|
||||||
|
|
||||||
|
.il-left,
|
||||||
|
.il-right {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.il-left {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.il-right {
|
||||||
|
width: 50%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.il-logo {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ← tamaño logo */
|
||||||
|
|
||||||
|
/* Caja superior derecha con esquinas */
|
||||||
|
.il-company-box {
|
||||||
|
display: inline-block;
|
||||||
|
align-items: end;
|
||||||
|
/* para alinear a la derecha sin ocupar todo */
|
||||||
|
position: relative;
|
||||||
|
padding: 4mm 4mm;
|
||||||
|
/* ← espacio texto ↔ esquinas */
|
||||||
|
color: #000;
|
||||||
|
font-size: 10.5pt;
|
||||||
|
/* ← tamaño de letra */
|
||||||
|
line-height: 1;
|
||||||
|
/* ← separación entre líneas */
|
||||||
|
max-width: 75mm;
|
||||||
|
/* ← ancho máximo de la caja */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Esquinas */
|
||||||
|
.il-company-box .corner {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
/* ← anchura esquina */
|
||||||
|
height: 20px;
|
||||||
|
/* ← altura esquina */
|
||||||
|
border-color: #92b2a7;
|
||||||
|
/* ← color esquina */
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.tl {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 2px solid #92b2a7;
|
||||||
|
border-left: 2px solid #92b2a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.tr {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border-top: 2px solid #92b2a7;
|
||||||
|
border-right: 2px solid #92b2a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.bl {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 2px solid #92b2a7;
|
||||||
|
border-left: 2px solid #92b2a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner.br {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-bottom: 2px solid #92b2a7;
|
||||||
|
border-right: 2px solid #92b2a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.company-line {
|
||||||
|
margin: 1.5mm 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Nueva banda verde PRESUPUESTO */
|
||||||
|
.doc-banner {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #92b2a7 !important; /* ← tu verde corporativo */
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2mm 0;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
display: block; /* evita conflictos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-text {
|
||||||
|
font-family: "Open Sans", Arial, sans-serif !important;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20pt;
|
||||||
|
letter-spacing: 8px; /* ← configurable */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ficha superior */
|
/* ficha superior */
|
||||||
.sheet-info {
|
.sheet-info {
|
||||||
@ -57,41 +172,99 @@ html, body {
|
|||||||
margin: 4mm 0 6mm 0;
|
margin: 4mm 0 6mm 0;
|
||||||
font-size: 10.5pt;
|
font-size: 10.5pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-info td {
|
.sheet-info td {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
}
|
}
|
||||||
.sheet-info .lbl { color: var(--muted); margin-right: 4px; }
|
|
||||||
.sheet-info .val { font-weight: 700; }
|
.sheet-info .lbl {
|
||||||
|
color: var(--muted);
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.sheet-info .val {
|
||||||
|
}*/
|
||||||
|
|
||||||
/* Línea título libro */
|
/* Línea título libro */
|
||||||
.line-title {
|
.line-title {
|
||||||
|
font-family: "Open Sans", Arial, sans-serif !important;
|
||||||
margin: 3mm 0 5mm 0;
|
margin: 3mm 0 5mm 0;
|
||||||
padding: 4px 6px;
|
padding: 2px 4px;
|
||||||
background: var(--bg-tag);
|
|
||||||
border-left: 3px solid var(--accent);
|
|
||||||
font-size: 10.5pt;
|
font-size: 10.5pt;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #5c5c5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-title .lbl {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.line-title .lbl { color: var(--muted); margin-right: 6px; font-weight: 600; }
|
|
||||||
|
|
||||||
/* Specs 2 columnas */
|
/* Specs 2 columnas */
|
||||||
.specs { display: table; width: 100%; table-layout: fixed; margin-bottom: 6mm; }
|
.specs-wrapper {
|
||||||
.specs .col { display: table-cell; width: 50%; padding-right: 6mm; vertical-align: top; }
|
width: 180mm;
|
||||||
.specs .col:last-child { padding-right: 0; }
|
margin-left: 15mm; /* ← margen izquierdo real del A4 */
|
||||||
|
margin-right: auto; /* opcional */
|
||||||
|
color: #5c5c5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-with-text {
|
||||||
|
margin-left: 1mm;
|
||||||
|
margin-right: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
margin-bottom: 6mm;
|
||||||
|
}
|
||||||
|
.specs .col {
|
||||||
|
display: table-cell;
|
||||||
|
width: 50%;
|
||||||
|
padding-right: 6mm;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.specs .col:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.block-title {
|
.block-title {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
font-size: 10pt;
|
font-size: 8pt;
|
||||||
margin: 2mm 0 1mm 0;
|
margin: 2mm 0 1mm 0;
|
||||||
}
|
}
|
||||||
.kv { margin: 1mm 0; }
|
|
||||||
.kv span { color: var(--muted); display: inline-block; min-width: 55%; }
|
|
||||||
.kv b { font-weight: 600; }
|
|
||||||
.subblock { margin-top: 3mm; }
|
|
||||||
|
|
||||||
.services { margin: 0; padding-left: 14px; }
|
.kv {
|
||||||
.services li { margin: 1mm 0; }
|
margin: 1mm 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kv span {
|
||||||
|
color: var(--muted);
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kv b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subblock {
|
||||||
|
margin-top: 3mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.services {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.services li {
|
||||||
|
margin: 1mm 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Bloque marcapáginas */
|
/* Bloque marcapáginas */
|
||||||
.bookmark {
|
.bookmark {
|
||||||
@ -100,8 +273,10 @@ html, body {
|
|||||||
padding: 3mm;
|
padding: 3mm;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark .bk-title {
|
.bookmark .bk-title {
|
||||||
font-weight: 700; margin-bottom: 2mm;
|
font-weight: 700;
|
||||||
|
margin-bottom: 2mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tabla de precios (tiradas) */
|
/* Tabla de precios (tiradas) */
|
||||||
@ -111,6 +286,7 @@ html, body {
|
|||||||
margin-top: 6mm;
|
margin-top: 6mm;
|
||||||
font-size: 10.5pt;
|
font-size: 10.5pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prices thead th {
|
.prices thead th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@ -118,27 +294,81 @@ html, body {
|
|||||||
background: #eef8fe;
|
background: #eef8fe;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prices tbody td {
|
.prices tbody td {
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
.prices .col-tirada { width: 22%; font-weight: 700; }
|
|
||||||
|
.prices .col-tirada {
|
||||||
|
width: 22%;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 8mm;
|
position: fixed;
|
||||||
|
left: 14mm;
|
||||||
|
right: 14mm;
|
||||||
|
bottom: 18mm;
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
padding-top: 4mm;
|
padding-top: 4mm;
|
||||||
display: table;
|
font-size: 7.5pt;
|
||||||
width: 100%;
|
color: var(--muted);
|
||||||
font-size: 9.5pt;
|
z-index: 10; /* sobre la marca */
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer .address {
|
||||||
|
display: table-cell;
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .privacy {
|
||||||
|
display: table-cell;
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1mm;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pv-text {
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-count {
|
||||||
|
margin-top: 2mm;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 9pt;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
.footer .address { display: table-cell; width: 45%; }
|
|
||||||
.footer .privacy { display: table-cell; width: 55%; }
|
|
||||||
.pv-title { font-weight: 700; margin-bottom: 1mm; color: var(--ink); }
|
|
||||||
.pv-text { line-height: 1.25; }
|
|
||||||
|
|
||||||
.page-count { margin-top: 2mm; text-align: right; font-size: 9pt; color: var(--muted); }
|
.page::after {
|
||||||
.page::after { content: counter(page); }
|
content: counter(page);
|
||||||
.pages::after { content: counter(pages); }
|
}
|
||||||
|
|
||||||
|
.pages::after {
|
||||||
|
content: counter(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caja a página completa SIN vw/vh y SIN z-index negativo */
|
||||||
|
.watermark {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0; /* ocupa toda la HOJA */
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0; /* debajo del contenido */
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark img {
|
||||||
|
position: absolute;
|
||||||
|
top: 245mm; /* baja/sube (70–85%) */
|
||||||
|
left: 155mm; /* desplaza a la derecha si quieres */
|
||||||
|
transform: translate(-50%, -50%) rotate(-15deg);
|
||||||
|
width: 60%; /* tamaño grande, ya no hay recorte por márgenes */
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|||||||
BIN
src/main/resources/static/assets/images/logo-watermark.png
Normal file
BIN
src/main/resources/static/assets/images/logo-watermark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@ -1,142 +1,199 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html xmlns:th="http://www.thymeleaf.org" lang="es">
|
<html xmlns:th="http://www.thymeleaf.org" lang="es">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title th:text="'Presupuesto ' + ${numero}">Presupuesto</title>
|
<title th:text="'Presupuesto ' + ${numero}">Presupuesto</title>
|
||||||
|
<link rel="stylesheet" href="assets/css/bootstrap-for-pdf.css" />
|
||||||
<link rel="stylesheet" href="assets/css/presupuestopdf.css" />
|
<link rel="stylesheet" href="assets/css/presupuestopdf.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
<!-- BANDA SUPERIOR -->
|
<body class="has-watermark">
|
||||||
<div class="topbar">
|
|
||||||
<div class="brand">
|
<div class="watermark">
|
||||||
<img src="assets/img/logo-imprimelibros.png" alt="ImprimeLibros" class="logo"/>
|
<img src="assets/images/logo-watermark.png" alt="Marca de agua" />
|
||||||
<div class="brand-lines">
|
|
||||||
<div class="brand-name" th:text="${empresa?.nombre} ?: 'ImprimeLibros ERP'">ImprimeLibros ERP</div>
|
|
||||||
<div class="brand-meta">
|
|
||||||
<span th:text="${empresa?.direccion} ?: 'C. José Picón, 28 local A · 28028 Madrid'">C. José Picón, 28 local A · 28028 Madrid</span>
|
|
||||||
· <span th:text="${empresa?.telefono} ?: '910052574'">910052574</span>
|
|
||||||
· <span th:text="${empresa?.email} ?: 'info@imprimelibros.com'">info@imprimelibros.com</span>
|
|
||||||
· <span th:text="${empresa?.cif} ?: 'B04998886'">B04998886</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="doc-title">P R E S U P U E S T O</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FICHA Nº / CLIENTE / FECHA -->
|
<div class="page-content">
|
||||||
<table class="sheet-info">
|
<!-- HEADER: logo izq + caja empresa dcha -->
|
||||||
<tr>
|
|
||||||
<td><span class="lbl">PRESUPUESTO Nº:</span> <span class="val" th:text="${numero}">153153</span></td>
|
|
||||||
<td><span class="lbl">CLIENTE:</span> <span class="val" th:text="${cliente?.nombre} ?: '-'">JUAN JOSÉ MÉNDEZ</span></td>
|
|
||||||
<td><span class="lbl">FECHA:</span> <span class="val" th:text="${#temporals.format(fecha, 'dd/MM/yyyy')}">10/10/2025</span></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- TÍTULO DEL LIBRO -->
|
<!-- HEADER: logo izq + caja empresa dcha (tabla, sin flex) -->
|
||||||
<div class="line-title">
|
<table class="il-header">
|
||||||
<span class="lbl">Título:</span>
|
|
||||||
<span class="val" th:text="${titulo} ?: '-'">Libro de prueba</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- DATOS TÉCNICOS EN 2 COLUMNAS -->
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TABLA TIRADAS -->
|
|
||||||
<table class="prices">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-tirada">TIRADA</th>
|
<td class="il-left">
|
||||||
<th>IMPRESIÓN</th>
|
<img src="assets/images/logo-light.png" alt="ImprimeLibros" class="il-logo" />
|
||||||
<th>IVA</th>
|
</td>
|
||||||
<th>TOTAL</th>
|
<td class="il-right">
|
||||||
<th>UNIDAD</th>
|
<div class="il-company-box">
|
||||||
|
<span class="corner tl"></span>
|
||||||
|
<span class="corner tr"></span>
|
||||||
|
<span class="corner bl"></span>
|
||||||
|
<span class="corner br"></span>
|
||||||
|
|
||||||
|
<div class="company-line company-name" th:text="#{pdf.company.name} ?: 'ImprimeLibros'">
|
||||||
|
ImprimeLibros ERP</div>
|
||||||
|
<div class="company-line" th:text="#{pdf.company.address} ?: ''">C/ José Picón, 28 local A</div>
|
||||||
|
<div class="company-line">
|
||||||
|
<span th:text="#{pdf.company.postalcode} ?: '28028'">28028</span>
|
||||||
|
<span th:text="#{pdf.company.city} ?: 'Madrid'">Madrid</span>
|
||||||
|
</div>
|
||||||
|
<div class="company-line" th:text="#{pdf.company.phone} ?: '+34 910052574'">+34 910052574</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</table>
|
||||||
<tbody>
|
|
||||||
<!-- Espera un modelo { tiradas:[...], impresion:[...], iva:[...], total:[...], unidad:[...] } -->
|
|
||||||
<tr th:each="i,idx : ${#numbers.sequence(0, #lists.size(pricing?.tiradas) - 1)}">
|
<!-- BANDA SUPERIOR -->
|
||||||
<td class="col-tirada" th:text="${pricing.tiradas[i]} + ' uds.'">100 uds.</td>
|
<div class="doc-banner">
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.impresion[i],1,'POINT',2,'COMMA')} + '€'">152,15€</td>
|
<div th:text="#{pdf.presupuesto} ?: 'PRESUPUESTO'" class="banner-text">PRESUPUESTO</div>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.iva[i],1,'POINT',2,'COMMA')} + '€'">7,68€</td>
|
</div>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.total[i],1,'POINT',2,'COMMA')} + '€'">159,99€</td>
|
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.unidad[i],1,'POINT',2,'COMMA')} + '€'">1,52€</td>
|
<!-- FICHA Nº / CLIENTE / FECHA -->
|
||||||
|
<table class="sheet-info">
|
||||||
|
<tr>
|
||||||
|
<td class="text-start"><span th:text="#{'pdf.presupuesto.number'}" class="lbl">PRESUPUESTO Nº:</span> <span
|
||||||
|
class="val" th:text="${numero}">153153</span></td>
|
||||||
|
<td class="text-center"><span th:text="#{pdf.presupuesto.client}" class="lbl">CLIENTE:</span> <span class="val"
|
||||||
|
th:text="${cliente?.nombre} ?: '-'">JUAN JOSÉ
|
||||||
|
MÉNDEZ</span></td>
|
||||||
|
<td class="text-end"><span class="lbl" th:text="#{pdf.presupuesto.date}">FECHA:</span> <span class="val"
|
||||||
|
th:text="${#temporals.format(fecha, 'dd/MM/yyyy')}">10/10/2025</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
|
<!-- TÍTULO DEL LIBRO -->
|
||||||
|
<div class="line-title">
|
||||||
|
<span class="lbl" th:text="#{pdf.presupuesto.titulo}">Título:</span>
|
||||||
|
<span class="val" th:text="${titulo} ?: '-'">Libro de prueba</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
</div>
|
||||||
|
</div> <!-- .specs-wrapper -->
|
||||||
|
|
||||||
|
<!-- TABLA TIRADAS -->
|
||||||
|
<table class="prices">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-tirada">TIRADA</th>
|
||||||
|
<th>IMPRESIÓN</th>
|
||||||
|
<th>IVA</th>
|
||||||
|
<th>TOTAL</th>
|
||||||
|
<th>UNIDAD</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>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
<!-- Si no hay pricing, no renderizar filas -->
|
||||||
|
<tbody th:if="${pricing == null or pricing.tiradas == null}">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">Sin datos de tiradas.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- PIE -->
|
<!-- PIE -->
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="address">
|
|
||||||
<div th:text="${empresa?.nombre} ?: 'Impresión Imprime libros SL'">Impresión Imprime libros SL</div>
|
|
||||||
<div th:text="${empresa?.direccion} ?: 'C. José Picón, 28 local A'">C. José Picón, 28 local A</div>
|
|
||||||
<div>28028 Madrid</div>
|
|
||||||
<div th:text="${empresa?.telefono} ?: '910052574'">910052574</div>
|
|
||||||
</div>
|
|
||||||
<div class="privacy">
|
<div class="privacy">
|
||||||
<div class="pv-title">Política de privacidad</div>
|
<div class="pv-title" th:text="#{pdf.politica-privacidad}">Política de privacidad</div>
|
||||||
<div class="pv-text">
|
<div class="pv-text" th:text="#{pdf.politica-privacidad.responsable}">Responsable: Impresión Imprime Libros - CIF:
|
||||||
Responsable: <span th:text="${empresa?.nombre} ?: 'Impresión Imprime Libros'">Impresión Imprime Libros</span> -
|
B04998886 - Teléfono de contacto: 910052574</div>
|
||||||
CIF: <span th:text="${empresa?.cif} ?: 'B04998886'">B04998886</span> -
|
<div class="pv-text" th:text="#{pdf.politica-privacidad.correo-direccion}">Correo electrónico:
|
||||||
Email: <span th:text="${empresa?.email} ?: 'info@imprimelibros.com'">info@imprimelibros.com</span> ·
|
info@imprimelibros.com - Dirección postal: Calle José Picón, Nº 28 Local A, 28028, Madrid</div>
|
||||||
Dirección: <span th:text="${empresa?.direccion} ?: 'Calle José Picón, Nº 28 Local A, 28028, Madrid'">Calle José Picón, Nº 28 Local A, 28028, Madrid</span>.
|
<div class="pv-text" th:text="#{pdf.politica-privacidad.aviso}">
|
||||||
Sus datos se tratarán para la adecuada gestión fiscal y contable…
|
Le comunicamos que los datos que usted nos facilite quedarán incorporados
|
||||||
|
en nuestro registro interno de actividades de tratamiento con el fin de
|
||||||
|
llevar a cabo una adecuada gestión fiscal y contable.
|
||||||
|
Los datos proporcionados se conservarán mientras se mantenga la relación
|
||||||
|
comercial o durante los años necesarios para cumplir con las obligaciones legales.
|
||||||
|
Así mismo, los datos no serán cedidos a terceros salvo en aquellos casos en que exista
|
||||||
|
una obligación legal. Tiene derecho a acceder a sus datos personales, rectificar
|
||||||
|
los datos inexactos, solicitar su supresión, limitar alguno de los tratamientos
|
||||||
|
u oponerse a algún uso vía e-mail, personalmente o mediante correo postal.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="page-count">Página <span class="page"></span> / <span class="pages"></span></div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
<div class="ribbon-content mt-4">
|
<div class="ribbon-content mt-4">
|
||||||
|
|
||||||
<div class="alert alert-danger alert-label-icon rounded-label fade show material-shadow d-none"
|
<div class="alert alert-danger alert-label-icon rounded-label fade show material-shadow d-none" role="alert"
|
||||||
role="alert" id="datos-generales-alert">
|
id="datos-generales-alert">
|
||||||
<i class="ri-error-warning-line label-icon"></i>
|
<i class="ri-error-warning-line label-icon"></i>
|
||||||
<strong th:text="#{presupuesto.errores-title}">Corrija los siguientes errores:</strong>
|
<strong th:text="#{presupuesto.errores-title}">Corrija los siguientes errores:</strong>
|
||||||
<ul class="mb-0" id="datos-generales-alert-list">
|
<ul class="mb-0" id="datos-generales-alert-list">
|
||||||
@ -33,13 +33,15 @@
|
|||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="autor" class="form-label" th:text="#{presupuesto.autor}">Autor</label>
|
<label for="autor" class="form-label" th:text="#{presupuesto.autor}">Autor</label>
|
||||||
<input type="text" class="form-control datos-generales-data" id="autor" th:value="${presupuesto?.autor} ?: ''">
|
<input type="text" class="form-control datos-generales-data" id="autor"
|
||||||
|
th:value="${presupuesto?.autor} ?: ''">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="isbn" class="form-label" th:text="#{presupuesto.isbn}">ISBN</label>
|
<label for="isbn" class="form-label" th:text="#{presupuesto.isbn}">ISBN</label>
|
||||||
<input type="text" class="form-control datos-generales-data" id="isbn" th:value="${presupuesto?.isbn} ?: ''" >
|
<input type="text" class="form-control datos-generales-data" id="isbn"
|
||||||
|
th:value="${presupuesto?.isbn} ?: ''">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -207,7 +209,7 @@
|
|||||||
|
|
||||||
<!-- Opción: Cosido -->
|
<!-- Opción: Cosido -->
|
||||||
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
||||||
th:attr="data-summary-text=#{presupuesto.cosido}" id="cosido">
|
th:attr="data-summary-text=#{presupuesto.cosido}" id="cosido">
|
||||||
<input type="radio" name="tipoEncuadernacion" value="cosido" hidden>
|
<input type="radio" name="tipoEncuadernacion" value="cosido" hidden>
|
||||||
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/cosido.png"
|
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/cosido.png"
|
||||||
alt="Cosido" />
|
alt="Cosido" />
|
||||||
@ -218,7 +220,7 @@
|
|||||||
|
|
||||||
<!-- Opción: Grapado -->
|
<!-- Opción: Grapado -->
|
||||||
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
||||||
th:attr="data-summary-text=#{presupuesto.grapado}" id="grapado">
|
th:attr="data-summary-text=#{presupuesto.grapado}" id="grapado">
|
||||||
<input type="radio" name="tipoEncuadernacion" value="grapado" hidden>
|
<input type="radio" name="tipoEncuadernacion" value="grapado" hidden>
|
||||||
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/grapado.png"
|
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/grapado.png"
|
||||||
alt="Grapado" />
|
alt="Grapado" />
|
||||||
@ -228,7 +230,7 @@
|
|||||||
|
|
||||||
<!-- Opción: Espiral -->
|
<!-- Opción: Espiral -->
|
||||||
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
||||||
th:attr="data-summary-text=#{presupuesto.espiral}" id="espiral">
|
th:attr="data-summary-text=#{presupuesto.espiral}" id="espiral">
|
||||||
<input type="radio" name="tipoEncuadernacion" value="espiral" hidden>
|
<input type="radio" name="tipoEncuadernacion" value="espiral" hidden>
|
||||||
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/espiral.png"
|
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/espiral.png"
|
||||||
alt="Espiral" />
|
alt="Espiral" />
|
||||||
@ -238,7 +240,7 @@
|
|||||||
|
|
||||||
<!-- Opción: Wire-O -->
|
<!-- Opción: Wire-O -->
|
||||||
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
<label class="tipo-libro image-container imagen-selector datos-generales-data"
|
||||||
th:attr="data-summary-text=#{presupuesto.wire-o}" id="wireo">
|
th:attr="data-summary-text=#{presupuesto.wire-o}" id="wireo">
|
||||||
<input type="radio" name="tipoEncuadernacion" value="wireo" hidden>
|
<input type="radio" name="tipoEncuadernacion" value="wireo" hidden>
|
||||||
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/wire-o.png"
|
<img class="image-presupuesto" src="/assets/images/imprimelibros/presupuestador/wire-o.png"
|
||||||
alt="Wire-O" />
|
alt="Wire-O" />
|
||||||
@ -251,9 +253,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Ribbon Shape -->
|
||||||
|
<div class="card ribbon-box border shadow-none mb-lg-0 material-shadow mt-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="ribbon ribbon-primary ribbon-shape" th:text="#{presupuesto.informacion-adicional}">Información
|
||||||
|
adicional
|
||||||
|
</div>
|
||||||
|
<h5 class="fs-14 text-end" th:text="#{presupuesto.informacion-adicional-descripcion}"></h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ribbon-content mt-4">
|
||||||
|
|
||||||
|
<div class="row justify-content-center mb-2">
|
||||||
|
<div class="col-sm-3 justify-content-center">
|
||||||
|
<div
|
||||||
|
class="form-check form-switch form-switch-custom form-switch-presupuesto mb-3 d-flex align-items-center">
|
||||||
|
<input type="checkbox" class="form-check-input datos-generales-data me-2" id="iva-reducido"
|
||||||
|
name="iva-reducido">
|
||||||
|
<label for="iva-reducido" class="form-label d-flex align-items-center mb-0">
|
||||||
|
<span th:text="#{presupuesto.iva-reducido}" class="me-2">I.V. reducido</span>
|
||||||
|
<button type="button" id="btn-iva-reducido-detail"
|
||||||
|
class="btn btn-outline-primary btn-border btn-sm">
|
||||||
|
<i class="ri-questionnaire-line label-icon align-middle fs-16"></i>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center mb-2">
|
||||||
|
<div class="col-sm-3 justify-content-center">
|
||||||
|
<label for="entrega" class="form-label mt-2" th:text="#{presupuesto.entrega}">Entrega</label>
|
||||||
|
<select class="form-select select2 datos-generales-data" id="entrega" name="entrega">
|
||||||
|
<option selected value="peninsula" th:text="#{presupuesto.entrega.peninsula}">Península
|
||||||
|
y
|
||||||
|
Baleares</option>
|
||||||
|
<option value="canarias" th:text="#{presupuesto.entrega.canarias}">Canarias</option>
|
||||||
|
<option value="paises_ue" th:text="#{presupuesto.entrega.paises-ue}">Países UE</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-center gap-3 mt-3">
|
<div class="d-flex align-items-center justify-content-center gap-3 mt-3">
|
||||||
|
|
||||||
<button type="button" id="next-datos-generales" class="btn btn-secondary d-flex align-items-center ms-auto order-2 order-md-3">
|
<button type="button" id="next-datos-generales"
|
||||||
|
class="btn btn-secondary d-flex align-items-center ms-auto order-2 order-md-3">
|
||||||
<span th:text="#{presupuesto.continuar-interior}">Continuar a diseño interior</span>
|
<span th:text="#{presupuesto.continuar-interior}">Continuar a diseño interior</span>
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -5,92 +5,129 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class PdfSmokeTest {
|
class PdfSmokeTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PdfService pdfService;
|
PdfService pdfService;
|
||||||
|
@Autowired
|
||||||
|
PdfTemplateEngine templateEngine;
|
||||||
|
@Autowired
|
||||||
|
TemplateRegistry templateRegistry;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generaPresupuesto() throws Exception {
|
void generaPresupuesto() throws Exception {
|
||||||
Map<String, Object> model = new HashMap<>();
|
|
||||||
model.put("numero", "2025-00123");
|
|
||||||
model.put("fecha", LocalDate.of(2025, 10, 12));
|
|
||||||
model.put("validezDias", 30);
|
|
||||||
|
|
||||||
model.put("empresa", Map.of(
|
Map<String, Object> model = new HashMap<>();
|
||||||
"nombre", "ImprimeLibros ERP",
|
model.put("numero", "2025-00123");
|
||||||
"direccion", "C/ Dirección 123, 28000 Madrid",
|
model.put("fecha", LocalDate.of(2025, 10, 12));
|
||||||
"telefono", "+34 600 000 000",
|
model.put("validezDias", 30);
|
||||||
"email", "info@imprimelibros.com",
|
|
||||||
"cif", "B-12345678",
|
|
||||||
"web", "www.imprimelibros.com"));
|
|
||||||
|
|
||||||
model.put("cliente", Map.of(
|
model.put("empresa", Map.of(
|
||||||
"nombre", "Editorial Ejemplo S.L.",
|
"nombre", "ImprimeLibros ERP",
|
||||||
"cif", "B-00000000",
|
"direccion", "C/ Dirección 123, 28000 Madrid",
|
||||||
"direccion", "Av. de los Libros, 45",
|
"telefono", "+34 600 000 000",
|
||||||
"cp", "28001",
|
"email", "info@imprimelibros.com",
|
||||||
"poblacion", "Madrid",
|
"cif", "B-12345678",
|
||||||
"provincia", "Madrid",
|
"cp", "28000",
|
||||||
"email", "compras@editorial.com"));
|
"poblacion", "Madrid",
|
||||||
|
"web", "www.imprimelibros.com"));
|
||||||
|
|
||||||
model.put("titulo", "Libro de Ejemplo");
|
model.put("cliente", Map.of(
|
||||||
model.put("autor", "Autora Demo");
|
"nombre", "Editorial Ejemplo S.L.",
|
||||||
model.put("isbn", "978-1-2345-6789-0");
|
"cif", "B-00000000",
|
||||||
model.put("ancho", 150);
|
"direccion", "Av. de los Libros, 45",
|
||||||
model.put("alto", 210);
|
"cp", "28001",
|
||||||
model.put("formatoPersonalizado", true);
|
"poblacion", "Madrid",
|
||||||
model.put("paginasNegro", "300");
|
"provincia", "Madrid",
|
||||||
model.put("paginasColor", null);
|
"email", "compras@editorial.com"));
|
||||||
model.put("tiradas", List.of(300, 500, 1000));
|
|
||||||
|
|
||||||
model.put("lineas", List.of(
|
model.put("titulo", "Libro de Ejemplo");
|
||||||
Map.of("descripcion", "Impresión interior B/N offset 80 g",
|
model.put("autor", "Autora Demo");
|
||||||
"meta", "300 páginas · tinta negra · papel 80 g",
|
model.put("isbn", "978-1-2345-6789-0");
|
||||||
"uds", 1000,
|
model.put("ancho", 150);
|
||||||
"precio", 2.15,
|
model.put("alto", 210);
|
||||||
"dto", 0,
|
model.put("formatoPersonalizado", true);
|
||||||
"importe", 2150.0),
|
model.put("paginasNegro", "300");
|
||||||
Map.of("descripcion", "Cubierta color 300 g laminado mate",
|
model.put("paginasColor", null);
|
||||||
"meta", "Lomo 15 mm · 4/0 · laminado mate",
|
model.put("tiradas", List.of(300, 500, 1000));
|
||||||
"uds", 1000,
|
|
||||||
"precio", 0.38,
|
|
||||||
"dto", 5.0,
|
|
||||||
"importe", 361.0)));
|
|
||||||
|
|
||||||
model.put("servicios", List.of(
|
model.put("lineas", List.of(
|
||||||
Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00)));
|
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("baseImponible", 2601.0);
|
model.put("servicios", List.of(
|
||||||
model.put("ivaTipo", 21);
|
Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00)));
|
||||||
model.put("ivaImporte", 546.21);
|
|
||||||
model.put("totalConIva", 3147.21);
|
|
||||||
model.put("peso", 120.0);
|
|
||||||
|
|
||||||
model.put("observaciones", "Presupuesto válido 30 días.<br/>Incluye embalaje estándar.");
|
model.put("baseImponible", 2601.0);
|
||||||
model.put("condiciones", "Entrega 7-10 días laborables tras confirmación de artes finales.");
|
model.put("ivaTipo", 21);
|
||||||
|
model.put("ivaImporte", 546.21);
|
||||||
|
model.put("totalConIva", 3147.21);
|
||||||
|
model.put("peso", 120.0);
|
||||||
|
|
||||||
var spec = new DocumentSpec(
|
model.put("observaciones", "Presupuesto válido 30 días.<br/>Incluye embalaje estándar.");
|
||||||
DocumentType.PRESUPUESTO,
|
model.put("condiciones", "Entrega 7-10 días laborables tras confirmación de artes finales.");
|
||||||
"presupuesto-a4",
|
|
||||||
Locale.forLanguageTag("es-ES"),
|
|
||||||
model);
|
|
||||||
|
|
||||||
byte[] pdf = pdfService.generate(spec);
|
Map<String, Object> pricing = new HashMap<>();
|
||||||
|
pricing.put("tiradas", List.of(100, 200, 300));
|
||||||
|
pricing.put("impresion", List.of(152.15, 276.12, 377.36));
|
||||||
|
pricing.put("iva", List.of(7.68, 12.60, 16.72));
|
||||||
|
pricing.put("total", List.of(159.99, 287.91, 395.03));
|
||||||
|
pricing.put("unidad", List.of(1.52, 1.38, 1.26));
|
||||||
|
model.put("pricing", pricing);
|
||||||
|
|
||||||
Path out = Path.of("target/presupuesto-test.pdf");
|
var spec = new DocumentSpec(
|
||||||
Files.createDirectories(out.getParent());
|
DocumentType.PRESUPUESTO,
|
||||||
Files.write(out, pdf);
|
"presupuesto-a4",
|
||||||
|
Locale.forLanguageTag("es-ES"),
|
||||||
|
model);
|
||||||
|
|
||||||
System.out.println("✅ PDF generado en: " + out.toAbsolutePath());
|
byte[] pdf = pdfService.generate(spec);
|
||||||
}
|
|
||||||
|
// HTML
|
||||||
|
|
||||||
|
String templateName = templateRegistry.resolve(DocumentType.PRESUPUESTO, "presupuesto-a4");
|
||||||
|
String html = templateEngine.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);
|
||||||
|
|
||||||
|
System.out.println("✅ HTML exportado: target/presupuesto-test.html");
|
||||||
|
|
||||||
|
// 🟢 Abrir en el navegador (si está soportado)
|
||||||
|
if (Desktop.isDesktopSupported()) {
|
||||||
|
Desktop.getDesktop().browse(htmlPath.toUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
Path out = Path.of("target/presupuesto-test.pdf");
|
||||||
|
Files.createDirectories(out.getParent());
|
||||||
|
Files.write(out, pdf);
|
||||||
|
|
||||||
|
System.out.println("✅ PDF generado en: " + out.toAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user