mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-30 15:48:49 +00:00
Compare commits
3 Commits
f26f96a490
...
ea8a005cde
| Author | SHA1 | Date | |
|---|---|---|---|
| ea8a005cde | |||
| ff9c04afb6 | |||
| 060b435388 |
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.5.3</version>
|
<version>3.5.6</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
<relativePath /> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.imprimelibros</groupId>
|
<groupId>com.imprimelibros</groupId>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
package com.imprimelibros.erp.pdf;
|
package com.imprimelibros.erp.pdf;
|
||||||
|
|
||||||
public enum DocumentType {
|
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;
|
package com.imprimelibros.erp.pdf;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
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.Locale;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/pdf")
|
@RequestMapping("/api/pdf")
|
||||||
public class PdfController {
|
public class PdfController {
|
||||||
private final PdfService pdfService;
|
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(
|
public ResponseEntity<byte[]> generate(
|
||||||
@PathVariable("type") DocumentType type,
|
@PathVariable("type") String type,
|
||||||
@PathVariable String templateId,
|
@PathVariable String id,
|
||||||
@RequestBody Map<String,Object> model,
|
@RequestParam(defaultValue = "inline") String mode,
|
||||||
Locale locale) {
|
Locale locale) {
|
||||||
|
|
||||||
var spec = new DocumentSpec(type, templateId, locale, model);
|
if (type.equals(DocumentType.PRESUPUESTO.toString()) && id == null) {
|
||||||
var pdf = pdfService.generate(spec);
|
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;
|
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 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
|
@Service
|
||||||
public class PdfService {
|
public class PdfService {
|
||||||
private final TemplateRegistry registry;
|
private final TemplateRegistry registry;
|
||||||
private final PdfTemplateEngine engine;
|
private final PdfTemplateEngine engine;
|
||||||
private final PdfRenderer renderer;
|
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.registry = registry;
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
|
this.presupuestoRepository = presupuestoRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] generate(DocumentSpec spec) {
|
private byte[] generate(DocumentSpec spec) {
|
||||||
var template = registry.resolve(spec.type(), spec.templateId());
|
var template = registry.resolve(spec.type(), spec.templateId());
|
||||||
if (template == null) {
|
if (template == null) {
|
||||||
throw new IllegalArgumentException("Plantilla no registrada: " + spec.type() + ":" + spec.templateId());
|
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());
|
var html = engine.render(template, spec.locale(), spec.model());
|
||||||
return renderer.renderHtmlToPdf(html);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -478,14 +478,19 @@ public class PresupuestoController {
|
|||||||
Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class);
|
Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class);
|
||||||
Boolean save = objectMapper.convertValue(body.get("save"), Boolean.class);
|
Boolean save = objectMapper.convertValue(body.get("save"), Boolean.class);
|
||||||
String mode = objectMapper.convertValue(body.get("mode"), String.class);
|
String mode = objectMapper.convertValue(body.get("mode"), String.class);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> datosMaquetacion = (Map<String, Object>) objectMapper
|
||||||
|
.convertValue(body.get("datosMaquetacion"), Map.class);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> datosMarcapaginas = (Map<String, Object>) objectMapper
|
||||||
|
.convertValue(body.get("datosMarcapaginas"), Map.class);
|
||||||
|
|
||||||
String sessionId = request.getSession(true).getId();
|
String sessionId = request.getSession(true).getId();
|
||||||
String ip = IpUtils.getClientIp(request);
|
String ip = IpUtils.getClientIp(request);
|
||||||
|
|
||||||
var resumen = presupuestoService.getResumen(p, serviciosList, save, mode, locale, sessionId, ip);
|
var resumen = presupuestoService.getResumen(p, serviciosList, datosMaquetacion, datosMarcapaginas, save, mode, locale, sessionId, ip);
|
||||||
|
|
||||||
return ResponseEntity.ok(resumen);
|
return ResponseEntity.ok(resumen);
|
||||||
}
|
}
|
||||||
@ -705,7 +710,7 @@ public class PresupuestoController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(path = "api/save")
|
@PostMapping(path = "/api/save")
|
||||||
public ResponseEntity<?> save(
|
public ResponseEntity<?> save(
|
||||||
@RequestBody Map<String, Object> body,
|
@RequestBody Map<String, Object> body,
|
||||||
Locale locale, HttpServletRequest request) {
|
Locale locale, HttpServletRequest request) {
|
||||||
|
|||||||
@ -7,11 +7,29 @@ import jakarta.persistence.*;
|
|||||||
public class MaquetacionMatrices {
|
public class MaquetacionMatrices {
|
||||||
|
|
||||||
public enum Formato{
|
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{
|
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
|
@Id
|
||||||
|
|||||||
@ -10,23 +10,61 @@ import jakarta.validation.constraints.Min;
|
|||||||
public class Marcapaginas {
|
public class Marcapaginas {
|
||||||
|
|
||||||
public enum Acabado{
|
public enum Acabado{
|
||||||
ninguno,
|
ninguno("presupuesto.marcapaginas.acabado.ninguno"),
|
||||||
plastificado_brillo_1c,
|
plastificado_brillo_1c("presupuesto.marcapaginas.acabado.plastificado-brillo-1c"),
|
||||||
plastificado_brillo_2c,
|
plastificado_brillo_2c("presupuesto.marcapaginas.acabado.plastificado-brillo-2c"),
|
||||||
plastificado_mate_1c,
|
plastificado_mate_1c("presupuesto.marcapaginas.acabado.plastificado-mate-1c"),
|
||||||
plastificado_mate_2c
|
plastificado_mate_2c("presupuesto.marcapaginas.acabado.plastificado-mate-2c");
|
||||||
|
|
||||||
|
private final String messageKey;
|
||||||
|
|
||||||
|
Acabado(String messageKey) {
|
||||||
|
this.messageKey = messageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageKey() {
|
||||||
|
return messageKey;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public enum Tamanios{
|
public enum Tamanios{
|
||||||
_50x140_, _50x170_, _50x210_
|
_50x140_,
|
||||||
|
_50x170_,
|
||||||
|
_50x210_;
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
Tamanios() {
|
||||||
|
this.label = this.name().replace("_", "") + " mm";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public enum Papeles{
|
public enum Papeles{
|
||||||
cartulina_grafica, estucado_mate
|
cartulina_grafica,
|
||||||
|
estucado_mate;
|
||||||
|
|
||||||
|
private final String messageKey;
|
||||||
|
Papeles() {
|
||||||
|
this.messageKey = "presupuesto.marcapaginas.papel." + this.name().replace("_", "-");
|
||||||
|
}
|
||||||
|
public String getMessageKey() {
|
||||||
|
return messageKey;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public enum Caras_Impresion{
|
public enum Caras_Impresion{
|
||||||
una_cara, dos_caras
|
una_cara("presupuesto.marcapaginas.caras-impresion-1"),
|
||||||
|
dos_caras("presupuesto.marcapaginas.caras-impresion-2");
|
||||||
|
private final String messageKey;
|
||||||
|
Caras_Impresion(String messageKey) {
|
||||||
|
this.messageKey = messageKey;
|
||||||
|
}
|
||||||
|
public String getMessageKey() {
|
||||||
|
return messageKey;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
@ -100,11 +100,11 @@ public class PresupuestoFormDataMapper {
|
|||||||
|
|
||||||
public static class DatosMarcapaginas {
|
public static class DatosMarcapaginas {
|
||||||
public Integer marcapaginas_tirada = 100;
|
public Integer marcapaginas_tirada = 100;
|
||||||
public String tamanio_marcapaginas = "_50x140_";
|
public String tamanio = "_50x140_";
|
||||||
public String caras_impresion = "una_cara";
|
public String carasImpresion = "una_cara";
|
||||||
public String papel_marcapaginas = "cartulina_grafica";
|
public String papel = "cartulina_grafica";
|
||||||
public Integer gramaje_marcapaginas = 300;
|
public Integer gramaje = 300;
|
||||||
public String acabado_marcapaginas = "ninguno";
|
public String acabado = "ninguno";
|
||||||
public Resultado resultado = new Resultado();
|
public Resultado resultado = new Resultado();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,6 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.classes.PresupuestoMarcapaginas;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto.TipoCubierta;
|
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.maquetacion.MaquetacionMatricesRepository;
|
||||||
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
|
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
|
||||||
import com.imprimelibros.erp.users.UserDao;
|
import com.imprimelibros.erp.users.UserDao;
|
||||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
|
||||||
|
|
||||||
import jakarta.persistence.criteria.CriteriaBuilder.In;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||||
@ -119,13 +116,11 @@ public class PresupuestoService {
|
|||||||
opcionColorHq.setSelected(true);
|
opcionColorHq.setSelected(true);
|
||||||
opciones.add(opcionColorHq);
|
opciones.add(opcionColorHq);
|
||||||
} else {
|
} else {
|
||||||
if (!this.isPOD(presupuesto)) {
|
ImagenPresupuesto opcionNegro = this.presupuestadorItems.getImpresionNegro(locale);
|
||||||
// POD solo negro premium
|
if (Presupuesto.TipoImpresion.negro.equals(presupuesto.getTipoImpresion()))
|
||||||
ImagenPresupuesto opcionNegro = this.presupuestadorItems.getImpresionNegro(locale);
|
opcionNegro.setSelected(true);
|
||||||
if (Presupuesto.TipoImpresion.negro.equals(presupuesto.getTipoImpresion()))
|
opciones.add(opcionNegro);
|
||||||
opcionNegro.setSelected(true);
|
|
||||||
opciones.add(opcionNegro);
|
|
||||||
}
|
|
||||||
ImagenPresupuesto opcionNegroHq = this.presupuestadorItems.getImpresionNegroPremium(locale);
|
ImagenPresupuesto opcionNegroHq = this.presupuestadorItems.getImpresionNegroPremium(locale);
|
||||||
if (Presupuesto.TipoImpresion.negrohq.equals(presupuesto.getTipoImpresion()))
|
if (Presupuesto.TipoImpresion.negrohq.equals(presupuesto.getTipoImpresion()))
|
||||||
opcionNegroHq.setSelected(true);
|
opcionNegroHq.setSelected(true);
|
||||||
@ -148,12 +143,14 @@ public class PresupuestoService {
|
|||||||
List<ImagenPresupuesto> opciones = new ArrayList<>();
|
List<ImagenPresupuesto> opciones = new ArrayList<>();
|
||||||
|
|
||||||
opciones.add(this.presupuestadorItems.getPapelOffsetBlanco(locale));
|
opciones.add(this.presupuestadorItems.getPapelOffsetBlanco(locale));
|
||||||
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
|
if ((presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro
|
||||||
|
&& !this.isPOD(presupuesto)) ||
|
||||||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
||||||
opciones.add(this.presupuestadorItems.getPapelOffsetBlancoVolumen(locale));
|
opciones.add(this.presupuestadorItems.getPapelOffsetBlancoVolumen(locale));
|
||||||
}
|
}
|
||||||
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesado(locale));
|
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesado(locale));
|
||||||
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
|
if ((presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro
|
||||||
|
&& !this.isPOD(presupuesto)) ||
|
||||||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
||||||
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesadoVolumen(locale));
|
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesadoVolumen(locale));
|
||||||
}
|
}
|
||||||
@ -226,7 +223,8 @@ public class PresupuestoService {
|
|||||||
gramajes.add("100");
|
gramajes.add("100");
|
||||||
gramajes.add("115");
|
gramajes.add("115");
|
||||||
}
|
}
|
||||||
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
|
if ((presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro
|
||||||
|
&& !this.isPOD(presupuesto)) ||
|
||||||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
|
||||||
gramajes.add("120");
|
gramajes.add("120");
|
||||||
}
|
}
|
||||||
@ -749,7 +747,7 @@ public class PresupuestoService {
|
|||||||
* Mantiene firma para no romper llamadas existentes.
|
* Mantiene firma para no romper llamadas existentes.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> getTextosResumen(Presupuesto presupuesto, List<Map<String, Object>> servicios,
|
public Map<String, Object> getTextosResumen(Presupuesto presupuesto, List<Map<String, Object>> servicios,
|
||||||
Locale locale) {
|
Map<String, Object> datosMaquetacion, Map<String, Object> datosMarcapaginas, Locale locale) {
|
||||||
Map<String, Object> resumen = new HashMap<>();
|
Map<String, Object> resumen = new HashMap<>();
|
||||||
resumen.put("titulo", presupuesto.getTitulo());
|
resumen.put("titulo", presupuesto.getTitulo());
|
||||||
|
|
||||||
@ -812,7 +810,61 @@ public class PresupuestoService {
|
|||||||
for (Map<String, Object> servicio : servicios) {
|
for (Map<String, Object> servicio : servicios) {
|
||||||
HashMap<String, Object> servicioData = new HashMap<>();
|
HashMap<String, Object> servicioData = new HashMap<>();
|
||||||
servicioData.put("id", servicio.get("id"));
|
servicioData.put("id", servicio.get("id"));
|
||||||
servicioData.put("descripcion", servicio.get("label"));
|
if (servicio.get("id").equals("marcapaginas")) {
|
||||||
|
String descripcion = servicio.get("label").toString();
|
||||||
|
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 += "</li></ul>";
|
||||||
|
servicioData.put("descripcion", descripcion);
|
||||||
|
|
||||||
|
} 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 {
|
||||||
|
servicioData.put("descripcion", servicio.get("label"));
|
||||||
|
}
|
||||||
|
|
||||||
servicioData.put("precio", servicio.get("id").equals("marcapaginas")
|
servicioData.put("precio", servicio.get("id").equals("marcapaginas")
|
||||||
? Double.parseDouble(servicio.get("price").toString())
|
? Double.parseDouble(servicio.get("price").toString())
|
||||||
/ Double.parseDouble(servicio.get("units").toString())
|
/ Double.parseDouble(servicio.get("units").toString())
|
||||||
@ -834,6 +886,8 @@ public class PresupuestoService {
|
|||||||
public Map<String, Object> getResumen(
|
public Map<String, Object> getResumen(
|
||||||
Presupuesto presupuesto,
|
Presupuesto presupuesto,
|
||||||
List<Map<String, Object>> servicios,
|
List<Map<String, Object>> servicios,
|
||||||
|
Map<String, Object> datosMaquetacion,
|
||||||
|
Map<String, Object> datosMarcapaginas,
|
||||||
Boolean save,
|
Boolean save,
|
||||||
String mode,
|
String mode,
|
||||||
Locale locale,
|
Locale locale,
|
||||||
@ -841,7 +895,17 @@ public class PresupuestoService {
|
|||||||
String ip) {
|
String ip) {
|
||||||
|
|
||||||
// 1) Calcula el resumen (como ya haces)
|
// 1) Calcula el resumen (como ya haces)
|
||||||
Map<String, Object> resumen = getTextosResumen(presupuesto, servicios, locale);
|
try {
|
||||||
|
presupuesto.setDatosMaquetacionJson(
|
||||||
|
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
|
||||||
|
presupuesto.setDatosMarcapaginasJson(
|
||||||
|
datosMarcapaginas != null ? new ObjectMapper().writeValueAsString(datosMarcapaginas) : null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Error guardando datos adicionales: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resumen = getTextosResumen(presupuesto, servicios, datosMaquetacion, datosMarcapaginas,
|
||||||
|
locale);
|
||||||
if (resumen.containsKey("error"))
|
if (resumen.containsKey("error"))
|
||||||
return resumen;
|
return resumen;
|
||||||
|
|
||||||
@ -876,6 +940,7 @@ public class PresupuestoService {
|
|||||||
entidad = mergePresupuesto(entidad, presupuesto);
|
entidad = mergePresupuesto(entidad, presupuesto);
|
||||||
|
|
||||||
if (save != null && save) {
|
if (save != null && save) {
|
||||||
|
|
||||||
// Si NO es para guardar (solo calcular resumen), devolver sin persistir
|
// Si NO es para guardar (solo calcular resumen), devolver sin persistir
|
||||||
presupuestoRepository.saveAndFlush(presupuesto);
|
presupuestoRepository.saveAndFlush(presupuesto);
|
||||||
}
|
}
|
||||||
@ -994,21 +1059,7 @@ public class PresupuestoService {
|
|||||||
else if (s.get("id").equals("ejemplar-prueba")) {
|
else if (s.get("id").equals("ejemplar-prueba")) {
|
||||||
serviciosIva4 = BigDecimal.valueOf(
|
serviciosIva4 = BigDecimal.valueOf(
|
||||||
s.get("price") != null ? Double.parseDouble(String.valueOf(s.get("price"))) : 0.0);
|
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 unidades = Double.parseDouble(String.valueOf(s.getOrDefault("units", 0)));
|
||||||
double precio = Double.parseDouble(String.valueOf(
|
double precio = Double.parseDouble(String.valueOf(
|
||||||
s.get("id").equals("marcapaginas")
|
s.get("id").equals("marcapaginas")
|
||||||
@ -1105,7 +1156,8 @@ public class PresupuestoService {
|
|||||||
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
|
datosMaquetacion != null ? new ObjectMapper().writeValueAsString(datosMaquetacion) : null);
|
||||||
presupuesto.setDatosMarcapaginasJson(
|
presupuesto.setDatosMarcapaginasJson(
|
||||||
datosMarcapaginas != null ? new ObjectMapper().writeValueAsString(datosMarcapaginas) : null);
|
datosMarcapaginas != null ? new ObjectMapper().writeValueAsString(datosMarcapaginas) : null);
|
||||||
var resumen = this.getTextosResumen(presupuesto, serviciosList, locale);
|
var resumen = this.getTextosResumen(presupuesto, serviciosList, datosMaquetacion, datosMarcapaginas,
|
||||||
|
locale);
|
||||||
|
|
||||||
Object serviciosObj = resumen.get("servicios");
|
Object serviciosObj = resumen.get("servicios");
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@ app.guardar=Guardar
|
|||||||
app.editar=Editar
|
app.editar=Editar
|
||||||
app.eliminar=Eliminar
|
app.eliminar=Eliminar
|
||||||
app.imprimir=Imprimir
|
app.imprimir=Imprimir
|
||||||
|
app.acciones.siguiente=Siguiente
|
||||||
|
app.acciones.anterior=Anterior
|
||||||
|
|
||||||
app.bienvenido=Bienvenido
|
app.bienvenido=Bienvenido
|
||||||
app.perfil=Perfil
|
app.perfil=Perfil
|
||||||
|
|||||||
@ -15,6 +15,13 @@ pdf.presupuesto.date=FECHA:
|
|||||||
|
|
||||||
pdf.presupuesto.titulo=Título:
|
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=Política de privacidad
|
||||||
pdf.politica-privacidad.responsable=Responsable: Impresión Imprime Libros - CIF: B04998886 - Teléfono de contacto: 910052574
|
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.correo-direccion=Correo electrónico: info@imprimelibros.com - Dirección postal: Calle José Picón, Nº 28 Local A, 28028, Madrid
|
||||||
|
|||||||
@ -203,6 +203,7 @@ presupuesto.calcular-presupuesto=Calcular presupuesto
|
|||||||
presupuesto.consultar-soporte=Consultar con soporte
|
presupuesto.consultar-soporte=Consultar con soporte
|
||||||
|
|
||||||
# Pestaña resumen del presupuesto
|
# Pestaña resumen del presupuesto
|
||||||
|
presupuesto.resumen.tamanio=Tamaño
|
||||||
presupuesto.resumen.tabla.descripcion=Descripción
|
presupuesto.resumen.tabla.descripcion=Descripción
|
||||||
presupuesto.resumen.tabla.cantidad=Cantidad
|
presupuesto.resumen.tabla.cantidad=Cantidad
|
||||||
presupuesto.resumen.tabla.precio-unidad=Precio/unidad
|
presupuesto.resumen.tabla.precio-unidad=Precio/unidad
|
||||||
@ -241,6 +242,7 @@ presupuesto.papel-gramaje=Papel y gramaje
|
|||||||
# Presupuesto de maquetación
|
# Presupuesto de maquetación
|
||||||
presupuesto.maquetacion=Presupuesto de maquetación
|
presupuesto.maquetacion=Presupuesto de maquetación
|
||||||
presupuesto.maquetacion.num-caracteres=Número de caracteres
|
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.num-caracteres-descripcion=Caracteres con espacios (obtenidos desde Word)
|
||||||
presupuesto.maquetacion.formato=Formato
|
presupuesto.maquetacion.formato=Formato
|
||||||
presupuesto.maquetacion.formato-descripcion=Seleccione el tamaño que más se aproxime
|
presupuesto.maquetacion.formato-descripcion=Seleccione el tamaño que más se aproxime
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
|
/* habilita container queries en tu contenedor principal */
|
||||||
|
#presupuesto-row {
|
||||||
|
container-type: inline-size;
|
||||||
|
container-name: presupuesto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* === Contenedor de cada opción === */
|
/* === Contenedor de cada opción === */
|
||||||
.image-container {
|
.image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -248,6 +255,10 @@
|
|||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease, background .22s ease;
|
transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease, background .22s ease;
|
||||||
|
display: block;
|
||||||
|
width: clamp(155px, 28vw, 250px); /* crece fluido pero nunca <155 ni >250 */
|
||||||
|
min-width: 155px !important;
|
||||||
|
max-width: 250px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sin elevación al hover */
|
/* sin elevación al hover */
|
||||||
@ -485,4 +496,85 @@
|
|||||||
#presupuesto-row .summary-col{
|
#presupuesto-row .summary-col{
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ==== BOTONES ==== */
|
||||||
|
/* --- Base de la barra (dos filas) --- */
|
||||||
|
.buttons-bar{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:.75rem;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila 1: acciones centradas */
|
||||||
|
.buttons-row.center{
|
||||||
|
display:flex;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
justify-content:center;
|
||||||
|
gap:.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila 2: extremos (prev izq / next dcha) */
|
||||||
|
.buttons-row.split{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
gap:.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* El “slot” derecho (puede contener Siguiente o Login) */
|
||||||
|
.buttons-row.split .right-slot{
|
||||||
|
margin-left:auto; /* en desktop empuja a la derecha */
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Limpieza de márgenes heredados para consistencia de gap --- */
|
||||||
|
.buttons-bar .btn.mx-2{ margin-left:0 !important; margin-right:0 !important; }
|
||||||
|
|
||||||
|
/* ================= MOBILE / CONTENEDOR ESTRECHO ================= */
|
||||||
|
/* A partir de aquí, todo ocurre cuando el contenedor #presupuesto-row
|
||||||
|
es estrecho (p.ej., móvil u offcanvas abierto). Ajusta 576px si lo necesitas */
|
||||||
|
@container presupuesto (max-width: 576px){
|
||||||
|
|
||||||
|
/* Fila 1: acciones apiladas y a 100% ancho */
|
||||||
|
.buttons-row.center{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
.buttons-row.center .btn{
|
||||||
|
width:100%;
|
||||||
|
justify-content:center; /* icono + texto centrados */
|
||||||
|
max-width: 100%; /* sin límites */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila 2: apilar elementos; cada bloque a 100% */
|
||||||
|
.buttons-row.split{
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:stretch; /* hace que los hijos estiren a 100% */
|
||||||
|
justify-content:flex-start;
|
||||||
|
}
|
||||||
|
.buttons-row.split > *{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón “Anterior” si existe → 100% y centrado */
|
||||||
|
.buttons-row.split > button.btn{
|
||||||
|
width:100%;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloque derecho (Siguiente / Login) a 100% */
|
||||||
|
.buttons-row.split .right-slot{
|
||||||
|
width:100%;
|
||||||
|
margin-left:0; /* neutraliza el empuje a la derecha */
|
||||||
|
justify-content:stretch; /* estira su contenido */
|
||||||
|
}
|
||||||
|
.buttons-row.split .right-slot .btn{
|
||||||
|
width:100%;
|
||||||
|
justify-content:center; /* texto centrado */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,7 +3,6 @@ import { formateaMoneda } from "../utils.js";
|
|||||||
|
|
||||||
|
|
||||||
$(document).on('change', '#maquetacion', function (e) {
|
$(document).on('change', '#maquetacion', function (e) {
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if ($('#maquetacion').is(':checked')) {
|
if ($('#maquetacion').is(':checked')) {
|
||||||
$.get("/presupuesto/public/maquetacion/form", function (data) {
|
$.get("/presupuesto/public/maquetacion/form", function (data) {
|
||||||
|
|||||||
@ -152,11 +152,11 @@ $(document).on('change', '.marcapaginas-item', () => {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
marcapaginas_tirada: parseInt($('#marcapaginas-tirada').val()) || 100,
|
marcapaginas_tirada: parseInt($('#marcapaginas-tirada').val()) || 100,
|
||||||
tamanio_marcapaginas: $('#tamanio-marcapaginas').val() || '_50x140_',
|
tamanio: $('#tamanio-marcapaginas').val() || '_50x140_',
|
||||||
caras_impresion: $('#caras-impresion').val() || 'una_cara',
|
carasImpresion: $('#caras-impresion').val() || 'una_cara',
|
||||||
papel_marcapaginas: $('#papel-marcapaginas').val() || 'cartulina_grafica',
|
papel: $('#papel-marcapaginas').val() || 'cartulina_grafica',
|
||||||
gramaje_marcapaginas: parseInt($('#gramaje-marcapaginas').val()) || 300,
|
gramaje: parseInt($('#gramaje-marcapaginas').val()) || 300,
|
||||||
acabado_marcapaginas: $('#acabado-marcapaginas').val() || 'ninguno',
|
acabado: $('#acabado-marcapaginas').val() || 'ninguno',
|
||||||
};
|
};
|
||||||
$(document).trigger('marcapaginas:update', [payload]);
|
$(document).trigger('marcapaginas:update', [payload]);
|
||||||
});
|
});
|
||||||
@ -167,11 +167,11 @@ function loadMarcapaginasData() {
|
|||||||
|
|
||||||
$(document).one('marcapaginas:response', (e, stored) => {
|
$(document).one('marcapaginas:response', (e, stored) => {
|
||||||
$('#marcapaginas-tirada').val(stored.marcapaginas_tirada);
|
$('#marcapaginas-tirada').val(stored.marcapaginas_tirada);
|
||||||
$('#tamanio-marcapaginas').val(stored.tamanio_marcapaginas);
|
$('#tamanio-marcapaginas').val(stored.tamanio);
|
||||||
$('#caras-impresion').val(stored.caras_impresion);
|
$('#caras-impresion').val(stored.carasImpresion);
|
||||||
$('#papel-marcapaginas').val(stored.papel_marcapaginas);
|
$('#papel-marcapaginas').val(stored.papel);
|
||||||
$('#gramaje-marcapaginas').val(stored.gramaje_marcapaginas);
|
$('#gramaje-marcapaginas').val(stored.gramaje);
|
||||||
$('#acabado-marcapaginas').val(stored.acabado_marcapaginas);
|
$('#acabado-marcapaginas').val(stored.acabado);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).trigger('marcapaginas:request');
|
$(document).trigger('marcapaginas:request');
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class TiradaCard {
|
|||||||
|
|
||||||
const col = $(`
|
const col = $(`
|
||||||
<div class="col d-flex">
|
<div class="col d-flex">
|
||||||
<label class="tirada-card ${this.selected ? 'selected' : ''} w-100 h-100" for="tirada-${this.id}">
|
<label class="tirada-card ${this.selected ? 'selected' : ''} h-100" for="tirada-${this.id}">
|
||||||
<input type="radio" name="${this.name}" id="tirada-${this.id}" value="${this.unidades}" ${this.selected ? 'checked' : ''}>
|
<input type="radio" name="${this.name}" id="tirada-${this.id}" value="${this.unidades}" ${this.selected ? 'checked' : ''}>
|
||||||
<div class="title">${this.#title()}</div>
|
<div class="title">${this.#title()}</div>
|
||||||
|
|
||||||
|
|||||||
@ -70,14 +70,19 @@ export default class PresupuestoWizard {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
servicios: {
|
servicios: {
|
||||||
servicios: [],
|
servicios: [{
|
||||||
|
"id": "ferro-digital",
|
||||||
|
"label": "Ferro Digital",
|
||||||
|
"price": 0,
|
||||||
|
"units": 1
|
||||||
|
}],
|
||||||
datosMarcapaginas: {
|
datosMarcapaginas: {
|
||||||
marcapaginas_tirada: 100,
|
marcapaginas_tirada: 100,
|
||||||
tamanio_marcapaginas: '_50x140_',
|
tamanio: '_50x140_',
|
||||||
caras_impresion: 'una_cara',
|
carasImpresion: 'una_cara',
|
||||||
papel_marcapaginas: 'cartulina_grafica',
|
papel: 'cartulina_grafica',
|
||||||
gramaje_marcapaginas: 300,
|
gramaje: 300,
|
||||||
acabado_marcapaginas: 'ninguno',
|
acabado: 'ninguno',
|
||||||
resultado: {
|
resultado: {
|
||||||
precio_unitario: 0,
|
precio_unitario: 0,
|
||||||
precio: 0
|
precio: 0
|
||||||
@ -127,7 +132,6 @@ export default class PresupuestoWizard {
|
|||||||
this.entregaTipo = $('#entregaTipo');
|
this.entregaTipo = $('#entregaTipo');
|
||||||
this.ivaReducido = $('#iva-reducido');
|
this.ivaReducido = $('#iva-reducido');
|
||||||
this.btnIvaReducidoDetail = $('#btn-iva-reducido-detail');
|
this.btnIvaReducidoDetail = $('#btn-iva-reducido-detail');
|
||||||
this.btn_next_datos_generales = $('#next-datos-generales');
|
|
||||||
this.datos_generales_alert = $('#datos-generales-alert');
|
this.datos_generales_alert = $('#datos-generales-alert');
|
||||||
|
|
||||||
// pestaña interior
|
// pestaña interior
|
||||||
@ -207,6 +211,7 @@ export default class PresupuestoWizard {
|
|||||||
this.#initDatosGenerales();
|
this.#initDatosGenerales();
|
||||||
|
|
||||||
if (presupuestoId && mode !== 'public') {
|
if (presupuestoId && mode !== 'public') {
|
||||||
|
|
||||||
await fetch(`/presupuesto/api/get?id=${encodeURIComponent(presupuestoId)}`, {
|
await fetch(`/presupuesto/api/get?id=${encodeURIComponent(presupuestoId)}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
@ -214,12 +219,16 @@ export default class PresupuestoWizard {
|
|||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(dto => {
|
.then(dto => {
|
||||||
|
sessionStorage.removeItem("formData");
|
||||||
this.formData = dto;
|
this.formData = dto;
|
||||||
|
this.#cacheFormData();
|
||||||
this.#loadDatosGeneralesData();
|
this.#loadDatosGeneralesData();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (stored) {
|
if (stored) {
|
||||||
|
sessionStorage.removeItem("formData");
|
||||||
this.formData = JSON.parse(stored);
|
this.formData = JSON.parse(stored);
|
||||||
|
this.#cacheFormData();
|
||||||
this.#loadDatosGeneralesData();
|
this.#loadDatosGeneralesData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,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() {
|
async #guardarPresupuesto() {
|
||||||
@ -481,7 +511,7 @@ export default class PresupuestoWizard {
|
|||||||
Summary.updateFormato();
|
Summary.updateFormato();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.btn_next_datos_generales.on('click', () => {
|
$('.btn-change-tab-datos-generales').on('click', () => {
|
||||||
this.#nextDatosGenerales();
|
this.#nextDatosGenerales();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -715,7 +745,7 @@ export default class PresupuestoWizard {
|
|||||||
$('.tipo-libro#grapado').removeClass('d-none');
|
$('.tipo-libro#grapado').removeClass('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(selectedTipo && $('.tipo-libro#' + selectedTipo).length > 0 && !$('.tipo-libro#' + selectedTipo).hasClass('d-none'))) {
|
if (!(selectedTipo && $(`.tipo-libro input[value="${selectedTipo}"]`).length && !$(`.tipo-libro input[value="${selectedTipo}"]`).closest('.tipo-libro').hasClass('d-none'))) {
|
||||||
|
|
||||||
let firstVisible = $('.tipo-libro').not('.d-none').first();
|
let firstVisible = $('.tipo-libro').not('.d-none').first();
|
||||||
|
|
||||||
@ -883,7 +913,7 @@ export default class PresupuestoWizard {
|
|||||||
$('.btn-change-tab-interior').on('click', (e) => {
|
$('.btn-change-tab-interior').on('click', (e) => {
|
||||||
|
|
||||||
let data = this.#getPresupuestoData();
|
let data = this.#getPresupuestoData();
|
||||||
const id = e.currentTarget.id;
|
const action = $(e.currentTarget).data('btn-action') || 'next';
|
||||||
this.interior_alert.addClass('d-none').find('#form-errors-alert-list').empty();
|
this.interior_alert.addClass('d-none').find('#form-errors-alert-list').empty();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -891,7 +921,7 @@ export default class PresupuestoWizard {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data,
|
data: data,
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
if (id === 'btn-prev-interior') {
|
if (action === 'previous') {
|
||||||
this.summaryTableInterior.addClass('d-none');
|
this.summaryTableInterior.addClass('d-none');
|
||||||
this.#changeTab('pills-general-data');
|
this.#changeTab('pills-general-data');
|
||||||
} else {
|
} else {
|
||||||
@ -1247,9 +1277,9 @@ export default class PresupuestoWizard {
|
|||||||
$('.btn-change-tab-cubierta').on('click', (e) => {
|
$('.btn-change-tab-cubierta').on('click', (e) => {
|
||||||
|
|
||||||
const data = this.#getPresupuestoData();
|
const data = this.#getPresupuestoData();
|
||||||
const id = e.currentTarget.id;
|
const action = $(e.currentTarget).data('btn-action') || 'next';
|
||||||
|
|
||||||
if (id === 'btn-prev-cubierta') {
|
if (action === 'previous') {
|
||||||
data.calcular = false;
|
data.calcular = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1261,7 +1291,7 @@ export default class PresupuestoWizard {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data,
|
data: data,
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
if (id === 'btn-prev-cubierta') {
|
if (action === 'previous') {
|
||||||
this.summaryTableCubierta.addClass('d-none');
|
this.summaryTableCubierta.addClass('d-none');
|
||||||
this.#changeTab('pills-inside');
|
this.#changeTab('pills-inside');
|
||||||
}
|
}
|
||||||
@ -1272,7 +1302,7 @@ export default class PresupuestoWizard {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (xhr, status, error) => {
|
error: (xhr, status, error) => {
|
||||||
if (id === 'btn-prev-cubierta') {
|
if (action === 'previous') {
|
||||||
this.#changeTab('pills-inside');
|
this.#changeTab('pills-inside');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1540,9 +1570,9 @@ export default class PresupuestoWizard {
|
|||||||
|
|
||||||
$(document).on('click', '.btn-change-tab-seleccion-tirada', (e) => {
|
$(document).on('click', '.btn-change-tab-seleccion-tirada', (e) => {
|
||||||
|
|
||||||
const id = e.currentTarget.id;
|
const action = $(e.currentTarget).data('btn-action') || 'next';
|
||||||
|
|
||||||
if (id === 'btn-prev-seleccion-tirada') {
|
if (action === 'previous') {
|
||||||
this.#changeTab('pills-cover');
|
this.#changeTab('pills-cover');
|
||||||
} else {
|
} else {
|
||||||
const data = this.#getPresupuestoData();
|
const data = this.#getPresupuestoData();
|
||||||
@ -1621,9 +1651,9 @@ export default class PresupuestoWizard {
|
|||||||
|
|
||||||
$(document).on('click', '.btn-change-tab-extras', (e) => {
|
$(document).on('click', '.btn-change-tab-extras', (e) => {
|
||||||
|
|
||||||
const id = e.currentTarget.id;
|
const action = $(e.currentTarget).data('btn-action') || 'next';
|
||||||
|
|
||||||
if (id === 'btn-prev-extras') {
|
if (action === 'previous') {
|
||||||
this.#changeTab('pills-seleccion-tirada');
|
this.#changeTab('pills-seleccion-tirada');
|
||||||
this.summaryTableExtras.addClass('d-none');
|
this.summaryTableExtras.addClass('d-none');
|
||||||
} else {
|
} else {
|
||||||
@ -1631,11 +1661,22 @@ export default class PresupuestoWizard {
|
|||||||
const servicios = [];
|
const servicios = [];
|
||||||
$('.service-checkbox:checked').each(function () {
|
$('.service-checkbox:checked').each(function () {
|
||||||
const $servicio = $(this);
|
const $servicio = $(this);
|
||||||
|
let price = 0;
|
||||||
|
if ($servicio.attr('id') === 'marcapaginas') {
|
||||||
|
price = self.formData.servicios.datosMarcapaginas.resultado.precio;
|
||||||
|
}
|
||||||
|
else if ($servicio.attr('id') === 'maquetacion') {
|
||||||
|
price = self.formData.servicios.datosMaquetacion.resultado.precio;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
price = $servicio.data('price') ?? $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), '');
|
||||||
|
}
|
||||||
|
|
||||||
servicios.push({
|
servicios.push({
|
||||||
id: $servicio.attr('id') ?? $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
id: $servicio.attr('id') ?? $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
||||||
label: $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
label: $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(),
|
||||||
units: $servicio.attr('id') === 'marcapaginas' ? self.formData.servicios.datosMarcapaginas.marcapaginas_tirada : 1,
|
units: $servicio.attr('id') === 'marcapaginas' ? self.formData.servicios.datosMarcapaginas.marcapaginas_tirada : 1,
|
||||||
price: $servicio.data('price') ?? $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), ''),
|
price: price,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1643,7 +1684,9 @@ export default class PresupuestoWizard {
|
|||||||
presupuesto: this.#getPresupuestoData(),
|
presupuesto: this.#getPresupuestoData(),
|
||||||
save: !this.opts.canSave,
|
save: !this.opts.canSave,
|
||||||
mode: this.opts.mode,
|
mode: this.opts.mode,
|
||||||
servicios: servicios
|
servicios: servicios,
|
||||||
|
datosMaquetacion: this.formData.servicios.datosMaquetacion,
|
||||||
|
datosMarcapaginas: this.formData.servicios.datosMarcapaginas,
|
||||||
};
|
};
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -1673,15 +1716,25 @@ export default class PresupuestoWizard {
|
|||||||
const $target = $(e.currentTarget);
|
const $target = $(e.currentTarget);
|
||||||
|
|
||||||
if ($target.prop('checked')) {
|
if ($target.prop('checked')) {
|
||||||
|
let price = 0;
|
||||||
|
if ($target.attr('id') === 'marcapaginas') {
|
||||||
|
price = self.formData.servicios.datosMarcapaginas.resultado.precio;
|
||||||
|
}
|
||||||
|
else if ($target.attr('id') === 'maquetacion') {
|
||||||
|
price = self.formData.servicios.datosMaquetacion.resultado.precio;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
price = $target.data('price') ?? $(`label[for="${$target.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), '');
|
||||||
|
}
|
||||||
this.formData.servicios.servicios.push(
|
this.formData.servicios.servicios.push(
|
||||||
{
|
{
|
||||||
id: $target.attr('id') ?? $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
id: $target.attr('id') ?? $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
||||||
label: $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
label: $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
||||||
units: $target.attr('id') === 'marcapaginas' ? self.formData.servicios.datosMarcapaginas.marcapaginas_tirada : 1,
|
units: $target.attr('id') === 'marcapaginas' ? self.formData.servicios.datosMarcapaginas.marcapaginas_tirada : 1,
|
||||||
price: $target.data('price') ?? $(`label[for="${$target.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), ''),
|
price: price,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const index = this.formData.servicios.servicios.indexOf($target.val());
|
const index = this.formData.servicios.servicios.findIndex(item => item.id == $target.attr('id'));
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.formData.servicios.servicios.splice(index, 1);
|
this.formData.servicios.servicios.splice(index, 1);
|
||||||
}
|
}
|
||||||
@ -1705,7 +1758,7 @@ export default class PresupuestoWizard {
|
|||||||
if (this.formData.servicios.servicios.some(s => s.id === extra.id) && !extra.checked) {
|
if (this.formData.servicios.servicios.some(s => s.id === extra.id) && !extra.checked) {
|
||||||
extra.checked = true;
|
extra.checked = true;
|
||||||
if (extra.id === "marcapaginas" || extra.id === "maquetacion") {
|
if (extra.id === "marcapaginas" || extra.id === "maquetacion") {
|
||||||
extra.price = extra.id === "marcapaginas" ?
|
extra.price = (extra.id === "marcapaginas") ?
|
||||||
this.formData.servicios.datosMarcapaginas.resultado.precio :
|
this.formData.servicios.datosMarcapaginas.resultado.precio :
|
||||||
this.formData.servicios.datosMaquetacion.resultado.precio;
|
this.formData.servicios.datosMaquetacion.resultado.precio;
|
||||||
extra.priceUnit = this.divExtras.data('currency');
|
extra.priceUnit = this.divExtras.data('currency');
|
||||||
@ -1715,7 +1768,7 @@ export default class PresupuestoWizard {
|
|||||||
this.divExtras.append(item.render());
|
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({
|
this.formData.servicios.servicios.push({
|
||||||
id: "ferro-digital",
|
id: "ferro-digital",
|
||||||
label: "Ferro Digital",
|
label: "Ferro Digital",
|
||||||
@ -1749,8 +1802,14 @@ export default class PresupuestoWizard {
|
|||||||
...result,
|
...result,
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = this.formData.servicios.servicios;
|
if (!this.formData.servicios.servicios.some(s => s.id === "maquetacion") && result.precio > 0) {
|
||||||
if (!list.includes('maquetacion')) list.push('maquetacion');
|
this.formData.servicios.servicios.push({
|
||||||
|
id: "maquetacion",
|
||||||
|
label: $(`label[for="maquetacion"] .service-title`).text().trim(),
|
||||||
|
units: 1,
|
||||||
|
price: result.precio,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.#cacheFormData();
|
this.#cacheFormData();
|
||||||
});
|
});
|
||||||
@ -1779,10 +1838,6 @@ export default class PresupuestoWizard {
|
|||||||
...result,
|
...result,
|
||||||
};
|
};
|
||||||
|
|
||||||
// asegúrate de añadir el servicio seleccionado
|
|
||||||
const list = this.formData.servicios.servicios;
|
|
||||||
if (!list.includes('marcapaginas')) list.push('marcapaginas');
|
|
||||||
|
|
||||||
this.#cacheFormData();
|
this.#cacheFormData();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1811,7 +1866,8 @@ export default class PresupuestoWizard {
|
|||||||
|
|
||||||
// 2) Botón "atrás" en Resumen
|
// 2) Botón "atrás" en Resumen
|
||||||
$(document).on('click', '.btn-change-tab-resumen', (e) => {
|
$(document).on('click', '.btn-change-tab-resumen', (e) => {
|
||||||
if (e.currentTarget.id === 'btn-prev-resumen') {
|
const action = $(e.currentTarget).data('btn-action') || 'next';
|
||||||
|
if (action === 'previous') {
|
||||||
this.#changeTab('pills-extras');
|
this.#changeTab('pills-extras');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -71,72 +71,8 @@
|
|||||||
<!-- DATOS TÉCNICOS EN 2 COLUMNAS -->
|
<!-- DATOS TÉCNICOS EN 2 COLUMNAS -->
|
||||||
<div class="specs-wrapper align-with-text ">
|
<div class="specs-wrapper align-with-text ">
|
||||||
<div class="specs">
|
<div class="specs">
|
||||||
<div class="col">
|
<div th:utext="${especificaciones} ?: '<em>Sin especificaciones técnicas.</em>'">
|
||||||
<div class="block-title">Encuadernación</div>
|
<em>Sin especificaciones técnicas.</em>
|
||||||
<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>
|
</div>
|
||||||
</div> <!-- .specs-wrapper -->
|
</div> <!-- .specs-wrapper -->
|
||||||
@ -145,20 +81,22 @@
|
|||||||
<table class="prices">
|
<table class="prices">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-tirada">TIRADA</th>
|
<th class="text-center col-tirada" th:text="#{pdf.table.tirada}">TIRADA</th>
|
||||||
<th>IMPRESIÓN</th>
|
<th class="text-center" th:text="#{pdf.table.impresion}">IMPRESIÓN</th>
|
||||||
<th>IVA</th>
|
<th class="text-center" th:text="#{pdf.table.servicios}">SERVICIOS</th>
|
||||||
<th>TOTAL</th>
|
<th class="text-center" th:if="${pricing.show_iva_4}" th:text="#{pdf.table.iva-4}">IVA 4%</th>
|
||||||
<th>UNIDAD</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody th:if="${pricing != null and pricing.tiradas != null}">
|
<tbody th:if="${pricing != null and pricing.tiradas != null}">
|
||||||
<tr th:each="t, st : ${pricing.tiradas}">
|
<tr th:each="t, st : ${pricing.tiradas}">
|
||||||
<td class="col-tirada" th:text="${t} + ' uds.'">100 uds.</td>
|
<td class="text-center col-tirada" th:text="${t}">100 uds.</td>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.impresion[st.index],1,'POINT',2,'COMMA')} + '€'">152,15€</td>
|
<td class="text-center" th:text="${pricing.impresion[st.index]}">152,15€</td>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.iva[st.index],1,'POINT',2,'COMMA')} + '€'">7,68€</td>
|
<td class="text-center" th:text="${pricing.servicios[st.index]}">7,68€</td>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.total[st.index],1,'POINT',2,'COMMA')} + '€'">159,99€</td>
|
<td class="text-center" th:if="${pricing.show_iva_4}" th:text="${pricing.iva_4[st.index]}">7,68€</td>
|
||||||
<td th:text="${#numbers.formatDecimal(pricing.unidad[st.index],1,'POINT',2,'COMMA')} + '€'">1,52€</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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
@ -177,7 +115,8 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="privacy">
|
<div class="privacy">
|
||||||
<div class="pv-title" th:text="#{pdf.politica-privacidad}">Política de privacidad</div>
|
<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>
|
B04998886 - Teléfono de contacto: 910052574</div>
|
||||||
<div class="pv-text" th:text="#{pdf.politica-privacidad.correo-direccion}">Correo electrónico:
|
<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>
|
info@imprimelibros.com - Dirección postal: Calle José Picón, Nº 28 Local A, 28028, Madrid</div>
|
||||||
|
|||||||
@ -1,21 +1,57 @@
|
|||||||
<!-- templates/fragments/common.html -->
|
<div th:fragment="buttons(appMode, btnClass, showPrev, showNext, showActions, showCart)"
|
||||||
<div th:fragment="buttons(appMode)"
|
class="buttons-bar mt-2">
|
||||||
class="order-3 order-md-2 mx-md-auto d-flex">
|
|
||||||
<button th:if="${appMode == 'add' or appMode == 'edit'}" id="btn-guardar" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 guardar-presupuesto">
|
|
||||||
<i class="ri-save-3-line me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button th:if="${appMode == 'add' or appMode == 'edit'}" id="btn-add-cart" type="button"
|
<!-- Fila 1: ACCIONES, centradas -->
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
<div class="buttons-row center" th:if="${showActions}">
|
||||||
<i class="ri-shopping-cart-line me-2"></i>
|
<button th:if="${appMode == 'add' or appMode == 'edit'}"
|
||||||
<span th:text="#{presupuesto.add-to-cart}">Añadir al carrito</span>
|
type="button"
|
||||||
</button>
|
class="btn btn-secondary d-flex align-items-center mx-2 guardar-presupuesto">
|
||||||
|
<i class="ri-save-3-line me-2"></i>
|
||||||
|
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button id="btn-imprimir" type="button"
|
<button th:if="${appMode == 'add' or appMode == 'edit'}"
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 imprimir-presupuesto">
|
type="button"
|
||||||
<i class="ri-printer-line me-2"></i>
|
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||||
<span th:text="#{app.imprimir}">Imprimir</span>
|
<i class="ri-shopping-cart-line me-2"></i>
|
||||||
</button>
|
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-secondary d-flex align-items-center mx-2 btn-imprimir">
|
||||||
|
<i class="ri-printer-line me-2"></i>
|
||||||
|
<span th:text="#{app.imprimir}">Imprimir</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Alternativa cuando no está autenticado -->
|
||||||
|
<button sec:authorize="!isAuthenticated()"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary d-flex align-items-center btn-login-required ms-2">
|
||||||
|
<i class="mdi mdi-login label-icon align-middle fs-16 me-2"></i>
|
||||||
|
<span th:text="#{presupuesto.resumen.inicie-sesion}">Inicie sesión para continuar</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fila 2: PREV (izq) / NEXT o LOGIN (dcha) -->
|
||||||
|
<div class="buttons-row split">
|
||||||
|
<button th:if="${showPrev}"
|
||||||
|
th:id="|${btnClass}-prev|"
|
||||||
|
data-btn-action="previous"
|
||||||
|
th:class="'btn btn-light ' + ${btnClass}"
|
||||||
|
type="button">
|
||||||
|
<i class="ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
||||||
|
<span th:text="#{app.acciones.anterior}">Anterior</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="right-slot d-flex align-items-center">
|
||||||
|
<button th:if="${showNext}"
|
||||||
|
data-btn-action="next"
|
||||||
|
th:id="|${btnClass}-next|"
|
||||||
|
th:class="'btn btn-secondary d-flex align-items-center ' + ${btnClass}"
|
||||||
|
type="button">
|
||||||
|
<span th:text="#{app.acciones.siguiente}">Siguiente</span>
|
||||||
|
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -408,18 +408,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||||
<button id="btn-prev-cubierta" type="button"
|
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-cubierta', true, true, true, false)}"></div>
|
||||||
class="btn btn-light d-flex align-items-center btn-change-tab-cubierta order-1">
|
|
||||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.volver-interior}">Volver a interior</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode})}"></div>
|
|
||||||
|
|
||||||
<button id="btn-next-cubierta" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center btn-change-tab-cubierta order-2 order-md-3">
|
|
||||||
<span th:text="#{presupuesto.continuar-seleccion-tirada}">Continuar a selección de tirada</span>
|
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -270,7 +270,7 @@
|
|||||||
<div
|
<div
|
||||||
class="form-check form-switch form-switch-custom form-switch-presupuesto mb-3 d-flex align-items-center">
|
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"
|
<input type="checkbox" class="form-check-input datos-generales-data me-2" id="iva-reducido"
|
||||||
name="iva-reducido" checked/>
|
name="iva-reducido" checked />
|
||||||
<label for="iva-reducido" class="form-label d-flex align-items-center mb-0">
|
<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>
|
<span th:text="#{presupuesto.iva-reducido}" class="me-2">I.V. reducido</span>
|
||||||
<button type="button" id="btn-iva-reducido-detail"
|
<button type="button" id="btn-iva-reducido-detail"
|
||||||
@ -282,7 +282,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center mb-2">
|
<div class="row justify-content-center mb-2">
|
||||||
<div class="col-sm-3 justify-content-center">
|
<div class="col-sm-3 justify-content-center">
|
||||||
<label for="entregaTipo" class="form-label mt-2" th:text="#{presupuesto.entrega}">Entrega</label>
|
<label for="entregaTipo" class="form-label mt-2"
|
||||||
|
th:text="#{presupuesto.entrega}">Entrega</label>
|
||||||
<select class="form-select select2 datos-generales-data" id="entregaTipo" name="entregaTipo">
|
<select class="form-select select2 datos-generales-data" id="entregaTipo" name="entregaTipo">
|
||||||
<option selected value="peninsula" th:text="#{presupuesto.entrega.peninsula}">Península
|
<option selected value="peninsula" th:text="#{presupuesto.entrega.peninsula}">Península
|
||||||
y
|
y
|
||||||
@ -297,12 +298,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-center gap-3 mt-3">
|
<div
|
||||||
|
th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-datos-generales', false, true, false, false)}">
|
||||||
<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>
|
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -17,18 +17,6 @@
|
|||||||
<!-- End Ribbon Shape -->
|
<!-- End Ribbon Shape -->
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||||
<button id="btn-prev-extras" type="button"
|
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-extras', true, true, true, false)}"></div>
|
||||||
class="btn btn-light d-flex align-items-center btn-change-tab-extras order-1">
|
|
||||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.volver-seleccion-tirada}">Volver a selección de tirada</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode})}"></div>
|
|
||||||
|
|
||||||
<button id="btn-next-extras" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center btn-change-tab-extras order-2 order-md-3">
|
|
||||||
<span><b th:text="#{presupuesto.resumen}">Resumen</b></span>
|
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,20 +57,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
<div
|
||||||
<button id="btn-prev-interior" type="button"
|
th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-interior', true, true, true, false)}">
|
||||||
class="btn btn-change-tab-interior btn-light d-flex align-items-center order-1">
|
|
||||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.volver-datos-generales}">Volver a datos generales</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode})}"></div>
|
|
||||||
|
|
||||||
<button id="btn-next-interior" type="button"
|
|
||||||
class="btn btn-change-tab-interior btn-secondary d-flex align-items-center order-2 order-md-3">
|
|
||||||
<span th:text="#{presupuesto.continuar-cubierta}">Continuar a diseño cubierta</span>
|
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -50,24 +50,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||||
<button id="btn-prev-resumen" type="button"
|
<div
|
||||||
class="btn btn-light d-flex align-items-center btn-change-tab-resumen order-1">
|
th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-resumen', true, false, true, true)}">
|
||||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
</div>
|
||||||
<span th:text="#{presupuesto.volver-extras}">Volver a extras</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode})}"></div>
|
|
||||||
|
|
||||||
<button th:unless="${#authorization.expression('isAuthenticated()')}" id="btn-add-cart" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center order-2 order-md-3">
|
|
||||||
<i class="mdi mdi-login label-icon align-middle fs-16 me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.resumen.inicie-sesion}">Inicie sesión para continuar</span>
|
|
||||||
</button>
|
|
||||||
<button th:if="${#authorization.expression('isAuthenticated()')}" id="btn-add-cart" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center order-2 order-md-3">
|
|
||||||
<span th:text="#{presupuesto.resumen.agregar-cesta}">Agregar a la cesta</span>
|
|
||||||
<i class="ri-shopping-cart-2-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="div-tiradas"
|
<div id="div-tiradas"
|
||||||
class="row row-cols-1 row-cols-sm-2 row-cols-lg-4 g-4 justify-content-center align-items-stretch mx-auto mb-4"
|
class="row row-cols-auto justify-content-center g-4 mx-auto mb-4"
|
||||||
style="max-width:1120px" th:data-per-unit="#{presupuesto.precio-unidad}"
|
style="max-width:1120px" th:data-per-unit="#{presupuesto.precio-unidad}"
|
||||||
th:data-total="#{presupuesto.total}" th:data-select="#{presupuesto.seleccionar-tirada}"
|
th:data-total="#{presupuesto.total}" th:data-select="#{presupuesto.seleccionar-tirada}"
|
||||||
th:data-selected="#{presupuesto.tirada-seleccionada}" th:data-units="#{presupuesto.unidades}">
|
th:data-selected="#{presupuesto.tirada-seleccionada}" th:data-units="#{presupuesto.unidades}">
|
||||||
@ -27,18 +27,6 @@
|
|||||||
<!-- End Ribbon Shape -->
|
<!-- End Ribbon Shape -->
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||||
<button id="btn-prev-seleccion-tirada" type="button"
|
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode}, 'btn-change-tab-seleccion-tirada', true, true, true, false)}"></div>
|
||||||
class="btn btn-light d-flex align-items-center btn-change-tab-seleccion-tirada order-1">
|
|
||||||
<i class=" ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
|
||||||
<span th:text="#{presupuesto.volver-cubierta}">Volver a diseño de cubierta</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div th:replace="~{imprimelibros/presupuestos/presupuestador-items/_buttons :: buttons(${appMode})}"></div>
|
|
||||||
|
|
||||||
<button id="btn-next-seleccion-tirada" type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center btn-change-tab-seleccion-tirada order-2 order-md-3">
|
|
||||||
<span><b th:text="#{presupuesto.continuar-extras-libro}">Continuar a extras del libro</b></span>
|
|
||||||
<i class="ri-arrow-right-circle-line fs-16 ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,8 +32,8 @@
|
|||||||
data-bs-target="#pills-general-data" type="button" role="tab"
|
data-bs-target="#pills-general-data" type="button" role="tab"
|
||||||
aria-controls="pills-general-data" aria-selected="true">
|
aria-controls="pills-general-data" aria-selected="true">
|
||||||
<i
|
<i
|
||||||
class="ri-information-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-information-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.datos-generales}">Datos Generales</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.datos-generales}">Datos Generales</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -41,8 +41,8 @@
|
|||||||
data-bs-target="#pills-inside" type="button" role="tab"
|
data-bs-target="#pills-inside" type="button" role="tab"
|
||||||
aria-controls="pills-inside" aria-selected="false">
|
aria-controls="pills-inside" aria-selected="false">
|
||||||
<i
|
<i
|
||||||
class="ri-book-open-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-book-open-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.interior}">Interior</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.interior}">Interior</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -50,8 +50,8 @@
|
|||||||
data-bs-target="#pills-cover" type="button" role="tab"
|
data-bs-target="#pills-cover" type="button" role="tab"
|
||||||
aria-controls="pills-cover" aria-selected="false">
|
aria-controls="pills-cover" aria-selected="false">
|
||||||
<i
|
<i
|
||||||
class="ri-book-2-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-book-2-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.cubierta}">Cubierta</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.cubierta}">Cubierta</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -59,8 +59,8 @@
|
|||||||
data-bs-target="#pills-seleccion-tirada" type="button" role="tab"
|
data-bs-target="#pills-seleccion-tirada" type="button" role="tab"
|
||||||
aria-controls="pills-seleccion-tirada" aria-selected="false">
|
aria-controls="pills-seleccion-tirada" aria-selected="false">
|
||||||
<i
|
<i
|
||||||
class="ri-add-box-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-add-box-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.seleccion-tirada}">Seleccion de tirada</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.seleccion-tirada}">Seleccion de tirada</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -68,8 +68,8 @@
|
|||||||
data-bs-target="#pills-extras" type="button" role="tab"
|
data-bs-target="#pills-extras" type="button" role="tab"
|
||||||
aria-controls="pills-extras" aria-selected="false">
|
aria-controls="pills-extras" aria-selected="false">
|
||||||
<i
|
<i
|
||||||
class="ri-add-box-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-add-box-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.extras}">Extras</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.extras}">Extras</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@ -77,8 +77,8 @@
|
|||||||
data-bs-target="#pills-resumen" type="button" role="tab"
|
data-bs-target="#pills-resumen" type="button" role="tab"
|
||||||
aria-controls="pills-resumen" aria-selected="false">
|
aria-controls="pills-resumen" aria-selected="false">
|
||||||
<i
|
<i
|
||||||
class="ri-add-box-line fs-16 p-2 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
class="ri-add-box-line fs-5 p-1 bg-soft-primary text-primary rounded-circle align-middle me-2"></i>
|
||||||
<label th:text="#{presupuesto.resumen}">Resumen</label>
|
<label class="fs-13 my-2" th:text="#{presupuesto.resumen}">Resumen</label>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class PdfSmokeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generaPresupuesto() throws Exception {
|
void generaPresupuesto() throws Exception {
|
||||||
|
/*
|
||||||
Map<String, Object> model = new HashMap<>();
|
Map<String, Object> model = new HashMap<>();
|
||||||
model.put("numero", "2025-00123");
|
model.put("numero", "2025-00123");
|
||||||
model.put("fecha", LocalDate.of(2025, 10, 12));
|
model.put("fecha", LocalDate.of(2025, 10, 12));
|
||||||
@ -129,5 +129,6 @@ class PdfSmokeTest {
|
|||||||
Files.write(out, pdf);
|
Files.write(out, pdf);
|
||||||
|
|
||||||
System.out.println("✅ PDF generado en: " + out.toAbsolutePath());
|
System.out.println("✅ PDF generado en: " + out.toAbsolutePath());
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user