diff --git a/pom.xml b/pom.xml index 2463cb2..a6602d9 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ - 24 + 21 diff --git a/src/main/java/com/imprimelibros/erp/config/WebConfig.java b/src/main/java/com/imprimelibros/erp/config/WebConfig.java new file mode 100644 index 0000000..21c9a1f --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/config/WebConfig.java @@ -0,0 +1,13 @@ +package com.imprimelibros.erp.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WebConfig { + @Bean + public ResourceUrlEncodingFilter resourceUrlEncodingFilter() { + return new ResourceUrlEncodingFilter(); + } +} diff --git a/src/main/java/com/imprimelibros/erp/i18n/TranslationService.java b/src/main/java/com/imprimelibros/erp/i18n/TranslationService.java index 65f2cc6..e46de46 100644 --- a/src/main/java/com/imprimelibros/erp/i18n/TranslationService.java +++ b/src/main/java/com/imprimelibros/erp/i18n/TranslationService.java @@ -4,6 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Component; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion; +import com.imprimelibros.erp.presupuesto.Presupuesto.TipoImpresion; + import java.util.*; @Component @@ -19,4 +23,16 @@ public class TranslationService { } return translations; } + + public String label(TipoEncuadernacion t, Locale locale) { + return messageSource.getMessage(t.getMessageKey(), null, locale); + } + public String label(TipoImpresion t, Locale locale) { + return messageSource.getMessage(t.getMessageKey(), null, locale); + } + public String label(TipoCubierta t, Locale locale) { + return messageSource.getMessage(t.getMessageKey(), null, locale); + } + + } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/Presupuesto.java b/src/main/java/com/imprimelibros/erp/presupuesto/Presupuesto.java index d768ab2..50033f0 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/Presupuesto.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/Presupuesto.java @@ -19,15 +19,54 @@ import jakarta.persistence.*; public class Presupuesto implements Cloneable{ public enum TipoEncuadernacion { - fresado, cosido, grapado, espiral, wireo + fresado("presupuesto.fresado"), + cosido("presupuesto.cosido"), + grapado("presupuesto.grapado"), + espiral("presupuesto.espiral"), + wireo("presupuesto.wireo"); + + private final String messageKey; + + TipoEncuadernacion(String messageKey) { + this.messageKey = messageKey; + } + + public String getMessageKey() { + return messageKey; + } } public enum TipoImpresion { - negro, negrohq, color, colorhq + negro("presupuesto.blanco-negro"), + negrohq("presupuesto.blanco-negro-premium"), + color("presupuesto.color"), + colorhq("presupuesto.color-premium"); + + private final String messageKey; + + TipoImpresion(String messageKey) { + this.messageKey = messageKey; + } + + public String getMessageKey() { + return messageKey; + } } public enum TipoCubierta { - tapaBlanda, tapaDura, tapaDuraLomoRedondo + tapaBlanda("presupuesto.tapa-blanda"), + tapaDura("presupuesto.tapa-dura"), + tapaDuraLomoRedondo("presupuesto.tapa-dura-lomo-redondo"); + + private final String messageKey; + + TipoCubierta(String messageKey) { + this.messageKey = messageKey; + } + + public String getMessageKey() { + return messageKey; + } } @Override @@ -44,6 +83,7 @@ public class Presupuesto implements Cloneable{ private Long id; @NotNull(message = "{presupuesto.errores.tipo-encuadernacion}", groups = PresupuestoValidationGroups.DatosGenerales.class) + @Enumerated(EnumType.STRING) @Column(name = "tipo_encuadernacion") private TipoEncuadernacion tipoEncuadernacion = TipoEncuadernacion.fresado; @@ -104,6 +144,7 @@ public class Presupuesto implements Cloneable{ private Integer paginasColorTotal; @NotNull(message = "{presupuesto.errores.tipo-impresion}", groups = PresupuestoValidationGroups.Interior.class) + @Enumerated(EnumType.STRING) @Column(name = "tipo_impresion") private TipoImpresion tipoImpresion = TipoImpresion.negro; @@ -116,6 +157,7 @@ public class Presupuesto implements Cloneable{ private Integer gramajeInterior; @NotNull(message = "{presupuesto.errores.tipo-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class) + @Enumerated(EnumType.STRING) @Column(name = "tipo_cubierta") private TipoCubierta tipoCubierta = TipoCubierta.tapaBlanda; @@ -535,4 +577,16 @@ public class Presupuesto implements Cloneable{ public void setPresupuestoMaquetacionData(String presupuestoMaquetacionData) { this.presupuestoMaquetacionData = presupuestoMaquetacionData; } + + public String resumenPresupuesto() { + return String.format("%s - %s - %dx%d mm - %d Páginas (N:%d C:%d) - Tira:%d", + this.titulo, + this.tipoEncuadernacion, + this.ancho, + this.alto, + this.paginasNegro + this.paginasColorTotal, + this.paginasNegro, + this.paginasColorTotal, + this.selectedTirada != null ? this.selectedTirada : 0); + } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java index 3106521..4f4883e 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java @@ -3,6 +3,7 @@ package com.imprimelibros.erp.presupuesto; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -18,11 +19,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.http.MediaType; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.core.type.TypeReference; import com.imprimelibros.erp.externalApi.skApiClient; import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion; @@ -44,6 +44,12 @@ public class PresupuestoController { @Autowired protected MessageSource messageSource; + private final ObjectMapper objectMapper; + + public PresupuestoController(ObjectMapper objectMapper){ + this.objectMapper = objectMapper; + } + @PostMapping("/public/validar/datos-generales") public ResponseEntity validarDatosGenerales( @Validated(PresupuestoValidationGroups.DatosGenerales.class) Presupuesto presupuesto, @@ -115,21 +121,14 @@ public class PresupuestoController { if (calcular) { - HashMap price = new HashMap<>(); - String priceStr = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto)); + HashMap price = presupuestoService.calcularPresupuesto(presupuesto, locale); - try { - price = new ObjectMapper().readValue(priceStr, new TypeReference<>() { - }); - } catch (JsonProcessingException e) { - price = new HashMap<>(); - price.put("error", messageSource.getMessage("presupuesto.error-obtener-precio", null, locale)); - } if (!price.containsKey("data")) { return ResponseEntity.badRequest() .body(messageSource.getMessage("presupuesto.error-obtener-precio", null, locale)); } return ResponseEntity.ok(price.get("data")); + } return ResponseEntity.ok().build(); } @@ -153,7 +152,7 @@ public class PresupuestoController { Map resultado = new HashMap<>(); // servicios extra - resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient)); + resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale)); Map language = new HashMap<>(); language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale)); resultado.put("language", language); @@ -375,4 +374,15 @@ public class PresupuestoController { return ResponseEntity.ok(resultado); } + // Se hace un post para no tener problemas con la longitud de la URL + @PostMapping("/public/resumen") + public ResponseEntity getResumen(@RequestBody Map body, Locale locale) { + Presupuesto p = objectMapper.convertValue(body.get("presupuesto"), Presupuesto.class); + + @SuppressWarnings("unchecked") + List> serviciosList = (List>) body.getOrDefault("servicios", List.of()); + + return ResponseEntity.ok(presupuestoService.getResumen(p, serviciosList, locale)); + } + } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java index 8850973..412c51a 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java @@ -6,12 +6,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.Locale; import java.text.NumberFormat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; @@ -25,6 +27,7 @@ import com.imprimelibros.erp.configurationERP.VariableService; import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta; import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto; import com.imprimelibros.erp.presupuesto.classes.PresupuestadorItems; +import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter; import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPrecios; import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionPreciosRepository; import com.imprimelibros.erp.presupuesto.marcapaginas.Marcapaginas; @@ -43,9 +46,6 @@ public class PresupuestoService { @Autowired protected MessageSource messageSource; - @Autowired - protected skApiClient skApiClient; - @Autowired protected MaquetacionPreciosRepository maquetacionPreciosRepository; @@ -56,9 +56,13 @@ public class PresupuestoService { protected MarcapaginasRepository marcapaginasRepository; private final PresupuestadorItems presupuestadorItems; + private final PresupuestoFormatter presupuestoFormatter; + private final skApiClient apiClient; - public PresupuestoService(PresupuestadorItems presupuestadorItems) { + public PresupuestoService(PresupuestadorItems presupuestadorItems, PresupuestoFormatter presupuestoFormatter, skApiClient apiClient) { this.presupuestadorItems = presupuestadorItems; + this.presupuestoFormatter = presupuestoFormatter; + this.apiClient = apiClient; } public boolean validateDatosGenerales(int[] tiradas) { @@ -301,15 +305,6 @@ public class PresupuestoService { "cabezada", presupuesto.getCabezada(), "lomoRedondo", presupuesto.getTipoCubierta() == TipoCubierta.tapaDuraLomoRedondo ? 1 : 0); - /* - * Map servicios = Map.of( - * "retractilado", 0, - * "retractilado5", 0, - * "ferro", 0, - * "ferroDigital", 0, - * "marcapaginas", 0, - * "prototipo", 0); - */ Map body = new HashMap<>(); body.put("tipo_impresion_id", this.getTipoImpresionId(presupuesto)); body.put("tirada", Arrays.stream(presupuesto.getTiradas()) @@ -459,18 +454,18 @@ public class PresupuestoService { Map requestBody = new HashMap<>(); requestBody.put("tirada", presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : tirada_min); - Double precio_retractilado = skApiClient.getRetractilado(requestBody); + Double precio_retractilado = apiClient.getRetractilado(requestBody); return precio_retractilado != null ? NumberFormat.getNumberInstance(locale) .format(Math.round(precio_retractilado * 100.0) / 100.0) : "0,00"; } - public Map obtenerServiciosExtras(Presupuesto presupuesto, Locale locale, skApiClient apiClient) { + public Map obtenerServiciosExtras(Presupuesto presupuesto, Locale locale) { List opciones = new ArrayList<>(); - Double price_prototipo = this.obtenerPrototipo(presupuesto, apiClient); + Double price_prototipo = this.obtenerPrototipo(presupuesto); opciones.add(new HashMap() { { @@ -567,7 +562,7 @@ public class PresupuestoService { return response; } - private Double obtenerPrototipo(Presupuesto presupuesto, skApiClient apiClient) { + private Double obtenerPrototipo(Presupuesto presupuesto) { // Obtenemos el precio de 1 unidad para el ejemplar de prueba HashMap price = new HashMap<>(); @@ -760,12 +755,15 @@ public class PresupuestoService { resultado.put("precio_unitario", precio_unidad); resultado.put("precio_total", pvp); HashMap language = new HashMap<>(); - language.put("precio_unidad", messageSource.getMessage("presupuesto.marcapaginas.precio-unidad", null, locale)); - language.put("precio_total", messageSource.getMessage("presupuesto.marcapaginas.precio-total", null, locale)); + language.put("precio_unidad", + messageSource.getMessage("presupuesto.marcapaginas.precio-unidad", null, locale)); + language.put("precio_total", + messageSource.getMessage("presupuesto.marcapaginas.precio-total", null, locale)); language.put("add_to_presupuesto", messageSource.getMessage("presupuesto.add-to-presupuesto", null, locale)); language.put("cancel", messageSource.getMessage("app.cancelar", null, locale)); - language.put("presupuesto_marcapaginas", messageSource.getMessage("presupuesto.marcapaginas", null, locale)); + language.put("presupuesto_marcapaginas", + messageSource.getMessage("presupuesto.marcapaginas", null, locale)); resultado.put("language", language); return resultado; @@ -777,4 +775,85 @@ public class PresupuestoService { out.put("precio_total", 0.0); return out; } + + public Map getResumen(Presupuesto presupuesto, List> servicios, Locale locale) { + + Map resumen = new HashMap<>(); + resumen.put("titulo", presupuesto.getTitulo()); + + Presupuesto pressupuestoTemp = presupuesto.clone(); + + resumen.put("imagen", "/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion() + ".png"); + resumen.put("imagen_alt", messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null, locale)); + + boolean hayDepositoLegal = servicios != null && servicios.stream() + .map(m -> java.util.Objects.toString(m.get("id"), "")) // null-safe -> String + .map(String::trim) + .anyMatch("deposito-legal"::equals); + + if(hayDepositoLegal){ + pressupuestoTemp.setSelectedTirada(presupuesto.getSelectedTirada()+4); + } + + HashMap precios = this.calcularPresupuesto(pressupuestoTemp, locale); + if(precios.containsKey("error")){ + resumen.put("error", precios.get("error")); + return resumen; + } + + HashMap linea = new HashMap<>(); + Double precio_unitario = 0.0; + Double precio_total = 0.0; + Integer counter = 0; + linea.put("descripcion", presupuestoFormatter.resumen(presupuesto, servicios, locale)); + linea.put("cantidad", presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0); + precio_unitario = ((List) ((Map) precios.get("data")).get("precios")) + .get(0); + precio_total = precio_unitario * presupuesto.getSelectedTirada(); + linea.put("precio_unitario", precio_unitario); + linea.put("precio_total", BigDecimal.valueOf(precio_total).setScale(2, RoundingMode.HALF_UP)); + resumen.put("linea" + counter, linea); + counter++; + + if(hayDepositoLegal) { + + linea = new HashMap<>(); + linea.put("descripcion", messageSource.getMessage("presupuesto.resumen-deposito-legal", null, locale)); + linea.put("cantidad", 4); + linea.put("precio_unitario", precio_unitario); + linea.put("precio_total", BigDecimal.valueOf(precio_unitario * 4).setScale(2, RoundingMode.HALF_UP)); + resumen.put("linea" + counter, linea); + counter++; + } + + List> serviciosExtras = new ArrayList<>(); + + if(servicios != null){ + for (Map servicio : servicios) { + HashMap servicioData = new HashMap<>(); + servicioData.put("descripcion", servicio.get("label")); + servicioData.put("precio", servicio.get("price")); + serviciosExtras.add(servicioData); + } + } + resumen.put("servicios", serviciosExtras); + + return resumen; + } + + public HashMap calcularPresupuesto(Presupuesto presupuesto, Locale locale) { + + HashMap price = new HashMap<>(); + String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuesto)); + + try { + price = new ObjectMapper().readValue(priceStr, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + price = new HashMap<>(); + price.put("error", messageSource.getMessage("presupuesto.error-obtener-precio", null, locale)); + } + + return price; + } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestadorItems.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestadorItems.java index 83c5bbe..8684c45 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestadorItems.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestadorItems.java @@ -10,6 +10,8 @@ import java.util.Map; @Component public class PresupuestadorItems { + + @Autowired private MessageSource messageSource; diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoAcabados.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoAcabados.java new file mode 100644 index 0000000..b34060a --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoAcabados.java @@ -0,0 +1,33 @@ +package com.imprimelibros.erp.presupuesto.classes; + +import java.util.Locale; +import java.util.Map; + +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Component; + +@Component +public class PresupuestoAcabados { + + private final MessageSource messageSource; + + public PresupuestoAcabados(MessageSource messageSource) { + this.messageSource = messageSource; + } + + private static final Map ACABADO_KEYS = Map.of( + 0, "presupuesto.acabado-ninguno", + 1, "presupuesto.acabado-plastificado-brillo-1c", + 5, "presupuesto.acabado-plastificado-mate-1c", + 8, "presupuesto.acabado-plastificado-mate-1c-antirrayado", + 2, "presupuesto.acabado-plastificado-mate-uvi", + 3, "presupuesto.acabado-plastificado-mate-uvi3d", + 4, "presupuesto.acabado-plastificado-mate-uvi-braile", + 9, "presupuesto.acabado-plastificado-sandy-1c" + ); + + public String labelAcabado(int id, Locale locale) { + String key = ACABADO_KEYS.get(id); + return key != null ? messageSource.getMessage(key, null, locale) : String.valueOf(id); + } +} diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoFormatter.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoFormatter.java new file mode 100644 index 0000000..c3e6980 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoFormatter.java @@ -0,0 +1,133 @@ +package com.imprimelibros.erp.presupuesto.classes; + +import org.springframework.stereotype.Component; + +import com.imprimelibros.erp.i18n.TranslationService; +import com.imprimelibros.erp.presupuesto.Presupuesto; +import org.springframework.context.MessageSource; + +import java.util.Arrays; +import java.util.Locale; +import java.util.List; +import java.util.Map; + +@Component +public class PresupuestoFormatter { + + private final TranslationService translationService; + private final MessageSource ms; + private final PresupuestoPapeles papeles; + private final PresupuestoAcabados acabados; + + public PresupuestoFormatter( + TranslationService translationService, + MessageSource ms, + PresupuestoPapeles papeles, + PresupuestoAcabados acabados) { + this.translationService = translationService; + this.ms = ms; + this.papeles = papeles; + this.acabados = acabados; + } + + public String resumen(Presupuesto p, List> servicios, Locale locale) { + String encuadernacion = translationService.label(p.getTipoEncuadernacion(), locale); + String tipoImpresion = translationService.label(p.getTipoImpresion(), locale); + String tapaCubierta = translationService.label(p.getTipoCubierta(), locale); + + Object[] args = { + + p.getSelectedTirada(), + encuadernacion, + tipoImpresion, + (p.getPaginasColorTotal() != null ? p.getPaginasColorTotal() : p.getPaginasColor()) + + p.getPaginasNegro(), + p.getAncho(), p.getAlto(), + papeles.labelPapel(p.getPapelInteriorId(), locale), p.getGramajeInterior(), + tapaCubierta, + papeles.labelPapel(p.getPapelCubiertaId(), locale), p.getGramajeCubierta(), + }; + String textoResumen = ms.getMessage("presupuesto.resumen-texto", args, locale); + textoResumen += "
    "; + + // tapa blanda + if (p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaBlanda) { + String impresionCubierta = ms.getMessage( + "presupuesto.resumen-texto-impresion-caras-cubierta", + new Object[] { + p.getCubiertaCaras() == 2 + ? ms.getMessage("presupuesto.una-cara", null, locale) + : ms.getMessage("presupuesto.dos-caras", null, locale) + }, + locale); + textoResumen += impresionCubierta; + + if (p.getSolapasCubierta()) { + textoResumen += ms.getMessage( + "presupuesto.resumen-texto-solapas-cubierta", + new Object[] { p.getTamanioSolapasCubierta() }, + locale); + } + } + // tapa dura + else if (p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura + || p.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) { + + String textImpresionGuardas = ""; + if (p.getGuardasImpresas() == 0) { + textImpresionGuardas = ms.getMessage("presupuesto.guardas-no-impresas", null, locale); + } else if (p.getGuardasImpresas() == 4) { + textImpresionGuardas = ms.getMessage("presupuesto.guardas-impresas-una-cara", null, locale); + } else if (p.getGuardasImpresas() == 8) { + textImpresionGuardas = ms.getMessage("presupuesto.guardas-impresas-dos-caras", null, locale); + } + + textoResumen += ms.getMessage( + "presupuesto.resumen-texto-guardas-cabezada", + new Object[] { + textImpresionGuardas, + papeles.labelPapel(p.getPapelGuardasId(), locale), + p.getGramajeGuardas(), + papeles.labelCabezada(p.getCabezada(), locale), + }, + locale); + } + + if (p.getAcabado() != null) { + textoResumen += ms.getMessage( + "presupuesto.resumen-texto-acabado-cubierta", + new Object[] { acabados.labelAcabado(p.getAcabado(), locale) }, + locale); + } + + textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale); + + if (Boolean.TRUE.equals(p.getSobrecubierta())) { + textoResumen += ms.getMessage( + "presupuesto.resumen-texto-sobrecubierta", + new Object[] { + papeles.labelPapel(p.getPapelSobrecubiertaId(), locale), + p.getGramajeSobrecubierta(), + acabados.labelAcabado(p.getAcabadoSobrecubierta(), locale), + p.getTamanioSolapasSobrecubierta() + }, + locale); + } + + if (Boolean.TRUE.equals(p.getFaja())) { + textoResumen += ms.getMessage( + "presupuesto.resumen-texto-faja", + new Object[] { + papeles.labelPapel(p.getPapelFajaId(), locale), + p.getGramajeFaja(), + p.getAltoFaja(), + acabados.labelAcabado(p.getAcabadoFaja(), locale), + p.getTamanioSolapasFaja() + }, + locale); + } + textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale); + + return textoResumen; + } +} diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java new file mode 100644 index 0000000..534b1d1 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoPapeles.java @@ -0,0 +1,43 @@ +package com.imprimelibros.erp.presupuesto.classes; + +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Component; + +import java.util.Locale; +import java.util.Map; + +@Component +public class PresupuestoPapeles { + + private final MessageSource messageSource; + + public PresupuestoPapeles(MessageSource messageSource) { + this.messageSource = messageSource; + } + + private static final Map PAPEL_KEYS = Map.of( + 2, "presupuesto.estucado-mate", + 3, "presupuesto.offset-blanco", + 4, "presupuesto.offset-ahuesado", + 5, "presupuesto.cartulina-grafica-cubierta", + 6, "presupuesto.offset-ahuesado-volumen", + 7, "presupuesto.offset-blanco-volumen" + ); + + private static final Map CABEZADA_COLOR_KEYS = Map.of( + "WHI", "presupuesto.cabezada-blanca", + "GRE", "presupuesto.cabezada-verde", + "BLUE", "presupuesto.cabezada-azul", + "REDYEL", "presupuesto.cabezada-roja-amarilla" + ); + + public String labelPapel(int id, Locale locale) { + String key = PAPEL_KEYS.get(id); + return key != null ? messageSource.getMessage(key, null, locale) : String.valueOf(id); + } + + public String labelCabezada(String code, Locale locale) { + String key = CABEZADA_COLOR_KEYS.get(code); + return key != null ? messageSource.getMessage(key, null, locale) : code; + } +} diff --git a/src/main/resources/i18n/presupuesto_es.properties b/src/main/resources/i18n/presupuesto_es.properties index 13a8e98..72ec2d2 100644 --- a/src/main/resources/i18n/presupuesto_es.properties +++ b/src/main/resources/i18n/presupuesto_es.properties @@ -79,6 +79,9 @@ presupuesto.impresion-cubierta=Impresión de cubierta presupuesto.impresion-cubierta-help=La cubierta se puede imprimir por anverso y reverso, como en el caso de las revistas, pero para un libro normal con portada y contraportada, la impresión de cubierta es a una cara. presupuesto.una-cara=Una cara presupuesto.dos-caras=Dos caras +presupuesto.guardas-impresas-una-cara=impresas a una cara +presupuesto.guardas-impresas-dos-caras=impresas a dos caras +presupuesto.guardas-no-impresas=no impresas presupuesto.tamanio-solapa=Tamaño solapas presupuesto.papel-guardas=Papel de guardas presupuesto.guardas-impresas=Guardas impresas @@ -157,7 +160,28 @@ presupuesto.calcular-presupuesto=Calcular presupuesto presupuesto.consultar-soporte=Consultar con soporte # Pestaña resumen del presupuesto +presupuesto.resumen.tabla.descripcion=Descripción +presupuesto.resumen.tabla.cantidad=Cantidad +presupuesto.resumen.tabla.precio-unidad=Precio/unidad +presupuesto.resumen.tabla.precio-total=Precio total +presupuesto.resumen.tabla.base=Base +presupuesto.resumen.tabla.iva=I.V.A. (4%) +presupuesto.resumen.tabla.total=Total presupuesto +presupuesto.resumen-texto=Impresion de {0} unidades encuadernadas en {1} en {2} con {3} páginas en formato {4} x {5} mm. \ +
      \ +
    • Papel interior {6} {7} gr.
    • \ +
    • Cubierta {8} en {9} {10} gr.
    • +presupuesto.resumen-texto-impresion-caras-cubierta=
    • Impresa a {0}.
    • +presupuesto.resumen-texto-solapas-cubierta=
    • Solapas de {0} mm.
    • +presupuesto.resumen-texto-guardas-cabezada=
    • Guardas {0} en {1} {2}. Color de la cabezada: {3}.
    • +presupuesto.resumen-texto-acabado-cubierta=
    • Acabado {0}.
    • +presupuesto.resumen-texto-end=
    +presupuesto.resumen-texto-sobrecubierta=
  • Sobrecubierta impresa en {0} {1} gr.
    • Acabado {2}
    • Solapas: {3} mm.
  • +presupuesto.resumen-texto-faja=
  • Faja impresa en {0} {1} gr. con un alto de {2} mm.
    • Acabado {3}
    • Solapas: {4} mm.
  • +presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal presupuesto.volver-extras=Volver a extras +presupuesto.resumen.inicie-sesion=Inicie sesión para continuar +presupuesto.resumen.agregar-cesta=Agregar a la cesta # Resumen del presupuesto presupuesto.resumen-presupuesto=Resumen presupuesto diff --git a/src/main/resources/static/assets/css/presupuestador.css b/src/main/resources/static/assets/css/presupuestador.css index 3e1ecca..e3cf208 100644 --- a/src/main/resources/static/assets/css/presupuestador.css +++ b/src/main/resources/static/assets/css/presupuestador.css @@ -213,7 +213,8 @@ /* ===== Tiradas (pricing cards) ===== */ .tirada-card { - --il-accent: #92b2a7; /* verde grisáceo */ + --il-accent: #92b2a7; + /* verde grisáceo */ --radius: 18px; --sel-scale-y: 1.12; /* cuánto más alta la seleccionada (crece arriba y abajo) */ @@ -352,19 +353,23 @@ .nav-link.active .bg-soft-primary { - background-color: #ffffff33 !important; /* #4c5c63 al 20% */ + background-color: #ffffff33 !important; + /* #4c5c63 al 20% */ } .nav-link.active .text-primary { - color: #ffffff !important; /* #4c5c63 al 20% */ + color: #ffffff !important; + /* #4c5c63 al 20% */ } .nav-link:not(.active) .bg-soft-primary { - background-color: #4c5c6366 !important; /* #4c5c63 al 20% */ + background-color: #4c5c6366 !important; + /* #4c5c63 al 20% */ } .nav-link:not(.active) .text-primary { - color: #000000 !important; /* #4c5c63 al 20% */ + color: #000000 !important; + /* #4c5c63 al 20% */ } /* base */ @@ -376,20 +381,20 @@ } /* hover no seleccionado */ -.btn-check-service + .btn-service-option:hover { +.btn-check-service+.btn-service-option:hover { background-color: rgba(146, 178, 167, 0.3); color: #92b2a7; } /* seleccionado */ -.btn-check-service:checked + .btn-service-option { +.btn-check-service:checked+.btn-service-option { background-color: #92b2a7; color: #fff; border-color: #92b2a7; } /* hover estando seleccionado (que no se aclare) */ -.btn-check-service:checked + .btn-service-option:hover { +.btn-check-service:checked+.btn-service-option:hover { background-color: #92b2a7; color: #fff; } @@ -407,4 +412,70 @@ .form-switch-custom.form-switch-presupuesto .form-check-input:checked::before { color: #92b2a7; +} + +/* ==== Paso al resumen ==== */ +/* ---- Ajustes rápidos ---- */ +/* Valores por defecto (col-9 / col-3 ≈ 75% / 25%) */ +#presupuesto-row{ + --main-col: 75%; + --summary-col: 25%; + --il-dur-main: 1.4s; + --il-dur-summary: .6s; + --il-delay-main: .15s; /* empieza un poco después */ + --il-delay-summary: 0s; + --il-ease-main: cubic-bezier(.2,.8,.2,1); + --il-ease-summary: cubic-bezier(.4,0,.2,1); + --il-shift: 32px; +} + +/* Forzamos que el ancho venga de las variables (y sea animable) */ +@media (prefers-reduced-motion: no-preference){ + #presupuesto-row .col-main{ + flex: 0 0 var(--main-col) !important; + max-width: var(--main-col) !important; + transition: + flex-basis var(--il-dur-main) var(--il-ease-main) var(--il-delay-main), + max-width var(--il-dur-main) var(--il-ease-main) var(--il-delay-main); + } + #presupuesto-row .summary-col{ + flex: 0 0 var(--summary-col) !important; + max-width: var(--summary-col) !important; + transition: + flex-basis var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary), + max-width var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary), + opacity var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary), + transform var(--il-dur-summary) var(--il-ease-summary) var(--il-delay-summary); + } +} + +/* Estado expandido: cambiamos SOLO las variables (esto sí se anima) */ +#presupuesto-row.expanded{ + --main-col: 100%; + --summary-col: 0%; +} + +#presupuesto-row.expanded .summary-col{ + opacity: 0; + transform: translateX(var(--il-shift)); + pointer-events: none; + overflow: hidden; +} + +@media (min-width: 1200px){ + /* Evita que las columnas se vayan a la siguiente línea durante la animación */ + #presupuesto-row{ + display: flex; /* por si acaso algún wrapper cambia el display */ + flex-wrap: nowrap; /* <-- clave */ + align-items: stretch; + } + /* Permite que las columnas puedan encoger sin forzar salto de línea */ + #presupuesto-row .col-main, + #presupuesto-row .summary-col{ + min-width: 0; /* <-- clave para que el contenido no fuerce ancho */ + } + /* Opcional: evita “asomar” algo durante el slide */ + #presupuesto-row .summary-col{ + overflow: hidden; + } } \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js index 303654b..11809bc 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js @@ -2,6 +2,7 @@ import imagen_presupuesto from "./imagen-presupuesto.js"; import ServiceOptionCard from "./service-option-card.js"; import TiradaCard from "./tirada-price-card.js"; import * as Summary from "./summary.js"; +import { formateaMoneda } from "../utils.js"; class PresupuestoCliente { @@ -36,8 +37,8 @@ class PresupuestoCliente { solapasCubierta: 0, tamanioSolapasCubierta: '80', cubiertaCaras: 2, - guardasPapelId: 3, - guardasGramaje: 170, + papelGuardasId: 3, + gramajeGuardas: 170, guardasImpresas: 0, cabezada: 'WHI', papelCubiertaId: 3, @@ -156,6 +157,9 @@ class PresupuestoCliente { // pestaña extras this.divExtras = $('#div-extras'); + // pestaña resumen + this.tablaResumen = $('#resumen-tabla-final'); + // resumen this.summaryTableInterior = $('#summary-interior'); this.summaryTableCubierta = $('#summary-cubierta'); @@ -1123,8 +1127,8 @@ class PresupuestoCliente { const solapas = $('.solapas-cubierta.selected').id == 'sin-solapas' ? 0 : 1 || 0; const tamanioSolapasCubierta = $('#tamanio-solapas-cubierta').val() || '80'; const cubiertaCaras = parseInt(this.carasImpresionCubierta.val()) || 2; - const guardasPapelId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3; - const guardasGramaje = parseInt($('#papel-guardas option:selected').data('gramaje')) || 170; + const papelGuardasId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3; + const gramajeGuardas = parseInt($('#papel-guardas option:selected').data('gramaje')) || 170; const guardasImpresas = parseInt(this.guardasImpresas) || 0; const cabezada = this.cabezada.val() || 'WHI'; const papelCubiertaId = $('#div-papel-cubierta .image-container.selected').data('sk-id') || this.formData.cubierta.papelCubiertaId || 3; @@ -1144,11 +1148,11 @@ class PresupuestoCliente { return { tipoCubierta: tipoCubierta, - solapas: solapas, + solapasCubierta: solapas, tamanioSolapasCubierta: tamanioSolapasCubierta, cubiertaCaras: cubiertaCaras, - guardasPapelId: guardasPapelId, - guardasGramaje: guardasGramaje, + papelGuardasId: papelGuardasId, + gramajeGuardas: gramajeGuardas, guardasImpresas: guardasImpresas, cabezada: cabezada, papelCubiertaId: papelCubiertaId, @@ -1175,11 +1179,11 @@ class PresupuestoCliente { #updateCubiertaData(data) { this.formData.cubierta.tipoCubierta = data.tipoCubierta; - this.formData.cubierta.solapas = data.solapas; + this.formData.cubierta.solapasCubierta = data.solapasCubierta; this.formData.cubierta.tamanioSolapasCubierta = data.tamanioSolapasCubierta; this.formData.cubierta.cubiertaCaras = data.cubiertaCaras; - this.formData.cubierta.guardasPapelId = data.guardasPapelId; - this.formData.cubierta.guardasGramaje = data.guardasGramaje; + this.formData.cubierta.papelGuardasId = data.papelGuardasId; + this.formData.cubierta.gramajeGuardas = data.gramajeGuardas; this.formData.cubierta.guardasImpresas = data.guardasImpresas; this.formData.cubierta.cabezada = data.cabezada; this.formData.cubierta.papelCubiertaId = data.papelCubiertaId; @@ -1236,15 +1240,15 @@ class PresupuestoCliente { $('.tapa-dura-options').removeClass('d-none'); $('.tapa-blanda-options').addClass('d-none'); $('#papel-guardas option[data-papel-id="' + - this.formData.cubierta.guardasPapelId + '"][data-gramaje="' + - this.formData.cubierta.guardasGramaje + '"]').prop('selected', true).trigger('change'); + this.formData.cubierta.papelGuardasId + '"][data-gramaje="' + + this.formData.cubierta.gramajeGuardas + '"]').prop('selected', true).trigger('change'); this.guardasImpresas.val(this.formData.cubierta.guardasImpresas); this.cabezada.val(this.formData.cubierta.cabezada); } $(`#${this.formData.cubierta.tipoCubierta}`).trigger('click'); - if (this.formData.cubierta.solapas === 0) { + if (this.formData.cubierta.solapasCubierta === 0) { $('.solapas-cubierta#sin-solapas').addClass('selected'); this.divSolapasCubierta.addClass('d-none'); } @@ -1380,6 +1384,8 @@ class PresupuestoCliente { ******************************/ #initExtras() { + const self = this; + $(document).on('click', '.btn-change-tab-extras', (e) => { const id = e.currentTarget.id; @@ -1388,6 +1394,34 @@ class PresupuestoCliente { this.#changeTab('pills-seleccion-tirada'); this.summaryTableExtras.addClass('d-none'); } else { + + const servicios = []; + $('.service-checkbox:checked').each(function () { + const $servicio = $(this); + servicios.push({ + id: $servicio.attr('id') ?? $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(), + label: $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim(), + price: $servicio.data('price') ?? $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim().replace(" " + self.divExtras.data('currency'), ''), + }); + }); + + const body = { + presupuesto: this.#getPresupuestoData(), + servicios: servicios + }; + + $.ajax({ + url: '/presupuesto/public/resumen', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify(body) + }).then((data) => { + $('#resumen-titulo').text(data.titulo); + this.#updateResumenTable(data); + }).catch((error) => { + console.error("Error obtener resumen: ", error); + }); + this.#changeTab('pills-resumen'); } }); @@ -1443,21 +1477,86 @@ class PresupuestoCliente { * END EXTRAS ******************************/ - + /****************************** - * EXTRAS + * RESUMEN ******************************/ #initResumen() { + const $row = $('#presupuesto-row'); + // 1) Transición al cambiar de pestaña (click o programático) + $(document).on('shown.bs.tab', '.custom-nav .nav-link', (e) => { + const targetSelector = $(e.target).data('bs-target'); // ej: "#pills-resumen" + if (targetSelector === '#pills-resumen') { + $row.addClass('expanded'); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + $row.removeClass('expanded'); + } + }); + + // 2) Botón "atrás" en Resumen $(document).on('click', '.btn-change-tab-resumen', (e) => { - const id = e.currentTarget.id; - if (id === 'btn-prev-resumen') { + if (e.currentTarget.id === 'btn-prev-resumen') { this.#changeTab('pills-extras'); } }); + + // 3) Estado inicial si ya cargas en Resumen + $(function () { + const activeTarget = $('.custom-nav .nav-link.active').data('bs-target'); + $('#presupuesto-row').toggleClass('expanded', activeTarget === '#pills-resumen'); + }); } + + #updateResumenTable(data) { + this.tablaResumen.find('tbody').empty(); + + const lineas = Object.keys(data).filter(k => k.startsWith("linea")).sort((a, b) => { + const numA = parseInt(a.replace("linea", ""), 10); + const numB = parseInt(b.replace("linea", ""), 10); + return numA - numB; + }); + const servicios = data.servicios || []; + + let total = 0; + + const locale = document.documentElement.lang || 'es-ES'; + + for (const l of lineas) { + const row = ` + + ${l=="linea0" ? `${data.imagen_alt}` : ''} + ${data[l].descripcion} + ${data[l].cantidad} + ${formateaMoneda(data[l].precio_unitario, 4, locale)} + ${formateaMoneda(data[l].precio_total, 2, locale)} + + `; + total += data[l].precio_total; + this.tablaResumen.find('tbody').append(row); + } + for (const s of servicios) { + const row = ` + + + ${s.descripcion} + 1 + ${formateaMoneda(s.precio, 2, locale)} + ${formateaMoneda(s.precio, 2, locale)} + + `; + total += s.precio; + this.tablaResumen.find('tbody').append(row); + } + + $('#resumen-base').text(formateaMoneda(total, 2, locale)); + $('#resumen-iva').text(formateaMoneda(total * 0.04, 2, locale)); + $('#resumen-total').text(formateaMoneda(total * 1.04, 2, locale)); + } + /****************************** - * END EXTRAS + * END RESUMEN ******************************/ } diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js index 6b7e8db..cbe874e 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js @@ -75,6 +75,9 @@ $(document).on("submit", "#maquetacionForm", function (e) { stored.servicios.datosMaquetacion.resultado.num_paginas_estimadas = json.numPaginasEstimadas; stored.servicios.datosMaquetacion.resultado.precio_pagina_estimado = json.precioPaginaEstimado; stored.servicios.datosMaquetacion.resultado.precio = json.precio; + if(stored.servicios.servicios.includes("maquetacion") === false) { + stored.servicios.servicios.push("maquetacion"); + } sessionStorage.setItem("formData", JSON.stringify(stored)); } else { diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-marcapaginas.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-marcapaginas.js index 34e4a3b..58654ee 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-marcapaginas.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-marcapaginas.js @@ -73,6 +73,9 @@ $(document).on("submit", "#marcapaginasForm", function (e) { const stored = JSON.parse(sessionStorage.getItem("formData")); stored.servicios.datosMarcapaginas.resultado.precio_unitario = json.precio_unitario; stored.servicios.datosMarcapaginas.resultado.precio = json.precio_total; + if(stored.servicios.servicios.includes("marcapaginas") === false) { + stored.servicios.servicios.push("marcapaginas"); + } sessionStorage.setItem("formData", JSON.stringify(stored)); } else { diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js index 84032f1..7e7fe4a 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js @@ -152,7 +152,7 @@ export function updateExtras() { const price = $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim() || $servicio.attr('price'); const $row = $('').append( $('').append($('').text(resumen)), - $('').text(price) + $('').text(price) ); $tbody.append($row); }); @@ -161,4 +161,5 @@ export function updateExtras() { } else { $table.addClass('d-none'); } -} \ No newline at end of file +} + diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/utils.js b/src/main/resources/static/assets/js/pages/imprimelibros/utils.js index 15a4a3a..01ace58 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/utils.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/utils.js @@ -1,14 +1,13 @@ -function formateaMoneda(valor, digits = 2, locale = 'es-ES', currency = 'EUR') { +export function formateaMoneda(valor, digits = 2, locale = 'es-ES', currency = 'EUR') { try { return new Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits: digits, useGrouping: true }).format(valor); } catch { return valor; } } -export { formateaMoneda }; -function formateaNumero({ +export function formateaNumero({ valor, digits = 2, style = 'decimal', @@ -31,10 +30,38 @@ function formateaNumero({ return new Intl.NumberFormat(locale, opts).format(n); } -export { formateaNumero }; -function isNumber(value) { - return !isNaN(Number(value)) && value.trim() !== ''; +export function isNumber(value) { + + if(typeof value === 'string') { + if(value.trim() === '') return false; + } + return !isNaN(Number(value)); } -export { isNumber }; \ No newline at end of file + +// Aplana un objeto a "prefijo.clave" (sin arrays) +export function dotify(obj, prefix = '') { + const out = {}; + const walk = (o, path) => { + Object.entries(o).forEach(([k, v]) => { + const key = path ? `${path}.${k}` : k; + if (v !== null && typeof v === 'object' && !Array.isArray(v)) { + walk(v, key); + } else { + out[key] = v; + } + }); + }; + walk(obj, prefix); + return out; +} + +// Convierte {a:1, b:2} en {"summary[a]":1, "summary[b]":2} +export function bracketPrefix(obj, prefix) { + const out = {}; + Object.entries(obj).forEach(([k, v]) => { + out[`${prefix}[${k}]`] = v; + }); + return out; +} \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen.html deleted file mode 100644 index 50330b6..0000000 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen.html +++ /dev/null @@ -1,24 +0,0 @@ -
    - - -
    -
    -
    Resumen -
    -
    - -
    -
    -
    -
    -
    - - -
    - -
    -
    \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen_final.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen_final.html new file mode 100644 index 0000000..e976517 --- /dev/null +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_resumen_final.html @@ -0,0 +1,70 @@ +
    + + +
    +
    +
    Resumen +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    DescripciónCantidadPrecio unitarioPrecio total
    Total0,00 €
    IVA (4%)0,00 €
    Total con IVA0,00 €
    +
    + +
    + + +
    + +
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html index d181f17..ab5fd02 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html @@ -1,4 +1,4 @@ -
    +
    @@ -20,19 +20,19 @@ - Fresado + Fresado - + - + @@ -60,19 +60,19 @@ - + - + - + @@ -87,61 +87,61 @@ - + - + - + - + - + - + - + - + - + - + @@ -156,19 +156,19 @@ - + - + - + @@ -183,25 +183,25 @@ - + - + - + - + diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador.html index 07f0245..dba5144 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador.html @@ -7,8 +7,8 @@
    -
    -
    +
    +
    @@ -118,7 +118,7 @@
    -
    +