mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-24 09:40:21 +00:00
falta la vista de los presupuestos aceptados
This commit is contained in:
@ -11,6 +11,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||||
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
@ -20,10 +22,10 @@ import com.imprimelibros.erp.cart.dto.DireccionCardDTO;
|
|||||||
import com.imprimelibros.erp.cart.dto.DireccionShipment;
|
import com.imprimelibros.erp.cart.dto.DireccionShipment;
|
||||||
import com.imprimelibros.erp.cart.dto.UpdateCartRequest;
|
import com.imprimelibros.erp.cart.dto.UpdateCartRequest;
|
||||||
import com.imprimelibros.erp.common.Utils;
|
import com.imprimelibros.erp.common.Utils;
|
||||||
import com.imprimelibros.erp.direcciones.Direccion;
|
|
||||||
import com.imprimelibros.erp.direcciones.DireccionService;
|
import com.imprimelibros.erp.direcciones.DireccionService;
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
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;
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -191,14 +193,13 @@ public class CartService {
|
|||||||
return resumen;
|
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 base = 0.0;
|
||||||
double iva4 = 0.0;
|
double iva4 = 0.0;
|
||||||
double iva21 = 0.0;
|
double iva21 = 0.0;
|
||||||
double shipment = 0.0;
|
double shipment = 0.0;
|
||||||
|
boolean errorShipementCost = false;
|
||||||
Boolean errorShipementCost = false;
|
|
||||||
|
|
||||||
List<CartItem> items = cart.getItems();
|
List<CartItem> items = cart.getItems();
|
||||||
List<CartDireccion> direcciones = cart.getDirecciones();
|
List<CartDireccion> direcciones = cart.getDirecciones();
|
||||||
@ -209,28 +210,29 @@ public class CartService {
|
|||||||
base += p.getBaseImponible().doubleValue();
|
base += p.getBaseImponible().doubleValue();
|
||||||
iva4 += p.getIvaImporte4().doubleValue();
|
iva4 += p.getIvaImporte4().doubleValue();
|
||||||
iva21 += p.getIvaImporte21().doubleValue();
|
iva21 += p.getIvaImporte21().doubleValue();
|
||||||
|
|
||||||
if (cart.getOnlyOneShipment() != null && cart.getOnlyOneShipment()) {
|
if (cart.getOnlyOneShipment() != null && cart.getOnlyOneShipment()) {
|
||||||
// Si es envío único, que es a españa y no ha canarias
|
if (direcciones != null && !direcciones.isEmpty()) {
|
||||||
if (direcciones != null && direcciones.size() > 0) {
|
|
||||||
CartDireccion cd = direcciones.get(0);
|
CartDireccion cd = direcciones.get(0);
|
||||||
Boolean freeShipment = direccionService.checkFreeShipment(cd.getDireccion().getCp(),
|
boolean freeShipment = direccionService.checkFreeShipment(
|
||||||
|
cd.getDireccion().getCp(),
|
||||||
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
||||||
|
|
||||||
if (!freeShipment) {
|
if (!freeShipment) {
|
||||||
Integer unidades = p.getSelectedTirada();
|
Integer unidades = p.getSelectedTirada();
|
||||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
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;
|
errorShipementCost = true;
|
||||||
} else {
|
} else {
|
||||||
shipment += (Double) res.get("shipment");
|
shipment += (Double) res.get("shipment");
|
||||||
iva21 += (Double) res.get("iva21");
|
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);
|
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;
|
errorShipementCost = true;
|
||||||
} else {
|
} else {
|
||||||
shipment += (Double) res.get("shipment");
|
shipment += (Double) res.get("shipment");
|
||||||
@ -239,23 +241,27 @@ public class CartService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// envio por cada presupuesto
|
|
||||||
// buscar la direccion asignada a este presupuesto
|
|
||||||
if (direcciones == null)
|
if (direcciones == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
List<CartDireccion> cd_presupuesto = direcciones.stream()
|
List<CartDireccion> cd_presupuesto = direcciones.stream()
|
||||||
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(p.getId())
|
.filter(d -> d.getPresupuesto() != null
|
||||||
&& d.getUnidades() != null && d.getUnidades() != null && d.getUnidades() > 0)
|
&& d.getPresupuesto().getId().equals(p.getId())
|
||||||
|
&& d.getUnidades() != null
|
||||||
|
&& d.getUnidades() > 0)
|
||||||
.toList();
|
.toList();
|
||||||
Boolean firstDirection = true;
|
|
||||||
|
boolean firstDirection = true;
|
||||||
for (CartDireccion cd : cd_presupuesto) {
|
for (CartDireccion cd : cd_presupuesto) {
|
||||||
Integer unidades = cd.getUnidades();
|
Integer unidades = cd.getUnidades();
|
||||||
if (firstDirection) {
|
if (firstDirection) {
|
||||||
Boolean freeShipment = direccionService.checkFreeShipment(cd.getDireccion().getCp(),
|
boolean freeShipment = direccionService.checkFreeShipment(
|
||||||
|
cd.getDireccion().getCp(),
|
||||||
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
cd.getDireccion().getPaisCode3()) && !cd.getIsPalets();
|
||||||
|
|
||||||
if (!freeShipment && unidades != null && unidades > 0) {
|
if (!freeShipment && unidades != null && unidades > 0) {
|
||||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
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;
|
errorShipementCost = true;
|
||||||
} else {
|
} else {
|
||||||
shipment += (Double) res.get("shipment");
|
shipment += (Double) res.get("shipment");
|
||||||
@ -265,7 +271,7 @@ public class CartService {
|
|||||||
firstDirection = false;
|
firstDirection = false;
|
||||||
} else {
|
} else {
|
||||||
Map<String, Object> res = getShippingCost(cd, peso, unidades, locale);
|
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;
|
errorShipementCost = true;
|
||||||
} else {
|
} else {
|
||||||
shipment += (Double) res.get("shipment");
|
shipment += (Double) res.get("shipment");
|
||||||
@ -273,15 +279,17 @@ public class CartService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ejemplar de prueba
|
// ejemplar de prueba
|
||||||
CartDireccion cd_prueba = direcciones.stream()
|
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)
|
&& d.getUnidades() == null)
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
if (cd_prueba != null) {
|
|
||||||
|
|
||||||
|
if (cd_prueba != null) {
|
||||||
Map<String, Object> res = getShippingCost(cd_prueba, peso, 1, locale);
|
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;
|
errorShipementCost = true;
|
||||||
} else {
|
} else {
|
||||||
shipment += (Double) res.get("shipment");
|
shipment += (Double) res.get("shipment");
|
||||||
@ -291,11 +299,45 @@ public class CartService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double total = base + iva4 + iva21 + shipment;
|
double totalBeforeDiscount = base + iva4 + iva21 + shipment;
|
||||||
|
|
||||||
int fidelizacion = pedidoService.getDescuentoFidelizacion();
|
int fidelizacion = pedidoService.getDescuentoFidelizacion();
|
||||||
double descuento = (total) * fidelizacion / 100.0;
|
double descuento = totalBeforeDiscount * fidelizacion / 100.0;
|
||||||
total -= descuento;
|
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<>();
|
Map<String, Object> summary = new HashMap<>();
|
||||||
summary.put("base", Utils.formatCurrency(base, locale));
|
summary.put("base", Utils.formatCurrency(base, locale));
|
||||||
@ -303,11 +345,11 @@ public class CartService {
|
|||||||
summary.put("iva21", Utils.formatCurrency(iva21, locale));
|
summary.put("iva21", Utils.formatCurrency(iva21, locale));
|
||||||
summary.put("shipment", Utils.formatCurrency(shipment, locale));
|
summary.put("shipment", Utils.formatCurrency(shipment, locale));
|
||||||
summary.put("fidelizacion", fidelizacion + "%");
|
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("total", Utils.formatCurrency(total, locale));
|
||||||
summary.put("amountCents", Math.round(total * 100));
|
summary.put("amountCents", raw.get("amountCents"));
|
||||||
summary.put("errorShipmentCost", errorShipementCost);
|
summary.put("errorShipmentCost", raw.get("errorShipmentCost"));
|
||||||
summary.put("cartId", cart.getId());
|
summary.put("cartId", raw.get("cartId"));
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
@ -407,12 +449,13 @@ public class CartService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Long crearPedido(Long cartId) {
|
public Long crearPedido(Long cartId, Locale locale) {
|
||||||
|
|
||||||
Cart cart = this.getCartById(cartId);
|
Cart cart = this.getCartById(cartId);
|
||||||
List<CartItem> items = cart.getItems();
|
List<CartItem> items = cart.getItems();
|
||||||
|
|
||||||
List<Map<String, Object>> presupuestoRequests = new ArrayList<>();
|
List<Map<String, Object>> presupuestoRequests = new ArrayList<>();
|
||||||
|
List<Long> presupuestoIds = new ArrayList<>();
|
||||||
|
|
||||||
for (Integer i = 0; i < items.size(); i++) {
|
for (Integer i = 0; i < items.size(); i++) {
|
||||||
CartItem item = items.get(i);
|
CartItem item = items.get(i);
|
||||||
@ -427,9 +470,8 @@ public class CartService {
|
|||||||
if (datosCabecera != null) {
|
if (datosCabecera != null) {
|
||||||
Object tituloOriginal = datosCabecera.get("titulo");
|
Object tituloOriginal = datosCabecera.get("titulo");
|
||||||
datosCabecera.put(
|
datosCabecera.put(
|
||||||
"titulo",
|
"titulo",
|
||||||
"[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : "")
|
"[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : ""));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
|
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
|
||||||
@ -453,24 +495,36 @@ public class CartService {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> dataMap = (Map<String, Object>) dataRaw;
|
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);
|
presupuestoRequests.add(dataMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear el pedido en base a los presupuestos guardados
|
// Crear el pedido en base a los presupuestos guardados
|
||||||
if(presupuestoRequests.isEmpty()) {
|
if (presupuestoRequests.isEmpty()) {
|
||||||
throw new IllegalStateException("No se pudieron guardar los presupuestos en SK.");
|
throw new IllegalStateException("No se pudieron guardar los presupuestos en SK.");
|
||||||
}
|
} else {
|
||||||
else{
|
ArrayList<Long> presupuestoSkIds = new ArrayList<>();
|
||||||
List<Long> presupuestoIds = new ArrayList<>();
|
|
||||||
for (Map<String, Object> presData : presupuestoRequests) {
|
for (Map<String, Object> presData : presupuestoRequests) {
|
||||||
Long presId = ((Number) presData.get("id")).longValue();
|
Long presId = ((Number) presData.get("id")).longValue();
|
||||||
presupuestoIds.add(presId);
|
presupuestoSkIds.add(presId);
|
||||||
}
|
}
|
||||||
Map<String, Object> ids = new HashMap<>();
|
Map<String, Object> ids = new HashMap<>();
|
||||||
ids.put("presupuesto_ids", presupuestoIds);
|
ids.put("presupuesto_ids", presupuestoSkIds);
|
||||||
Long pedidoId = skApiClient.crearPedido(ids);
|
Long pedidoId = skApiClient.crearPedido(ids);
|
||||||
return pedidoId;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +564,7 @@ public class CartService {
|
|||||||
for (CartDireccion cd : cart.getDirecciones()) {
|
for (CartDireccion cd : cart.getDirecciones()) {
|
||||||
|
|
||||||
// direccion de ejemplar de prueba
|
// direccion de ejemplar de prueba
|
||||||
if(cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) {
|
if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cd.getUnidades() == null || cd.getUnidades() <= 0) {
|
if (cd.getUnidades() == null || cd.getUnidades() <= 0) {
|
||||||
@ -535,9 +589,9 @@ public class CartService {
|
|||||||
}
|
}
|
||||||
Map<String, Object> direccionesRet = new HashMap<>();
|
Map<String, Object> direccionesRet = new HashMap<>();
|
||||||
direccionesRet.put("direcciones", direccionesPresupuesto);
|
direccionesRet.put("direcciones", direccionesPresupuesto);
|
||||||
if(!direccionesPrueba.isEmpty())
|
if (!direccionesPrueba.isEmpty())
|
||||||
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
|
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
|
||||||
else{
|
else {
|
||||||
direccionesRet.put("direccionesFP1", new ArrayList<>());
|
direccionesRet.put("direccionesFP1", new ArrayList<>());
|
||||||
}
|
}
|
||||||
return direccionesRet;
|
return direccionesRet;
|
||||||
|
|||||||
@ -47,10 +47,17 @@ public class Utils {
|
|||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double round2(double value) {
|
||||||
|
return BigDecimal.valueOf(value)
|
||||||
|
.setScale(2, RoundingMode.HALF_UP)
|
||||||
|
.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isCurrentUserAdmin() {
|
public static boolean isCurrentUserAdmin() {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
return auth.getAuthorities().stream()
|
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) {
|
public static Long currentUserId(Principal principal) {
|
||||||
|
|||||||
@ -295,11 +295,11 @@ public class PaymentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/transfer/completed/{id}", produces = "application/json")
|
@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;
|
Map<String, Object> response;
|
||||||
try {
|
try {
|
||||||
paymentService.markBankTransferAsCaptured(id);
|
paymentService.markBankTransferAsCaptured(id, locale);
|
||||||
response = Map.of("success", true);
|
response = Map.of("success", true);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import com.imprimelibros.erp.payments.repo.WebhookEventRepository;
|
import com.imprimelibros.erp.payments.repo.WebhookEventRepository;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -82,7 +83,7 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@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
|
// 0) Intentamos parsear la notificación. Si falla, registramos el webhook crudo
|
||||||
// y salimos.
|
// y salimos.
|
||||||
@ -197,7 +198,7 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authorized) {
|
if (authorized) {
|
||||||
processOrder(notif.cartId);
|
processOrder(notif.cartId, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
payRepo.save(p);
|
payRepo.save(p);
|
||||||
@ -317,7 +318,7 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void markBankTransferAsCaptured(Long paymentId) {
|
public void markBankTransferAsCaptured(Long paymentId, Locale locale) {
|
||||||
Payment p = payRepo.findById(paymentId)
|
Payment p = payRepo.findById(paymentId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Payment no encontrado: " + paymentId));
|
.orElseThrow(() -> new IllegalArgumentException("Payment no encontrado: " + paymentId));
|
||||||
|
|
||||||
@ -354,7 +355,7 @@ public class PaymentService {
|
|||||||
|
|
||||||
// 4) Procesar el pedido asociado al carrito (si existe)
|
// 4) Procesar el pedido asociado al carrito (si existe)
|
||||||
if (p.getOrderId() != null) {
|
if (p.getOrderId() != null) {
|
||||||
processOrder(p.getOrderId());
|
processOrder(p.getOrderId(), locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,19 +451,26 @@ public class PaymentService {
|
|||||||
return code >= 0 && code <= 99;
|
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);
|
Cart cart = this.cartService.findById(cartId);
|
||||||
if (cart != null) {
|
if (cart != null) {
|
||||||
// Bloqueamos el carrito
|
// Bloqueamos el carrito
|
||||||
this.cartService.lockCartById(cart.getId());
|
this.cartService.lockCartById(cart.getId());
|
||||||
// Creamos el pedido
|
// Creamos el pedido
|
||||||
this.cartService.crearPedido(cart.getId());
|
Long orderId = this.cartService.crearPedido(cart.getId(), locale);
|
||||||
// order ID es generado dentro de createOrderFromCart donde se marcan los
|
if(orderId == null){
|
||||||
// presupuestos como no editables
|
return false;
|
||||||
// Long orderId =
|
}
|
||||||
// this.cartService.pedidoService.createOrderFromCart(cart.getId(), p.getId());
|
else{
|
||||||
// p.setOrderId(orderId);
|
// envio de correo de confirmacion de pedido podria ir aqui
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
package com.imprimelibros.erp.pedido;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import com.imprimelibros.erp.externalApi.skApiClient;
|
|
||||||
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class PedidoService {
|
|
||||||
|
|
||||||
protected final skApiClient skApiClient;
|
|
||||||
protected final PresupuestoService presupuestoService;
|
|
||||||
|
|
||||||
public PedidoService(skApiClient skApiClient, PresupuestoService presupuestoService) {
|
|
||||||
this.skApiClient = skApiClient;
|
|
||||||
this.presupuestoService = presupuestoService;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -573,7 +573,7 @@ public class PresupuestoController {
|
|||||||
String path = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
|
String path = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
|
||||||
.getRequest().getRequestURI();
|
.getRequest().getRequestURI();
|
||||||
String mode = path.contains("/view/") ? "view" : "edit";
|
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");
|
model.addAttribute("appMode", "view");
|
||||||
} else {
|
} else {
|
||||||
model.addAttribute("cliente_id", presupuestoOpt.get().getUser().getId());
|
model.addAttribute("cliente_id", presupuestoOpt.get().getUser().getId());
|
||||||
|
|||||||
@ -120,7 +120,7 @@ public class PresupuestoDatatableService {
|
|||||||
String id = String.valueOf(p.getId());
|
String id = String.valueOf(p.getId());
|
||||||
String editBtn = "<a href=\"javascript:void(0);\" data-id=\"" + id + "\" class=\"link-success btn-edit-" +
|
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) ? "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
|
String deleteBtn = borrador ? "<a href=\"javascript:void(0);\" data-id=\"" + id
|
||||||
+ "\" class=\"link-danger btn-delete-"
|
+ "\" class=\"link-danger btn-delete-"
|
||||||
|
|||||||
@ -99,23 +99,27 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
|||||||
modificado("presupuesto.estado.modificado");
|
modificado("presupuesto.estado.modificado");
|
||||||
|
|
||||||
private final String messageKey;
|
private final String messageKey;
|
||||||
|
|
||||||
Estado(String messageKey) {
|
Estado(String messageKey) {
|
||||||
this.messageKey = messageKey;
|
this.messageKey = messageKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessageKey() {
|
public String getMessageKey() {
|
||||||
return messageKey;
|
return messageKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Entrega{
|
public enum Entrega {
|
||||||
peninsula("presupuesto.entrega.peninsula"),
|
peninsula("presupuesto.entrega.peninsula"),
|
||||||
canarias("presupuesto.entrega.canarias"),
|
canarias("presupuesto.entrega.canarias"),
|
||||||
paises_ue("presupuesto.entrega.paises-ue");
|
paises_ue("presupuesto.entrega.paises-ue");
|
||||||
|
|
||||||
private final String messageKey;
|
private final String messageKey;
|
||||||
|
|
||||||
Entrega(String messageKey) {
|
Entrega(String messageKey) {
|
||||||
this.messageKey = messageKey;
|
this.messageKey = messageKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessageKey() {
|
public String getMessageKey() {
|
||||||
return messageKey;
|
return messageKey;
|
||||||
}
|
}
|
||||||
@ -371,6 +375,18 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
|||||||
@Column(name = "alto_faja")
|
@Column(name = "alto_faja")
|
||||||
private Integer altoFaja = 0;
|
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 ======
|
// ====== MÉTODOS AUX ======
|
||||||
|
|
||||||
public String resumenPresupuesto() {
|
public String resumenPresupuesto() {
|
||||||
@ -912,16 +928,48 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
|
|||||||
this.altoFaja = altoFaja;
|
this.altoFaja = altoFaja;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getId(){
|
public Long getId() {
|
||||||
|
|
||||||
return id;
|
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;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Double getPeso(){
|
public Double getPeso() {
|
||||||
// get peso from first element of pricingSnapshotJson (need to parse JSON)
|
// get peso from first element of pricingSnapshotJson (need to parse JSON)
|
||||||
// pricingSnapshotJson = {"xxx":{"peso":0.5,...}} is a String
|
// pricingSnapshotJson = {"xxx":{"peso":0.5,...}} is a String
|
||||||
if (this.pricingSnapshotJson != null && !this.pricingSnapshotJson.isEmpty()) {
|
if (this.pricingSnapshotJson != null && !this.pricingSnapshotJson.isEmpty()) {
|
||||||
|
|||||||
@ -103,10 +103,10 @@ public class RedsysController {
|
|||||||
@PostMapping(value = "/ok", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
@PostMapping(value = "/ok", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ResponseEntity<String> okPost(@RequestParam("Ds_Signature") String signature,
|
public ResponseEntity<String> okPost(@RequestParam("Ds_Signature") String signature,
|
||||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||||
try {
|
try {
|
||||||
// opcional: idempotente, si /notify ya ha hecho el trabajo no pasa nada
|
// 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>");
|
return ResponseEntity.ok("<h2>Pago realizado correctamente</h2><a href=\"/cart\">Volver</a>");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
@ -127,11 +127,11 @@ public class RedsysController {
|
|||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ResponseEntity<String> koPost(
|
public ResponseEntity<String> koPost(
|
||||||
@RequestParam("Ds_Signature") String signature,
|
@RequestParam("Ds_Signature") String signature,
|
||||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Procesamos la notificación IGUAL que en /ok y /notify
|
// 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)
|
// Mensaje para el usuario (pago cancelado/rechazado)
|
||||||
String html = "<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>";
|
String html = "<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>";
|
||||||
@ -146,9 +146,9 @@ public class RedsysController {
|
|||||||
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
||||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||||||
try {
|
try {
|
||||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||||||
return "OK";
|
return "OK";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
||||||
|
|||||||
@ -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
|
||||||
@ -19,3 +19,5 @@ databaseChangeLog:
|
|||||||
file: db/changelog/changesets/0009-add-composite-unique-txid-type.yml
|
file: db/changelog/changesets/0009-add-composite-unique-txid-type.yml
|
||||||
- include:
|
- 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
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
<div id="presupuesto-app" th:data-mode="${appMode} ?: 'public'" th:data-id="${id} ?: ''" th:fragment="presupuestador">
|
||||||
|
|
||||||
|
<div class="row" id="presupuesto-row">
|
||||||
|
<div class="animate-fadeInUpBounce">
|
||||||
|
|
||||||
|
<!-- Ribbon Shape -->
|
||||||
|
<div class="card ribbon-box border shadow-none mb-lg-0 material-shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="ribbon ribbon-primary ribbon-shape" th:text="#{presupuesto.resumen}">Resumen
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ribbon-content mt-4">
|
||||||
|
<div id="div-extras" class="hstack gap-2 justify-content-center flex-wrap">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End Ribbon Shape -->
|
||||||
|
|
||||||
|
<div class="col-9 mx-auto mt-4">
|
||||||
|
<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 th:text="#{presupuesto.resumen.tabla.cantidad}">Cantidad</th>
|
||||||
|
<th th:text="#{presupuesto.resumen.tabla.precio-unidad}">Precio unitario</th>
|
||||||
|
<th th:text="#{presupuesto.resumen.tabla.precio-total}">Precio total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="table-active">
|
||||||
|
<th colspan="4" class="text-end" th:text="#{presupuesto.resumen.tabla.base}">Total</th>
|
||||||
|
<th class="text-end" id="resumen-base">0,00 €</th>
|
||||||
|
</tr>
|
||||||
|
<tr 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" id="resumen-iva4">0,00 €</th>
|
||||||
|
</tr>
|
||||||
|
<tr 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" id="resumen-iva21">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" id="resumen-total">0,00 €</th>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-4 w-100">
|
||||||
|
<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>
|
||||||
|
<!--end row-->
|
||||||
|
</div>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package com.imprimelibros.erp.presupuesto;
|
package com.imprimelibros.erp.presupuesto;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
@ -14,7 +16,8 @@ public class savePresupuestosTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGuardarPresupuesto() {
|
void testGuardarPresupuesto() {
|
||||||
Long resultado = cartService.crearPedido(9L);
|
Locale locale = new Locale("es", "ES");
|
||||||
|
Long resultado = cartService.crearPedido(9L, locale);
|
||||||
|
|
||||||
System.out.println("📦 Presupuesto guardado:");
|
System.out.println("📦 Presupuesto guardado:");
|
||||||
System.out.println(resultado);
|
System.out.println(resultado);
|
||||||
|
|||||||
Reference in New Issue
Block a user