mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
Merge branch 'feat/pedidos' into 'main'
Feat/pedidos See merge request jjimenez/erp-imprimelibros!22
This commit is contained in:
5
pom.xml
5
pom.xml
@ -193,6 +193,11 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<!-- IMPORTANTE: incluir dependencias con scope=system en el fat-jar -->
|
||||
<configuration>
|
||||
<!-- Esto hace que meta las dependencias con scope=system en BOOT-INF/lib -->
|
||||
<includeSystemScope>true</includeSystemScope>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- (Migraciones) Plugin Maven para generar/ejecutar changelogs -->
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.imprimelibros.erp.cart;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import com.imprimelibros.erp.cart.dto.DireccionCardDTO;
|
||||
@ -98,4 +100,53 @@ public class CartDireccion {
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> toSkMap(Integer numeroUnidades, Double pesoKg, Boolean palets, Boolean ejemplarPrueba) {
|
||||
|
||||
Map<String, Object> direccion = new HashMap<>();
|
||||
direccion.put("cantidad", numeroUnidades);
|
||||
direccion.put("peso", pesoKg);
|
||||
direccion.put("att", this.getDireccion().getAtt());
|
||||
direccion.put("email", this.getDireccion().getUser().getUserName());
|
||||
direccion.put("direccion", this.getDireccion().getDireccion());
|
||||
direccion.put("pais_code3", this.getDireccion().getPaisCode3());
|
||||
direccion.put("cp", this.getDireccion().getCp());
|
||||
direccion.put("municipio", this.getDireccion().getCiudad());
|
||||
direccion.put("provincia", this.getDireccion().getProvincia());
|
||||
direccion.put("telefono", this.getDireccion().getTelefono());
|
||||
direccion.put("entregaPieCalle", palets ? 1 : 0);
|
||||
direccion.put("is_ferro_prototipo", ejemplarPrueba ? 1 : 0);
|
||||
direccion.put("num_ferro_prototipo", ejemplarPrueba ? 1 : 0);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("direccion", direccion);
|
||||
map.put("unidades", numeroUnidades);
|
||||
map.put("entregaPalets", palets ? 1 : 0);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public Map<String, Object> toSkMapDepositoLegal() {
|
||||
Map<String, Object> direccion = new HashMap<>();
|
||||
direccion.put("cantidad", 4);
|
||||
direccion.put("peso", 0);
|
||||
direccion.put("att", "Unidades para Depósito Legal (sin envío)");
|
||||
direccion.put("email", "");
|
||||
direccion.put("direccion", "");
|
||||
direccion.put("pais_code3", "esp");
|
||||
direccion.put("cp", "");
|
||||
direccion.put("municipio", "");
|
||||
direccion.put("provincia", "");
|
||||
direccion.put("telefono", "");
|
||||
direccion.put("entregaPieCalle", 0);
|
||||
direccion.put("is_ferro_prototipo", 0);
|
||||
direccion.put("num_ferro_prototipo", 0);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("direccion", direccion);
|
||||
map.put("unidades", 4);
|
||||
map.put("entregaPalets", 0);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import java.util.Objects;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
||||
import com.imprimelibros.erp.cart.dto.CartDireccionRepository;
|
||||
import com.imprimelibros.erp.cart.dto.DireccionCardDTO;
|
||||
import com.imprimelibros.erp.cart.dto.DireccionShipment;
|
||||
@ -21,7 +22,8 @@ import com.imprimelibros.erp.cart.dto.UpdateCartRequest;
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.direcciones.DireccionService;
|
||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
||||
import com.imprimelibros.erp.pedido.PedidoService;
|
||||
import com.imprimelibros.erp.pedidos.Pedido;
|
||||
import com.imprimelibros.erp.pedidos.PedidoService;
|
||||
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||
|
||||
@Service
|
||||
@ -36,12 +38,13 @@ public class CartService {
|
||||
private final DireccionService direccionService;
|
||||
private final skApiClient skApiClient;
|
||||
private final PedidoService pedidoService;
|
||||
private final PresupuestoService presupuestoService;
|
||||
|
||||
public CartService(CartRepository cartRepo, CartItemRepository itemRepo,
|
||||
CartDireccionRepository cartDireccionRepo, MessageSource messageSource,
|
||||
PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo,
|
||||
Utils utils, DireccionService direccionService, skApiClient skApiClient,
|
||||
PedidoService pedidoService) {
|
||||
PedidoService pedidoService, PresupuestoService presupuestoService) {
|
||||
this.cartRepo = cartRepo;
|
||||
this.itemRepo = itemRepo;
|
||||
this.cartDireccionRepo = cartDireccionRepo;
|
||||
@ -51,16 +54,14 @@ public class CartService {
|
||||
this.direccionService = direccionService;
|
||||
this.skApiClient = skApiClient;
|
||||
this.pedidoService = pedidoService;
|
||||
this.presupuestoService = presupuestoService;
|
||||
}
|
||||
|
||||
|
||||
public Cart findById(Long cartId) {
|
||||
return cartRepo.findById(cartId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Carrito no encontrado"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Devuelve el carrito activo o lo crea si no existe. */
|
||||
@Transactional
|
||||
public Cart getOrCreateActiveCart(Long userId) {
|
||||
@ -190,14 +191,13 @@ public class CartService {
|
||||
return resumen;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCartSummary(Cart cart, Locale locale) {
|
||||
public Map<String, Object> getCartSummaryRaw(Cart cart, Locale locale) {
|
||||
|
||||
double base = 0.0;
|
||||
double iva4 = 0.0;
|
||||
double iva21 = 0.0;
|
||||
double shipment = 0.0;
|
||||
|
||||
Boolean errorShipementCost = false;
|
||||
boolean errorShipementCost = false;
|
||||
|
||||
List<CartItem> items = cart.getItems();
|
||||
List<CartDireccion> direcciones = cart.getDirecciones();
|
||||
@ -208,55 +208,58 @@ public class CartService {
|
||||
base += p.getBaseImponible().doubleValue();
|
||||
iva4 += p.getIvaImporte4().doubleValue();
|
||||
iva21 += p.getIvaImporte21().doubleValue();
|
||||
|
||||
if (cart.getOnlyOneShipment() != null && cart.getOnlyOneShipment()) {
|
||||
// Si es envío único, que es a españa y no ha canarias
|
||||
if (direcciones != null && direcciones.size() > 0) {
|
||||
if (direcciones != null && !direcciones.isEmpty()) {
|
||||
CartDireccion cd = direcciones.get(0);
|
||||
Boolean freeShipment = direccionService.checkFreeShipment(cd.getDireccion().getCp(),
|
||||
boolean freeShipment = direccionService.checkFreeShipment(
|
||||
cd.getDireccion().getCp(),
|
||||
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
||||
|
||||
if (!freeShipment) {
|
||||
Integer unidades = p.getSelectedTirada();
|
||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
||||
if (res.get("success").equals(Boolean.FALSE)) {
|
||||
if (Boolean.FALSE.equals(res.get("success"))) {
|
||||
errorShipementCost = true;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
shipment += (Double) res.get("shipment");
|
||||
iva21 += (Double) res.get("iva21");
|
||||
}
|
||||
|
||||
}
|
||||
// si tiene prueba de envio, hay que añadir el coste
|
||||
if (p.getServiciosJson() != null && p.getServiciosJson().contains("ejemplar-prueba")) {
|
||||
|
||||
// ejemplar de prueba
|
||||
if (p.getServiciosJson() != null && p.getServiciosJson().contains("ejemplar-prueba")) {
|
||||
Map<String, Object> res = getShippingCost(cd, peso, 1, locale);
|
||||
if (res.get("success").equals(Boolean.FALSE)) {
|
||||
if (Boolean.FALSE.equals(res.get("success"))) {
|
||||
errorShipementCost = true;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
shipment += (Double) res.get("shipment");
|
||||
iva21 += (Double) res.get("iva21");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// envio por cada presupuesto
|
||||
// buscar la direccion asignada a este presupuesto
|
||||
if (direcciones == null)
|
||||
continue;
|
||||
|
||||
List<CartDireccion> cd_presupuesto = direcciones.stream()
|
||||
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(p.getId())
|
||||
&& d.getUnidades() != null && d.getUnidades() != null && d.getUnidades() > 0)
|
||||
.filter(d -> d.getPresupuesto() != null
|
||||
&& d.getPresupuesto().getId().equals(p.getId())
|
||||
&& d.getUnidades() != null
|
||||
&& d.getUnidades() > 0)
|
||||
.toList();
|
||||
Boolean firstDirection = true;
|
||||
|
||||
boolean firstDirection = true;
|
||||
for (CartDireccion cd : cd_presupuesto) {
|
||||
Integer unidades = cd.getUnidades();
|
||||
if (firstDirection) {
|
||||
Boolean freeShipment = direccionService.checkFreeShipment(cd.getDireccion().getCp(),
|
||||
boolean freeShipment = direccionService.checkFreeShipment(
|
||||
cd.getDireccion().getCp(),
|
||||
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
||||
|
||||
if (!freeShipment && unidades != null && unidades > 0) {
|
||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
||||
if (res.get("success").equals(Boolean.FALSE)) {
|
||||
if (Boolean.FALSE.equals(res.get("success"))) {
|
||||
errorShipementCost = true;
|
||||
} else {
|
||||
shipment += (Double) res.get("shipment");
|
||||
@ -266,7 +269,7 @@ public class CartService {
|
||||
firstDirection = false;
|
||||
} else {
|
||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
||||
if (res.get("success").equals(Boolean.FALSE)) {
|
||||
if (Boolean.FALSE.equals(res.get("success"))) {
|
||||
errorShipementCost = true;
|
||||
} else {
|
||||
shipment += (Double) res.get("shipment");
|
||||
@ -274,18 +277,19 @@ public class CartService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ejemplar de prueba
|
||||
CartDireccion cd_prueba = direcciones.stream()
|
||||
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(p.getId())
|
||||
.filter(d -> d.getPresupuesto() != null
|
||||
&& d.getPresupuesto().getId().equals(p.getId())
|
||||
&& d.getUnidades() == null)
|
||||
.findFirst().orElse(null);
|
||||
if (cd_prueba != null) {
|
||||
|
||||
if (cd_prueba != null) {
|
||||
Map<String, Object> res = getShippingCost(cd_prueba, peso, 1, locale);
|
||||
if (res.get("success").equals(Boolean.FALSE)) {
|
||||
if (Boolean.FALSE.equals(res.get("success"))) {
|
||||
errorShipementCost = true;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
shipment += (Double) res.get("shipment");
|
||||
iva21 += (Double) res.get("iva21");
|
||||
}
|
||||
@ -293,11 +297,44 @@ public class CartService {
|
||||
}
|
||||
}
|
||||
|
||||
double total = base + iva4 + iva21 + shipment;
|
||||
|
||||
double totalBeforeDiscount = base + iva4 + iva21 + shipment;
|
||||
int fidelizacion = pedidoService.getDescuentoFidelizacion();
|
||||
double descuento = (total) * fidelizacion / 100.0;
|
||||
total -= descuento;
|
||||
double descuento = totalBeforeDiscount * fidelizacion / 100.0;
|
||||
double total = totalBeforeDiscount - descuento;
|
||||
|
||||
// Redondeo a 2 decimales
|
||||
base = Utils.round2(base);
|
||||
iva4 = Utils.round2(iva4);
|
||||
iva21 = Utils.round2(iva21);
|
||||
shipment = Utils.round2(shipment);
|
||||
descuento = Utils.round2(descuento);
|
||||
total = Utils.round2(total);
|
||||
|
||||
Map<String, Object> summary = new HashMap<>();
|
||||
summary.put("base", base);
|
||||
summary.put("iva4", iva4);
|
||||
summary.put("iva21", iva21);
|
||||
summary.put("shipment", shipment);
|
||||
summary.put("fidelizacion", fidelizacion);
|
||||
summary.put("descuento", descuento);
|
||||
summary.put("total", total);
|
||||
summary.put("amountCents", Math.round(total * 100));
|
||||
summary.put("errorShipmentCost", errorShipementCost);
|
||||
summary.put("cartId", cart.getId());
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCartSummary(Cart cart, Locale locale) {
|
||||
Map<String, Object> raw = getCartSummaryRaw(cart, locale);
|
||||
|
||||
double base = (Double) raw.get("base");
|
||||
double iva4 = (Double) raw.get("iva4");
|
||||
double iva21 = (Double) raw.get("iva21");
|
||||
double shipment = (Double) raw.get("shipment");
|
||||
int fidelizacion = (Integer) raw.get("fidelizacion");
|
||||
double descuento = (Double) raw.get("descuento");
|
||||
double total = (Double) raw.get("total");
|
||||
|
||||
Map<String, Object> summary = new HashMap<>();
|
||||
summary.put("base", Utils.formatCurrency(base, locale));
|
||||
@ -305,11 +342,11 @@ public class CartService {
|
||||
summary.put("iva21", Utils.formatCurrency(iva21, locale));
|
||||
summary.put("shipment", Utils.formatCurrency(shipment, locale));
|
||||
summary.put("fidelizacion", fidelizacion + "%");
|
||||
summary.put("descuento", Utils.formatCurrency(-descuento, locale));
|
||||
summary.put("descuento", Utils.formatCurrency(-descuento, locale)); // negativo para mostrar
|
||||
summary.put("total", Utils.formatCurrency(total, locale));
|
||||
summary.put("amountCents", Math.round(total * 100));
|
||||
summary.put("errorShipmentCost", errorShipementCost);
|
||||
summary.put("cartId", cart.getId());
|
||||
summary.put("amountCents", raw.get("amountCents"));
|
||||
summary.put("errorShipmentCost", raw.get("errorShipmentCost"));
|
||||
summary.put("cartId", raw.get("cartId"));
|
||||
|
||||
return summary;
|
||||
}
|
||||
@ -404,13 +441,160 @@ public class CartService {
|
||||
// delete cart directions by direccion id in ACTIVE carts
|
||||
@Transactional
|
||||
public void deleteCartDireccionesByDireccionId(Long direccionId) {
|
||||
/*List<CartDireccion> cartDirecciones = cartDireccionRepo.findByDireccion_IdAndCart_Status(direccionId, Cart.Status.ACTIVE);
|
||||
for (CartDireccion cd : cartDirecciones) {
|
||||
cartDireccionRepo.deleteById(cd.getId());
|
||||
}*/
|
||||
|
||||
cartDireccionRepo.deleteByDireccionIdAndCartStatus(direccionId, Cart.Status.ACTIVE);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long crearPedido(Long cartId, Locale locale) {
|
||||
|
||||
Cart cart = this.getCartById(cartId);
|
||||
List<CartItem> items = cart.getItems();
|
||||
|
||||
List<Map<String, Object>> presupuestoRequests = new ArrayList<>();
|
||||
List<Long> presupuestoIds = new ArrayList<>();
|
||||
|
||||
for (Integer i = 0; i < items.size(); i++) {
|
||||
CartItem item = items.get(i);
|
||||
Presupuesto p = item.getPresupuesto();
|
||||
|
||||
Map<String, Object> data_to_send = presupuestoService.toSkApiRequest(p, true);
|
||||
data_to_send.put("createPedido", 0);
|
||||
if (items.size() > 1) {
|
||||
// Recuperar el mapa anidado datosCabecera
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> datosCabecera = (Map<String, Object>) data_to_send.get("datosCabecera");
|
||||
if (datosCabecera != null) {
|
||||
Object tituloOriginal = datosCabecera.get("titulo");
|
||||
datosCabecera.put(
|
||||
"titulo",
|
||||
"[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : ""));
|
||||
}
|
||||
}
|
||||
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
|
||||
data_to_send.put("direcciones", direcciones_presupuesto.get("direcciones"));
|
||||
data_to_send.put("direccionesFP1", direcciones_presupuesto.get("direccionesFP1"));
|
||||
|
||||
Map<String, Object> result = skApiClient.savePresupuesto(data_to_send);
|
||||
|
||||
if (result.containsKey("error")) {
|
||||
System.out.println("Error al guardar presupuesto en SK: " + result.get("error"));
|
||||
// decide si seguir con otros items o abortar:
|
||||
// continue; o bien throw ...
|
||||
continue;
|
||||
}
|
||||
|
||||
Object dataObj = result.get("data");
|
||||
if (!(dataObj instanceof Map<?, ?> dataRaw)) {
|
||||
System.out.println("Formato inesperado de 'data' en savePresupuesto: " + result);
|
||||
continue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> dataMap = (Map<String, Object>) dataRaw;
|
||||
Long presId = ((Number) dataMap.get("id")).longValue();
|
||||
String skin = ((String) dataMap.get("iskn")).toString();
|
||||
p.setProveedor("Safekat");
|
||||
p.setProveedorRef1(skin);
|
||||
p.setProveedorRef2(presId);
|
||||
p.setEstado(Presupuesto.Estado.aceptado);
|
||||
presupuestoRepo.save(p);
|
||||
|
||||
presupuestoIds.add(p.getId());
|
||||
|
||||
presupuestoRequests.add(dataMap);
|
||||
}
|
||||
|
||||
// Crear el pedido en base a los presupuestos guardados
|
||||
if (presupuestoRequests.isEmpty()) {
|
||||
throw new IllegalStateException("No se pudieron guardar los presupuestos en SK.");
|
||||
} else {
|
||||
ArrayList<Long> presupuestoSkIds = new ArrayList<>();
|
||||
for (Map<String, Object> presData : presupuestoRequests) {
|
||||
Long presId = ((Number) presData.get("id")).longValue();
|
||||
presupuestoSkIds.add(presId);
|
||||
}
|
||||
Map<String, Object> ids = new HashMap<>();
|
||||
ids.put("presupuesto_ids", presupuestoSkIds);
|
||||
Long pedidoId = skApiClient.crearPedido(ids);
|
||||
if (pedidoId == null) {
|
||||
throw new IllegalStateException("No se pudo crear el pedido en SK.");
|
||||
}
|
||||
Pedido pedidoInterno = pedidoService.crearPedido(presupuestoIds, this.getCartSummaryRaw(cart, locale),
|
||||
"Safekat", String.valueOf(pedidoId), cart.getUserId());
|
||||
return pedidoInterno.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getDireccionesPresupuesto(Cart cart, Presupuesto presupuesto) {
|
||||
|
||||
List<Map<String, Object>> direccionesPresupuesto = new ArrayList<>();
|
||||
List<Map<String, Object>> direccionesPrueba = new ArrayList<>();
|
||||
List<CartDireccion> direcciones = cart.getDirecciones().stream()
|
||||
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(presupuesto.getId()))
|
||||
.toList();
|
||||
if (cart.getOnlyOneShipment()) {
|
||||
direcciones = direcciones.stream().limit(1).toList();
|
||||
if (!direcciones.isEmpty()) {
|
||||
direccionesPresupuesto.add(direcciones.get(0).toSkMap(
|
||||
presupuesto.getSelectedTirada(),
|
||||
presupuesto.getPeso(),
|
||||
direcciones.get(0).getIsPalets(),
|
||||
false));
|
||||
if (presupuesto.getServiciosJson() != null
|
||||
&& presupuesto.getServiciosJson().contains("ejemplar-prueba")) {
|
||||
direccionesPrueba.add(direcciones.get(0).toSkMap(
|
||||
1,
|
||||
presupuesto.getPeso(),
|
||||
false,
|
||||
true));
|
||||
}
|
||||
if (presupuesto.getServiciosJson() != null
|
||||
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
|
||||
direccionesPresupuesto.add(direcciones.get(0).toSkMapDepositoLegal());
|
||||
}
|
||||
Map<String, Object> direccionesRet = new HashMap<>();
|
||||
direccionesRet.put("direcciones", direccionesPresupuesto);
|
||||
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
|
||||
return direccionesRet;
|
||||
}
|
||||
} else {
|
||||
for (CartDireccion cd : cart.getDirecciones()) {
|
||||
|
||||
// direccion de ejemplar de prueba
|
||||
if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) {
|
||||
continue;
|
||||
}
|
||||
if (cd.getUnidades() == null || cd.getUnidades() <= 0) {
|
||||
direccionesPrueba.add(cd.toSkMap(
|
||||
1,
|
||||
presupuesto.getPeso(),
|
||||
false,
|
||||
true));
|
||||
} else {
|
||||
direccionesPresupuesto.add(cd.toSkMap(
|
||||
cd.getUnidades(),
|
||||
presupuesto.getPeso(),
|
||||
cd.getIsPalets(),
|
||||
false));
|
||||
}
|
||||
}
|
||||
if (presupuesto.getServiciosJson() != null
|
||||
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
|
||||
CartDireccion cd = new CartDireccion();
|
||||
direccionesPresupuesto.add(cd.toSkMapDepositoLegal());
|
||||
}
|
||||
}
|
||||
Map<String, Object> direccionesRet = new HashMap<>();
|
||||
direccionesRet.put("direcciones", direccionesPresupuesto);
|
||||
if (!direccionesPrueba.isEmpty())
|
||||
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
|
||||
else {
|
||||
direccionesRet.put("direccionesFP1", new ArrayList<>());
|
||||
}
|
||||
return direccionesRet;
|
||||
}
|
||||
|
||||
/***************************************
|
||||
* MÉTODOS PRIVADOS
|
||||
***************************************/
|
||||
|
||||
@ -47,10 +47,37 @@ public class Utils {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public static List<Map<String, Object>> decodeJsonList(String json) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
return mapper.readValue(json, new TypeReference<List<Map<String, Object>>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> decodeJsonMap(String json) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
return mapper.readValue(json, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static double round2(double value) {
|
||||
return BigDecimal.valueOf(value)
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.doubleValue();
|
||||
}
|
||||
|
||||
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"));
|
||||
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")
|
||||
|| a.getAuthority().equals("ROLE_SUPERADMIN"));
|
||||
}
|
||||
|
||||
public static Long currentUserId(Principal principal) {
|
||||
|
||||
@ -2,6 +2,8 @@ package com.imprimelibros.erp.direcciones;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
|
||||
@ -128,6 +128,125 @@ public class skApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
public Map<String, Object> savePresupuesto(Map<String, Object> requestBody) {
|
||||
return performWithRetryMap(() -> {
|
||||
String url = this.skApiUrl + "api/guardar";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(authService.getToken());
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
try {
|
||||
Map<String, Object> responseBody = mapper.readValue(
|
||||
response.getBody(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
// Si la API devuelve "error" a nivel raíz
|
||||
if (responseBody.get("error") != null) {
|
||||
// Devolvemos un mapa con sólo el error para que el caller decida
|
||||
return Map.of("error", responseBody.get("error"));
|
||||
}
|
||||
|
||||
Object dataObj = responseBody.get("data");
|
||||
if (dataObj instanceof Map<?, ?> dataRaw) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) dataRaw;
|
||||
|
||||
Boolean success = (Boolean) (responseBody.get("success") != null ? responseBody.get("success")
|
||||
: false);
|
||||
Long id = ((Integer) data.get("id")).longValue();
|
||||
String iskn = (String) data.get("iskn");
|
||||
|
||||
// OJO: aquí mantengo tu lógica tal cual (success == null o false => OK)
|
||||
// Si tu API realmente usa success=true como éxito, esto habría que invertirlo.
|
||||
if (success != null && success) {
|
||||
if (id != null && iskn != null) {
|
||||
data.put("id", Long.valueOf(id));
|
||||
data.put("iskn", iskn);
|
||||
}
|
||||
} else {
|
||||
// Tu lógica actual: si success es true u otra cosa → error 2
|
||||
return Map.of("error", 2);
|
||||
}
|
||||
|
||||
// Devolvemos sólo la parte interesante: el data ya enriquecido
|
||||
return Map.of("data", data);
|
||||
}
|
||||
|
||||
// Si data no es un Map, devolvemos error genérico
|
||||
return Map.of("error", 1);
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return Map.of("error", 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Long crearPedido(Map<String, Object> requestBody) {
|
||||
Map<String, Object> result = performWithRetryMap(() -> {
|
||||
String url = this.skApiUrl + "api/crear-pedido";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(authService.getToken());
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
try {
|
||||
Map<String, Object> responseBody = mapper.readValue(
|
||||
response.getBody(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
// Si la API devuelve "error" a nivel raíz
|
||||
if (responseBody.get("error") != null) {
|
||||
// Devolvemos un mapa con sólo el error para que el caller decida
|
||||
return Map.of("error", responseBody.get("error"));
|
||||
}
|
||||
|
||||
Boolean success = (Boolean) (responseBody.get("success") != null ? responseBody.get("success") : false);
|
||||
Long id = ((Integer) responseBody.get("id")).longValue();
|
||||
|
||||
if (success != null && id != null && success) {
|
||||
return Map.of("data", id);
|
||||
} else {
|
||||
// Tu lógica actual: si success es true u otra cosa → error 2
|
||||
return Map.of("error", 2);
|
||||
}
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return Map.of("error", 1);
|
||||
}
|
||||
});
|
||||
|
||||
if (result.get("error") != null) {
|
||||
throw new RuntimeException("Error al crear el pedido: " + result.get("error"));
|
||||
}
|
||||
return (Long) result.get("data");
|
||||
}
|
||||
|
||||
public Integer getMaxSolapas(Map<String, Object> requestBody, Locale locale) {
|
||||
try {
|
||||
String jsonResponse = performWithRetry(() -> {
|
||||
@ -238,7 +357,6 @@ public class skApiClient {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(authService.getToken());
|
||||
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
uri,
|
||||
HttpMethod.GET,
|
||||
@ -255,10 +373,10 @@ public class skApiClient {
|
||||
return Map.of("error", messageSource.getMessage("direcciones.error.noShippingCost", null, locale));
|
||||
} else {
|
||||
Double total = Optional.ofNullable(responseBody.get("data"))
|
||||
.filter(Number.class::isInstance)
|
||||
.map(Number.class::cast)
|
||||
.map(Number::doubleValue)
|
||||
.orElse(0.0);
|
||||
.filter(Number.class::isInstance)
|
||||
.map(Number.class::cast)
|
||||
.map(Number::doubleValue)
|
||||
.orElse(0.0);
|
||||
|
||||
return Map.of("data", total);
|
||||
}
|
||||
|
||||
@ -295,11 +295,11 @@ public class PaymentController {
|
||||
}
|
||||
|
||||
@PostMapping(value = "/transfer/completed/{id}", produces = "application/json")
|
||||
public ResponseEntity<Map<String, Object>> markTransferAsCaptured(@PathVariable Long id) {
|
||||
public ResponseEntity<Map<String, Object>> markTransferAsCaptured(@PathVariable Long id, Locale locale) {
|
||||
|
||||
Map<String, Object> response;
|
||||
try {
|
||||
paymentService.markBankTransferAsCaptured(id);
|
||||
paymentService.markBankTransferAsCaptured(id, locale);
|
||||
response = Map.of("success", true);
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import com.imprimelibros.erp.payments.repo.WebhookEventRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@ -36,7 +37,7 @@ public class PaymentService {
|
||||
this.payRepo = payRepo;
|
||||
this.txRepo = txRepo;
|
||||
this.refundRepo = refundRepo;
|
||||
this.redsysService = redsysService;
|
||||
this.redsysService = redsysService;
|
||||
this.webhookEventRepo = webhookEventRepo;
|
||||
this.cartService = cartService;
|
||||
}
|
||||
@ -82,7 +83,7 @@ public class PaymentService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParameters) throws Exception {
|
||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParameters, Locale locale) throws Exception {
|
||||
|
||||
// 0) Intentamos parsear la notificación. Si falla, registramos el webhook crudo
|
||||
// y salimos.
|
||||
@ -197,7 +198,7 @@ public class PaymentService {
|
||||
}
|
||||
|
||||
if (authorized) {
|
||||
processOrder(notif.cartId);
|
||||
processOrder(notif.cartId, locale);
|
||||
}
|
||||
|
||||
payRepo.save(p);
|
||||
@ -317,7 +318,7 @@ public class PaymentService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void markBankTransferAsCaptured(Long paymentId) {
|
||||
public void markBankTransferAsCaptured(Long paymentId, Locale locale) {
|
||||
Payment p = payRepo.findById(paymentId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Payment no encontrado: " + paymentId));
|
||||
|
||||
@ -354,7 +355,7 @@ public class PaymentService {
|
||||
|
||||
// 4) Procesar el pedido asociado al carrito (si existe)
|
||||
if (p.getOrderId() != null) {
|
||||
processOrder(p.getOrderId());
|
||||
processOrder(p.getOrderId(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,17 +451,26 @@ public class PaymentService {
|
||||
return code >= 0 && code <= 99;
|
||||
}
|
||||
|
||||
private Boolean processOrder(Long cartId) {
|
||||
// GENERAR PEDIDO A PARTIR DEL CARRITO
|
||||
/**
|
||||
* Procesa el pedido asociado al carrito:
|
||||
* - bloquea el carrito
|
||||
* - crea el pedido a partir del carrito
|
||||
*
|
||||
*/
|
||||
private Boolean processOrder(Long cartId, Locale locale) {
|
||||
|
||||
Cart cart = this.cartService.findById(cartId);
|
||||
if (cart != null) {
|
||||
// Bloqueamos el carrito
|
||||
this.cartService.lockCartById(cart.getId());
|
||||
// order ID es generado dentro de createOrderFromCart donde se marcan los
|
||||
// presupuestos como no editables
|
||||
// Long orderId =
|
||||
// this.cartService.pedidoService.createOrderFromCart(cart.getId(), p.getId());
|
||||
// p.setOrderId(orderId);
|
||||
// Creamos el pedido
|
||||
Long orderId = this.cartService.crearPedido(cart.getId(), locale);
|
||||
if(orderId == null){
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
// envio de correo de confirmacion de pedido podria ir aqui
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
package com.imprimelibros.erp.pedido;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PedidoService {
|
||||
|
||||
public int getDescuentoFidelizacion() {
|
||||
// descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el ultimo año)
|
||||
double totalGastado = 1600.0; // Ejemplo, deberías obtenerlo del historial del cliente
|
||||
if(totalGastado < 1200) {
|
||||
return 0;
|
||||
} else if(totalGastado >= 1200 && totalGastado < 1999) {
|
||||
return 1;
|
||||
} else if(totalGastado >= 2000 && totalGastado < 2999) {
|
||||
return 2;
|
||||
} else if(totalGastado >= 3000 && totalGastado < 3999) {
|
||||
return 3;
|
||||
} else if(totalGastado >= 4000 && totalGastado < 4999) {
|
||||
return 4;
|
||||
} else if(totalGastado >= 5000) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
191
src/main/java/com/imprimelibros/erp/pedidos/Pedido.java
Normal file
191
src/main/java/com/imprimelibros/erp/pedidos/Pedido.java
Normal file
@ -0,0 +1,191 @@
|
||||
package com.imprimelibros.erp.pedidos;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "pedidos")
|
||||
public class Pedido {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// Campos económicos
|
||||
@Column(name = "base", nullable = false)
|
||||
private Double base;
|
||||
|
||||
@Column(name = "envio", nullable = false)
|
||||
private Double envio = 0.0;
|
||||
|
||||
@Column(name = "iva4", nullable = false)
|
||||
private Double iva4 = 0.0;
|
||||
|
||||
@Column(name = "iva21", nullable = false)
|
||||
private Double iva21 = 0.0;
|
||||
|
||||
@Column(name = "descuento", nullable = false)
|
||||
private Double descuento = 0.0;
|
||||
|
||||
@Column(name = "total", nullable = false)
|
||||
private Double total = 0.0;
|
||||
|
||||
// Datos de proveedor
|
||||
@Column(name = "proveedor", length = 100)
|
||||
private String proveedor;
|
||||
|
||||
@Column(name = "proveedor_ref", length = 100)
|
||||
private String proveedorRef;
|
||||
|
||||
// Auditoría básica (coincidiendo con las columnas que se ven en la captura)
|
||||
@Column(name = "created_by")
|
||||
private Long createdBy;
|
||||
|
||||
@Column(name = "updated_by")
|
||||
private Long updatedBy;
|
||||
|
||||
@Column(name = "deleted_by")
|
||||
private Long deletedBy;
|
||||
|
||||
@Column(name = "deleted", nullable = false)
|
||||
private boolean deleted = false;
|
||||
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
// --- Getters y setters ---
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Double getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(Double base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public Double getEnvio() {
|
||||
return envio;
|
||||
}
|
||||
|
||||
public void setEnvio(Double envio) {
|
||||
this.envio = envio;
|
||||
}
|
||||
|
||||
public Double getIva4() {
|
||||
return iva4;
|
||||
}
|
||||
|
||||
public void setIva4(Double iva4) {
|
||||
this.iva4 = iva4;
|
||||
}
|
||||
|
||||
public Double getIva21() {
|
||||
return iva21;
|
||||
}
|
||||
|
||||
public void setIva21(Double iva21) {
|
||||
this.iva21 = iva21;
|
||||
}
|
||||
|
||||
public Double getDescuento() {
|
||||
return descuento;
|
||||
}
|
||||
|
||||
public void setDescuento(Double descuento) {
|
||||
this.descuento = descuento;
|
||||
}
|
||||
|
||||
public Double getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Double total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public String getProveedor() {
|
||||
return proveedor;
|
||||
}
|
||||
|
||||
public void setProveedor(String proveedor) {
|
||||
this.proveedor = proveedor;
|
||||
}
|
||||
|
||||
public String getProveedorRef() {
|
||||
return proveedorRef;
|
||||
}
|
||||
|
||||
public void setProveedorRef(String proveedorRef) {
|
||||
this.proveedorRef = proveedorRef;
|
||||
}
|
||||
|
||||
public Long getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(Long createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Long getUpdatedBy() {
|
||||
return updatedBy;
|
||||
}
|
||||
|
||||
public void setUpdatedBy(Long updatedBy) {
|
||||
this.updatedBy = updatedBy;
|
||||
}
|
||||
|
||||
public Long getDeletedBy() {
|
||||
return deletedBy;
|
||||
}
|
||||
|
||||
public void setDeletedBy(Long deletedBy) {
|
||||
this.deletedBy = deletedBy;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getDeletedAt() {
|
||||
return deletedAt;
|
||||
}
|
||||
|
||||
public void setDeletedAt(LocalDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
}
|
||||
71
src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java
Normal file
71
src/main/java/com/imprimelibros/erp/pedidos/PedidoLinea.java
Normal file
@ -0,0 +1,71 @@
|
||||
package com.imprimelibros.erp.pedidos;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||
|
||||
@Entity
|
||||
@Table(name = "pedidos_lineas")
|
||||
public class PedidoLinea {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "pedido_id", nullable = false)
|
||||
private Pedido pedido;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "presupuesto_id", nullable = false)
|
||||
private Presupuesto presupuesto;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "created_by", nullable = false)
|
||||
private Long createdBy;
|
||||
|
||||
// --- Getters y setters ---
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Pedido getPedido() {
|
||||
return pedido;
|
||||
}
|
||||
|
||||
public void setPedido(Pedido pedido) {
|
||||
this.pedido = pedido;
|
||||
}
|
||||
|
||||
public Presupuesto getPresupuesto() {
|
||||
return presupuesto;
|
||||
}
|
||||
|
||||
public void setPresupuesto(Presupuesto presupuesto) {
|
||||
this.presupuesto = presupuesto;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Long getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(Long createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.imprimelibros.erp.pedidos;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PedidoLineaRepository extends JpaRepository<PedidoLinea, Long> {
|
||||
|
||||
List<PedidoLinea> findByPedidoId(Long pedidoId);
|
||||
|
||||
List<PedidoLinea> findByPresupuestoId(Long presupuestoId);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.imprimelibros.erp.pedidos;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
|
||||
// aquí podrás añadir métodos tipo:
|
||||
// List<Pedido> findByDeletedFalse();
|
||||
}
|
||||
102
src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java
Normal file
102
src/main/java/com/imprimelibros/erp/pedidos/PedidoService.java
Normal file
@ -0,0 +1,102 @@
|
||||
package com.imprimelibros.erp.pedidos;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
||||
|
||||
@Service
|
||||
public class PedidoService {
|
||||
|
||||
private final PedidoRepository pedidoRepository;
|
||||
private final PedidoLineaRepository pedidoLineaRepository;
|
||||
private final PresupuestoRepository presupuestoRepository;
|
||||
|
||||
public PedidoService(PedidoRepository pedidoRepository, PedidoLineaRepository pedidoLineaRepository,
|
||||
PresupuestoRepository presupuestoRepository) {
|
||||
this.pedidoRepository = pedidoRepository;
|
||||
this.pedidoLineaRepository = pedidoLineaRepository;
|
||||
this.presupuestoRepository = presupuestoRepository;
|
||||
}
|
||||
|
||||
public int getDescuentoFidelizacion() {
|
||||
// descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el
|
||||
// ultimo año)
|
||||
double totalGastado = 1600.0; // Ejemplo, deberías obtenerlo del historial del cliente
|
||||
if (totalGastado < 1200) {
|
||||
return 0;
|
||||
} else if (totalGastado >= 1200 && totalGastado < 1999) {
|
||||
return 1;
|
||||
} else if (totalGastado >= 2000 && totalGastado < 2999) {
|
||||
return 2;
|
||||
} else if (totalGastado >= 3000 && totalGastado < 3999) {
|
||||
return 3;
|
||||
} else if (totalGastado >= 4000 && totalGastado < 4999) {
|
||||
return 4;
|
||||
} else if (totalGastado >= 5000) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea un pedido a partir de:
|
||||
* - lista de IDs de presupuesto
|
||||
* - resumen numérico del carrito (getCartSummaryRaw)
|
||||
* - datos de proveedor
|
||||
* - usuario que crea el pedido
|
||||
*/
|
||||
@Transactional
|
||||
public Pedido crearPedido(List<Long> presupuestoIds,
|
||||
Map<String, Object> cartSummaryRaw,
|
||||
String proveedor,
|
||||
String proveedorRef,
|
||||
Long userId) {
|
||||
|
||||
Pedido pedido = new Pedido();
|
||||
|
||||
// Datos económicos (ojo con las claves, son las del summaryRaw)
|
||||
pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d));
|
||||
pedido.setEnvio((Double) cartSummaryRaw.getOrDefault("shipment", 0.0d));
|
||||
pedido.setIva4((Double) cartSummaryRaw.getOrDefault("iva4", 0.0d));
|
||||
pedido.setIva21((Double) cartSummaryRaw.getOrDefault("iva21", 0.0d));
|
||||
pedido.setDescuento((Double) cartSummaryRaw.getOrDefault("descuento", 0.0d));
|
||||
pedido.setTotal((Double) cartSummaryRaw.getOrDefault("total", 0.0d));
|
||||
|
||||
// Proveedor
|
||||
pedido.setProveedor(proveedor);
|
||||
pedido.setProveedorRef(proveedorRef);
|
||||
|
||||
// Auditoría mínima
|
||||
pedido.setCreatedBy(userId);
|
||||
pedido.setCreatedAt(LocalDateTime.now());
|
||||
pedido.setDeleted(false);
|
||||
pedido.setUpdatedAt(LocalDateTime.now());
|
||||
pedido.setUpdatedBy(userId);
|
||||
|
||||
// Guardamos el pedido
|
||||
Pedido saved = pedidoRepository.save(pedido);
|
||||
|
||||
// Crear líneas del pedido
|
||||
for (Long presupuestoId : presupuestoIds) {
|
||||
Presupuesto presupuesto = presupuestoRepository.getReferenceById(presupuestoId);
|
||||
|
||||
PedidoLinea linea = new PedidoLinea();
|
||||
linea.setPedido(saved);
|
||||
linea.setPresupuesto(presupuesto);
|
||||
linea.setCreatedBy(userId);
|
||||
linea.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
pedidoLineaRepository.save(linea);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
}
|
||||
@ -52,6 +52,7 @@ import com.imprimelibros.erp.users.UserDao;
|
||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
|
||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.common.web.IpUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -561,6 +562,20 @@ public class PresupuestoController {
|
||||
return "redirect:/presupuesto";
|
||||
}
|
||||
|
||||
if(presupuestoOpt.get().getEstado() == Presupuesto.Estado.aceptado){
|
||||
|
||||
Map<String, Object> resumen = presupuestoService.getTextosResumen(
|
||||
presupuestoOpt.get(),
|
||||
Utils.decodeJsonList(presupuestoOpt.get().getServiciosJson()),
|
||||
Utils.decodeJsonMap(presupuestoOpt.get().getDatosMaquetacionJson()),
|
||||
Utils.decodeJsonMap(presupuestoOpt.get().getDatosMarcapaginasJson()),
|
||||
locale);
|
||||
|
||||
model.addAttribute("resumen", resumen);
|
||||
model.addAttribute("presupuesto", presupuestoOpt.get());
|
||||
return "imprimelibros/presupuestos/presupuestador-view";
|
||||
}
|
||||
|
||||
if (!presupuestoService.canAccessPresupuesto(presupuestoOpt.get(), authentication)) {
|
||||
// Añadir mensaje flash para mostrar alerta
|
||||
redirectAttributes.addFlashAttribute("errorMessage",
|
||||
@ -573,7 +588,7 @@ public class PresupuestoController {
|
||||
String path = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
|
||||
.getRequest().getRequestURI();
|
||||
String mode = path.contains("/view/") ? "view" : "edit";
|
||||
if (mode.equals("view")) {
|
||||
if (mode.equals("view") || presupuestoOpt.get().getEstado() != Presupuesto.Estado.borrador) {
|
||||
model.addAttribute("appMode", "view");
|
||||
} else {
|
||||
model.addAttribute("cliente_id", presupuestoOpt.get().getUser().getId());
|
||||
|
||||
@ -120,7 +120,7 @@ public class PresupuestoDatatableService {
|
||||
String id = String.valueOf(p.getId());
|
||||
String editBtn = "<a href=\"javascript:void(0);\" data-id=\"" + id + "\" class=\"link-success btn-edit-" +
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) ? "anonimo" : "privado") + " fs-15\"><i class=\"ri-" +
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) ? "eye" : "pencil") + "-line\"></i></a>";
|
||||
(p.getOrigen().equals(Presupuesto.Origen.publico) || p.getEstado() == Presupuesto.Estado.aceptado ? "eye" : "pencil") + "-line\"></i></a>";
|
||||
|
||||
String deleteBtn = borrador ? "<a href=\"javascript:void(0);\" data-id=\"" + id
|
||||
+ "\" class=\"link-danger btn-delete-"
|
||||
|
||||
@ -99,23 +99,27 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
||||
modificado("presupuesto.estado.modificado");
|
||||
|
||||
private final String messageKey;
|
||||
|
||||
Estado(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Entrega{
|
||||
public enum Entrega {
|
||||
peninsula("presupuesto.entrega.peninsula"),
|
||||
canarias("presupuesto.entrega.canarias"),
|
||||
paises_ue("presupuesto.entrega.paises-ue");
|
||||
|
||||
private final String messageKey;
|
||||
|
||||
Entrega(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return messageKey;
|
||||
}
|
||||
@ -371,6 +375,18 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
||||
@Column(name = "alto_faja")
|
||||
private Integer altoFaja = 0;
|
||||
|
||||
@Column(name = "comentario", columnDefinition = "TEXT")
|
||||
private String comentario;
|
||||
|
||||
@Column(name = "proveedor", length = 100)
|
||||
private String proveedor;
|
||||
|
||||
@Column(name = "proveedor_ref1", length = 100)
|
||||
private String proveedorRef1;
|
||||
|
||||
@Column(name = "proveedor_ref2")
|
||||
private Long proveedorRef2;
|
||||
|
||||
// ====== MÉTODOS AUX ======
|
||||
|
||||
public String resumenPresupuesto() {
|
||||
@ -912,16 +928,48 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
||||
this.altoFaja = altoFaja;
|
||||
}
|
||||
|
||||
public Long getId(){
|
||||
public Long getId() {
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id){
|
||||
public String getComentario() {
|
||||
return comentario;
|
||||
}
|
||||
|
||||
public void setComentario(String comentario) {
|
||||
this.comentario = comentario;
|
||||
}
|
||||
|
||||
public String getProveedor() {
|
||||
return proveedor;
|
||||
}
|
||||
|
||||
public void setProveedor(String proveedor) {
|
||||
this.proveedor = proveedor;
|
||||
}
|
||||
|
||||
public String getProveedorRef1() {
|
||||
return proveedorRef1;
|
||||
}
|
||||
|
||||
public void setProveedorRef1(String proveedorRef1) {
|
||||
this.proveedorRef1 = proveedorRef1;
|
||||
}
|
||||
|
||||
public Long getProveedorRef2() {
|
||||
return proveedorRef2;
|
||||
}
|
||||
|
||||
public void setProveedorRef2(Long proveedorRef2) {
|
||||
this.proveedorRef2 = proveedorRef2;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Double getPeso(){
|
||||
public Double getPeso() {
|
||||
// get peso from first element of pricingSnapshotJson (need to parse JSON)
|
||||
// pricingSnapshotJson = {"xxx":{"peso":0.5,...}} is a String
|
||||
if (this.pricingSnapshotJson != null && !this.pricingSnapshotJson.isEmpty()) {
|
||||
|
||||
@ -290,6 +290,10 @@ public class PresupuestoService {
|
||||
}
|
||||
|
||||
public Map<String, Object> toSkApiRequest(Presupuesto presupuesto) {
|
||||
return toSkApiRequest(presupuesto, false);
|
||||
}
|
||||
|
||||
public Map<String, Object> toSkApiRequest(Presupuesto presupuesto, Boolean toSave) {
|
||||
final int SK_CLIENTE_ID = 1284;
|
||||
final int SK_PAGINAS_CUADERNILLO = 32;
|
||||
|
||||
@ -311,9 +315,16 @@ public class PresupuestoService {
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("tipo_impresion_id", this.getTipoImpresionId(presupuesto));
|
||||
body.put("tirada", Arrays.stream(presupuesto.getTiradas())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
if (toSave) {
|
||||
body.put("tirada", Arrays.stream(presupuesto.getTiradas())
|
||||
.filter(Objects::nonNull)
|
||||
.map(tirada -> tirada + 4)
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
body.put("tirada", Arrays.stream(presupuesto.getTiradas())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
body.put("tamanio", tamanio);
|
||||
body.put("tipo", presupuesto.getTipoEncuadernacion());
|
||||
body.put("clienteId", SK_CLIENTE_ID);
|
||||
@ -343,9 +354,39 @@ public class PresupuestoService {
|
||||
faja.put("alto", presupuesto.getAltoFaja());
|
||||
body.put("faja", faja);
|
||||
}
|
||||
// body.put("servicios", servicios);
|
||||
|
||||
if (toSave) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("input_data", body);
|
||||
data.put("ferroDigital", 1);
|
||||
data.put("ferro", 0);
|
||||
data.put("marcapaginas", 0);
|
||||
data.put("retractilado5", 0);
|
||||
if (presupuesto.getServiciosJson() != null
|
||||
&& presupuesto.getServiciosJson().indexOf("ejemplar-prueba") > 0) {
|
||||
data.put("prototipo", 1);
|
||||
} else {
|
||||
data.put("prototipo", 0);
|
||||
}
|
||||
if (presupuesto.getServiciosJson() != null && presupuesto.getServiciosJson().indexOf("retractilado") > 0) {
|
||||
data.put("retractilado", 1);
|
||||
} else {
|
||||
data.put("retractilado", 0);
|
||||
}
|
||||
data.put("ivaReducido", presupuesto.getIvaReducido() ? 1 : 0);
|
||||
data.put("confirmar", 1);
|
||||
Map<String, Object> datosCabecera = new HashMap<>();
|
||||
datosCabecera.put("titulo", presupuesto.getTitulo());
|
||||
datosCabecera.put("autor", presupuesto.getAutor());
|
||||
datosCabecera.put("isbn", presupuesto.getIsbn());
|
||||
datosCabecera.put("coleccion", "");
|
||||
datosCabecera.put("referenciaCliente", presupuesto.getId());
|
||||
data.put("datosCabecera", datosCabecera);
|
||||
return data;
|
||||
|
||||
}
|
||||
return body;
|
||||
|
||||
}
|
||||
|
||||
public Integer getTipoImpresionId(Presupuesto presupuesto) {
|
||||
@ -599,11 +640,11 @@ public class PresupuestoService {
|
||||
.doubleValue();
|
||||
}
|
||||
|
||||
// precio calculado por matrices * num. cols -1 * precio por columna
|
||||
// precio calculado por matrices * num. cols -1 * precio por columna
|
||||
if (presupuestoMaquetacion.getNumColumnas() > 1) {
|
||||
precio = precio.add(precio.multiply(
|
||||
BigDecimal.valueOf(presupuestoMaquetacion.getNumColumnas() - 1))
|
||||
.multiply(BigDecimal.valueOf(price.apply("columnas"))) );
|
||||
.multiply(BigDecimal.valueOf(price.apply("columnas"))));
|
||||
}
|
||||
|
||||
precio = precio
|
||||
@ -877,6 +918,7 @@ public class PresupuestoService {
|
||||
/ Double.parseDouble(servicio.get("units").toString())
|
||||
: servicio.get("price"));
|
||||
servicioData.put("unidades", servicio.get("units"));
|
||||
servicioData.put("id", servicio.get("id"));
|
||||
serviciosExtras.add(servicioData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,22 @@
|
||||
package com.imprimelibros.erp.redsys;
|
||||
|
||||
import com.imprimelibros.erp.common.Utils;
|
||||
import com.imprimelibros.erp.payments.PaymentService;
|
||||
import com.imprimelibros.erp.payments.model.Payment;
|
||||
import com.imprimelibros.erp.redsys.RedsysService.FormPayload;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.thymeleaf.context.WebContext;
|
||||
import org.thymeleaf.web.IWebExchange;
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -22,37 +35,51 @@ public class RedsysController {
|
||||
|
||||
private final PaymentService paymentService;
|
||||
private final MessageSource messageSource;
|
||||
private final SpringTemplateEngine templateEngine;
|
||||
private final ServletContext servletContext;
|
||||
|
||||
public RedsysController(PaymentService paymentService, MessageSource messageSource) {
|
||||
public RedsysController(PaymentService paymentService, MessageSource messageSource,
|
||||
SpringTemplateEngine templateEngine, ServletContext servletContext) {
|
||||
this.paymentService = paymentService;
|
||||
this.messageSource = messageSource;
|
||||
this.templateEngine = templateEngine;
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<byte[]> crearPago(@RequestParam("amountCents") Long amountCents,
|
||||
@RequestParam("method") String method, @RequestParam("cartId") Long cartId) throws Exception {
|
||||
@RequestParam("method") String method, @RequestParam("cartId") Long cartId,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response, Locale locale)
|
||||
throws Exception {
|
||||
|
||||
if ("bank-transfer".equalsIgnoreCase(method)) {
|
||||
// 1) Creamos el Payment interno SIN orderId (null)
|
||||
Payment p = paymentService.createBankTransferPayment(cartId, amountCents, "EUR");
|
||||
|
||||
// 2) Mostramos instrucciones de transferencia
|
||||
String html = """
|
||||
<html><head><meta charset="utf-8"><title>Pago por transferencia</title></head>
|
||||
<body>
|
||||
<h2>Pago por transferencia bancaria</h2>
|
||||
<p>Hemos registrado tu intención de pedido.</p>
|
||||
<p><strong>Importe:</strong> %s €</p>
|
||||
<p><strong>IBAN:</strong> ES00 1234 5678 9012 3456 7890</p>
|
||||
<p><strong>Concepto:</strong> TRANSF-%d</p>
|
||||
<p>En cuanto recibamos la transferencia, procesaremos tu pedido.</p>
|
||||
<p><a href="/checkout/resumen">Volver al resumen</a></p>
|
||||
</body></html>
|
||||
""".formatted(
|
||||
String.format("%.2f", amountCents / 100.0),
|
||||
p.getId() // usamos el ID del Payment como referencia
|
||||
);
|
||||
// 1️⃣ Crear la "aplicación" web de Thymeleaf (Jakarta)
|
||||
JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext);
|
||||
|
||||
// 2️⃣ Construir el intercambio web desde request/response
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
IWebExchange exchange = app.buildExchange(request, response);
|
||||
|
||||
// 3️⃣ Crear el contexto WebContext con Locale
|
||||
WebContext ctx = new WebContext(exchange, locale);
|
||||
|
||||
String importeFormateado = Utils.formatCurrency(amountCents / 100.0, locale);
|
||||
ctx.setVariable("importe", importeFormateado);
|
||||
ctx.setVariable("concepto", "TRANSF-" + p.getId());
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
boolean isAuth = auth != null
|
||||
&& auth.isAuthenticated()
|
||||
&& !(auth instanceof AnonymousAuthenticationToken);
|
||||
ctx.setVariable("isAuth", isAuth);
|
||||
|
||||
// 3) Renderizamos la plantilla a HTML
|
||||
String html = templateEngine.process("imprimelibros/pagos/transfer", ctx);
|
||||
|
||||
byte[] body = html.getBytes(StandardCharsets.UTF_8);
|
||||
return ResponseEntity.ok()
|
||||
@ -92,7 +119,8 @@ public class RedsysController {
|
||||
// GET: cuando el usuario cae aquí sin parámetros, o Redsys redirige por GET
|
||||
@GetMapping("/ok")
|
||||
public String okGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||||
String msg = messageSource.getMessage("checkout.success.payment", null, "Pago realizado con éxito. Gracias por su compra.", locale);
|
||||
String msg = messageSource.getMessage("checkout.success.payment", null,
|
||||
"Pago realizado con éxito. Gracias por su compra.", locale);
|
||||
model.addAttribute("successPago", msg);
|
||||
redirectAttrs.addFlashAttribute("successPago", msg);
|
||||
return "redirect:/cart";
|
||||
@ -103,10 +131,10 @@ public class RedsysController {
|
||||
@PostMapping(value = "/ok", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> okPost(@RequestParam("Ds_Signature") String signature,
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||
try {
|
||||
// opcional: idempotente, si /notify ya ha hecho el trabajo no pasa nada
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||||
return ResponseEntity.ok("<h2>Pago realizado correctamente</h2><a href=\"/cart\">Volver</a>");
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest()
|
||||
@ -117,7 +145,9 @@ public class RedsysController {
|
||||
@GetMapping("/ko")
|
||||
public String koGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||||
|
||||
String msg = messageSource.getMessage("checkout.error.payment", null, "Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo.", locale);
|
||||
String msg = messageSource.getMessage("checkout.error.payment", null,
|
||||
"Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo.",
|
||||
locale);
|
||||
model.addAttribute("errorPago", msg);
|
||||
redirectAttrs.addFlashAttribute("errorPago", msg);
|
||||
return "redirect:/cart";
|
||||
@ -127,11 +157,11 @@ public class RedsysController {
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> koPost(
|
||||
@RequestParam("Ds_Signature") String signature,
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||
|
||||
try {
|
||||
// Procesamos la notificación IGUAL que en /ok y /notify
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||||
|
||||
// Mensaje para el usuario (pago cancelado/rechazado)
|
||||
String html = "<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>";
|
||||
@ -146,9 +176,9 @@ public class RedsysController {
|
||||
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||
try {
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||||
return "OK";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
||||
|
||||
@ -1,29 +1,39 @@
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 0009-drop-unique-refund-gateway-id
|
||||
id: 0010-drop-unique-tx-gateway
|
||||
author: JJO
|
||||
changes:
|
||||
# 1️⃣ Eliminar la UNIQUE constraint sobre gateway_refund_id
|
||||
- dropUniqueConstraint:
|
||||
constraintName: uq_refund_gateway_id
|
||||
tableName: refunds
|
||||
|
||||
# 2️⃣ Crear un índice normal (no único) para acelerar búsquedas por gateway_refund_id
|
||||
# ✅ Solo ejecuta el changeSet si existe la UNIQUE constraint
|
||||
preConditions:
|
||||
- onFail: MARK_RAN
|
||||
- uniqueConstraintExists:
|
||||
tableName: payment_transactions
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
changes:
|
||||
# 1️⃣ Eliminar la UNIQUE constraint si existe
|
||||
- dropUniqueConstraint:
|
||||
tableName: payment_transactions
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
# 2️⃣ Crear un índice normal (no único) sobre gateway_transaction_id
|
||||
- createIndex:
|
||||
tableName: refunds
|
||||
indexName: idx_refunds_gateway_refund_id
|
||||
tableName: payment_transactions
|
||||
indexName: idx_payment_tx_gateway_txid
|
||||
columns:
|
||||
- column:
|
||||
name: gateway_refund_id
|
||||
name: gateway_transaction_id
|
||||
|
||||
rollback:
|
||||
# 🔙 1) Eliminar el índice normal creado en este changeSet
|
||||
- dropIndex:
|
||||
indexName: idx_refunds_gateway_refund_id
|
||||
tableName: refunds
|
||||
tableName: payment_transactions
|
||||
indexName: idx_payment_tx_gateway_txid
|
||||
# Si tu versión de Liquibase lo soporta, puedes añadir:
|
||||
# ifExists: true
|
||||
|
||||
# 🔙 2) Restaurar la UNIQUE constraint original
|
||||
- addUniqueConstraint:
|
||||
tableName: refunds
|
||||
columnNames: gateway_refund_id
|
||||
constraintName: uq_refund_gateway_id
|
||||
tableName: payment_transactions
|
||||
columnNames: gateway_transaction_id, type
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
@ -2,14 +2,21 @@ databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 0010-drop-unique-tx-gateway
|
||||
author: JJO
|
||||
changes:
|
||||
# 1️⃣ Eliminar la UNIQUE constraint sobre (gateway_transaction_id, type)
|
||||
- dropUniqueConstraint:
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
# ✅ Solo ejecuta el changeSet si existe la UNIQUE constraint
|
||||
preConditions:
|
||||
- onFail: MARK_RAN
|
||||
- uniqueConstraintExists:
|
||||
tableName: payment_transactions
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
changes:
|
||||
# 1️⃣ Eliminar la UNIQUE constraint si existe
|
||||
- dropUniqueConstraint:
|
||||
tableName: payment_transactions
|
||||
constraintName: uq_tx_gateway_txid_type
|
||||
|
||||
# 2️⃣ Crear un índice normal (no único) sobre gateway_transaction_id
|
||||
# para poder seguir buscando rápido por este campo
|
||||
- createIndex:
|
||||
tableName: payment_transactions
|
||||
indexName: idx_payment_tx_gateway_txid
|
||||
@ -20,8 +27,8 @@ databaseChangeLog:
|
||||
rollback:
|
||||
# 🔙 1) Eliminar el índice normal creado en este changeSet
|
||||
- dropIndex:
|
||||
indexName: idx_payment_tx_gateway_txid
|
||||
tableName: payment_transactions
|
||||
indexName: idx_payment_tx_gateway_txid
|
||||
|
||||
# 🔙 2) Restaurar la UNIQUE constraint original
|
||||
- addUniqueConstraint:
|
||||
|
||||
@ -0,0 +1,210 @@
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 0011-update-pedidos-presupuesto
|
||||
author: jjo
|
||||
|
||||
changes:
|
||||
# 1) Nuevas columnas en PRESUPUESTO
|
||||
- addColumn:
|
||||
tableName: presupuesto
|
||||
columns:
|
||||
- column:
|
||||
name: comentario
|
||||
type: TEXT
|
||||
afterColumn: pricing_snapshot
|
||||
constraints:
|
||||
nullable: true
|
||||
- column:
|
||||
name: proveedor
|
||||
type: VARCHAR(100)
|
||||
constraints:
|
||||
nullable: true
|
||||
- column:
|
||||
name: proveedor_ref1
|
||||
type: VARCHAR(100)
|
||||
constraints:
|
||||
nullable: true
|
||||
- column:
|
||||
name: proveedor_ref2
|
||||
type: BIGINT
|
||||
constraints:
|
||||
nullable: true
|
||||
|
||||
# 2) Cambios en PEDIDOS
|
||||
# 2.1 Eliminar FK fk_pedidos_presupuesto
|
||||
- dropForeignKeyConstraint:
|
||||
baseTableName: pedidos
|
||||
constraintName: fk_pedidos_presupuesto
|
||||
|
||||
# 2.2 Eliminar columna presupuesto_id
|
||||
- dropColumn:
|
||||
tableName: pedidos
|
||||
columnName: presupuesto_id
|
||||
|
||||
# 2.3 Añadir nuevas columnas después de id
|
||||
- addColumn:
|
||||
tableName: pedidos
|
||||
columns:
|
||||
- column:
|
||||
name: base
|
||||
type: DOUBLE
|
||||
afterColumn: id
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: envio
|
||||
type: DOUBLE
|
||||
defaultValueNumeric: 0
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: iva4
|
||||
type: DOUBLE
|
||||
defaultValueNumeric: 0
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: iva21
|
||||
type: DOUBLE
|
||||
defaultValueNumeric: 0
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: descuento
|
||||
type: DOUBLE
|
||||
defaultValueNumeric: 0
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: total
|
||||
type: DOUBLE
|
||||
defaultValueNumeric: 0
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: proveedor
|
||||
type: VARCHAR(100)
|
||||
afterColumn: total
|
||||
constraints:
|
||||
nullable: true
|
||||
- column:
|
||||
name: proveedor_ref
|
||||
type: VARCHAR(100)
|
||||
afterColumn: proveedor
|
||||
constraints:
|
||||
nullable: true
|
||||
|
||||
# 3) Crear tabla PEDIDOS_LINEAS
|
||||
- createTable:
|
||||
tableName: pedidos_lineas
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: BIGINT
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
- column:
|
||||
name: pedido_id
|
||||
type: BIGINT
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: presupuesto_id
|
||||
type: BIGINT
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: created_at
|
||||
type: DATETIME(3)
|
||||
constraints:
|
||||
nullable: true
|
||||
- column:
|
||||
name: created_by
|
||||
type: BIGINT
|
||||
constraints:
|
||||
nullable: false
|
||||
|
||||
# FKs de pedidos_lineas
|
||||
- addForeignKeyConstraint:
|
||||
baseTableName: pedidos_lineas
|
||||
baseColumnNames: pedido_id
|
||||
constraintName: fk_pedidos_lineas_pedido
|
||||
referencedTableName: pedidos
|
||||
referencedColumnNames: id
|
||||
onDelete: RESTRICT
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
baseTableName: pedidos_lineas
|
||||
baseColumnNames: presupuesto_id
|
||||
constraintName: fk_pedidos_lineas_presupuesto
|
||||
referencedTableName: presupuesto
|
||||
referencedColumnNames: id
|
||||
onDelete: RESTRICT
|
||||
|
||||
- addForeignKeyConstraint:
|
||||
baseTableName: pedidos_lineas
|
||||
baseColumnNames: created_by
|
||||
constraintName: fk_pedidos_lineas_created_by_user
|
||||
referencedTableName: users
|
||||
referencedColumnNames: id
|
||||
onDelete: RESTRICT
|
||||
|
||||
rollback:
|
||||
# 3) Eliminar tabla pedidos_lineas y sus FKs
|
||||
- dropTable:
|
||||
tableName: pedidos_lineas
|
||||
|
||||
# 2) Revertir cambios en PEDIDOS
|
||||
- dropColumn:
|
||||
tableName: pedidos
|
||||
columns:
|
||||
- column:
|
||||
name: base
|
||||
- column:
|
||||
name: envio
|
||||
- column:
|
||||
name: iva4
|
||||
- column:
|
||||
name: iva21
|
||||
- column:
|
||||
name: descuento
|
||||
- column:
|
||||
name: total
|
||||
- column:
|
||||
name: proveedor
|
||||
- column:
|
||||
name: proveedor_ref
|
||||
|
||||
# 2.2 Volver a crear presupuesto_id
|
||||
- addColumn:
|
||||
tableName: pedidos
|
||||
columns:
|
||||
- column:
|
||||
name: presupuesto_id
|
||||
type: BIGINT
|
||||
constraints:
|
||||
nullable: true
|
||||
|
||||
# 2.1 Volver a crear la FK fk_pedidos_presupuesto
|
||||
- addForeignKeyConstraint:
|
||||
baseTableName: pedidos
|
||||
baseColumnNames: presupuesto_id
|
||||
constraintName: fk_pedidos_presupuesto
|
||||
referencedTableName: presupuesto
|
||||
referencedColumnNames: id
|
||||
onDelete: RESTRICT
|
||||
|
||||
# 1) Eliminar columnas añadidas en PRESUPUESTO
|
||||
- dropColumn:
|
||||
tableName: presupuesto
|
||||
columns:
|
||||
- column:
|
||||
name: comentario
|
||||
- column:
|
||||
name: proveedor
|
||||
- column:
|
||||
name: proveedor_ref1
|
||||
- column:
|
||||
name: proveedor_ref2
|
||||
@ -18,4 +18,6 @@ databaseChangeLog:
|
||||
- include:
|
||||
file: db/changelog/changesets/0009-add-composite-unique-txid-type.yml
|
||||
- include:
|
||||
file: db/changelog/changesets/0010-drop-unique-tx-gateway.yml
|
||||
file: db/changelog/changesets/0010-drop-unique-tx-gateway.yml
|
||||
- include:
|
||||
file: db/changelog/changesets/0011-update-pedidos-presupuesto.yml
|
||||
@ -7,6 +7,7 @@ app.seleccionar=Seleccionar
|
||||
app.guardar=Guardar
|
||||
app.editar=Editar
|
||||
app.add=Añadir
|
||||
app.back=Volver
|
||||
app.eliminar=Eliminar
|
||||
app.imprimir=Imprimir
|
||||
app.acciones.siguiente=Siguiente
|
||||
|
||||
@ -28,6 +28,9 @@ pagos.transferencia.finalizar.text=¿Estás seguro de que deseas marcar esta tra
|
||||
pagos.transferencia.finalizar.success=Transferencia bancaria marcada como completada con éxito.
|
||||
pagos.transferencia.finalizar.error.general=Error al finalizar la transferencia bancaria
|
||||
|
||||
pagos.transferencia.ok.title=Pago por transferencia bancaria
|
||||
pagos.transferencia.ok.text=Ha realizado su pedido correctamente. Para completar el pago, realice una transferencia bancaria con los siguientes datos:<br>Titular de la cuenta: Impresión Imprime Libros SL<br>IBAN: ES00 1234 5678 9012 3456 7890<br>Importe: {0}<br>Concepto: {1}<br>Le rogamos que nos envíe el justificante de la transferencia respondiendo al correo de confirmación de pedido que le acabamos de enviar.<br>Si no encuentra el mensaje, por favor revise la carpeta de correo no deseado y añada <a href="mailto:contacto@imprimelibros.com">contacto@imprimelibros.com</a>
|
||||
|
||||
pagos.refund.title=Devolución
|
||||
pagos.refund.text=Introduce la cantidad a devolver (en euros):
|
||||
pagos.refund.success=Devolución solicitada con éxito. Si no se refleja inmediatamente, espere unos minutos y actualiza la página.
|
||||
|
||||
@ -12,6 +12,11 @@ $(() => {
|
||||
// remove name from container . direccion-card
|
||||
container.find('.direccion-card input[type="hidden"]').removeAttr('name');
|
||||
|
||||
if (container.find('.direccion-card').length === 0) {
|
||||
// no addresses, no need to submit
|
||||
$("alert-empty").removeClass("d-none");
|
||||
}
|
||||
|
||||
container.find('.direccion-card').each(function (i) {
|
||||
$(this).find('.direccion-id').attr('name', 'direcciones[' + i + '].id');
|
||||
$(this).find('.direccion-cp').attr('name', 'direcciones[' + i + '].cp');
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { formateaMoneda } from "../utils.js";
|
||||
$(() => {
|
||||
|
||||
const locale = $("html").attr("lang") || "es-ES";
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr) {
|
||||
const token = document.querySelector('meta[name="_csrf"]')?.content;
|
||||
const header = document.querySelector('meta[name="_csrf_header"]')?.content;
|
||||
if (token && header) xhr.setRequestHeader(header, token);
|
||||
}
|
||||
});
|
||||
|
||||
$(".moneda").each((index, element) => {
|
||||
const valor = $(element).text().trim();
|
||||
const tr = $(element).closest(".tr");
|
||||
if (tr.data("servicio-id") == "marcapaginas") {
|
||||
$(element).text(formateaMoneda(valor, 4, locale, 'EUR'));
|
||||
}
|
||||
else {
|
||||
$(element).text(formateaMoneda(valor, 2, locale, 'EUR'));
|
||||
}
|
||||
});
|
||||
|
||||
$(".moneda4").each((index, element) => {
|
||||
const valor = $(element).text().trim();
|
||||
$(element).text(formateaMoneda(valor, 4, locale, 'EUR'));
|
||||
|
||||
});
|
||||
|
||||
$('.btn-imprimir').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// obtén el id de donde lo tengas (data-attr o variable global)
|
||||
const id = $('#presupuesto-id').val();
|
||||
|
||||
const url = `/api/pdf/presupuesto/${id}?mode=download`;
|
||||
|
||||
// Truco: crear <a> y hacer click
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_self'; // descarga en la misma pestaña
|
||||
// a.download = `presupuesto-${id}.pdf`; // opcional, tu server ya pone filename
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
|
||||
$('.add-cart-btn').on('click', async () => {
|
||||
|
||||
const presupuestoId = $('#presupuesto-id').val();
|
||||
const res = await $.ajax({
|
||||
url: `/cart/add/${presupuestoId}`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
// Si el backend devuelve { redirect: "/cart" }
|
||||
if (res?.redirect) {
|
||||
window.location.assign(res.redirect); // o replace()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@ -37,10 +37,8 @@
|
||||
</div>
|
||||
|
||||
<div th:if="${successPago}" class="alert alert-success alert-fadeout my-1" role="alert" th:text="${successPago}"></div>
|
||||
|
||||
<div th:if="${items.isEmpty()}">
|
||||
<div id="alert-empty"class="alert alert-info" role="alert" th:text="#{cart.empty}"></div>
|
||||
</div>
|
||||
|
||||
<div id="alert-empty" th:class="'alert alert-info ' + ${items.isEmpty() ? '' : 'd-none'}" role="alert" th:text="#{cart.empty}"></div>
|
||||
|
||||
<div th:insert="~{imprimelibros/cart/_cartContent :: cartContent(${items}, ${cartId})}"></div>
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
<html th:lang="${#locale.country != '' ? #locale.language + '-' + #locale.country : #locale.language}"
|
||||
th:with="isAuth=${#authorization.expression('isAuthenticated()')}"
|
||||
<html th:lang="${#locale.country != '' ? #locale.language + '-' + #locale.country : #locale.language}" th:with="isAuth=${isAuth != null
|
||||
? isAuth
|
||||
: (#authorization == null ? false : #authorization.expression('isAuthenticated()'))}"
|
||||
th:attrappend="data-layout=${isAuth} ? 'semibox' : 'horizontal'" data-sidebar-visibility="show" data-topbar="light"
|
||||
data-sidebar="light" data-sidebar-size="lg" data-sidebar-image="none" data-preloader="disable"
|
||||
xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head>
|
||||
<meta name="_csrf" th:content="${_csrf.token}" />
|
||||
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
|
||||
<meta name="_csrf" th:content="${_csrf != null ? _csrf.token : ''}" />
|
||||
<meta name="_csrf_header" th:content="${_csrf != null ? _csrf.headerName : ''}" />
|
||||
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
@ -18,10 +19,11 @@
|
||||
|
||||
<body>
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:if="${isAuth}">
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
|
||||
</div>
|
||||
|
||||
|
||||
<section class="main-content">
|
||||
<div class="page-content">
|
||||
<div class="container-fluid">
|
||||
@ -39,10 +41,11 @@
|
||||
<th:block layout:fragment="pagejs" />
|
||||
<script th:src="@{/assets/js/app.js}"></script>
|
||||
<script th:src="@{/assets/js/pages/imprimelibros/languageBundle.js}"></script>
|
||||
<th:block th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<th:block th:if="${isAuth}">
|
||||
<script src="/assets/js/pages/imprimelibros/cart-badge.js"></script>
|
||||
</th:block>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -0,0 +1,73 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{imprimelibros/layout}">
|
||||
|
||||
<head>
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link href="/assets/libs/datatables/dataTables.bootstrap5.min.css" rel="stylesheet" />
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
|
||||
sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${isAuth}">
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row" id="card">
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<h3 th:text="#{pagos.transferencia.ok.title}"></h3>
|
||||
<span th:utext="#{pagos.transferencia.ok.text(${importe}, ${concepto})}"></span>
|
||||
<div class="d-flex flex-wrap justify-content-center">
|
||||
<a th:href="@{/}" class="btn btn-secondary mt-3">
|
||||
<i class="ri-home-5-fill me-1"></i>
|
||||
<span th:text="#{app.back}">Volver</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end row-->
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||
<th:block layout:fragment="pagejs">
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
</script>
|
||||
|
||||
<!-- JS de Buttons y dependencias -->
|
||||
<div th:if="${appMode} == 'view'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-publicos.js}"></script>
|
||||
</div>
|
||||
<div th:if="${appMode} == 'edit'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-privado.js}"></script>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script>
|
||||
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}"
|
||||
<div th:if="${isAuth}"
|
||||
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">
|
||||
@ -80,7 +80,7 @@
|
||||
|
||||
|
||||
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:if="${isAuth}">
|
||||
<div class="dropdown ms-sm-3 header-item topbar-user">
|
||||
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
@ -114,9 +114,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Si NO está autenticado -->
|
||||
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
||||
<div th:unless="${isAuth}">
|
||||
<a href="/login" class="btn btn-outline-primary ms-sm-3">
|
||||
<i class="mdi mdi-login"></i> <label th:text="#{login.login}">Iniciar sesión</p>
|
||||
<i class="mdi mdi-login"></i> <label th:text="#{login.login}">Iniciar sesión</label>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@ -0,0 +1,196 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{imprimelibros/layout}">
|
||||
|
||||
<head>
|
||||
<th:block layout:fragment="pagetitle" />
|
||||
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||
</th:block>
|
||||
<th:block layout:fragment="pagecss">
|
||||
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet" />
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
|
||||
sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
<li class="breadcrumb-item"><a href="/presupuesto" th:text="#{presupuesto.title}"></a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" th:if="${appMode == 'add'}"
|
||||
th:text="#{presupuesto.add}">
|
||||
Nuevo presupuesto
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page" th:text="#{presupuesto.editar.title}"
|
||||
th:if="${appMode == 'edit'}">
|
||||
Editar presupuesto
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<input type="hidden" id="presupuesto-id" th:value="${presupuesto.id}" />
|
||||
|
||||
<div class="row" id="card presupuesto-row animate-fadeInUpBounce">
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title mb-0 text-uppercase" th:text="${resumen.titulo}">Resumen del
|
||||
presupuesto</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="card col-12 col-sm-9 mx-auto">
|
||||
<h5 id="resumen-titulo" class="text-center"></h5>
|
||||
<table id="resumen-tabla-final" class="table table-borderless table-striped mt-3"
|
||||
th:data-currency="#{app.currency}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th th:text="#{presupuesto.resumen.tabla.descripcion}">Descripción
|
||||
</th>
|
||||
<th class="text-end" th:text="#{presupuesto.resumen.tabla.cantidad}">
|
||||
Cantidad</th>
|
||||
<th class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.precio-unidad}">Precio
|
||||
unitario
|
||||
</th>
|
||||
<th class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.precio-total}">Precio total
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${resumen['linea0']}">
|
||||
<td><img style="max-width: 60px; height: auto;" th:src="${resumen['imagen']}" th:alt="${resumen['imagen_alt']}" class="img-fluid" /></td>
|
||||
<td class="text-start" th:utext="${resumen['linea0'].descripcion}">
|
||||
Descripción 1</td>
|
||||
<td class="text-end" th:text="${resumen['linea0'].cantidad}">1</td>
|
||||
<td class="text-end moneda4"
|
||||
th:text="${resumen['linea0'].precio_unitario}">
|
||||
100,00 €
|
||||
</td>
|
||||
<td class="text-end moneda" th:text="${resumen['linea0'].precio_total}">
|
||||
100,00
|
||||
€
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${resumen['linea1']}">
|
||||
<td></td>
|
||||
<td class="text-start" th:utext="${resumen['linea1'].descripcion}">
|
||||
Descripción 2</td>
|
||||
<td class="text-end" th:text="${resumen['linea1'].cantidad}">1</td>
|
||||
<td class="text-end moneda4"
|
||||
th:text="${resumen['linea1'].precio_unitario}">
|
||||
50,00 €
|
||||
</td>
|
||||
<td class="text-end moneda" th:text="${resumen['linea1'].precio_total}">
|
||||
50,00 €
|
||||
</td>
|
||||
</tr>
|
||||
<th:block th:each="servicio :${resumen['servicios']}">
|
||||
<tr th:attr="data-servicio-id=${servicio['id']}">
|
||||
<td></td>
|
||||
<td class="text-start" th:utext="${servicio['descripcion']}">
|
||||
Descripción 3</td>
|
||||
<td class="text-end" th:text="${servicio['unidades']}">1</td>
|
||||
<td class="text-end moneda" th:text="${servicio['precio']}">
|
||||
25,00 €
|
||||
</td>
|
||||
<td class="text-end moneda"
|
||||
th:text="${servicio['precio'] * servicio['unidades']}">
|
||||
25,00 €
|
||||
</td>
|
||||
</tr>
|
||||
</th:block>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.base}">Total</th>
|
||||
<th class="text-end moneda" id="resumen-base"
|
||||
th:text="${presupuesto.baseImponible}">0,00 €</th>
|
||||
</tr>
|
||||
<tr th:if="${presupuesto.ivaImporte4 > 0}" id="tr-resumen-iva4"
|
||||
class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.iva4}">IVA (4%)</th>
|
||||
<th class="text-end moneda" id="resumen-iva4"
|
||||
th:text="${presupuesto.ivaImporte4}">0,00 €</th>
|
||||
</tr>
|
||||
<tr th:if="${presupuesto.ivaImporte21 > 0}" id="tr-resumen-iva21"
|
||||
class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.iva21}">IVA (21%)</th>
|
||||
<th class="text-end moneda" id="resumen-iva21"
|
||||
th:text="${presupuesto.ivaImporte21}">0,00 €</th>
|
||||
</tr>
|
||||
<tr class="table-active">
|
||||
<th colspan="4" class="text-end"
|
||||
th:text="#{presupuesto.resumen.tabla.total}">Total con IVA
|
||||
</th>
|
||||
<th class="text-end moneda" id="resumen-total"
|
||||
th:text="${presupuesto.totalConIva}">0,00 €</th>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="buttons-row center">
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 btn-imprimir">
|
||||
<i class="ri-printer-line me-2"></i>
|
||||
<span th:text="#{app.imprimir}">Imprimir</span>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||
<i class="ri-shopping-cart-line me-2"></i>
|
||||
<span th:text="#{presupuesto.add-to-cart}">Añadir a la cesta</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end row-->
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||
<th:block layout:fragment="pagejs">
|
||||
<script th:inline="javascript">
|
||||
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||
</script>
|
||||
|
||||
<!-- JS de Buttons y dependencias -->
|
||||
<div th:if="${appMode} == 'view'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-publicos.js}"></script>
|
||||
</div>
|
||||
<div th:if="${appMode} == 'edit'">
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/wizard-privado.js}"></script>
|
||||
</div>
|
||||
|
||||
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestos/resumen-view.js}"></script>
|
||||
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -0,0 +1,27 @@
|
||||
package com.imprimelibros.erp.presupuesto;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import com.imprimelibros.erp.cart.CartService;
|
||||
|
||||
@SpringBootTest
|
||||
public class savePresupuestosTest {
|
||||
|
||||
@Autowired
|
||||
private CartService cartService;
|
||||
|
||||
@Test
|
||||
void testGuardarPresupuesto() {
|
||||
Locale locale = new Locale("es", "ES");
|
||||
Long resultado = cartService.crearPedido(9L, locale);
|
||||
|
||||
System.out.println("📦 Presupuesto guardado:");
|
||||
System.out.println(resultado);
|
||||
|
||||
// Aquí irían las aserciones para verificar que el presupuesto se guardó correctamente
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user