diff --git a/src/main/java/com/imprimelibros/erp/cart/CartController.java b/src/main/java/com/imprimelibros/erp/cart/CartController.java index da93051..5fa7504 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartController.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartController.java @@ -59,6 +59,8 @@ public class CartController { "cart.shipping.send-in-palets", "cart.shipping.send-in-palets.info", "cart.shipping.tipo-envio", + "cart.pass-to.customer.error", + "cart.pass-to.customer.error-move", "app.yes", "app.aceptar", "app.cancelar"); @@ -73,14 +75,14 @@ public class CartController { model.addAttribute("items", items); Map direcciones = service.getCartDirecciones(cart.getId(), locale); - if(direcciones != null && direcciones.containsKey("mainDir")) + if (direcciones != null && direcciones.containsKey("mainDir")) model.addAttribute("mainDir", direcciones.get("mainDir")); - else if(direcciones != null && direcciones.containsKey("direcciones")) + else if (direcciones != null && direcciones.containsKey("direcciones")) model.addAttribute("direcciones", direcciones.get("direcciones")); var summary = service.getCartSummary(cart, locale); model.addAttribute("cartSummary", summary); - if(summary.get("errorShipmentCost") != null && (Boolean)summary.get("errorShipmentCost")) + if (summary.get("errorShipmentCost") != null && (Boolean) summary.get("errorShipmentCost")) model.addAttribute("errorEnvio", true); else model.addAttribute("errorEnvio", false); @@ -158,7 +160,8 @@ public class CartController { } @PostMapping(value = "/update/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public String updateCart(@PathVariable Long id, UpdateCartRequest updateRequest, Model model, Locale locale, Principal principal) { + public String updateCart(@PathVariable Long id, UpdateCartRequest updateRequest, Model model, Locale locale, + Principal principal) { try { service.updateCart(id, updateRequest); @@ -174,7 +177,26 @@ public class CartController { model.addAttribute("errorMessage", errorMessage); return "redirect:/cart"; } - } + @PostMapping(value = "/pass-to-customer/{customerId}") + public ResponseEntity moveToCustomer( + @PathVariable Long customerId, + Principal principal) { + + if(!Utils.isCurrentUserAdmin()) { + return ResponseEntity.status(403).body(Map.of("error", "Forbidden")); + } + + Long userId = Utils.currentUserId(principal); + Cart cart = service.getOrCreateActiveCart(userId); + + boolean ok = service.moveCartToCustomer(cart.getId(), customerId); + + if (ok) + return ResponseEntity.ok().build(); + return ResponseEntity.status(400).body(Map.of("error", "cart.errors.move-cart")); + } + + } diff --git a/src/main/java/com/imprimelibros/erp/cart/CartService.java b/src/main/java/com/imprimelibros/erp/cart/CartService.java index 00082ce..1980f4d 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartService.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartService.java @@ -112,8 +112,9 @@ public class CartService { @Transactional public void removeByPresupuesto(Long userId, Long presupuestoId) { Cart cart = getOrCreateActiveCart(userId); - itemRepo.findByCartIdAndPresupuestoId(cart.getId(), presupuestoId) - .ifPresent(itemRepo::delete); + CartItem item = itemRepo.findByCartIdAndPresupuestoId(cart.getId(), presupuestoId) + .orElseThrow(() -> new IllegalArgumentException("Item no encontrado")); + itemRepo.deleteById(item.getId()); } /** Vacía todo el carrito activo. */ @@ -355,6 +356,33 @@ public class CartService { } } + public Boolean moveCartToCustomer(Long cartId, Long customerId) { + try { + + // Remove the cart from the customer if they have one + Cart existingCart = cartRepo.findByUserIdAndStatus(customerId, Cart.Status.ACTIVE) + .orElse(null); + if (existingCart != null) { + cartRepo.delete(existingCart); + } + + Cart cart = cartRepo.findById(cartId) + .orElseThrow(() -> new IllegalArgumentException("Carrito no encontrado")); + + cart.setUserId(customerId); + cartRepo.save(cart); + return true; + + } catch (Exception e) { + // Manejo de excepciones + return false; + } + } + + /*************************************** + * MÉTODOS PRIVADOS + ***************************************/ + private Map getShippingCost( CartDireccion cd, Double peso, diff --git a/src/main/java/com/imprimelibros/erp/common/Utils.java b/src/main/java/com/imprimelibros/erp/common/Utils.java index 9d41573..a5262b7 100644 --- a/src/main/java/com/imprimelibros/erp/common/Utils.java +++ b/src/main/java/com/imprimelibros/erp/common/Utils.java @@ -14,6 +14,7 @@ import java.util.function.BiFunction; import org.springframework.context.MessageSource; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; @@ -44,6 +45,12 @@ public class Utils { this.messageSource = messageSource; } + public static boolean isCurrentUserAdmin() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + return auth.getAuthorities().stream() + .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN") || a.getAuthority().equals("ROLE_SUPERADMIN")); + } + public static Long currentUserId(Principal principal) { if (principal == null) { diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java index f0ed404..73b89a8 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; +import java.security.Principal; import java.time.Instant; import java.util.HashMap; import java.util.Locale; @@ -621,14 +622,14 @@ public class PresupuestoController { @ResponseBody public DataTablesResponse> datatable( HttpServletRequest request, Authentication auth, Locale locale, - @PathVariable("tipo") String tipo) { + @PathVariable("tipo") String tipo, Principal principal) { DataTablesRequest dt = DataTablesParser.from(request); if ("anonimos".equals(tipo)) { - return dtService.datatablePublicos(dt, locale); + return dtService.datatablePublicos(dt, locale, principal); } else if ("clientes".equals(tipo)) { - return dtService.datatablePrivados(dt, locale); + return dtService.datatablePrivados(dt, locale, principal); } else { throw new IllegalArgumentException("Tipo de datatable no válido"); } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java index 0555968..d81756a 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoDatatableService.java @@ -1,15 +1,18 @@ package com.imprimelibros.erp.presupuesto; import com.imprimelibros.erp.common.Utils; +import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuesto; import com.imprimelibros.erp.datatables.*; import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import jakarta.persistence.criteria.Expression; import org.springframework.context.MessageSource; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.security.Principal; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; @@ -26,18 +29,29 @@ public class PresupuestoDatatableService { } @Transactional(readOnly = true) - public DataTablesResponse> datatablePublicos(DataTablesRequest dt, Locale locale) { - return commonDataTable(dt, locale, "publico", true); + public DataTablesResponse> datatablePublicos(DataTablesRequest dt, Locale locale, + Principal principal) { + return commonDataTable(dt, locale, "publico", true, principal); } @Transactional(readOnly = true) - public DataTablesResponse> datatablePrivados(DataTablesRequest dt, Locale locale) { - return commonDataTable(dt, locale, "privado", false); + public DataTablesResponse> datatablePrivados(DataTablesRequest dt, Locale locale, + Principal principal) { + return commonDataTable(dt, locale, "privado", false, principal); } private DataTablesResponse> commonDataTable(DataTablesRequest dt, Locale locale, String origen, - boolean publico) { - Long count = repo.findAllByOrigen(Presupuesto.Origen.valueOf(origen)).stream().count(); + boolean publico, Principal principal) { + + Specification base = Specification.allOf( + (root, query, cb) -> cb.equal(root.get("origen"), Presupuesto.Origen.valueOf(origen))); + + Boolean isAdmin = Utils.isCurrentUserAdmin(); + if (!isAdmin) { + base = base.and((root, query, cb) -> cb.equal(root.get("user").get("id"), Utils.currentUserId(principal))); + } + + Long count = repo.count(base); List orderable = List.of( "id", "titulo", "user.fullName", "tipoEncuadernacion", "tipoCubierta", "tipoImpresion", @@ -74,6 +88,7 @@ public class PresupuestoDatatableService { .add("updatedAt", p -> formatDate(p.getUpdatedAt(), locale)) .addIf(!publico, "user", p -> p.getUser() != null ? p.getUser().getFullName() : "") .add("actions", this::generarBotones) + .where(base) .toJson(count); } diff --git a/src/main/java/com/imprimelibros/erp/users/UserService.java b/src/main/java/com/imprimelibros/erp/users/UserService.java index 6856390..228e2cd 100644 --- a/src/main/java/com/imprimelibros/erp/users/UserService.java +++ b/src/main/java/com/imprimelibros/erp/users/UserService.java @@ -1,6 +1,9 @@ package com.imprimelibros.erp.users; import org.springframework.security.core.userdetails.UserDetailsService; + +import java.util.Map; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java b/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java index 107c88b..76b6843 100644 --- a/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java +++ b/src/main/java/com/imprimelibros/erp/users/UserServiceImpl.java @@ -2,10 +2,21 @@ package com.imprimelibros.erp.users; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.text.Collator; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import com.imprimelibros.erp.direcciones.Direccion; + @Service public class UserServiceImpl implements UserService { @@ -29,5 +40,4 @@ public class UserServiceImpl implements UserService { if (query == null || query.isBlank()) query = null; return userDao.searchUsers(role, query, pageable); } - } diff --git a/src/main/resources/i18n/cart_es.properties b/src/main/resources/i18n/cart_es.properties index c867ff5..f6e9f46 100644 --- a/src/main/resources/i18n/cart_es.properties +++ b/src/main/resources/i18n/cart_es.properties @@ -44,6 +44,7 @@ cart.pass-to.customer.warning=Advertencia: Esta acción no se puede deshacer y s cart.pass-to.select-customer=Seleccione un cliente cart.pass-to.button=Mover cesta cart.pass-to.success=Cesta movida correctamente al cliente {0}. - +cart.pass-to.customer.error=Debe seleccionar un cliente para mover la cesta. +cart.pass-to.customer.error-move=Error al mover la cesta de la compra cart.errors.update-cart=Error al actualizar la cesta de la compra: {0} cart.errors.shipping=No se puede calcular el coste del envío para alguna de las direcciones seleccionadas. Por favor, póngase en contacto con el servicio de atención al cliente. \ 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 9a3fd32..aae1d3a 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 @@ -16,10 +16,10 @@ $(() => { $(this).find('.direccion-id').attr('name', 'direcciones[' + i + '].id'); $(this).find('.direccion-cp').attr('name', 'direcciones[' + i + '].cp'); $(this).find('.direccion-pais-code3').attr('name', 'direcciones[' + i + '].paisCode3'); - if($(this).find('.presupuesto-id').length > 0 && $(this).find('.presupuesto-id').val() !== null - && $(this).find('.presupuesto-id').val() !== "") + if ($(this).find('.presupuesto-id').length > 0 && $(this).find('.presupuesto-id').val() !== null + && $(this).find('.presupuesto-id').val() !== "") $(this).find('.presupuesto-id').attr('name', 'direcciones[' + i + '].presupuestoId'); - if($(this).find('.item-tirada').length > 0 && $(this).find('.item-tirada').val() !== null + if ($(this).find('.item-tirada').length > 0 && $(this).find('.item-tirada').val() !== null && $(this).find('.item-tirada').val() !== "") $(this).find('.item-tirada').attr('name', 'direcciones[' + i + '].unidades'); }); @@ -31,15 +31,28 @@ $(() => { }).always(() => { hideLoader(); }); - + checkAddressesForItems(); }); checkAddressesForItems(); - function checkAddressesForItems(){ - if($('#onlyOneShipment').is(':checked')){ - if($("#shippingAddressesContainer .direccion-card").length === 0){ + function checkAddressesForItems() { + if ($('.product').length === 0) { + $("#alert-empty").removeClass("d-none"); + $('.cart-content').addClass('d-none'); + return; + } + else { + $('.cart-content').removeClass('d-none'); + $("#alert-empty").addClass("d-none"); + // check if select2 is initialized + if ($('#select-customer').length && !$('#select-customer').hasClass('select2-hidden-accessible')) { + initMoveCartToCustomer(); + } + } + if ($('#onlyOneShipment').is(':checked')) { + if ($("#shippingAddressesContainer .direccion-card").length === 0) { $(".alert-shipment").removeClass("d-none"); $('#btn-checkout').prop('disabled', true); return; @@ -47,42 +60,42 @@ $(() => { $(".alert-shipment").addClass("d-none"); $('#btn-checkout').prop('disabled', false); } - else{ + else { const items = $(".product"); let errorFound = false; - for(let i=0; i { $(document).on("click", ".delete-item", async function (event) { event.preventDefault(); - const cartItemId = $(this).data("cart-item-id"); + const presupuestoId = $(this).data("cart-item-id"); const card = $(this).closest('.card.product'); // CSRF (Spring Security) @@ -100,7 +113,7 @@ $(() => { const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.content || 'X-CSRF-TOKEN'; try { - const res = await fetch(`/cart/delete/item/${cartItemId}`, { + const res = await fetch(`/cart/delete/item/${presupuestoId}`, { method: 'DELETE', headers: { [csrfHeader]: csrfToken } }); @@ -111,11 +124,79 @@ $(() => { } else { card?.remove(); - updateTotal(); + $(document).trigger('updateCart'); } } catch (err) { console.error('Error en la solicitud:', err); } }); + + function initMoveCartToCustomer() { + if ($('#select-customer').length) { + + $('#moveCart').on('click', async function (e) { + e.preventDefault(); + const customerId = $('#select-customer').val(); + if (!customerId) { + // set text and show alert + $('#alert-select-customer').text(window.languageBundle['cart.pass-to.customer.error'] || 'Debe seleccionar un cliente para mover la cesta.'); + $('#alert-select-customer').removeClass('d-none').hide().fadeIn(); + setTimeout(() => { + $('#alert-select-customer').fadeOut(function () { + $(this).addClass('d-none'); + }); + }, 5000); + return; + } + + // CSRF (Spring Security) + const csrfToken = document.querySelector('meta[name="_csrf"]')?.content || ''; + const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.content || 'X-CSRF-TOKEN'; + + try { + const res = await fetch(`/cart/pass-to-customer/${customerId}`, { + method: 'POST', + headers: { [csrfHeader]: csrfToken } + }); + + if (!res.ok) { + $('#alert-select-customer').text(window.languageBundle['cart.pass-to.customer.move.error'] || 'Error al mover la cesta de la compra'); + $('#alert-select-customer').removeClass('d-none').hide().fadeIn(); + setTimeout(() => { + $('#alert-select-customer').fadeOut(function () { + $(this).addClass('d-none'); + }); + }, 5000); + return; + } + else { + window.location.href = '/cart'; + } + + } catch (err) { + console.error('Error en la solicitud:', err); + $('#alert-select-customer').text(window.languageBundle['cart.errors.move-cart'] || 'Error al mover la cesta de la compra'); + $('#alert-select-customer').removeClass('d-none').hide().fadeIn(); + setTimeout(() => { + $('#alert-select-customer').fadeOut(function () { + $(this).addClass('d-none'); + }); + }, 5000); + } + }); + + $('#select-customer').select2({ + width: '100%', + ajax: { + url: 'users/api/get-users', + dataType: 'json', + delay: 250, + }, + allowClear: true + }); + } + } + initMoveCartToCustomer(); + }); \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/cart/_cartContent.html b/src/main/resources/templates/imprimelibros/cart/_cartContent.html index f724aad..378648c 100644 --- a/src/main/resources/templates/imprimelibros/cart/_cartContent.html +++ b/src/main/resources/templates/imprimelibros/cart/_cartContent.html @@ -1,4 +1,4 @@ -
+
@@ -7,11 +7,10 @@
-
- -
- - + + diff --git a/src/main/resources/templates/imprimelibros/cart/_cartItem.html b/src/main/resources/templates/imprimelibros/cart/_cartItem.html index bcb1f1c..01de3dc 100644 --- a/src/main/resources/templates/imprimelibros/cart/_cartItem.html +++ b/src/main/resources/templates/imprimelibros/cart/_cartItem.html @@ -198,7 +198,7 @@ diff --git a/src/main/resources/templates/imprimelibros/cart/_cartSummary.html b/src/main/resources/templates/imprimelibros/cart/_cartSummary.html index c03024e..516e328 100644 --- a/src/main/resources/templates/imprimelibros/cart/_cartSummary.html +++ b/src/main/resources/templates/imprimelibros/cart/_cartSummary.html @@ -25,7 +25,9 @@ - : + + : @@ -37,8 +39,8 @@
- - + +
@@ -47,25 +49,27 @@
- -
+
-
-
- - -
- + +
+ + +
+
+ +
- +
diff --git a/src/main/resources/templates/imprimelibros/cart/cart.html b/src/main/resources/templates/imprimelibros/cart/cart.html index 0170d5b..7498643 100644 --- a/src/main/resources/templates/imprimelibros/cart/cart.html +++ b/src/main/resources/templates/imprimelibros/cart/cart.html @@ -36,6 +36,10 @@ +
+ +
+