mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-31 08:08:50 +00:00
Compare commits
3 Commits
9b0a79e2cd
...
a33ba3256b
| Author | SHA1 | Date | |
|---|---|---|---|
| a33ba3256b | |||
| 90376e61c8 | |||
| 37ae61d6f7 |
56
src/main/java/com/imprimelibros/erp/cart/Cart.java
Normal file
56
src/main/java/com/imprimelibros/erp/cart/Cart.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "carts",
|
||||||
|
uniqueConstraints = @UniqueConstraint(name="uq_carts_user_active", columnNames={"user_id","status"}))
|
||||||
|
public class Cart {
|
||||||
|
|
||||||
|
public enum Status { ACTIVE, LOCKED, ABANDONED }
|
||||||
|
|
||||||
|
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 16)
|
||||||
|
private Status status = Status.ACTIVE;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 3)
|
||||||
|
private String currency = "EUR";
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private LocalDateTime createdAt = LocalDateTime.now();
|
||||||
|
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private LocalDateTime updatedAt = LocalDateTime.now();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
private List<CartItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
public void preUpdate() { this.updatedAt = LocalDateTime.now(); }
|
||||||
|
|
||||||
|
// Getters & Setters
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public Long getUserId() { return userId; }
|
||||||
|
public void setUserId(Long userId) { this.userId = userId; }
|
||||||
|
|
||||||
|
public Status getStatus() { return status; }
|
||||||
|
public void setStatus(Status status) { this.status = status; }
|
||||||
|
|
||||||
|
public String getCurrency() { return currency; }
|
||||||
|
public void setCurrency(String currency) { this.currency = currency; }
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
|
||||||
|
public List<CartItem> getItems() { return items; }
|
||||||
|
public void setItems(List<CartItem> items) { this.items = items; }
|
||||||
|
}
|
||||||
97
src/main/java/com/imprimelibros/erp/cart/CartController.java
Normal file
97
src/main/java/com/imprimelibros/erp/cart/CartController.java
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||||
|
import com.imprimelibros.erp.users.User;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/cart")
|
||||||
|
public class CartController {
|
||||||
|
|
||||||
|
private final CartService service;
|
||||||
|
|
||||||
|
public CartController(CartService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el ID de usuario desde tu seguridad.
|
||||||
|
* Adáptalo a tu UserDetails (e.g., SecurityContext con getId())
|
||||||
|
*/
|
||||||
|
private Long currentUserId(Principal principal) {
|
||||||
|
if (principal == null) {
|
||||||
|
throw new IllegalStateException("Usuario no autenticado");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal instanceof Authentication auth) {
|
||||||
|
Object principalObj = auth.getPrincipal();
|
||||||
|
|
||||||
|
if (principalObj instanceof UserDetailsImpl udi) {
|
||||||
|
return udi.getId();
|
||||||
|
} else if (principalObj instanceof User u && u.getId() != null) {
|
||||||
|
return u.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("No se pudo obtener el ID del usuario actual");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Vista del carrito */
|
||||||
|
@GetMapping
|
||||||
|
public String viewCart(Model model, Principal principal) {
|
||||||
|
var items = service.listItems(currentUserId(principal));
|
||||||
|
model.addAttribute("items", items);
|
||||||
|
return "imprimelibros/cart/cart"; // crea esta vista si quieres (tabla simple)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Añadir presupuesto via POST form */
|
||||||
|
@PostMapping("/add")
|
||||||
|
public String add(@RequestParam("presupuestoId") Long presupuestoId, Principal principal) {
|
||||||
|
service.addPresupuesto(currentUserId(principal), presupuestoId);
|
||||||
|
return "redirect:/cart";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Añadir presupuesto con ruta REST (opcional) */
|
||||||
|
@PostMapping("/add/{presupuestoId}")
|
||||||
|
public String addPath(@PathVariable Long presupuestoId, Principal principal) {
|
||||||
|
service.addPresupuesto(currentUserId(principal), presupuestoId);
|
||||||
|
return "redirect:/cart";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/count")
|
||||||
|
@ResponseBody
|
||||||
|
public long getCount(Principal principal) {
|
||||||
|
if (principal == null)
|
||||||
|
return 0;
|
||||||
|
return service.countItems(currentUserId(principal));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Eliminar línea por ID de item */
|
||||||
|
@DeleteMapping("/{itemId}/remove")
|
||||||
|
public String remove(@PathVariable Long itemId, Principal principal) {
|
||||||
|
service.removeItem(currentUserId(principal), itemId);
|
||||||
|
return "redirect:/cart";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Eliminar línea por presupuesto_id (opcional) */
|
||||||
|
@DeleteMapping("/remove/presupuesto/{presupuestoId}")
|
||||||
|
public String removeByPresupuesto(@PathVariable Long presupuestoId, Principal principal) {
|
||||||
|
service.removeByPresupuesto(currentUserId(principal), presupuestoId);
|
||||||
|
return "redirect:/cart";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vaciar carrito completo */
|
||||||
|
@DeleteMapping("/clear")
|
||||||
|
public String clear(Principal principal) {
|
||||||
|
service.clear(currentUserId(principal));
|
||||||
|
return "redirect:/cart";
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/main/java/com/imprimelibros/erp/cart/CartItem.java
Normal file
37
src/main/java/com/imprimelibros/erp/cart/CartItem.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "cart_items",
|
||||||
|
uniqueConstraints = @UniqueConstraint(name="uq_cartitem_unique", columnNames={"cart_id","presupuesto_id"})
|
||||||
|
)
|
||||||
|
public class CartItem {
|
||||||
|
|
||||||
|
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "cart_id", nullable = false)
|
||||||
|
private Cart cart;
|
||||||
|
|
||||||
|
@Column(name = "presupuesto_id", nullable = false)
|
||||||
|
private Long presupuestoId;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private LocalDateTime createdAt = LocalDateTime.now();
|
||||||
|
|
||||||
|
// Getters & Setters
|
||||||
|
public Long getId() { return id; }
|
||||||
|
|
||||||
|
public Cart getCart() { return cart; }
|
||||||
|
public void setCart(Cart cart) { this.cart = cart; }
|
||||||
|
|
||||||
|
public Long getPresupuestoId() { return presupuestoId; }
|
||||||
|
public void setPresupuestoId(Long presupuestoId) { this.presupuestoId = presupuestoId; }
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface CartItemRepository extends JpaRepository<CartItem, Long> {
|
||||||
|
|
||||||
|
List<CartItem> findByCartId(Long cartId);
|
||||||
|
|
||||||
|
Optional<CartItem> findByCartIdAndPresupuestoId(Long cartId, Long presupuestoId);
|
||||||
|
|
||||||
|
boolean existsByCartIdAndPresupuestoId(Long cartId, Long presupuestoId);
|
||||||
|
|
||||||
|
long deleteByCartId(Long cartId);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface CartRepository extends JpaRepository<Cart, Long> {
|
||||||
|
Optional<Cart> findByUserIdAndStatus(Long userId, Cart.Status status);
|
||||||
|
}
|
||||||
89
src/main/java/com/imprimelibros/erp/cart/CartService.java
Normal file
89
src/main/java/com/imprimelibros/erp/cart/CartService.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CartService {
|
||||||
|
|
||||||
|
private final CartRepository cartRepo;
|
||||||
|
private final CartItemRepository itemRepo;
|
||||||
|
|
||||||
|
public CartService(CartRepository cartRepo, CartItemRepository itemRepo) {
|
||||||
|
this.cartRepo = cartRepo;
|
||||||
|
this.itemRepo = itemRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Devuelve el carrito activo o lo crea si no existe. */
|
||||||
|
@Transactional
|
||||||
|
public Cart getOrCreateActiveCart(Long userId) {
|
||||||
|
return cartRepo.findByUserIdAndStatus(userId, Cart.Status.ACTIVE)
|
||||||
|
.orElseGet(() -> {
|
||||||
|
Cart c = new Cart();
|
||||||
|
c.setUserId(userId);
|
||||||
|
c.setStatus(Cart.Status.ACTIVE);
|
||||||
|
return cartRepo.save(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lista items (presupuestos) del carrito activo del usuario. */
|
||||||
|
@Transactional
|
||||||
|
public List<CartItem> listItems(Long userId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
return itemRepo.findByCartId(cart.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Añade un presupuesto al carrito. Si ya está, no hace nada (idempotente). */
|
||||||
|
@Transactional
|
||||||
|
public void addPresupuesto(Long userId, Long presupuestoId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
boolean exists = itemRepo.existsByCartIdAndPresupuestoId(cart.getId(), presupuestoId);
|
||||||
|
if (!exists) {
|
||||||
|
CartItem ci = new CartItem();
|
||||||
|
ci.setCart(cart);
|
||||||
|
ci.setPresupuestoId(presupuestoId);
|
||||||
|
itemRepo.save(ci);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Elimina una línea del carrito por ID de item (validando pertenencia). */
|
||||||
|
@Transactional
|
||||||
|
public void removeItem(Long userId, Long itemId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
CartItem item = itemRepo.findById(itemId).orElseThrow(() -> new IllegalArgumentException("Item no existe"));
|
||||||
|
if (!item.getCart().getId().equals(cart.getId()))
|
||||||
|
throw new IllegalStateException("El item no pertenece a tu carrito");
|
||||||
|
itemRepo.delete(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Elimina una línea del carrito buscando por presupuesto_id. */
|
||||||
|
@Transactional
|
||||||
|
public void removeByPresupuesto(Long userId, Long presupuestoId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
itemRepo.findByCartIdAndPresupuestoId(cart.getId(), presupuestoId)
|
||||||
|
.ifPresent(itemRepo::delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vacía todo el carrito activo. */
|
||||||
|
@Transactional
|
||||||
|
public void clear(Long userId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
itemRepo.deleteByCartId(cart.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marca el carrito como bloqueado (por ejemplo, antes de crear un pedido). */
|
||||||
|
@Transactional
|
||||||
|
public void lockCart(Long userId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
cart.setStatus(Cart.Status.LOCKED);
|
||||||
|
cartRepo.save(cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public long countItems(Long userId) {
|
||||||
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
|
return itemRepo.findByCartId(cart.getId()).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,32 +2,36 @@ package com.imprimelibros.erp.common.web;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.util.Arrays;
|
public class IpUtils {
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class IpUtils {
|
|
||||||
private IpUtils() {}
|
|
||||||
|
|
||||||
private static final List<String> HEADERS = Arrays.asList(
|
|
||||||
"X-Forwarded-For",
|
|
||||||
"X-Real-IP",
|
|
||||||
"CF-Connecting-IP",
|
|
||||||
"True-Client-IP",
|
|
||||||
"X-Client-IP",
|
|
||||||
"X-Forwarded",
|
|
||||||
"Forwarded-For",
|
|
||||||
"Forwarded"
|
|
||||||
);
|
|
||||||
|
|
||||||
public static String getClientIp(HttpServletRequest request) {
|
public static String getClientIp(HttpServletRequest request) {
|
||||||
for (String h : HEADERS) {
|
String[] headers = {
|
||||||
String v = request.getHeader(h);
|
"X-Forwarded-For",
|
||||||
if (v != null && !v.isBlank() && !"unknown".equalsIgnoreCase(v)) {
|
"Proxy-Client-IP",
|
||||||
// X-Forwarded-For puede traer lista: "client, proxy1, proxy2"
|
"WL-Proxy-Client-IP",
|
||||||
String first = v.split(",")[0].trim();
|
"HTTP_X_FORWARDED_FOR",
|
||||||
if (!first.isBlank()) return first;
|
"HTTP_X_FORWARDED",
|
||||||
|
"HTTP_X_CLUSTER_CLIENT_IP",
|
||||||
|
"HTTP_CLIENT_IP",
|
||||||
|
"HTTP_FORWARDED_FOR",
|
||||||
|
"HTTP_FORWARDED",
|
||||||
|
"HTTP_VIA",
|
||||||
|
"REMOTE_ADDR"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String header : headers) {
|
||||||
|
String ip = request.getHeader(header);
|
||||||
|
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
|
||||||
|
// Si hay varios (X-Forwarded-For), toma el primero
|
||||||
|
return ip.split(",")[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return request.getRemoteAddr();
|
|
||||||
|
// Fallback
|
||||||
|
String ip = request.getRemoteAddr();
|
||||||
|
if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
|
||||||
|
return "127.0.0.1";
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import com.imprimelibros.erp.users.UserDao;
|
|||||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
|
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
|
||||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
|
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
|
||||||
|
import com.imprimelibros.erp.common.web.IpUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolation;
|
||||||
@ -482,7 +483,7 @@ public class PresupuestoController {
|
|||||||
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
List<Map<String, Object>> serviciosList = (List<Map<String, Object>>) body.getOrDefault("servicios", List.of());
|
||||||
|
|
||||||
String sessionId = request.getSession(true).getId();
|
String sessionId = request.getSession(true).getId();
|
||||||
String ip = request.getRemoteAddr();
|
String ip = IpUtils.getClientIp(request);
|
||||||
|
|
||||||
var resumen = presupuestoService.getResumen(p, serviciosList, save, mode, locale, sessionId, ip);
|
var resumen = presupuestoService.getResumen(p, serviciosList, save, mode, locale, sessionId, ip);
|
||||||
|
|
||||||
@ -591,7 +592,9 @@ public class PresupuestoController {
|
|||||||
"presupuesto.impresion-cubierta",
|
"presupuesto.impresion-cubierta",
|
||||||
"presupuesto.impresion-cubierta-help",
|
"presupuesto.impresion-cubierta-help",
|
||||||
"presupuesto.exito.guardado",
|
"presupuesto.exito.guardado",
|
||||||
"presupuesto.add.error.save.title");
|
"presupuesto.add.error.save.title",
|
||||||
|
"presupuesto.iva-reducido",
|
||||||
|
"presupuesto.iva-reducido-descripcion");
|
||||||
|
|
||||||
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||||
model.addAttribute("languageBundle", translations);
|
model.addAttribute("languageBundle", translations);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
import com.imprimelibros.erp.presupuesto.validation.ConsistentTiradas;
|
import com.imprimelibros.erp.presupuesto.validation.ConsistentTiradas;
|
||||||
|
import com.imprimelibros.erp.presupuesto.validation.PaginasCosido;
|
||||||
import com.imprimelibros.erp.presupuesto.validation.Par;
|
import com.imprimelibros.erp.presupuesto.validation.Par;
|
||||||
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
|
||||||
import com.imprimelibros.erp.presupuesto.validation.Tamanio;
|
import com.imprimelibros.erp.presupuesto.validation.Tamanio;
|
||||||
@ -22,6 +23,7 @@ import java.math.BigDecimal;
|
|||||||
import com.imprimelibros.erp.users.User;
|
import com.imprimelibros.erp.users.User;
|
||||||
|
|
||||||
@ConsistentTiradas(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
@ConsistentTiradas(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||||
|
@PaginasCosido(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||||
@Tamanio(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
@Tamanio(groups = PresupuestoValidationGroups.DatosGenerales.class)
|
||||||
@EntityListeners(AuditingEntityListener.class)
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
@Entity
|
@Entity
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.common.web.IpUtils;
|
||||||
import com.imprimelibros.erp.configurationERP.VariableService;
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
||||||
import com.imprimelibros.erp.presupuesto.GeoIpService;
|
import com.imprimelibros.erp.presupuesto.GeoIpService;
|
||||||
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
@ -845,6 +846,8 @@ public class PresupuestoService {
|
|||||||
if (mode.equals("public")) {
|
if (mode.equals("public")) {
|
||||||
|
|
||||||
presupuesto = getDatosLocalizacion(presupuesto, sessionId, ip);
|
presupuesto = getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||||
|
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||||
|
presupuesto = this.getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||||
|
|
||||||
} else
|
} else
|
||||||
presupuesto.setOrigen(Presupuesto.Origen.privado);
|
presupuesto.setOrigen(Presupuesto.Origen.privado);
|
||||||
@ -962,8 +965,10 @@ public class PresupuestoService {
|
|||||||
BigDecimal precioTotalTirada = BigDecimal.valueOf(precioUnit)
|
BigDecimal precioTotalTirada = BigDecimal.valueOf(precioUnit)
|
||||||
.multiply(BigDecimal.valueOf(cantidad))
|
.multiply(BigDecimal.valueOf(cantidad))
|
||||||
.setScale(2, RoundingMode.HALF_UP);
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
if( hayDepositoLegal ){
|
if (hayDepositoLegal) {
|
||||||
precioTotalTirada = precioTotalTirada.add(BigDecimal.valueOf(precioUnit).multiply(BigDecimal.valueOf(4))).setScale(6, RoundingMode.HALF_UP);
|
precioTotalTirada = precioTotalTirada
|
||||||
|
.add(BigDecimal.valueOf(precioUnit).multiply(BigDecimal.valueOf(4)))
|
||||||
|
.setScale(6, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// servicios_total
|
// servicios_total
|
||||||
@ -1020,7 +1025,7 @@ public class PresupuestoService {
|
|||||||
|
|
||||||
// Si la entrega es en peninsula, se mira el valor del iva
|
// Si la entrega es en peninsula, se mira el valor del iva
|
||||||
// Canarias y paises UE no llevan IVA
|
// Canarias y paises UE no llevan IVA
|
||||||
if (presupuesto.getEntregaTipo() == Presupuesto.Entrega.peninsula){
|
if (presupuesto.getEntregaTipo() == Presupuesto.Entrega.peninsula) {
|
||||||
// Si el iva es reducido, el precio de la tirada y el del prototipo llevan IVA
|
// Si el iva es reducido, el precio de la tirada y el del prototipo llevan IVA
|
||||||
// 4%
|
// 4%
|
||||||
if (presupuesto.getIvaReducido()) {
|
if (presupuesto.getIvaReducido()) {
|
||||||
@ -1035,7 +1040,7 @@ public class PresupuestoService {
|
|||||||
BigDecimal.valueOf(100), 2,
|
BigDecimal.valueOf(100), 2,
|
||||||
RoundingMode.HALF_UP);
|
RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baseImponible = baseImponible.add(serviciosTotal);
|
baseImponible = baseImponible.add(serviciosTotal);
|
||||||
BigDecimal totalConIva = baseImponible.add(ivaImporte21).add(ivaImporte4);
|
BigDecimal totalConIva = baseImponible.add(ivaImporte21).add(ivaImporte4);
|
||||||
|
|
||||||
@ -1120,7 +1125,7 @@ public class PresupuestoService {
|
|||||||
if (mode.equals("public")) {
|
if (mode.equals("public")) {
|
||||||
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
presupuesto.setOrigen(Presupuesto.Origen.publico);
|
||||||
String sessionId = request.getSession(true).getId();
|
String sessionId = request.getSession(true).getId();
|
||||||
String ip = request.getRemoteAddr();
|
String ip = IpUtils.getClientIp(request);
|
||||||
|
|
||||||
presupuesto = this.getDatosLocalizacion(presupuesto, sessionId, ip);
|
presupuesto = this.getDatosLocalizacion(presupuesto, sessionId, ip);
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
@ -1178,6 +1183,7 @@ public class PresupuestoService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
// Métodos privados
|
// Métodos privados
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Constraint(validatedBy = PaginasCosidoValidator.class)
|
||||||
|
@Target({ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface PaginasCosido {
|
||||||
|
String message() default "Las tiradas deben ser todas mayores o todas menores al valor POD";
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.imprimelibros.erp.presupuesto.validation;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.configurationERP.VariableService;
|
||||||
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
|
||||||
|
public class PaginasCosidoValidator implements ConstraintValidator<PaginasCosido, Presupuesto> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MessageSource messageSource;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Presupuesto presupuesto, ConstraintValidatorContext context) {
|
||||||
|
if (presupuesto == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (presupuesto.getTipoEncuadernacion() != null &&
|
||||||
|
presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.cosido) {
|
||||||
|
if (presupuesto.getPaginasColor() > 0 && presupuesto.getPaginasNegro() > 0) {
|
||||||
|
String mensajeInterpolado = messageSource.getMessage(
|
||||||
|
"presupuesto.errores.tipo-paginas-cosido",
|
||||||
|
null,
|
||||||
|
LocaleContextHolder.getLocale() // respeta el idioma actual
|
||||||
|
);
|
||||||
|
context.disableDefaultConstraintViolation();
|
||||||
|
context.buildConstraintViolationWithTemplate(mensajeInterpolado)
|
||||||
|
.addConstraintViolation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -312,6 +312,7 @@ presupuesto.errores.paginasNegro.required=El número de páginas en negro es obl
|
|||||||
presupuesto.errores.paginasNegro.par=El número de páginas en negro debe ser par
|
presupuesto.errores.paginasNegro.par=El número de páginas en negro debe ser par
|
||||||
presupuesto.errores.paginasColor.required=El número de páginas en color es obligatorio
|
presupuesto.errores.paginasColor.required=El número de páginas en color es obligatorio
|
||||||
presupuesto.errores.paginasColor.par=El número de páginas en color debe ser par
|
presupuesto.errores.paginasColor.par=El número de páginas en color debe ser par
|
||||||
|
presupuesto.errores.tipo-paginas-cosido=Para encuadernación cosido, sólo se pueden seleccionar o bien páginas a color o páginas en blanco y negro. No se pueden mezclar.
|
||||||
presupuesto.errores.tipo-encuadernacion=Seleccione el tipo de libro
|
presupuesto.errores.tipo-encuadernacion=Seleccione el tipo de libro
|
||||||
presupuesto.errores.ancho=El ancho no puede estar vacío
|
presupuesto.errores.ancho=El ancho no puede estar vacío
|
||||||
presupuesto.errores.ancho.min_max=El ancho tiene que estar en el rango [{0}, {1}] mm;
|
presupuesto.errores.ancho.min_max=El ancho tiene que estar en el rango [{0}, {1}] mm;
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
$(() => {
|
||||||
|
const badge = document.getElementById("cart-item-count");
|
||||||
|
if (!badge) return;
|
||||||
|
|
||||||
|
function updateCartCount() {
|
||||||
|
fetch("/cart/count")
|
||||||
|
.then(res => res.ok ? res.text() : "0")
|
||||||
|
.then(count => {
|
||||||
|
const n = parseInt(count || "0", 10);
|
||||||
|
if (isNaN(n) || n === 0) {
|
||||||
|
badge.classList.add("d-none");
|
||||||
|
} else {
|
||||||
|
badge.textContent = n;
|
||||||
|
badge.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => badge.classList.add("d-none"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar al cargar
|
||||||
|
updateCartCount();
|
||||||
|
|
||||||
|
// Si quieres refrescar cada 60s:
|
||||||
|
setInterval(updateCartCount, 60000);
|
||||||
|
|
||||||
|
// generate a custom event to update the cart count from other scripts
|
||||||
|
document.addEventListener("update-cart", updateCartCount);
|
||||||
|
});
|
||||||
@ -26,7 +26,7 @@
|
|||||||
pageLength: 50,
|
pageLength: 50,
|
||||||
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||||
responsive: true,
|
responsive: true,
|
||||||
dom: 'BlrBtip',
|
dom: 'lrBtip',
|
||||||
buttons: {
|
buttons: {
|
||||||
dom: {
|
dom: {
|
||||||
button: {
|
button: {
|
||||||
|
|||||||
@ -39,6 +39,10 @@
|
|||||||
<th:block layout:fragment="pagejs" />
|
<th:block layout:fragment="pagejs" />
|
||||||
<script th:src="@{/assets/js/app.js}"></script>
|
<script th:src="@{/assets/js/app.js}"></script>
|
||||||
<script th:src="@{/assets/js/pages/imprimelibros/languageBundle.js}"></script>
|
<script th:src="@{/assets/js/pages/imprimelibros/languageBundle.js}"></script>
|
||||||
|
<th:block th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
<script src="/assets/js/pages/imprimelibros/cart-badge.js"></script>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -54,8 +54,8 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- item-->
|
<!-- item-->
|
||||||
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2" data-lang="en-GB"
|
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2"
|
||||||
title="English">
|
data-lang="en-GB" title="English">
|
||||||
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded"
|
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded"
|
||||||
height="18">
|
height="18">
|
||||||
<span class="align-middle">English</span>
|
<span class="align-middle">English</span>
|
||||||
@ -64,6 +64,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${#authorization.expression('isAuthenticated()')}"
|
||||||
|
class="ms-1 header-item d-none d-sm-flex">
|
||||||
|
<button type="button" id="btn_cart"
|
||||||
|
class="btn btn-icon btn-topbar material-shadow-none btn-ghost-secondary rounded-circle light-dark-mode">
|
||||||
|
<i class="bx bx-cart fs-22"></i>
|
||||||
|
<span id="cart-item-count"
|
||||||
|
class="position-absolute topbar-badge cartitem-badge fs-10 translate-middle badge rounded-pill bg-info d-none">
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
<div class="dropdown ms-sm-3 header-item topbar-user">
|
<div class="dropdown ms-sm-3 header-item topbar-user">
|
||||||
@ -92,7 +105,8 @@
|
|||||||
<a class="dropdown-item" href="#"
|
<a class="dropdown-item" href="#"
|
||||||
onclick="document.getElementById('logoutForm').submit(); return false;">
|
onclick="document.getElementById('logoutForm').submit(); return false;">
|
||||||
<i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i>
|
<i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i>
|
||||||
<span class="align-middle" data-key="t-logout" th:text="#{app.logout}">Cerrar sesión</span>
|
<span class="align-middle" data-key="t-logout" th:text="#{app.logout}">Cerrar
|
||||||
|
sesión</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user