mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-28 22:58:49 +00:00
primera version final del presupuesto
This commit is contained in:
@ -8,11 +8,8 @@ import com.imprimelibros.erp.users.UserDetailsImpl;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
|
||||||
import com.imprimelibros.erp.users.User;
|
import com.imprimelibros.erp.users.User;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.graphql.GraphQlProperties.Http;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package com.imprimelibros.erp.cart;
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
import jakarta.mail.Message;
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
@ -21,7 +20,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|||||||
227
src/main/java/com/imprimelibros/erp/common/Utils.java
Normal file
227
src/main/java/com/imprimelibros/erp/common/Utils.java
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package com.imprimelibros.erp.common;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||||
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices;
|
||||||
|
import com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
private final PresupuestoFormatter presupuestoFormatter;
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
public Utils(PresupuestoFormatter presupuestoFormatter,
|
||||||
|
MessageSource messageSource) {
|
||||||
|
this.presupuestoFormatter = presupuestoFormatter;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatCurrency(BigDecimal amount, Locale locale) {
|
||||||
|
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale);
|
||||||
|
return currencyFormatter.format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatCurrency(Double amount, Locale locale) {
|
||||||
|
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale);
|
||||||
|
return currencyFormatter.format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getTextoPresupuesto(Presupuesto presupuesto, Locale locale) {
|
||||||
|
|
||||||
|
Map<String, Object> resumen = new HashMap<>();
|
||||||
|
|
||||||
|
resumen.put("titulo", presupuesto.getTitulo());
|
||||||
|
|
||||||
|
resumen.put("imagen",
|
||||||
|
"/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion()
|
||||||
|
+ ".png");
|
||||||
|
resumen.put("imagen_alt",
|
||||||
|
messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null,
|
||||||
|
locale));
|
||||||
|
|
||||||
|
resumen.put("presupuestoId", presupuesto.getId());
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
List<Map<String, Object>> servicios = new ArrayList<>();
|
||||||
|
if (presupuesto.getServiciosJson() != null && !presupuesto.getServiciosJson().isBlank())
|
||||||
|
try {
|
||||||
|
servicios = mapper.readValue(presupuesto.getServiciosJson(), new TypeReference<>() {
|
||||||
|
});
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
// Manejar la excepción
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hayDepositoLegal = servicios != null && servicios.stream()
|
||||||
|
.map(m -> java.util.Objects.toString(m.get("id"), ""))
|
||||||
|
.map(String::trim)
|
||||||
|
.anyMatch("deposito-legal"::equals);
|
||||||
|
|
||||||
|
List<HashMap<String, Object>> lineas = new ArrayList<>();
|
||||||
|
HashMap<String, Object> linea = new HashMap<>();
|
||||||
|
Double precio_unitario = 0.0;
|
||||||
|
Double precio_total = 0.0;
|
||||||
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
|
linea.put("descripcion", presupuestoFormatter.resumen(presupuesto, servicios, locale));
|
||||||
|
linea.put("cantidad", presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0);
|
||||||
|
precio_unitario = (presupuesto.getPrecioUnitario() != null
|
||||||
|
? presupuesto.getPrecioUnitario().doubleValue()
|
||||||
|
: 0.0);
|
||||||
|
precio_total = (presupuesto.getPrecioTotalTirada() != null
|
||||||
|
? presupuesto.getPrecioTotalTirada().doubleValue()
|
||||||
|
: 0.0);
|
||||||
|
linea.put("precio_unitario", precio_unitario);
|
||||||
|
linea.put("precio_total", BigDecimal.valueOf(precio_total).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
total = total.add(BigDecimal.valueOf(precio_total));
|
||||||
|
lineas.add(linea);
|
||||||
|
|
||||||
|
if (hayDepositoLegal) {
|
||||||
|
linea = new HashMap<>();
|
||||||
|
linea.put("descripcion",
|
||||||
|
messageSource.getMessage("pdf.ejemplares-deposito-legal", new Object[]{4}, locale));
|
||||||
|
lineas.add(linea);
|
||||||
|
}
|
||||||
|
|
||||||
|
String serviciosExtras = "";
|
||||||
|
if (servicios != null) {
|
||||||
|
for (Map<String, Object> servicio : servicios) {
|
||||||
|
serviciosExtras += messageSource.getMessage(
|
||||||
|
"presupuesto.extras-" + servicio.get("id"), null, locale) + ", ";
|
||||||
|
}
|
||||||
|
if (!serviciosExtras.isEmpty()) {
|
||||||
|
serviciosExtras = serviciosExtras.substring(0, serviciosExtras.length() - 2);;
|
||||||
|
}
|
||||||
|
if (servicios.stream().anyMatch(service -> "marcapaginas".equals(service.get("id")))) {
|
||||||
|
ObjectMapper mapperServicio = new ObjectMapper();
|
||||||
|
Object raw = presupuesto.getDatosMarcapaginasJson();
|
||||||
|
Map<String, Object> datosMarcapaginas;
|
||||||
|
String descripcion = "";
|
||||||
|
try {
|
||||||
|
if (raw instanceof String s) {
|
||||||
|
datosMarcapaginas = mapperServicio.readValue(s,
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
} else if (raw instanceof Map<?, ?> m) {
|
||||||
|
datosMarcapaginas = mapperServicio.convertValue(m,
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Tipo no soportado para datosMarcapaginas: "
|
||||||
|
+ raw);
|
||||||
|
}
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException("Error parsing datosMarcapaginasJson", e);
|
||||||
|
}
|
||||||
|
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>";
|
||||||
|
resumen.put("datosMarcapaginas", descripcion);
|
||||||
|
}
|
||||||
|
if (servicios.stream().anyMatch(service -> "maquetacion".equals(service.get("id")))) {
|
||||||
|
ObjectMapper mapperServicio = new ObjectMapper();
|
||||||
|
Object raw = presupuesto.getDatosMaquetacionJson();
|
||||||
|
Map<String, Object> datosMaquetacion;
|
||||||
|
String descripcion = "";
|
||||||
|
try {
|
||||||
|
if (raw instanceof String s) {
|
||||||
|
datosMaquetacion = mapperServicio.readValue(s,
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
} else if (raw instanceof Map<?, ?> m) {
|
||||||
|
datosMaquetacion = mapperServicio.convertValue(m,
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Tipo no soportado para datosMaquetacion: "
|
||||||
|
+ raw);
|
||||||
|
}
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException("Error parsing datosMaquetacionJson", e);
|
||||||
|
}
|
||||||
|
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>";
|
||||||
|
resumen.put("datosMaquetacion", descripcion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
|
||||||
|
String formattedString = currencyFormat.format(total.setScale(2, RoundingMode.HALF_UP).doubleValue());
|
||||||
|
resumen.put("total", formattedString);
|
||||||
|
resumen.put("lineas", lineas);
|
||||||
|
resumen.put("servicios", serviciosExtras);
|
||||||
|
return resumen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@ package com.imprimelibros.erp.pdf;
|
|||||||
import java.nio.charset.StandardCharsets;
|
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.text.NumberFormat;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -16,12 +15,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.common.Utils;
|
||||||
|
|
||||||
@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;
|
private final PresupuestoRepository presupuestoRepository;
|
||||||
|
private final Utils utils;
|
||||||
|
|
||||||
private final Map<String, String> empresa = Map.of(
|
private final Map<String, String> empresa = Map.of(
|
||||||
"nombre", "ImprimeLibros ERP",
|
"nombre", "ImprimeLibros ERP",
|
||||||
@ -86,11 +88,12 @@ public class PdfService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PdfService(TemplateRegistry registry, PdfTemplateEngine engine, PdfRenderer renderer,
|
public PdfService(TemplateRegistry registry, PdfTemplateEngine engine, PdfRenderer renderer,
|
||||||
PresupuestoRepository presupuestoRepository) {
|
PresupuestoRepository presupuestoRepository, Utils utils) {
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.presupuestoRepository = presupuestoRepository;
|
this.presupuestoRepository = presupuestoRepository;
|
||||||
|
this.utils = utils;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generate(DocumentSpec spec) {
|
private byte[] generate(DocumentSpec spec) {
|
||||||
@ -140,6 +143,9 @@ public class PdfService {
|
|||||||
model.put("servicios", List.of(
|
model.put("servicios", List.of(
|
||||||
Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00)));
|
Map.of("descripcion", "Transporte península", "unidades", 1, "precio", 90.00)));
|
||||||
|
|
||||||
|
Map<String, Object> specs = utils.getTextoPresupuesto(presupuesto, locale);
|
||||||
|
model.put("specs", specs);
|
||||||
|
|
||||||
Map<String, Object> pricing = new HashMap<>();
|
Map<String, Object> pricing = new HashMap<>();
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
@ -150,22 +156,22 @@ public class PdfService {
|
|||||||
List<Integer> tiradas = snapshot.keySet().stream().toList();
|
List<Integer> tiradas = snapshot.keySet().stream().toList();
|
||||||
pricing.put("tiradas", tiradas);
|
pricing.put("tiradas", tiradas);
|
||||||
pricing.put("impresion", snapshot.values().stream()
|
pricing.put("impresion", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getPrecioTotalTirada(), locale))
|
.map(p -> Utils.formatCurrency(p.getPrecioTotalTirada(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("servicios", snapshot.values().stream()
|
pricing.put("servicios", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getServiciosTotal(), locale))
|
.map(p -> Utils.formatCurrency(p.getServiciosTotal(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("peso", snapshot.values().stream()
|
pricing.put("peso", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getPeso(), locale))
|
.map(p -> Utils.formatCurrency(p.getPeso(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("iva_4", snapshot.values().stream()
|
pricing.put("iva_4", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getIvaImporte4(), locale))
|
.map(p -> Utils.formatCurrency(p.getIvaImporte4(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("iva_21", snapshot.values().stream()
|
pricing.put("iva_21", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getIvaImporte21(), locale))
|
.map(p -> Utils.formatCurrency(p.getIvaImporte21(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("total", snapshot.values().stream()
|
pricing.put("total", snapshot.values().stream()
|
||||||
.map(p -> formatCurrency(p.getTotalConIva(), locale))
|
.map(p -> Utils.formatCurrency(p.getTotalConIva(), locale))
|
||||||
.toList());
|
.toList());
|
||||||
pricing.put("show_iva_4", presupuesto.getIvaImporte4().floatValue() > 0);
|
pricing.put("show_iva_4", presupuesto.getIvaImporte4().floatValue() > 0);
|
||||||
pricing.put("show_iva_21", presupuesto.getIvaImporte21().floatValue() > 0);
|
pricing.put("show_iva_21", presupuesto.getIvaImporte21().floatValue() > 0);
|
||||||
@ -196,9 +202,4 @@ public class PdfService {
|
|||||||
throw new RuntimeException("Error generando presupuesto PDF", 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,15 @@
|
|||||||
package com.imprimelibros.erp.presupuesto;
|
package com.imprimelibros.erp.presupuesto;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.common.Utils;
|
||||||
import com.imprimelibros.erp.datatables.*;
|
import com.imprimelibros.erp.datatables.*;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
|
||||||
import jakarta.persistence.criteria.Expression;
|
import jakarta.persistence.criteria.Expression;
|
||||||
import jakarta.persistence.criteria.JoinType;
|
|
||||||
|
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -71,7 +67,7 @@ public class PresupuestoDatatableService {
|
|||||||
cb.coalesce(root.get("paginasNegro"), cb.literal(0))))
|
cb.coalesce(root.get("paginasNegro"), cb.literal(0))))
|
||||||
|
|
||||||
.add("estado", p -> msg(p.getEstado().getMessageKey(), locale))
|
.add("estado", p -> msg(p.getEstado().getMessageKey(), locale))
|
||||||
.add("totalConIva", p -> formatCurrency(p.getTotalConIva(), locale))
|
.add("totalConIva", p -> Utils.formatCurrency(p.getTotalConIva(), locale))
|
||||||
.addIf(publico, "pais", Presupuesto::getPais)
|
.addIf(publico, "pais", Presupuesto::getPais)
|
||||||
.addIf(publico, "region", Presupuesto::getRegion)
|
.addIf(publico, "region", Presupuesto::getRegion)
|
||||||
.addIf(publico, "ciudad", Presupuesto::getCiudad)
|
.addIf(publico, "ciudad", Presupuesto::getCiudad)
|
||||||
@ -105,12 +101,6 @@ public class PresupuestoDatatableService {
|
|||||||
return df.format(instant);
|
return df.format(instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatCurrency(BigDecimal value, Locale locale) {
|
|
||||||
if (value == null)
|
|
||||||
return "";
|
|
||||||
return NumberFormat.getCurrencyInstance(locale).format(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generarBotones(Presupuesto p) {
|
private String generarBotones(Presupuesto p) {
|
||||||
boolean borrador = p.getEstado() == Presupuesto.Estado.borrador;
|
boolean borrador = p.getEstado() == Presupuesto.Estado.borrador;
|
||||||
String id = String.valueOf(p.getId());
|
String id = String.valueOf(p.getId());
|
||||||
|
|||||||
@ -14,6 +14,7 @@ pdf.presupuesto.client=CLIENTE:
|
|||||||
pdf.presupuesto.date=FECHA:
|
pdf.presupuesto.date=FECHA:
|
||||||
|
|
||||||
pdf.presupuesto.titulo=Título:
|
pdf.presupuesto.titulo=Título:
|
||||||
|
pdf.presupuesto.descripcion=Descripción:
|
||||||
|
|
||||||
pdf.table.tirada=TIRADA
|
pdf.table.tirada=TIRADA
|
||||||
pdf.table.impresion=IMPRESIÓN
|
pdf.table.impresion=IMPRESIÓN
|
||||||
@ -22,6 +23,11 @@ pdf.table.iva-4=IVA 4%
|
|||||||
pdf.table.iva-21=IVA 21%
|
pdf.table.iva-21=IVA 21%
|
||||||
pdf.table.precio-total=PRECIO TOTAL
|
pdf.table.precio-total=PRECIO TOTAL
|
||||||
|
|
||||||
|
pdf.servicios-adicionales=<u>Servicios adicionales:</u>
|
||||||
|
pdf.ejemplares-deposito-legal=Impresión de {0} ejemplares de depósito legal<br />
|
||||||
|
pdf.datos-maquetacion=Datos de maquetación:
|
||||||
|
pdf.datos-marcapaginas=Datos de marcapáginas:
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@ -187,6 +187,7 @@ presupuesto.extras=Servicios Extras
|
|||||||
presupuesto.extras-descripcion=Seleccione los servicios adicionales que desea añadir al presupuesto
|
presupuesto.extras-descripcion=Seleccione los servicios adicionales que desea añadir al presupuesto
|
||||||
presupuesto.extras-retractilado=Retractilado
|
presupuesto.extras-retractilado=Retractilado
|
||||||
presupuesto.extras-isbn=ISBN
|
presupuesto.extras-isbn=ISBN
|
||||||
|
presupuesto.extras-service-isbn=ISBN
|
||||||
presupuesto.extras-deposito-legal=Depósito Legal
|
presupuesto.extras-deposito-legal=Depósito Legal
|
||||||
presupuesto.extras-deposito-legal-descripcion=Se añadirán 4 ejemplares a la tirada
|
presupuesto.extras-deposito-legal-descripcion=Se añadirán 4 ejemplares a la tirada
|
||||||
presupuesto.extras-revision-archivos=Revisión de archivos
|
presupuesto.extras-revision-archivos=Revisión de archivos
|
||||||
|
|||||||
@ -207,6 +207,7 @@ body.has-watermark {
|
|||||||
margin-left: 15mm; /* ← margen izquierdo real del A4 */
|
margin-left: 15mm; /* ← margen izquierdo real del A4 */
|
||||||
margin-right: auto; /* opcional */
|
margin-right: auto; /* opcional */
|
||||||
color: #5c5c5c;
|
color: #5c5c5c;
|
||||||
|
font-size: 9pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-with-text {
|
.align-with-text {
|
||||||
@ -231,6 +232,26 @@ body.has-watermark {
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Listas sin margen superior por defecto */
|
||||||
|
ul, ol {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0rem; /* si quieres algo abajo */
|
||||||
|
padding-left: 1.25rem; /* sangría */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Párrafos con menos margen inferior */
|
||||||
|
p {
|
||||||
|
margin: 0 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Si una lista va justo después de un texto o título, que no tenga hueco arriba */
|
||||||
|
p + ul, p + ol,
|
||||||
|
h1 + ul, h2 + ul, h3 + ul, h4 + ul, h5 + ul, h6 + ul,
|
||||||
|
div + ul, div + ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.block-title {
|
.block-title {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -289,20 +310,20 @@ body.has-watermark {
|
|||||||
|
|
||||||
.prices thead th {
|
.prices thead th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 6px;
|
padding: 3px;
|
||||||
border-bottom: 2px solid var(--accent);
|
border-bottom: 2px solid var(--accent);
|
||||||
background: #eef8fe;
|
background: #eef8fe;
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prices tbody td {
|
.prices tbody td {
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
padding: 6px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prices .col-tirada {
|
.prices .col-tirada {
|
||||||
width: 22%;
|
width: 22%;
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|||||||
@ -68,17 +68,37 @@
|
|||||||
<span class="val" th:text="${titulo} ?: '-'">Libro de prueba</span>
|
<span class="val" th:text="${titulo} ?: '-'">Libro de prueba</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DATOS TÉCNICOS EN 2 COLUMNAS -->
|
<!-- DATOS TÉCNICOS -->
|
||||||
|
<div class="line-title">
|
||||||
|
<span class="lbl" th:text="#{pdf.presupuesto.descripcion}">Descripción:</span>
|
||||||
|
</div>
|
||||||
<div class="specs-wrapper align-with-text ">
|
<div class="specs-wrapper align-with-text ">
|
||||||
<div class="specs">
|
<div class="specs">
|
||||||
<div th:utext="${especificaciones} ?: '<em>Sin especificaciones técnicas.</em>'">
|
<div th:if="${specs} == null">
|
||||||
<em>Sin especificaciones técnicas.</em>
|
<em>Sin especificaciones técnicas.</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div th:if="${specs} != null">
|
||||||
</div> <!-- .specs-wrapper -->
|
<div th:each="spec : ${specs.lineas}" class="spec-row mb-1">
|
||||||
|
<span class="spec-label" th:utext="${spec.descripcion}"></span>
|
||||||
|
</div>
|
||||||
|
<div th:if="${specs.servicios != null}" class="spec-row mb-1">
|
||||||
|
<span th:utext="#{pdf.servicios-adicionales}">Servicios adicionales</span>
|
||||||
|
<span class="spec-label" th:text="${specs.servicios}"></span>
|
||||||
|
</div>
|
||||||
|
<div th:if="${specs.datosMaquetacion != null}" class="spec-row mb-1">
|
||||||
|
<span th:text="#{pdf.datos-maquetacion}">Datos de maquetación:</span>
|
||||||
|
<span th:utext="${specs.datosMaquetacion}"></span>
|
||||||
|
</div>
|
||||||
|
<div th:if="${specs.datosMarcapaginas != null}" class="spec-row mb-1">
|
||||||
|
<span th:text="#{pdf.datos-marcapaginas}">Datos de marcapáginas:</span>
|
||||||
|
<span th:utext="${specs.datosMarcapaginas}"></span>
|
||||||
|
</div>
|
||||||
|
</div> <!-- .specs -->
|
||||||
|
</div> <!-- .specs-wrapper -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- TABLA TIRADAS -->
|
<!-- TABLA TIRADAS -->
|
||||||
<table class="prices">
|
<table class="prices" class="align-items-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center col-tirada" th:text="#{pdf.table.tirada}">TIRADA</th>
|
<th class="text-center col-tirada" th:text="#{pdf.table.tirada}">TIRADA</th>
|
||||||
|
|||||||
@ -1,57 +1,46 @@
|
|||||||
<div th:fragment="buttons(appMode, btnClass, showPrev, showNext, showActions, showCart)"
|
<div th:fragment="buttons(appMode, btnClass, showPrev, showNext, showActions, showCart)" class="buttons-bar mt-2">
|
||||||
class="buttons-bar mt-2">
|
|
||||||
|
|
||||||
<!-- Fila 1: ACCIONES, centradas -->
|
<!-- Fila 1: ACCIONES, centradas -->
|
||||||
<div class="buttons-row center" th:if="${showActions}">
|
<div class="buttons-row center" th:if="${showActions}">
|
||||||
<button th:if="${appMode == 'add' or appMode == 'edit'}"
|
<button th:if="${appMode == 'add' or appMode == 'edit'}" type="button"
|
||||||
type="button"
|
class="btn btn-secondary d-flex align-items-center mx-2 guardar-presupuesto">
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 guardar-presupuesto">
|
|
||||||
<i class="ri-save-3-line me-2"></i>
|
<i class="ri-save-3-line me-2"></i>
|
||||||
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button th:if="${appMode == 'add' or appMode == 'edit'}"
|
<button type="button" class="btn btn-secondary d-flex align-items-center mx-2 btn-imprimir">
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
|
||||||
<i class="ri-shopping-cart-line me-2"></i>
|
|
||||||
<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>
|
<i class="ri-printer-line me-2"></i>
|
||||||
<span th:text="#{app.imprimir}">Imprimir</span>
|
<span th:text="#{app.imprimir}">Imprimir</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Alternativa cuando no está autenticado -->
|
<button th:if="${appMode == 'add' or appMode == 'edit'}" type="button"
|
||||||
<button sec:authorize="!isAuthenticated()"
|
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||||
type="button"
|
<i class="ri-shopping-cart-line me-2"></i>
|
||||||
class="btn btn-secondary d-flex align-items-center btn-login-required ms-2">
|
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span>
|
||||||
<i class="mdi mdi-login label-icon align-middle fs-16 me-2"></i>
|
</button>
|
||||||
<span th:text="#{presupuesto.resumen.inicie-sesion}">Inicie sesión para continuar</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>
|
</div>
|
||||||
|
|
||||||
<!-- Fila 2: PREV (izq) / NEXT o LOGIN (dcha) -->
|
<!-- Fila 2: PREV (izq) / NEXT o LOGIN (dcha) -->
|
||||||
<div class="buttons-row split">
|
<div class="buttons-row split">
|
||||||
<button th:if="${showPrev}"
|
<button th:if="${showPrev}" th:id="|${btnClass}-prev|" data-btn-action="previous"
|
||||||
th:id="|${btnClass}-prev|"
|
th:class="'btn btn-light ' + ${btnClass}" type="button">
|
||||||
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>
|
<i class="ri-arrow-left-circle-line label-icon align-middle fs-16 me-2"></i>
|
||||||
<span th:text="#{app.acciones.anterior}">Anterior</span>
|
<span th:text="#{app.acciones.anterior}">Anterior</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="right-slot d-flex align-items-center">
|
<div class="right-slot d-flex align-items-center">
|
||||||
<button th:if="${showNext}"
|
<button th:if="${showNext}" data-btn-action="next" th:id="|${btnClass}-next|"
|
||||||
data-btn-action="next"
|
th:class="'btn btn-secondary d-flex align-items-center ' + ${btnClass}" type="button">
|
||||||
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>
|
<span th:text="#{app.acciones.siguiente}">Siguiente</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4,7 +4,7 @@ package com.imprimelibros.erp.pdf;
|
|||||||
import org.junit.jupiter.api.Test;
|
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.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -15,7 +15,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
|
*/
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class PdfSmokeTest {
|
class PdfSmokeTest {
|
||||||
|
|||||||
Reference in New Issue
Block a user