From 4d451cc85ee717d074a6e6f6bf8e73dd06e2030c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Jim=C3=A9nez?= Date: Sun, 2 Nov 2025 11:57:05 +0100 Subject: [PATCH] a falta del pago --- .../imprimelibros/erp/cart/CartService.java | 1 + .../erp/checkout/CheckoutController.java | 37 ++-- .../erp/direcciones/DireccionController.java | 23 +++ .../erp/direcciones/DireccionRepository.java | 4 + .../erp/direcciones/DireccionService.java | 59 +++++++ src/main/resources/i18n/pedidos_es.properties | 23 ++- src/main/resources/static/assets/css/app.css | 5 +- .../resources/static/assets/css/checkout.css | 5 + .../static/assets/css/imprimelibros.css | 3 +- .../js/pages/imprimelibros/cart/cart.js | 10 +- .../pages/imprimelibros/checkout/checkout.js | 137 ++++++++++++++- .../imprimelibros/cart/_cartSummary.html | 10 +- .../imprimelibros/checkout/_envio.html | 43 ----- .../imprimelibros/checkout/_pago.html | 35 +++- .../imprimelibros/checkout/_summary.html | 51 ++++++ .../imprimelibros/checkout/checkout.html | 160 ++++-------------- .../direcciones/direccionBillingCard.html | 31 ++++ 17 files changed, 429 insertions(+), 208 deletions(-) create mode 100644 src/main/resources/static/assets/css/checkout.css delete mode 100644 src/main/resources/templates/imprimelibros/checkout/_envio.html create mode 100644 src/main/resources/templates/imprimelibros/checkout/_summary.html create mode 100644 src/main/resources/templates/imprimelibros/direcciones/direccionBillingCard.html diff --git a/src/main/java/com/imprimelibros/erp/cart/CartService.java b/src/main/java/com/imprimelibros/erp/cart/CartService.java index 7d07149..f900791 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartService.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartService.java @@ -291,6 +291,7 @@ public class CartService { summary.put("fidelizacion", fidelizacion + "%"); summary.put("descuento", Utils.formatCurrency(-descuento, locale)); summary.put("total", Utils.formatCurrency(total, locale)); + summary.put("amountCents", Math.round(total * 100)); summary.put("errorShipmentCost", errorShipementCost); return summary; diff --git a/src/main/java/com/imprimelibros/erp/checkout/CheckoutController.java b/src/main/java/com/imprimelibros/erp/checkout/CheckoutController.java index 00474fc..79a9563 100644 --- a/src/main/java/com/imprimelibros/erp/checkout/CheckoutController.java +++ b/src/main/java/com/imprimelibros/erp/checkout/CheckoutController.java @@ -6,17 +6,20 @@ import java.util.Locale; import java.util.Map; import org.springframework.context.MessageSource; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ResponseStatusException; import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.i18n.TranslationService; import com.imprimelibros.erp.paises.PaisesService; - +import com.imprimelibros.erp.direcciones.Direccion; import com.imprimelibros.erp.direcciones.DireccionService; - +import com.imprimelibros.erp.cart.Cart; import com.imprimelibros.erp.cart.CartService; @Controller @@ -44,23 +47,29 @@ public class CheckoutController { List keys = List.of( "app.cancelar", "app.seleccionar", - "checkout.shipping.add.title", - "checkout.shipping.select-placeholder", - "checkout.shipping.new-address", "app.yes", - "app.cancelar"); + "checkout.billing-address.title", + "checkout.billing-address.new-address", + "checkout.billing-address.select-placeholder", + "checkout.billing-address.errors.noAddressSelected"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); - var items = this.cartService.listItems(Utils.currentUserId(principal), locale); - for (var item : items) { - if (item.get("hasSample") != null && (Boolean) item.get("hasSample")) { - model.addAttribute("hasSample", true); - break; - } - } - model.addAttribute("items", items); + Long userId = Utils.currentUserId(principal); + Cart cart = cartService.getOrCreateActiveCart(userId); + model.addAttribute("summary", cartService.getCartSummary(cart, locale)); return "imprimelibros/checkout/checkout"; // crea esta vista si quieres (tabla simple) } + + @GetMapping("/get-address/{id}") + public String getDireccionCard(@PathVariable Long id, Model model, Locale locale) { + Direccion dir = direccionService.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + model.addAttribute("pais", messageSource.getMessage("paises." + dir.getPais().getKeyword(), null, + dir.getPais().getKeyword(), locale)); + model.addAttribute("direccion", dir); + + return "imprimelibros/direcciones/direccionBillingCard :: direccionBillingCard(direccion=${direccion}, pais=${pais})"; + } } diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java index 398500c..bcd42a5 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionController.java @@ -506,6 +506,29 @@ public class DireccionController { } + @GetMapping(value = "/facturacion/select2", produces = "application/json") + @ResponseBody + public Map getSelect2Facturacion( + @RequestParam(value = "q", required = false) String q1, + @RequestParam(value = "term", required = false) String q2, + Authentication auth) { + + boolean isAdmin = auth.getAuthorities().stream() + .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN") || a.getAuthority().equals("ROLE_SUPERADMIN")); + + Long currentUserId = null; + if (!isAdmin) { + if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) { + currentUserId = udi.getId(); + } else if (auth != null) { + currentUserId = userRepo.findIdByUserNameIgnoreCase(auth.getName()).orElse(null); + } + } + + return direccionService.getForSelectFacturacion(q1, q2, isAdmin ? null : currentUserId); + + } + private boolean isOwnerOrAdmin(Authentication auth, Long ownerId) { if (auth == null) { return false; diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java index fc45640..7abbf33 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionRepository.java @@ -38,6 +38,10 @@ public interface DireccionRepository // find by user_id List findByUserId(Long userId); + // find by user_id and direccion_facturacion = true + @Query("SELECT d FROM Direccion d WHERE (:userId IS NULL OR d.user.id = :userId) AND d.direccionFacturacion = true") + List findByUserIdAndDireccionFacturacion(@Param("userId") Long userId); + // find by user_id with deleted @Query(value = "SELECT * FROM direcciones WHERE user_id = :userId", nativeQuery = true) List findByUserIdWithDeleted(@Param("userId") Long userId); diff --git a/src/main/java/com/imprimelibros/erp/direcciones/DireccionService.java b/src/main/java/com/imprimelibros/erp/direcciones/DireccionService.java index 1f1d4d5..f74561a 100644 --- a/src/main/java/com/imprimelibros/erp/direcciones/DireccionService.java +++ b/src/main/java/com/imprimelibros/erp/direcciones/DireccionService.java @@ -77,6 +77,65 @@ public class DireccionService { } } + + public Map getForSelectFacturacion(String q1, String q2, Long userId) { + try { + + // Termino de búsqueda (Select2 usa 'q' o 'term' según versión/config) + String search = Optional.ofNullable(q1).orElse(q2); + if (search != null) { + search = search.trim(); + } + final String q = (search == null || search.isEmpty()) + ? null + : search.toLowerCase(); + + List all = repo.findByUserIdAndDireccionFacturacion(userId); + + // Mapear a opciones id/text con i18n y filtrar por búsqueda si llega + List> options = all.stream() + .map(cc -> { + String id = cc.getId().toString(); + String alias = cc.getAlias(); + String direccion = cc.getDireccion(); + String cp = String.valueOf(cc.getCp()); + String ciudad = cc.getCiudad(); + String att = cc.getAtt(); + Map m = new HashMap<>(); + m.put("id", id); // lo normal en Select2: id = valor que guardarás (code3) + m.put("text", alias); // texto mostrado, i18n con fallback a keyword + m.put("cp", cp); + m.put("ciudad", ciudad); + m.put("att", att); + m.put("alias", alias); + m.put("direccion", direccion); + return m; + }) + .filter(opt -> { + if (q == null || q.isEmpty()) + return true; + String cp = opt.get("cp"); + String ciudad = opt.get("ciudad").toLowerCase(); + String att = opt.get("att").toLowerCase(); + String alias = opt.get("alias").toLowerCase(); + String text = opt.get("text").toLowerCase(); + String direccion = opt.get("direccion").toLowerCase(); + return text.contains(q) || cp.contains(q) || ciudad.contains(q) || att.contains(q) + || alias.contains(q) || direccion.contains(q); + }) + .sorted(Comparator.comparing(m -> m.get("text"), Collator.getInstance())) + .collect(Collectors.toList()); + + // Estructura Select2 + Map resp = new HashMap<>(); + resp.put("results", options); + return resp; + } catch (Exception e) { + e.printStackTrace(); + return Map.of("results", List.of()); + } + } + public Optional findById(Long id) { return repo.findById(id); } diff --git a/src/main/resources/i18n/pedidos_es.properties b/src/main/resources/i18n/pedidos_es.properties index a56dd01..84a21ce 100644 --- a/src/main/resources/i18n/pedidos_es.properties +++ b/src/main/resources/i18n/pedidos_es.properties @@ -1,17 +1,16 @@ checkout.title=Finalizar compra -checkout.summay=Resumen de la compra -checkout.shipping=Envío +checkout.summary=Resumen de la compra +checkout.billing-address=Dirección de facturación checkout.payment=Método de pago -checkout.shipping.info=Todos los pedidos incluyen un envío gratuito a la Península y Baleares por línea de pedido. -checkout.shipping.order=Envío del pedido -checkout.shipping.samples=Envío de pruebas -checkout.shipping.onlyOneShipment=Todo el pedido se envía a una única dirección. +checkout.billing-address.title=Seleccione una dirección +checkout.billing-address.new-address=Nueva dirección +checkout.billing-address.select-placeholder=Buscar en direcciones... +checkout.billing-address.errors.noAddressSelected=Debe seleccionar una dirección de facturación para el pedido. -checkout.summary.presupuesto=#Presupuesto -checkout.summary.titulo=Título -checkout.summary.base=Base -checkout.summary.iva-4=IVA 4% -checkout.summary.iva-21=IVA 21% -checkout.summary.envio=Envío \ No newline at end of file +checkout.payment.card=Tarjeta de crédito / débito +checkout.payment.bizum=Bizum +checkout.payment.bank-transfer=Transferencia bancaria + +checkout.make-payment=Realizar el pago \ No newline at end of file diff --git a/src/main/resources/static/assets/css/app.css b/src/main/resources/static/assets/css/app.css index 525d693..79393de 100644 --- a/src/main/resources/static/assets/css/app.css +++ b/src/main/resources/static/assets/css/app.css @@ -8240,7 +8240,8 @@ a { display: none; } .card-radio .form-check-input:checked + .form-check-label { - border-color: #687cfe !important; + border-color: #ff7f5d !important; + background-color: rgba(255, 127, 93, 0.05); } .card-radio .form-check-input:checked + .form-check-label:before { content: "\eb80"; @@ -8249,7 +8250,7 @@ a { top: 2px; right: 6px; font-size: 16px; - color: #687cfe; + color: #ff7f5d; } .card-radio.dark .form-check-input:checked + .form-check-label:before { color: #fff; diff --git a/src/main/resources/static/assets/css/checkout.css b/src/main/resources/static/assets/css/checkout.css new file mode 100644 index 0000000..e61c7e5 --- /dev/null +++ b/src/main/resources/static/assets/css/checkout.css @@ -0,0 +1,5 @@ +.direccion-card { + flex: 1 1 350px; /* ancho mínimo 350px, crece si hay espacio */ + max-width: 350px; /* opcional, para que no se estiren demasiado */ + min-width: 340px; /* protege el ancho mínimo */ +} \ No newline at end of file diff --git a/src/main/resources/static/assets/css/imprimelibros.css b/src/main/resources/static/assets/css/imprimelibros.css index 01274ef..d3af090 100644 --- a/src/main/resources/static/assets/css/imprimelibros.css +++ b/src/main/resources/static/assets/css/imprimelibros.css @@ -55,4 +55,5 @@ body { .form-switch-custom.form-switch-presupuesto .form-check-input:checked::before { color: #92b2a7; -} \ No newline at end of file +} + diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js b/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js index b2f73d5..192b043 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/cart/cart.js @@ -42,6 +42,7 @@ $(() => { if ($('.product').length === 0) { $("#alert-empty").removeClass("d-none"); $('.cart-content').addClass('d-none'); + $('#btn-checkout').prop('disabled', true); return; } else { @@ -59,7 +60,12 @@ $(() => { return; } $(".alert-shipment").addClass("d-none"); - $('#btn-checkout').prop('disabled', false); + if ($("#errorEnvio").hasClass("d-none")) { + $('#btn-checkout').prop('disabled', false); + } + else { + $('#btn-checkout').prop('disabled', true); + } } else { const items = $(".product"); @@ -92,7 +98,7 @@ $(() => { item.find(".alert-icon-shipment").addClass("d-none"); } } - if (errorFound) { + if (errorFound || $("#errorEnvio").hasClass("d-none") === false) { $(".alert-shipment").removeClass("d-none"); $('#btn-checkout').prop('disabled', true); } diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js index 3aa8e86..cb48b09 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js @@ -1,3 +1,5 @@ +import { showLoader, hideLoader } from '../loader.js'; + $(() => { const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content'); @@ -14,8 +16,139 @@ $(() => { const modalEl = document.getElementById('direccionFormModal'); const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + $('#addBillingAddressBtn').on('click', seleccionarDireccionEnvio); - - + async function seleccionarDireccionEnvio() { + + const { value: direccionId, isDenied } = await Swal.fire({ + title: window.languageBundle['checkout.billing-address.title'] || 'Seleccione una dirección', + html: ` + + `, + showCancelButton: true, + showDenyButton: true, + buttonsStyling: false, + confirmButtonText: window.languageBundle['app.seleccionar'] || 'Seleccionar', + cancelButtonText: window.languageBundle['app.cancelar'] || 'Cancelar', + denyButtonText: window.languageBundle['checkout.billing-address.new-address'] || 'Nueva dirección', + customClass: { + confirmButton: 'btn btn-secondary me-2', + cancelButton: 'btn btn-light', + denyButton: 'btn btn-secondary me-2' + }, + focusConfirm: false, + + // Inicializa cuando el DOM del modal ya existe + didOpen: () => { + const $select = $('#direccionSelect'); + $select.empty(); // limpia placeholder estático + + $select.select2({ + width: '100%', + dropdownParent: $('.swal2-container'), + ajax: { + url: '/direcciones/facturacion/select2', + dataType: 'json', + delay: 250, + data: params => ({ q: params.term || '' }), + processResults: (data) => { + const items = Array.isArray(data) ? data : (data.results || []); + return { + results: items.map(item => ({ + id: item.id, + text: item.text, // ← Select2 necesita 'id' y 'text' + alias: item.alias || 'Sin alias', + att: item.att || '', + direccion: item.direccion || '', + cp: item.cp || '', + ciudad: item.ciudad || '', + html: ` +
+ ${item.alias || 'Sin alias'}
+ ${item.att ? `${item.att}
` : ''} + ${item.direccion || ''}${item.cp ? ', ' + item.cp : ''}${item.ciudad ? ', ' + item.ciudad : ''} +
+ ` + })), + pagination: { more: false } // opcional, evita que espere más páginas + }; + } + + }, + placeholder: window.languageBundle['checkout.billing-address.select-placeholder'] || 'Buscar en direcciones...', + language: language, + + templateResult: data => { + if (data.loading) return data.text; + return $(data.html || data.text); + }, + // Selección más compacta (solo alias + ciudad) + templateSelection: data => { + if (!data.id) return data.text; + const alias = data.alias || data.text; + const ciudad = data.ciudad ? ` — ${data.ciudad}` : ''; + return $(`${alias}${ciudad}`); + }, + escapeMarkup: m => m + }); + }, + + preConfirm: () => { + const $select = $('#direccionSelect'); + const val = $select.val(); + if (!val) { + Swal.showValidationMessage( + window.languageBundle['checkout.billing-address.errors.noAddressSelected'] || 'Por favor, seleccione una dirección.' + ); + return false; + } + return val; + }, + + didClose: () => { + // Limpieza: destruir select2 para evitar fugas + const $select = $('#direccionSelect'); + if ($select.data('select2')) { + $select.select2('destroy'); + } + } + }); + + if (isDenied) { + $.get('/direcciones/direction-form', function (html) { + $('#direccionFormModalBody').html(html); + const title = $('#direccionFormModalBody #direccionForm').data('add'); + $('#direccionFormModal .modal-title').text(title); + modal.show(); + }); + } + + if (direccionId) { + // Obtén el objeto completo seleccionado + showLoader(); + let uri = `/checkout/get-address/${direccionId}`; + const response = await fetch(uri); + if (response.ok) { + const html = await response.text(); + $('#direccion-div').append(html); + $('#addBillingAddressBtn').addClass('d-none'); + hideLoader(); + return true; + } + hideLoader(); + return false; + } + hideLoader(); + return false; + } + + $(document).on('click', '.btn-delete-direccion', function (e) { + e.preventDefault(); + const $card = $(this).closest('.direccion-card'); + const $div = $card.parent(); + $card.remove(); + $('#addBillingAddressBtn').removeClass('d-none'); + + }); }); diff --git a/src/main/resources/templates/imprimelibros/cart/_cartSummary.html b/src/main/resources/templates/imprimelibros/cart/_cartSummary.html index 516e328..88109cf 100644 --- a/src/main/resources/templates/imprimelibros/cart/_cartSummary.html +++ b/src/main/resources/templates/imprimelibros/cart/_cartSummary.html @@ -24,7 +24,7 @@ : - + : @@ -38,12 +38,8 @@ -
- - - -
+ diff --git a/src/main/resources/templates/imprimelibros/checkout/_envio.html b/src/main/resources/templates/imprimelibros/checkout/_envio.html deleted file mode 100644 index 893721c..0000000 --- a/src/main/resources/templates/imprimelibros/checkout/_envio.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
-
-
-
Envio del pedido -
-
-
-
-

-
- - -
- - -
-
- -
-
-
- -
-
-
Envio de pruebas -
-
- -
- -
-
- - -
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/checkout/_pago.html b/src/main/resources/templates/imprimelibros/checkout/_pago.html index 42eb8c5..269dd9e 100644 --- a/src/main/resources/templates/imprimelibros/checkout/_pago.html +++ b/src/main/resources/templates/imprimelibros/checkout/_pago.html @@ -1,3 +1,36 @@
- +
Método de pago
+
+
+
+ + +
+ +
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/checkout/_summary.html b/src/main/resources/templates/imprimelibros/checkout/_summary.html new file mode 100644 index 0000000..47eb645 --- /dev/null +++ b/src/main/resources/templates/imprimelibros/checkout/_summary.html @@ -0,0 +1,51 @@ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
:
:
+ :
: + +
+ +
+ +
+
+ +
+ + +
\ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/checkout/checkout.html b/src/main/resources/templates/imprimelibros/checkout/checkout.html index 5763197..34bc29f 100644 --- a/src/main/resources/templates/imprimelibros/checkout/checkout.html +++ b/src/main/resources/templates/imprimelibros/checkout/checkout.html @@ -9,7 +9,7 @@ - + @@ -22,6 +22,10 @@
+
+
+
+
+
+ Cargando… +
+
+
-
+
+
Dirección de envío
- + + +
+
-
-
- -
-
-
- - -
- -
-
-
- +
+
+
+
+
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PresupuestoTítulo - Base
- PRESUPUESTO-001 - - Título del presupuesto - - - 0,00 - - -
:
:
: - - -
- -
- -
-
+
- -
- - -
+
+
- + - - - - - - + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/direcciones/direccionBillingCard.html b/src/main/resources/templates/imprimelibros/direcciones/direccionBillingCard.html new file mode 100644 index 0000000..8e2d70a --- /dev/null +++ b/src/main/resources/templates/imprimelibros/direcciones/direccionBillingCard.html @@ -0,0 +1,31 @@ +
+
+ + + + + +
+
+ + + + + + + +
+ +
+ + +
+
\ No newline at end of file