mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
terminado carrito
This commit is contained in:
@ -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");
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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<String, Object> getShippingCost(
|
||||
CartDireccion cd,
|
||||
Double peso,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<Map<String, Object>> 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");
|
||||
}
|
||||
|
||||
@ -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<Map<String, Object>> datatablePublicos(DataTablesRequest dt, Locale locale) {
|
||||
return commonDataTable(dt, locale, "publico", true);
|
||||
public DataTablesResponse<Map<String, Object>> datatablePublicos(DataTablesRequest dt, Locale locale,
|
||||
Principal principal) {
|
||||
return commonDataTable(dt, locale, "publico", true, principal);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public DataTablesResponse<Map<String, Object>> datatablePrivados(DataTablesRequest dt, Locale locale) {
|
||||
return commonDataTable(dt, locale, "privado", false);
|
||||
public DataTablesResponse<Map<String, Object>> datatablePrivados(DataTablesRequest dt, Locale locale,
|
||||
Principal principal) {
|
||||
return commonDataTable(dt, locale, "privado", false, principal);
|
||||
}
|
||||
|
||||
private DataTablesResponse<Map<String, Object>> commonDataTable(DataTablesRequest dt, Locale locale, String origen,
|
||||
boolean publico) {
|
||||
Long count = repo.findAllByOrigen(Presupuesto.Origen.valueOf(origen)).stream().count();
|
||||
boolean publico, Principal principal) {
|
||||
|
||||
Specification<Presupuesto> 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<String> 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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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.
|
||||
@ -38,6 +38,19 @@ $(() => {
|
||||
checkAddressesForItems();
|
||||
|
||||
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");
|
||||
@ -92,7 +105,7 @@ $(() => {
|
||||
$(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();
|
||||
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
<div th:fragment="cartContent(items, cartId)" class="cart-content container-fluid row gy-4">
|
||||
<div th:fragment="cartContent(items, cartId)" th:class="${'cart-content container-fluid row gy-4' + (items.isEmpty() ? ' d-none' : '')}">
|
||||
|
||||
<div id="sectionLoader" class="position-absolute top-0 start-0 w-100 h-100 d-none justify-content-center align-items-center
|
||||
bg-body bg-opacity-75" style="z-index:10;">
|
||||
@ -7,11 +7,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div th:if="${items.isEmpty()}">
|
||||
<div class="alert alert-info" role="alert" th:text="#{cart.empty}"></div>
|
||||
</div>
|
||||
<div id="errorEnvio" th:class="${'alert alert-danger' + (errorEnvio ? '' : ' d-none')}" role="alert" th:text="#{cart.errors.shipping}"></div>
|
||||
<div th:if="${errorMessage}" class="alert alert-danger " role="alert" th:text="${errorMessage}"></div>
|
||||
<div id="errorEnvio" th:class="${'alert alert-danger' + (errorEnvio ? '' : ' d-none')}" role="alert"
|
||||
th:text="#{cart.errors.shipping}"></div>
|
||||
<div th:if="${!#strings.isEmpty(errorMessage) and items != null and !items.isEmpty()}" class="alert alert-danger "
|
||||
role="alert" th:text="${errorMessage}"></div>
|
||||
|
||||
<div class="alert alert-danger alert-shipment d-none" role="alert"
|
||||
th:text="#{cart.shipping.errors.fillAddressesItems}"></div>
|
||||
|
||||
@ -198,7 +198,7 @@
|
||||
<!-- Botón eliminar -->
|
||||
<div>
|
||||
<a href="javascript:void(0);" class="d-block text-body p-1 px-2 delete-item"
|
||||
th:attr="data-cart-item-id=${item.cartItemId}">
|
||||
th:attr="data-cart-item-id=${item.presupuestoId}">
|
||||
<i class="ri-delete-bin-fill text-muted align-bottom me-1"></i> Eliminar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -25,7 +25,9 @@
|
||||
<td class="text-end" id="iva-21-cesta" th:text="${summary.iva21}"></td>
|
||||
</tr>
|
||||
<tr id="tr-iva-21">
|
||||
<td><span th:text="#{cart.resumen.descuento} + ' (' + ${summary.fidelizacion} + ')'"></span> : </td>
|
||||
<td><span
|
||||
th:text="#{cart.resumen.descuento} + ' (' + ${summary.fidelizacion} + ')'"></span>
|
||||
: </td>
|
||||
<td class="text-end" id="descuento-cesta" th:text="${summary.descuento}"></td>
|
||||
</tr>
|
||||
<tr class="table-active">
|
||||
@ -47,22 +49,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')" class="card">
|
||||
<div class="card-header border-bottom-dashed">
|
||||
<h5 th:text="#{cart.pass-to.customer}" class="card-title mb-0"></h5>
|
||||
</div>
|
||||
<div class="card-body pt-2">
|
||||
<div class="alert alert-info" role="alert" th:text="#{cart.pass-to.customer.info}"></div>
|
||||
<div class="alert alert-warning" role="alert" th:text="#{cart.pass-to.customer.warning}"></div>
|
||||
<form th:action="@{/cart/pass-to-customer}" method="post">
|
||||
<div id="alert-select-customer" class="alert alert-danger d-none" role="alert"
|
||||
th:text="#{cart.pass-to.customer.error}"></div>
|
||||
<div class="mb-3">
|
||||
<label for="select-customer" class="form-label" th:text="#{cart.pass-to.select-customer}"></label>
|
||||
<select id="select-customer" name="customerId" class="form-select" required>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary w-100"
|
||||
th:text="#{cart.pass-to.button}">Mover cesta</button>
|
||||
<div class="mb-3">
|
||||
<button id="moveCart" class="btn btn-secondary w-100" th:text="#{cart.pass-to.button}">Mover
|
||||
cesta</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -36,6 +36,10 @@
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div th:if="${items.isEmpty()}">
|
||||
<div id="alert-empty"class="alert alert-info" role="alert" th:text="#{cart.empty}"></div>
|
||||
</div>
|
||||
|
||||
<div th:insert="~{imprimelibros/cart/_cartContent :: cartContent(${items}, ${cartId})}"></div>
|
||||
|
||||
</th:block>
|
||||
|
||||
Reference in New Issue
Block a user