Compare commits

..

20 Commits

Author SHA1 Message Date
27eabde40f solucionado problema con cargar solapas de cubierta y formato. También añadido los lomos desde sk al guardar 2026-02-25 21:09:04 +01:00
5286f12d76 trabajando en obtener lomos 2026-02-25 15:08:30 +01:00
355c5b6019 trabajando2 2026-02-24 20:57:51 +01:00
e4c1692ef0 añadido lomo 2026-02-24 14:50:27 +01:00
f3d96361a0 corregidos varios problemas 2026-02-24 14:32:47 +01:00
b7453b1138 trabajando 2026-02-24 13:24:39 +01:00
1c5d501e6d Merge branch 'feat/titulos_en_pedidoslist' into 'main'
Feat/titulos en pedidoslist

See merge request jjimenez/erp-imprimelibros!47
2026-02-15 11:54:13 +00:00
87368c434a hecho 2026-02-15 12:53:47 +01:00
a9cdc6d5d6 hecho 2026-02-15 12:53:19 +01:00
1a04d5ace1 Merge branch 'fix/listado_usuarios_infinito' into 'main'
arreglado, no se estaba mandando paginación

See merge request jjimenez/erp-imprimelibros!46
2026-02-15 10:25:48 +00:00
cc8d1b8d44 arreglado, no se estaba mandando paginación 2026-02-15 11:24:50 +01:00
6121562f9b Merge branch 'mod/order_user_select_by_name' into 'main'
hecho

See merge request jjimenez/erp-imprimelibros!45
2026-02-14 10:50:28 +00:00
f73f108c71 hecho 2026-02-14 11:50:06 +01:00
60080eb89e Merge branch 'fix/problema_dir_fact_multipedido' into 'main'
solucionado

See merge request jjimenez/erp-imprimelibros!44
2026-02-14 10:44:24 +00:00
e9f75b85c5 solucionado 2026-02-14 11:44:05 +01:00
ce6f4085a0 Merge branch 'mod/listados_cartItem' into 'main'
se modifican los item de los listados para que se abran en una nueva pestaña....

See merge request jjimenez/erp-imprimelibros!43
2026-02-14 10:19:32 +00:00
97ee53cefd se modifican los item de los listados para que se abran en una nueva pestaña. Se añade un badge al carrito item para editar el presupuesto 2026-02-14 11:19:07 +01:00
014079cf04 Merge branch 'fix/tipo_entrega' into 'main'
quitado tipo de entrega de todos sitios. Trabajando en el iva dependiendo de...

See merge request jjimenez/erp-imprimelibros!42
2026-02-14 10:06:36 +00:00
8acd0bb890 terminado. Ahora se mira la direcicón de facturación para el iva. modificado, el valor en pedido, factura, presupuesto 2026-02-14 11:06:12 +01:00
fffc2b91c1 quitado tipo de entrega de todos sitios. Trabajando en el iva dependiendo de la dirección de facturación 2026-02-13 21:19:25 +01:00
41 changed files with 688 additions and 203 deletions

View File

@ -80,7 +80,7 @@ public class CartController {
else if (direcciones != null && direcciones.containsKey("direcciones")) else if (direcciones != null && direcciones.containsKey("direcciones"))
model.addAttribute("direcciones", direcciones.get("direcciones")); model.addAttribute("direcciones", direcciones.get("direcciones"));
var summary = service.getCartSummary(cart, locale); var summary = service.getCartSummary(cart, locale, true);
model.addAttribute("cartSummary", summary); model.addAttribute("cartSummary", summary);
if (summary.get("errorShipmentCost") != null && (Boolean) summary.get("errorShipmentCost")) if (summary.get("errorShipmentCost") != null && (Boolean) summary.get("errorShipmentCost"))
model.addAttribute("errorEnvio", true); model.addAttribute("errorEnvio", true);
@ -165,7 +165,7 @@ public class CartController {
try { try {
service.updateCart(id, updateRequest); service.updateCart(id, updateRequest);
var cartSummary = service.getCartSummary(service.getCartById(id), locale); var cartSummary = service.getCartSummary(service.getCartById(id), locale, true);
model.addAttribute("cartSummary", cartSummary); model.addAttribute("cartSummary", cartSummary);
return "imprimelibros/cart/_cartSummary :: cartSummary(summary=${cartSummary})"; return "imprimelibros/cart/_cartSummary :: cartSummary(summary=${cartSummary})";

View File

@ -163,7 +163,7 @@ public class CartService {
return itemRepo.findByCartId(cart.getId()).size(); return itemRepo.findByCartId(cart.getId()).size();
} }
public Map<String, Object> getCartSummaryRaw(Cart cart, Locale locale) { public Map<String, Object> getCartSummaryRaw(Cart cart, Locale locale, Boolean hasTaxes) {
double base = 0.0; double base = 0.0;
double iva4 = 0.0; double iva4 = 0.0;
@ -269,6 +269,11 @@ public class CartService {
} }
} }
if(!hasTaxes) {
iva4 = 0.0;
iva21 = 0.0;
}
double totalBeforeDiscount = base + iva4 + iva21 + shipment; double totalBeforeDiscount = base + iva4 + iva21 + shipment;
int fidelizacion = this.getDescuentoFidelizacion(cart.getUserId()); int fidelizacion = this.getDescuentoFidelizacion(cart.getUserId());
double descuento = totalBeforeDiscount * fidelizacion / 100.0; double descuento = totalBeforeDiscount * fidelizacion / 100.0;
@ -318,8 +323,8 @@ public class CartService {
return 0; return 0;
} }
public Map<String, Object> getCartSummary(Cart cart, Locale locale) { public Map<String, Object> getCartSummary(Cart cart, Locale locale, Boolean hasTaxes) {
Map<String, Object> raw = getCartSummaryRaw(cart, locale); Map<String, Object> raw = getCartSummaryRaw(cart, locale, hasTaxes);
double base = (Double) raw.get("base"); double base = (Double) raw.get("base");
double iva4 = (Double) raw.get("iva4"); double iva4 = (Double) raw.get("iva4");

View File

@ -58,10 +58,28 @@ public class CheckoutController {
Long userId = Utils.currentUserId(principal); Long userId = Utils.currentUserId(principal);
Cart cart = cartService.getOrCreateActiveCart(userId); Cart cart = cartService.getOrCreateActiveCart(userId);
model.addAttribute("summary", cartService.getCartSummary(cart, locale)); model.addAttribute("summary", cartService.getCartSummary(cart, locale, true));
return "imprimelibros/checkout/checkout"; // crea esta vista si quieres (tabla simple) return "imprimelibros/checkout/checkout"; // crea esta vista si quieres (tabla simple)
} }
@GetMapping({ "/get-summary", "/get-summary/{direccionId}/{method}" })
public String getCheckoutSummary(@PathVariable(required = false) Long direccionId,
@PathVariable(required = false) String method, Principal principal, Model model, Locale locale) {
Long userId = Utils.currentUserId(principal);
Cart cart = cartService.getOrCreateActiveCart(userId);
Boolean hasTaxes = true;
if (direccionId != null) {
hasTaxes = direccionService.hasTaxes(direccionId);
}
Map<String, Object> summary = cartService.getCartSummary(cart, locale, hasTaxes);
model.addAttribute("summary", summary);
if (method != null) {
model.addAttribute("method", method);
}
return "imprimelibros/checkout/_summary :: checkoutSummary(summary=${summary})";
}
@GetMapping("/get-address/{id}") @GetMapping("/get-address/{id}")
public String getDireccionCard(@PathVariable Long id, Model model, Locale locale) { public String getDireccionCard(@PathVariable Long id, Model model, Locale locale) {
Direccion dir = direccionService.findById(id) Direccion dir = direccionService.findById(id)

View File

@ -153,4 +153,26 @@ public class DireccionService {
return false; return false;
} }
public Boolean hasTaxes(Long direccionId) {
if(direccionId == null) {
return true; // Si no hay dirección, asumimos que sí tiene impuestos
}
Optional<Direccion> dir = repo.findById(direccionId);
if (dir == null || dir.isEmpty()) {
throw new RuntimeException("Dirección no encontrada");
}
if(dir.get().getPaisCode3().toLowerCase().equals("esp")) {
int provincia = dir.get().getCp() / 1000;
if (provincia == 35 || provincia == 38 ) {
return false; // Canarias (sin IVA)lñ.
}
return true; // España (todas las provincias)
}
else{
// Fuera de España, asumimos que no tiene impuestos (puedes ajustar esto según tus necesidades)
return false;
}
}
} }

View File

@ -0,0 +1,47 @@
package com.imprimelibros.erp.error;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.imprimelibros.erp.common.Utils;
import java.time.ZonedDateTime;
@Controller
public class ErrorPageController implements ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request, Model model) {
Object statusObj = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
Integer statusCode = statusObj != null ? Integer.valueOf(statusObj.toString()) : 500;
HttpStatus status = HttpStatus.resolve(statusCode);
if (status == null) status = HttpStatus.INTERNAL_SERVER_ERROR;
Object message = request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
Object path = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
model.addAttribute("status", status.value());
model.addAttribute("error", status.getReasonPhrase());
model.addAttribute("message", message != null ? message : "");
model.addAttribute("path", path != null ? path : "");
model.addAttribute("timestamp", ZonedDateTime.now());
// Puedes usar esto para cambiar iconos/texto según status
model.addAttribute("is404", status == HttpStatus.NOT_FOUND);
model.addAttribute("is403", status == HttpStatus.FORBIDDEN);
model.addAttribute("is500", status.is5xxServerError());
if(Utils.isCurrentUserAdmin())
// una sola vista para todos los errores
return "imprimelibros/error/error";
else
return "redirect:/"; // redirige a home para usuarios no admin
}
}

View File

@ -128,6 +128,62 @@ public class skApiClient {
}); });
} }
public Map<String, Object> getLomos(Map<String, Object> requestBody) {
try {
String jsonResponse = performWithRetry(() -> {
String url = this.skApiUrl + "api/get-lomos";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authService.getToken()); // token actualizado
Map<String, Object> data = new HashMap<>();
data.put("clienteId", requestBody.get("clienteId"));
data.put("tamanio", requestBody.get("tamanio"));
data.put("tirada", requestBody.get("tirada"));
data.put("paginas", requestBody.get("paginas"));
data.put("paginasColor", requestBody.get("paginasColor"));
data.put("papelInteriorDiferente", 0);
data.put("paginasCuadernillo", requestBody.get("paginasCuadernillo"));
data.put("tipo", requestBody.get("tipo"));
data.put("isColor", requestBody.get("isColor"));
data.put("isHq", requestBody.get("isHq"));
data.put("interior", requestBody.get("interior"));
data.put("cubierta", requestBody.get("cubierta"));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(data, headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class);
return response.getBody();
});
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonResponse);
if (root.get("lomoInterior") == null || !root.get("lomoInterior").isDouble()) {
throw new RuntimeException();
}
Double lomoInterior = root.get("lomoInterior").asDouble();
Double lomoCubierta = root.get("lomoCubierta").asDouble();
return Map.of(
"lomoInterior", lomoInterior,
"lomoCubierta", lomoCubierta);
} catch (JsonProcessingException e) {
// Fallback al 80% del ancho
return Map.of(
"lomoInterior", 0.0,
"lomoCubierta", 0.0);
}
}
public Map<String, Object> savePresupuesto(Map<String, Object> requestBody) { public Map<String, Object> savePresupuesto(Map<String, Object> requestBody) {
return performWithRetryMap(() -> { return performWithRetryMap(() -> {
String url = this.skApiUrl + "api/guardar"; String url = this.skApiUrl + "api/guardar";

View File

@ -8,7 +8,6 @@ import com.imprimelibros.erp.facturacion.dto.FacturaDireccionMapper;
import com.imprimelibros.erp.facturacion.dto.FacturaGuardarDto; import com.imprimelibros.erp.facturacion.dto.FacturaGuardarDto;
import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto; import com.imprimelibros.erp.facturacion.dto.FacturaLineaUpsertDto;
import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto; import com.imprimelibros.erp.facturacion.dto.FacturaPagoUpsertDto;
import com.imprimelibros.erp.facturacion.repo.FacturaDireccionRepository;
import com.imprimelibros.erp.facturacion.repo.FacturaLineaRepository; import com.imprimelibros.erp.facturacion.repo.FacturaLineaRepository;
import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository; import com.imprimelibros.erp.facturacion.repo.FacturaPagoRepository;
import com.imprimelibros.erp.facturacion.repo.FacturaRepository; import com.imprimelibros.erp.facturacion.repo.FacturaRepository;

View File

@ -184,11 +184,14 @@ public class PaymentController {
// Campos ordenables // Campos ordenables
List<String> orderable = List.of( List<String> orderable = List.of(
"transferId", "client",
"transfer_id",
"status", "status",
"amountCents", "amountCents",
"payment.amountRefundedCents", "amountCentsRefund",
"createdAt", "updatedAt"); "payment.orderId",
"createdAt",
"processedAt");
Specification<PaymentTransaction> base = (root, query, cb) -> cb.or( Specification<PaymentTransaction> base = (root, query, cb) -> cb.or(
cb.equal(root.get("status"), PaymentTransactionStatus.pending), cb.equal(root.get("status"), PaymentTransactionStatus.pending),
@ -215,6 +218,26 @@ public class PaymentController {
return DataTable return DataTable
.of(repoPaymentTransaction, PaymentTransaction.class, dt, searchable) .of(repoPaymentTransaction, PaymentTransaction.class, dt, searchable)
.orderable(orderable) .orderable(orderable)
.orderable("client", (root, query, cb) -> {
var sq = query.subquery(String.class);
var u = sq.from(User.class);
sq.select(u.get("fullName"));
sq.where(cb.equal(u.get("id"), root.join("payment").get("userId")));
return sq;
})
.orderable("transfer_id", (root, query, cb) -> {
var orderId = root.join("payment").get("orderId").as(Long.class);
return cb.<Long>selectCase()
.when(cb.isNull(orderId), Long.MAX_VALUE)
.otherwise(orderId);
})
.orderable("payment.orderId", (root, query, cb) -> {
var orderId = root.join("payment").get("orderId").as(Long.class);
return cb.<Long>selectCase()
.when(cb.isNull(orderId), Long.MAX_VALUE)
.otherwise(orderId);
})
.orderable("amountCentsRefund", (root, query, cb) -> root.join("payment").get("amountRefundedCents"))
.add("created_at", pago -> Utils.formatDateTime(pago.getCreatedAt(), locale)) .add("created_at", pago -> Utils.formatDateTime(pago.getCreatedAt(), locale))
.add("processed_at", pago -> Utils.formatDateTime(pago.getProcessedAt(), locale)) .add("processed_at", pago -> Utils.formatDateTime(pago.getProcessedAt(), locale))
.add("client", pago -> { .add("client", pago -> {
@ -237,14 +260,14 @@ public class PaymentController {
return ""; return "";
}) })
.add("order_id", pago -> { .add("order_id", pago -> {
if (pago.getStatus() != PaymentTransactionStatus.pending) { //if (pago.getStatus() != PaymentTransactionStatus.pending) {
if (pago.getPayment() != null && pago.getPayment().getOrderId() != null) { if (pago.getPayment() != null && pago.getPayment().getOrderId() != null) {
return pago.getPayment().getOrderId().toString(); return pago.getPayment().getOrderId().toString();
} else { } else {
return ""; return "";
} }
} //}
return messageSource.getMessage("pagos.transferencia.no-pedido", null, "Pendiente", locale); //return messageSource.getMessage("pagos.transferencia.no-pedido", null, "Pendiente", locale);
}).add("amount_cents", pago -> Utils.formatCurrency(pago.getAmountCents() / 100.0, locale)) }).add("amount_cents", pago -> Utils.formatCurrency(pago.getAmountCents() / 100.0, locale))
.add("amount_cents_refund", pago -> .add("amount_cents_refund", pago ->

View File

@ -13,7 +13,7 @@ public interface PedidoDireccionRepository extends JpaRepository<PedidoDireccion
// Si en tu código sueles trabajar con el objeto: // Si en tu código sueles trabajar con el objeto:
List<PedidoDireccion> findByPedidoLinea(PedidoLinea pedidoLinea); List<PedidoDireccion> findByPedidoLinea(PedidoLinea pedidoLinea);
PedidoDireccion findByPedidoIdAndFacturacionTrue(Long pedidoId); PedidoDireccion findFirstByPedidoIdAndFacturacionTrue(Long pedidoId);
@Query(""" @Query("""
select distinct d select distinct d

View File

@ -14,14 +14,16 @@ public class PedidoLinea {
procesando_pago("pedido.estado.procesando_pago", 2), procesando_pago("pedido.estado.procesando_pago", 2),
denegado_pago("pedido.estado.denegado_pago", 3), denegado_pago("pedido.estado.denegado_pago", 3),
aprobado("pedido.estado.aprobado", 4), aprobado("pedido.estado.aprobado", 4),
maquetacion("pedido.estado.maquetacion", 5), procesando_pedido("pedido.estado.procesando_pedido", 5),
haciendo_ferro("pedido.estado.haciendo_ferro", 6), maquetacion("pedido.estado.maquetacion", 6),
esperando_aceptacion_ferro("pedido.estado.esperando_aceptacion_ferro", 7), haciendo_ferro_digital("pedido.estado.haciendo_ferro_digital", 7),
ferro_cliente("pedido.estado.ferro_cliente", 8), esperando_aceptacion_ferro_digital("pedido.estado.esperando_aceptacion_ferro_digital", 8),
produccion("pedido.estado.produccion", 9), haciendo_ferro("pedido.estado.haciendo_ferro", 9),
terminado("pedido.estado.terminado", 10), esperando_aceptacion_ferro("pedido.estado.esperando_aceptacion_ferro", 10),
enviado("pedido.estado.enviado", 11), produccion("pedido.estado.produccion", 11),
cancelado("pedido.estado.cancelado", 12); terminado("pedido.estado.terminado", 12),
enviado("pedido.estado.enviado", 13),
cancelado("pedido.estado.cancelado", 14);
private final String messageKey; private final String messageKey;
private final int priority; private final int priority;

View File

@ -1,5 +1,6 @@
package com.imprimelibros.erp.pedidos; package com.imprimelibros.erp.pedidos;
import java.math.BigDecimal;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -68,7 +69,7 @@ public class PedidoService {
} }
public PedidoDireccion getPedidoDireccionFacturacionByPedidoId(Long pedidoId) { public PedidoDireccion getPedidoDireccionFacturacionByPedidoId(Long pedidoId) {
return pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId); return pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
} }
@Transactional @Transactional
@ -81,7 +82,8 @@ public class PedidoService {
Pedido pedido = new Pedido(); Pedido pedido = new Pedido();
Cart cart = cartService.getCartById(cartId); Cart cart = cartService.getCartById(cartId);
Map<String, Object> cartSummaryRaw = cartService.getCartSummaryRaw(cart, Locale.getDefault()); Boolean hasTaxes = direccionService.hasTaxes(direccionFacturacionId);
Map<String, Object> cartSummaryRaw = cartService.getCartSummaryRaw(cart, Locale.getDefault(), hasTaxes);
// Datos económicos (ojo con las claves, son las del summaryRaw) // Datos económicos (ojo con las claves, son las del summaryRaw)
pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d)); pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d));
@ -137,6 +139,11 @@ public class PedidoService {
Presupuesto p = presupuestoRepository.findById(pCart.getId()) Presupuesto p = presupuestoRepository.findById(pCart.getId())
.orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId())); .orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId()));
p.setEstado(Presupuesto.Estado.aceptado); p.setEstado(Presupuesto.Estado.aceptado);
if(!hasTaxes){
p.setIvaImporte21(BigDecimal.ZERO);
p.setIvaImporte4(BigDecimal.ZERO);
p.setTotalConIva(p.getBaseImponible());
}
presupuestoRepository.save(p); presupuestoRepository.save(p);
PedidoLinea linea = new PedidoLinea(); PedidoLinea linea = new PedidoLinea();
@ -150,9 +157,12 @@ public class PedidoService {
// Guardar las direcciones asociadas a la línea del pedido // Guardar las direcciones asociadas a la línea del pedido
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p); Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
saveDireccionesPedidoLinea(direcciones_presupuesto, pedidoGuardado, linea, direccionFacturacionId); saveDireccionesPedidoLinea(direcciones_presupuesto, pedidoGuardado, linea);
} }
if(direccionFacturacionId != null && pedidoGuardado != null && pedidoGuardado.getId() != null){
saveDireccionFacturacionPedido(pedidoGuardado, direccionFacturacionId);
}
return pedidoGuardado; return pedidoGuardado;
} }
@ -196,7 +206,7 @@ public class PedidoService {
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null); Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if (pedido != null) { if (pedido != null) {
PedidoDireccion direccionPedido = pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId); PedidoDireccion direccionPedido = pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
if (direccionPedido == null) { if (direccionPedido == null) {
// crear // crear
@ -243,7 +253,7 @@ public class PedidoService {
} }
public PedidoDireccion getDireccionFacturacionPedido(Long pedidoId) { public PedidoDireccion getDireccionFacturacionPedido(Long pedidoId) {
return pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId); return pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
} }
public List<PedidoDireccion> getDireccionesEntregaPedidoLinea(Long pedidoLineaId) { public List<PedidoDireccion> getDireccionesEntregaPedidoLinea(Long pedidoLineaId) {
@ -261,7 +271,7 @@ public class PedidoService {
Integer counter = 1; Integer counter = 1;
for (PedidoLinea linea : lineas) { for (PedidoLinea linea : lineas) {
if (linea.getEstado() == Estado.pendiente_pago if (linea.getEstado() == Estado.pendiente_pago
|| linea.getEstado() == Estado.denegado_pago) { || linea.getEstado() == Estado.denegado_pago || linea.getEstado() == Estado.procesando_pago) {
Presupuesto presupuesto = linea.getPresupuesto(); Presupuesto presupuesto = linea.getPresupuesto();
linea.setEstado(getEstadoInicial(presupuesto)); linea.setEstado(getEstadoInicial(presupuesto));
@ -301,6 +311,7 @@ public class PedidoService {
return true; return true;
} }
@Transactional
public Map<String, Object> actualizarEstado(Long pedidoLineaId, Locale locale) { public Map<String, Object> actualizarEstado(Long pedidoLineaId, Locale locale) {
PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null); PedidoLinea pedidoLinea = pedidoLineaRepository.findById(pedidoLineaId).orElse(null);
@ -317,8 +328,8 @@ public class PedidoService {
"message", messageSource.getMessage("pedido.errors.cannot-update", null, locale)); "message", messageSource.getMessage("pedido.errors.cannot-update", null, locale));
} }
// Rango: >= haciendo_ferro y < enviado // Rango: >= procesando_pedido y < enviado
if (estadoOld.getPriority() < PedidoLinea.Estado.haciendo_ferro.getPriority() if (estadoOld.getPriority() < PedidoLinea.Estado.procesando_pedido.getPriority()
|| estadoOld.getPriority() >= PedidoLinea.Estado.enviado.getPriority()) { || estadoOld.getPriority() >= PedidoLinea.Estado.enviado.getPriority()) {
return Map.of( return Map.of(
"success", false, "success", false,
@ -645,7 +656,7 @@ public class PedidoService {
private void saveDireccionesPedidoLinea( private void saveDireccionesPedidoLinea(
Map<String, Object> direcciones, Map<String, Object> direcciones,
Pedido pedido, Pedido pedido,
PedidoLinea linea, Long direccionFacturacionId) { PedidoLinea linea) {
String email = pedido.getCreatedBy().getUserName(); String email = pedido.getCreatedBy().getUserName();
@ -692,7 +703,12 @@ public class PedidoService {
pedidoDireccionRepository.save(direccion); pedidoDireccionRepository.save(direccion);
} }
} }
}
private void saveDireccionFacturacionPedido(Pedido pedido, Long direccionFacturacionId) {
if (direccionFacturacionId != null) { if (direccionFacturacionId != null) {
String email = pedido.getCreatedBy().getUserName();
Direccion dirFact = direccionService.findById(direccionFacturacionId).orElse(null); Direccion dirFact = direccionService.findById(direccionFacturacionId).orElse(null);
if (dirFact != null) { if (dirFact != null) {
HashMap<String, Object> dirFactMap = new HashMap<>(); HashMap<String, Object> dirFactMap = new HashMap<>();
@ -713,7 +729,7 @@ public class PedidoService {
false, false,
dirFactMap, dirFactMap,
pedido, pedido,
linea, null,
false, false,
true); true);
pedidoDireccionRepository.save(direccion); pedidoDireccionRepository.save(direccion);
@ -773,7 +789,7 @@ public class PedidoService {
if (presupuestoService.hasMaquetacion(p)) { if (presupuestoService.hasMaquetacion(p)) {
return Estado.maquetacion; return Estado.maquetacion;
} else { } else {
return Estado.haciendo_ferro; return Estado.procesando_pedido;
} }
} }

View File

@ -12,6 +12,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
@ -29,23 +30,22 @@ import com.imprimelibros.erp.datatables.DataTablesResponse;
import com.imprimelibros.erp.facturacion.service.FacturacionService; import com.imprimelibros.erp.facturacion.service.FacturacionService;
import com.imprimelibros.erp.i18n.TranslationService; import com.imprimelibros.erp.i18n.TranslationService;
import com.imprimelibros.erp.paises.PaisesService; import com.imprimelibros.erp.paises.PaisesService;
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
import com.imprimelibros.erp.users.UserDao; import com.imprimelibros.erp.users.UserDao;
import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Subquery;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
@Controller @Controller
@RequestMapping("/pedidos") @RequestMapping("/pedidos")
public class PedidosController { public class PedidosController {
private final PresupuestoService presupuestoService;
private final PedidoRepository repoPedido; private final PedidoRepository repoPedido;
private final PedidoService pedidoService; private final PedidoService pedidoService;
private final UserDao repoUser; private final UserDao repoUser;
@ -58,7 +58,7 @@ public class PedidosController {
public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser, public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser,
MessageSource messageSource, TranslationService translationService, MessageSource messageSource, TranslationService translationService,
PedidoLineaRepository repoPedidoLinea, PaisesService paisesService, PedidoLineaRepository repoPedidoLinea, PaisesService paisesService,
FacturacionService facturacionService, PresupuestoService presupuestoService) { FacturacionService facturacionService) {
this.repoPedido = repoPedido; this.repoPedido = repoPedido;
this.pedidoService = pedidoService; this.pedidoService = pedidoService;
this.repoUser = repoUser; this.repoUser = repoUser;
@ -67,7 +67,6 @@ public class PedidosController {
this.repoPedidoLinea = repoPedidoLinea; this.repoPedidoLinea = repoPedidoLinea;
this.paisesService = paisesService; this.paisesService = paisesService;
this.facturacionService = facturacionService; this.facturacionService = facturacionService;
this.presupuestoService = presupuestoService;
} }
@GetMapping @GetMapping
@ -111,6 +110,7 @@ public class PedidosController {
"id", "id",
"createdBy.fullName", "createdBy.fullName",
"createdAt", "createdAt",
"titulos",
"total", "total",
"estado"); "estado");
@ -118,8 +118,10 @@ public class PedidosController {
if (!isAdmin) { if (!isAdmin) {
base = base.and((root, query, cb) -> cb.equal(root.get("createdBy").get("id"), currentUserId)); base = base.and((root, query, cb) -> cb.equal(root.get("createdBy").get("id"), currentUserId));
} }
String clientSearch = dt.getColumnSearch("cliente"); String clientSearch = dt.getColumnSearch("cliente");
String estadoSearch = dt.getColumnSearch("estado"); String estadoSearch = dt.getColumnSearch("estado");
String titulosSearch = dt.getColumnSearch("titulos");
// 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification // 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification
if (clientSearch != null) { if (clientSearch != null) {
@ -151,11 +153,45 @@ public class PedidosController {
base = base.and((root, query, cb) -> cb.disjunction()); base = base.and((root, query, cb) -> cb.disjunction());
} }
} }
if (titulosSearch != null && !titulosSearch.isBlank()) {
String like = "%" + titulosSearch.trim().toLowerCase() + "%";
base = base.and((root, query, cb) -> {
Subquery<Long> minLineaIdSq = query.subquery(Long.class);
var plMin = minLineaIdSq.from(PedidoLinea.class);
minLineaIdSq.select(cb.min(plMin.get("id")));
minLineaIdSq.where(cb.equal(plMin.get("pedido"), root));
Subquery<String> firstTitleSq = query.subquery(String.class);
var plFirst = firstTitleSq.from(PedidoLinea.class);
var prFirst = plFirst.join("presupuesto", JoinType.LEFT);
firstTitleSq.select(cb.lower(cb.coalesce(prFirst.get("titulo"), "")));
firstTitleSq.where(
cb.equal(plFirst.get("pedido"), root),
cb.equal(plFirst.get("id"), minLineaIdSq));
return cb.like(firstTitleSq, like);
});
}
Long total = repoPedido.count(base); Long total = repoPedido.count(base);
return DataTable return DataTable
.of(repoPedido, Pedido.class, dt, searchable) .of(repoPedido, Pedido.class, dt, searchable)
.orderable(orderable) .orderable(orderable)
.orderable("titulos", (root, query, cb) -> {
Subquery<Long> minLineaIdSq = query.subquery(Long.class);
var plMin = minLineaIdSq.from(PedidoLinea.class);
minLineaIdSq.select(cb.min(plMin.get("id")));
minLineaIdSq.where(cb.equal(plMin.get("pedido"), root));
Subquery<String> firstTitleSq = query.subquery(String.class);
var plFirst = firstTitleSq.from(PedidoLinea.class);
var prFirst = plFirst.join("presupuesto", JoinType.LEFT);
firstTitleSq.select(cb.lower(cb.coalesce(prFirst.get("titulo"), "")));
firstTitleSq.where(
cb.equal(plFirst.get("pedido"), root),
cb.equal(plFirst.get("id"), minLineaIdSq));
return firstTitleSq;
})
.add("id", Pedido::getId) .add("id", Pedido::getId)
.add("created_at", pedido -> Utils.formatInstant(pedido.getCreatedAt(), locale)) .add("created_at", pedido -> Utils.formatInstant(pedido.getCreatedAt(), locale))
.add("cliente", pedido -> { .add("cliente", pedido -> {
@ -171,6 +207,38 @@ public class PedidosController {
return ""; return "";
} }
}) })
.add("titulos", pedido -> {
List<PedidoLinea> lineas = repoPedidoLinea.findByPedidoIdOrderByIdAsc(pedido.getId());
if (lineas.isEmpty()) {
return "";
}
List<Presupuesto> presupuestos = lineas.stream()
.map(PedidoLinea::getPresupuesto)
.filter(presupuesto -> presupuesto != null)
.collect(Collectors.toList());
if (presupuestos.isEmpty()) {
return "";
}
String primerTitulo = presupuestos.get(0).getTitulo();
if (primerTitulo == null) {
primerTitulo = "";
} else {
primerTitulo = primerTitulo.trim();
}
int extras = presupuestos.size() - 1;
if (extras <= 0) {
return primerTitulo;
}
String suffix = messageSource.getMessage(
"pedido.table.titulos.and-more",
new Object[] { extras },
locale);
return primerTitulo + " " + suffix;
})
.add("estado", pedido -> { .add("estado", pedido -> {
List<PedidoLinea> lineas = repoPedidoLinea.findByPedidoId(pedido.getId()); List<PedidoLinea> lineas = repoPedidoLinea.findByPedidoId(pedido.getId());
if (lineas.isEmpty()) { if (lineas.isEmpty()) {

View File

@ -48,12 +48,12 @@ import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto; import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import com.imprimelibros.erp.presupuesto.dto.PresupuestoFormDataMapper;
import com.imprimelibros.erp.presupuesto.dto.PresupuestoFormDataMapper.PresupuestoFormDataDto;
import com.imprimelibros.erp.presupuesto.service.PresupuestoService; import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups; import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
import com.imprimelibros.erp.users.UserDao; import com.imprimelibros.erp.users.UserDao;
import com.imprimelibros.erp.users.UserDetailsImpl; import com.imprimelibros.erp.users.UserDetailsImpl;
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper;
import com.imprimelibros.erp.presupuesto.service.PresupuestoFormDataMapper.PresupuestoFormDataDto;
import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.Utils;
import com.imprimelibros.erp.common.web.IpUtils; import com.imprimelibros.erp.common.web.IpUtils;
@ -908,6 +908,14 @@ public class PresupuestoController {
return ResponseEntity.badRequest().body(errores); return ResponseEntity.badRequest().body(errores);
} }
try{
Map<String, Object> lomos = presupuestoService.obtenerLomos(presupuesto);
presupuesto.setLomo(Double.valueOf(lomos.get("lomoInterior").toString()));
presupuesto.setLomoCubierta(Double.valueOf(lomos.get("lomoCubierta").toString()));
}catch(Exception ex){
log.error("Error al obtener lomos desde SK API", ex);
}
try { try {
Map<String, Object> saveResult = presupuestoService.guardarPresupuesto( Map<String, Object> saveResult = presupuestoService.guardarPresupuesto(

View File

@ -126,6 +126,13 @@ public class PresupuestoFormatter {
}, },
locale); locale);
} }
textoResumen += ms.getMessage(
"presupuesto.resumen-lomos",
new Object[] {
p.getLomo() != null ? Math.round(p.getLomo()) : "N/D",
p.getLomoCubierta() != null ? Math.round(p.getLomoCubierta()) : "N/D"
},
locale);
textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale); textoResumen += ms.getMessage("presupuesto.resumen-texto-end", null, locale);
return textoResumen; return textoResumen;

View File

@ -109,21 +109,6 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
} }
} }
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;
}
}
@Override @Override
public Presupuesto clone() { public Presupuesto clone() {
@ -188,10 +173,6 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
@Column(name = "iva_reducido") @Column(name = "iva_reducido")
private Boolean ivaReducido; private Boolean ivaReducido;
@Column(name = "entrega_tipo")
@Enumerated(EnumType.STRING)
private Entrega entregaTipo;
@Column(name = "iva_importe_4", precision = 12, scale = 2) @Column(name = "iva_importe_4", precision = 12, scale = 2)
private BigDecimal ivaImporte4; private BigDecimal ivaImporte4;
@ -300,6 +281,12 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
@Column(name = "papel_interior_id") @Column(name = "papel_interior_id")
private Integer papelInteriorId; private Integer papelInteriorId;
@Column(name = "lomo")
private Double lomo;
@Column(name = "lomo_cubierta")
private Double lomoCubierta;
@NotNull(message = "{presupuesto.errores.gramaje-interior}", groups = PresupuestoValidationGroups.Interior.class) @NotNull(message = "{presupuesto.errores.gramaje-interior}", groups = PresupuestoValidationGroups.Interior.class)
@Column(name = "gramaje_interior") @Column(name = "gramaje_interior")
private Integer gramajeInterior; private Integer gramajeInterior;
@ -531,14 +518,6 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
this.ivaReducido = ivaReducido; this.ivaReducido = ivaReducido;
} }
public Entrega getEntregaTipo() {
return entregaTipo;
}
public void setEntregaTipo(Entrega entregaTipo) {
this.entregaTipo = entregaTipo;
}
public BigDecimal getIvaImporte4() { public BigDecimal getIvaImporte4() {
return ivaImporte4; return ivaImporte4;
} }
@ -747,6 +726,22 @@ public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
this.papelInteriorId = papelInteriorId; this.papelInteriorId = papelInteriorId;
} }
public Double getLomo() {
return lomo;
}
public void setLomo(Double lomo) {
this.lomo = lomo;
}
public Double getLomoCubierta() {
return lomoCubierta;
}
public void setLomoCubierta(Double lomoCubierta) {
this.lomoCubierta = lomoCubierta;
}
public Integer getGramajeInterior() { public Integer getGramajeInterior() {
return gramajeInterior; return gramajeInterior;
} }

View File

@ -1,9 +1,7 @@
package com.imprimelibros.erp.presupuesto.service; package com.imprimelibros.erp.presupuesto.dto;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -20,6 +18,8 @@ public class PresupuestoFormDataMapper {
public Cubierta cubierta = new Cubierta(); public Cubierta cubierta = new Cubierta();
public Servicios servicios = new Servicios(); public Servicios servicios = new Servicios();
public Integer selectedTirada; public Integer selectedTirada;
public Double lomo;
public Double lomoCubierta;
// ===== Datos Generales ===== // ===== Datos Generales =====
public static class DatosGenerales { public static class DatosGenerales {
@ -37,7 +37,6 @@ public class PresupuestoFormDataMapper {
public String paginasColor = ""; public String paginasColor = "";
public String posicionPaginasColor = ""; public String posicionPaginasColor = "";
public String tipoEncuadernacion = "fresado"; // enum name public String tipoEncuadernacion = "fresado"; // enum name
public String entregaTipo = "peninsula"; // enum name
public boolean ivaReducido = true; public boolean ivaReducido = true;
} }
@ -157,7 +156,6 @@ public class PresupuestoFormDataMapper {
vm.datosGenerales.tipoEncuadernacion = enumName(p.getTipoEncuadernacion(), "fresado"); vm.datosGenerales.tipoEncuadernacion = enumName(p.getTipoEncuadernacion(), "fresado");
vm.datosGenerales.entregaTipo = enumName(p.getEntregaTipo(), "peninsula");
vm.datosGenerales.ivaReducido = Boolean.TRUE.equals(p.getIvaReducido()); vm.datosGenerales.ivaReducido = Boolean.TRUE.equals(p.getIvaReducido());
// ===== Interior // ===== Interior
@ -196,6 +194,10 @@ public class PresupuestoFormDataMapper {
// ===== Selected tirada // ===== Selected tirada
vm.selectedTirada = p.getSelectedTirada(); vm.selectedTirada = p.getSelectedTirada();
// ===== Lomos
vm.lomo = p.getLomo();
vm.lomoCubierta = p.getLomoCubierta();
// ===== Servicios desde JSONs // ===== Servicios desde JSONs
vm.servicios.servicios = parse(p.getServiciosJson(), vm.servicios.servicios = parse(p.getServiciosJson(),
new TypeReference<List<PresupuestoFormDataDto.Servicios.DatosServicios>>() { new TypeReference<List<PresupuestoFormDataDto.Servicios.DatosServicios>>() {

View File

@ -1162,7 +1162,6 @@ public class PresupuestoService {
// Si la entrega es en peninsula, se mira el valor del iva // Si la entrega es en peninsula, se mira el valor del iva
// Canarias y paises UE no llevan IVA // Canarias y paises UE no llevan IVA
if (presupuesto.getEntregaTipo() == Presupuesto.Entrega.peninsula) {
// Si el iva es reducido, el precio de la tirada y el del prototipo llevan IVA // Si el iva es reducido, el precio de la tirada y el del prototipo llevan IVA
// 4% // 4%
if (presupuesto.getIvaReducido()) { if (presupuesto.getIvaReducido()) {
@ -1177,7 +1176,6 @@ public class PresupuestoService {
BigDecimal.valueOf(100), 2, BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP); RoundingMode.HALF_UP);
} }
}
baseImponible = baseImponible.add(serviciosTotal); baseImponible = baseImponible.add(serviciosTotal);
BigDecimal totalConIva = baseImponible.add(ivaImporte21).add(ivaImporte4); BigDecimal totalConIva = baseImponible.add(ivaImporte21).add(ivaImporte4);
@ -1299,6 +1297,17 @@ public class PresupuestoService {
} }
public Map<String, Object> obtenerLomos(Presupuesto presupuesto) {
try {
Map<String, Object> response = apiClient.getLomos(this.toSkApiRequest(presupuesto));
return response;
} catch (Exception e) {
System.out.println("Error obteniendo lomos: " + e.getMessage());
return Map.of();
}
}
/** /**
* PRIVADO (futuro botón "Guardar"): persiste el presupuesto como borrador. * PRIVADO (futuro botón "Guardar"): persiste el presupuesto como borrador.
*/ */
@ -1474,7 +1483,6 @@ public class PresupuestoService {
target.setServiciosTotal(src.getServiciosTotal()); target.setServiciosTotal(src.getServiciosTotal());
target.setBaseImponible(src.getBaseImponible()); target.setBaseImponible(src.getBaseImponible());
target.setIvaReducido(src.getIvaReducido()); target.setIvaReducido(src.getIvaReducido());
target.setEntregaTipo(src.getEntregaTipo());
target.setIvaImporte4(src.getIvaImporte4()); target.setIvaImporte4(src.getIvaImporte4());
target.setIvaImporte21(src.getIvaImporte21()); target.setIvaImporte21(src.getIvaImporte21());
target.setTotalConIva(src.getTotalConIva()); target.setTotalConIva(src.getTotalConIva());

View File

@ -29,6 +29,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import com.imprimelibros.erp.datatables.DataTablesRequest; import com.imprimelibros.erp.datatables.DataTablesRequest;
import com.imprimelibros.erp.datatables.DataTablesParser; import com.imprimelibros.erp.datatables.DataTablesParser;
@ -377,7 +378,7 @@ public class UserController {
@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) { @RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(Math.max(0, page - 1), size); Pageable pageable = PageRequest.of(Math.max(0, page - 1), size, Sort.by(Sort.Direction.ASC, "fullName"));
Page<User> users = userService.findByRoleAndSearch(role, q, pageable); Page<User> users = userService.findByRoleAndSearch(role, q, pageable);

View File

@ -0,0 +1,20 @@
databaseChangeLog:
- changeSet:
id: 0026-drop-entrega-tipo-from-presupuesto
author: jjo
changes:
- dropColumn:
tableName: presupuesto
columnName: entrega_tipo
rollback:
- addColumn:
tableName: presupuesto
columns:
- column:
name: entrega_tipo
type: ENUM('peninsula', 'canarias', 'paises_ue')
defaultValue: peninsula
afterColumn: base_imponible
constraints:
nullable: false

View File

@ -0,0 +1,18 @@
databaseChangeLog:
- changeSet:
id: 0027-add-lomo-to-presupuesto
author: jjo
changes:
- addColumn:
tableName: presupuesto
columns:
- column:
name: lomo
type: DECIMAL(12, 2)
defaultValueNumeric: 0
afterColumn: papel_interior_id
rollback:
- dropColumn:
tableName: presupuesto
columnName: lomo

View File

@ -0,0 +1,18 @@
databaseChangeLog:
- changeSet:
id: 0028-add-lomo-cubierta-to-presupuesto
author: jjo
changes:
- addColumn:
tableName: presupuesto
columns:
- column:
name: lomo_cubierta
type: DECIMAL(12, 2)
defaultValueNumeric: 0
afterColumn: lomo
rollback:
- dropColumn:
tableName: presupuesto
columnName: lomo_cubierta

View File

@ -0,0 +1,43 @@
databaseChangeLog:
- changeSet:
id: 0029-update-estados-pedidos-lineas
author: jjo
changes:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'pendiente_pago',
'procesando_pago',
'denegado_pago',
'aprobado',
'procesando_pedido',
'maquetacion',
'haciendo_ferro_digital',
'esperando_aceptacion_ferro_digital',
'haciendo_ferro',
'esperando_aceptacion_ferro',
'produccion',
'terminado',
'enviado',
'cancelado'
)
rollback:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'pendiente_pago',
'procesando_pago',
'denegado_pago',
'aprobado',
'maquetacion',
'haciendo_ferro',
'esperando_aceptacion_ferro',
'produccion',
'terminado',
'enviado',
'cancelado'
)

View File

@ -1,4 +1,4 @@
databaseChangeLog: databaseChangeLog:
- include: - include:
file: db/changelog/changesets/0001-baseline.yml file: db/changelog/changesets/0001-baseline.yml
- include: - include:
@ -49,3 +49,11 @@ databaseChangeLog:
file: db/changelog/changesets/0024-series-facturacion-seeder.yml file: db/changelog/changesets/0024-series-facturacion-seeder.yml
- include: - include:
file: db/changelog/changesets/0025-create-facturas-direcciones.yml file: db/changelog/changesets/0025-create-facturas-direcciones.yml
- include:
file: db/changelog/changesets/0026-drop-entrega-tipo-from-presupuesto.yml
- include:
file: db/changelog/changesets/0027-add-lomo-to-presupuesto.yml
- include:
file: db/changelog/changesets/0028-add-lomo-cubierta-to-presupuesto.yml
- include:
file: db/changelog/changesets/0029-update-estados-pedidos-lineas.yml

View File

@ -29,6 +29,8 @@ cart.shipping.tipo-envio=Tipo de envío:
cart.shipping.errors.units-error=Por favor, introduzca un número válido entre 1 y {max}. cart.shipping.errors.units-error=Por favor, introduzca un número válido entre 1 y {max}.
cart.shipping.errors.noAddressSelected=Debe seleccionar una dirección de envío para el pedido. cart.shipping.errors.noAddressSelected=Debe seleccionar una dirección de envío para el pedido.
cart.shipping.errors.fillAddressesItems=Debe seleccionar una dirección de envío para cada artículo de la cesta. cart.shipping.errors.fillAddressesItems=Debe seleccionar una dirección de envío para cada artículo de la cesta.
cart.shipping.errors.fillBillingAddressItems=Debe seleccionar una dirección de facturación para poder realizar el pago.
cart.resumen.title=Resumen de la cesta cart.resumen.title=Resumen de la cesta
cart.resumen.base=Base imponible cart.resumen.base=Base imponible

View File

@ -22,10 +22,12 @@ pedido.estado.pendiente_pago=Pendiente de pago
pedido.estado.procesando_pago=Procesando pago pedido.estado.procesando_pago=Procesando pago
pedido.estado.denegado_pago=Pago denegado pedido.estado.denegado_pago=Pago denegado
pedido.estado.aprobado=Aprobado pedido.estado.aprobado=Aprobado
pedido.estado.procesando_pedido=Procesando pedido
pedido.estado.maquetacion=Maquetación pedido.estado.maquetacion=Maquetación
pedido.estado.haciendo_ferro=Haciendo ferro pedido.estado.haciendo_ferro_digital=Haciendo ferro digital
pedido.estado.esperando_aceptacion_ferro=Esperando aceptación de ferro pedido.estado.esperando_aceptacion_ferro_digital=Esperando aceptación de ferro digital
pedido.estado.ferro_cliente=Esperando aprobación de ferro pedido.estado.haciendo_ferro=Haciendo ejemplar de prueba
pedido.estado.esperando_aceptacion_ferro=Esperando aceptación de ejemplar de prueba
pedido.estado.produccion=Producción pedido.estado.produccion=Producción
pedido.estado.terminado=Terminado pedido.estado.terminado=Terminado
pedido.estado.enviado=Enviado pedido.estado.enviado=Enviado
@ -47,6 +49,8 @@ pedido.prueba=Prueba
pedido.table.id=Num. Pedido pedido.table.id=Num. Pedido
pedido.table.cliente=Cliente pedido.table.cliente=Cliente
pedido.table.fecha=Fecha pedido.table.fecha=Fecha
pedido.table.titulos=Títulos
pedido.table.titulos.and-more=y {0} más
pedido.table.importe=Importe pedido.table.importe=Importe
pedido.table.estado=Estado pedido.table.estado=Estado
pedido.table.acciones=Acciones pedido.table.acciones=Acciones

View File

@ -234,6 +234,7 @@ presupuesto.resumen-texto-acabado-cubierta= <li>Acabado: {0}. </li>
presupuesto.resumen-texto-end=</ul> presupuesto.resumen-texto-end=</ul>
presupuesto.resumen-texto-sobrecubierta=<li>Sobrecubierta impresa en {0} {1} gr. <ul><li>Acabado: {2}</li><li>Solapas: {3} mm.</li></ul></li> presupuesto.resumen-texto-sobrecubierta=<li>Sobrecubierta impresa en {0} {1} gr. <ul><li>Acabado: {2}</li><li>Solapas: {3} mm.</li></ul></li>
presupuesto.resumen-texto-faja=<li>Faja impresa en {0} {1} gr. con un alto de {2} mm. <ul><li>Acabado: {3}</li><li>Solapas: {4} mm.</li></ul></li> presupuesto.resumen-texto-faja=<li>Faja impresa en {0} {1} gr. con un alto de {2} mm. <ul><li>Acabado: {3}</li><li>Solapas: {4} mm.</li></ul></li>
presupuesto.resumen-lomos=<li>Dimensiones de los lomos: <ul><li>Lomo interior: {0} mm</li><li>Lomo total: {1} mm</li></ul></li>
presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal
presupuesto.volver-extras=Extras del libro presupuesto.volver-extras=Extras del libro
presupuesto.resumen.inicie-sesion=Inicie sesión para continuar presupuesto.resumen.inicie-sesion=Inicie sesión para continuar

View File

@ -18,7 +18,7 @@ $(() => {
$('#addBillingAddressBtn').on('click', seleccionarDireccionEnvio); $('#addBillingAddressBtn').on('click', seleccionarDireccionEnvio);
$('#authorization-required').on('change', function () { $(document).on('change', '#authorization-required', function () {
if ($(this).is(':checked')) { if ($(this).is(':checked')) {
if ($('#direccion-div .direccion-card').length > 0) { if ($('#direccion-div .direccion-card').length > 0) {
$('#btn-checkout').prop('disabled', false); $('#btn-checkout').prop('disabled', false);
@ -148,6 +148,24 @@ $(() => {
$('#btn-checkout').prop('disabled', false); $('#btn-checkout').prop('disabled', false);
} }
hideLoader(); hideLoader();
if (direccionId) {
$.ajax({
url: `/checkout/get-summary/${direccionId}/${$('input[name="method"]').val()}`,
type: 'GET',
success: function (response) {
const parent = $('.cart-summary-container').parent();
$('.cart-summary-container').remove();
parent.append(response);
$('#dirFactId').val(direccionId);
$('#dirFactWarning').addClass('d-none');
},
error: function () {
console.error('Error al actualizar el resumen del carrito.');
}
});
}
return true; return true;
} }
hideLoader(); hideLoader();
@ -164,6 +182,19 @@ $(() => {
$card.remove(); $card.remove();
$('#addBillingAddressBtn').removeClass('d-none'); $('#addBillingAddressBtn').removeClass('d-none');
$('#btn-checkout').prop('disabled', true); $('#btn-checkout').prop('disabled', true);
$.ajax({
url: `/checkout/get-summary`,
type: 'GET',
success: function (response) {
const parent = $('.cart-summary-container').parent();
$('.cart-summary-container').remove();
$('#dirFactWarning').removeClass('d-none');
parent.append(response);
},
error: function () {
console.error('Error al actualizar el resumen del carrito.');
}
});
}); });
@ -194,7 +225,7 @@ $(() => {
type: 'POST', // PUT simulado via _method type: 'POST', // PUT simulado via _method
data: $form.serialize(), data: $form.serialize(),
dataType: 'html', dataType: 'html',
success: function (html) { success: async function (html) {
// Si por cualquier motivo llega 200 con fragmento, lo insertamos igual // Si por cualquier motivo llega 200 con fragmento, lo insertamos igual
if (typeof html === 'string' && html.indexOf('id="direccionForm"') !== -1 && html.indexOf('<html') === -1) { if (typeof html === 'string' && html.indexOf('id="direccionForm"') !== -1 && html.indexOf('<html') === -1) {
$('#direccionFormModalBody').html(html); $('#direccionFormModalBody').html(html);
@ -205,7 +236,8 @@ $(() => {
} }
// Éxito real: cerrar y recargar tabla // Éxito real: cerrar y recargar tabla
$('#direccionFormModal').modal('hide'); $('#direccionFormModal').modal('hide');
seleccionarDireccionEnvio(); await seleccionarDireccionEnvio();
}, },
error: function (xhr) { error: function (xhr) {
// Con 422 devolvemos el fragmento con errores aquí // Con 422 devolvemos el fragmento con errores aquí

View File

@ -71,7 +71,7 @@ $(() => {
// ----------------------------- // -----------------------------
$table.on('click', '.btn-view-factura', function () { $table.on('click', '.btn-view-factura', function () {
const row = dt.row($(this).closest('tr')).data(); const row = dt.row($(this).closest('tr')).data();
window.location.href = `/facturas/${row.id}`; window.open('/facturas/' + row.id, '_blank', 'noopener,noreferrer');
}); });
// ----------------------------- // -----------------------------

View File

@ -48,6 +48,7 @@ $(() => {
{ data: 'id', name: 'id', orderable: true }, { data: 'id', name: 'id', orderable: true },
{ data: 'cliente', name: 'createdBy.fullName', orderable: true }, { data: 'cliente', name: 'createdBy.fullName', orderable: true },
{ data: 'created_at', name: 'createdAt', orderable: true }, { data: 'created_at', name: 'createdAt', orderable: true },
{ data: 'titulos', name: 'titulos', orderable: true },
{ data: 'total', name: 'total', orderable: true }, { data: 'total', name: 'total', orderable: true },
{ data: 'estado', name: 'estado', orderable: true }, { data: 'estado', name: 'estado', orderable: true },
{ data: 'actions', name: 'actions', orderable: false, searchable: false } { data: 'actions', name: 'actions', orderable: false, searchable: false }

View File

@ -1,8 +1,8 @@
$(() => { $(() => {
$(document).on('click', '.btn-view', function () { $(document).on('click', '.btn-view', function () {
let pedidoId = $(this).data('id'); let pedidoId = $(this).data('id');
let url = `/pedidos/view/${pedidoId}`; if(!pedidoId) return;
window.location.href = url; window.open('/pedidos/view/' + pedidoId, '_blank', 'noopener,noreferrer');
}); });
$(document).on('click', '.btn-pay', async function () { $(document).on('click', '.btn-pay', async function () {

View File

@ -47,6 +47,7 @@ $(() => {
columns: [ columns: [
{ data: 'id', name: 'id', orderable: true }, { data: 'id', name: 'id', orderable: true },
{ data: 'created_at', name: 'createdAt', orderable: true }, { data: 'created_at', name: 'createdAt', orderable: true },
{ data: 'titulos', name: 'titulos', orderable: true },
{ data: 'total', name: 'total', orderable: true }, { data: 'total', name: 'total', orderable: true },
{ data: 'estado', name: 'estado', orderable: true }, { data: 'estado', name: 'estado', orderable: true },
{ data: 'actions', name: 'actions', orderable: false, searchable: false } { data: 'actions', name: 'actions', orderable: false, searchable: false }

View File

@ -34,7 +34,6 @@ export default class PresupuestoWizard {
paginasColor: 0, paginasColor: 0,
posicionPaginasColor: '', posicionPaginasColor: '',
tipoEncuadernacion: 'fresado', tipoEncuadernacion: 'fresado',
entregaTipo: 'peninsula',
ivaReducido: true, ivaReducido: true,
}, },
interior: { interior: {
@ -108,6 +107,8 @@ export default class PresupuestoWizard {
} }
}, },
selectedTirada: 10, selectedTirada: 10,
lomo: 0,
lomoCubierta: 0
} }
// pestaña datos generales // pestaña datos generales
@ -130,7 +131,6 @@ export default class PresupuestoWizard {
this.divPosicionPaginasColor = $('#div-posicion-paginas-color'); this.divPosicionPaginasColor = $('#div-posicion-paginas-color');
this.posicionPaginasColor = $('#posicionPaginasColor'); this.posicionPaginasColor = $('#posicionPaginasColor');
this.paginas = $('#paginas'); this.paginas = $('#paginas');
this.entregaTipo = $('#entregaTipo');
this.ivaReducido = $('#iva-reducido'); this.ivaReducido = $('#iva-reducido');
this.btnIvaReducidoDetail = $('#btn-iva-reducido-detail'); this.btnIvaReducidoDetail = $('#btn-iva-reducido-detail');
this.datos_generales_alert = $('#datos-generales-alert'); this.datos_generales_alert = $('#datos-generales-alert');
@ -445,7 +445,9 @@ export default class PresupuestoWizard {
...this.#getDatosGeneralesData(), ...this.#getDatosGeneralesData(),
...this.#getInteriorData(), ...this.#getInteriorData(),
...this.#getCubiertaData(), ...this.#getCubiertaData(),
selectedTirada: this.formData.selectedTirada selectedTirada: this.formData.selectedTirada,
lomo: this.formData.lomo,
lomoCubierta: this.formData.lomoCubierta
}; };
const sobrecubierta = data.sobrecubierta; const sobrecubierta = data.sobrecubierta;
@ -666,7 +668,6 @@ export default class PresupuestoWizard {
paginasColor: this.paginasColor.val(), paginasColor: this.paginasColor.val(),
posicionPaginasColor: this.posicionPaginasColor.val(), posicionPaginasColor: this.posicionPaginasColor.val(),
tipoEncuadernacion: $('.tipo-libro input:checked').val() || 'fresado', tipoEncuadernacion: $('.tipo-libro input:checked').val() || 'fresado',
entregaTipo: this.entregaTipo.val(),
ivaReducido: this.ivaReducido.is(':checked'), ivaReducido: this.ivaReducido.is(':checked'),
}; };
} }
@ -688,7 +689,6 @@ export default class PresupuestoWizard {
paginasColor: data.paginasColor, paginasColor: data.paginasColor,
posicionPaginasColor: data.posicionPaginasColor, posicionPaginasColor: data.posicionPaginasColor,
tipoEncuadernacion: data.tipoEncuadernacion, tipoEncuadernacion: data.tipoEncuadernacion,
entregaTipo: data.entregaTipo,
ivaReducido: data.ivaReducido, ivaReducido: data.ivaReducido,
}; };
} }
@ -718,17 +718,14 @@ export default class PresupuestoWizard {
.prop('checked', true); .prop('checked', true);
this.#updateTipoEncuadernacion(); this.#updateTipoEncuadernacion();
this.formatoPersonalizado.trigger('change');
$('.paginas').trigger('change');
if (this.formatoPersonalizado.is(':checked')) { if (this.formatoPersonalizado.is(':checked')) {
this.ancho.val(this.formData.datosGenerales.ancho); this.ancho.val(this.formData.datosGenerales.ancho);
this.alto.val(this.formData.datosGenerales.alto); this.alto.val(this.formData.datosGenerales.alto);
} else { } else {
const option = this.formato.find('option').filter(() => { const option = this.formato.find('option').filter((index, element) => {
return $(this).data('ancho') == this.formData.datosGenerales.ancho && return $(element).data('ancho') == this.formData.datosGenerales.ancho &&
$(this).data('alto') == this.formData.datosGenerales.alto; $(element).data('alto') == this.formData.datosGenerales.alto;
}); });
if (option.length) { if (option.length) {
@ -736,7 +733,8 @@ export default class PresupuestoWizard {
} }
} }
this.entregaTipo.val(this.formData.datosGenerales.entregaTipo); this.formatoPersonalizado.trigger('change');
$('.paginas').trigger('change');
this.ivaReducido.prop('checked', this.formData.datosGenerales.ivaReducido); this.ivaReducido.prop('checked', this.formData.datosGenerales.ivaReducido);
} }
@ -970,6 +968,7 @@ export default class PresupuestoWizard {
} else { } else {
const maxSolapas = data.solapas ?? 120; const maxSolapas = data.solapas ?? 120;
const lomo = data.lomo ?? 0; const lomo = data.lomo ?? 0;
$('.solapas-presupuesto').attr('max', maxSolapas); $('.solapas-presupuesto').attr('max', maxSolapas);
$('.max-solapa-text').text(function (_, textoActual) { $('.max-solapa-text').text(function (_, textoActual) {
return textoActual.replace(/\d+/, maxSolapas); return textoActual.replace(/\d+/, maxSolapas);
@ -1023,6 +1022,7 @@ export default class PresupuestoWizard {
const dataCubierta = this.#getCubiertaData(); const dataCubierta = this.#getCubiertaData();
this.#updateCubiertaData(dataCubierta); this.#updateCubiertaData(dataCubierta);
this.formData.lomo = lomo;
this.#cacheFormData(); this.#cacheFormData();
} }
@ -1442,7 +1442,10 @@ export default class PresupuestoWizard {
#getCubiertaData() { #getCubiertaData() {
const tipoCubierta = $('.tapa-cubierta input:checked').val() || 'tapaBlanda'; const tipoCubierta = $('.tapa-cubierta input:checked').val() || 'tapaBlanda';
const solapas = $('.solapas-cubierta input:checked').val() == 'sin-solapas' ? 0 : 1 || 0; let solapas = 0;
if(tipoCubierta === 'tapaBlanda'){
solapas = $('.solapas-cubierta input:checked').val() == 'conSolapas' ? 1 : 0 || 0;
}
const tamanioSolapasCubierta = $('#tamanio-solapas-cubierta').val() || '80'; const tamanioSolapasCubierta = $('#tamanio-solapas-cubierta').val() || '80';
const cubiertaCaras = parseInt(this.carasImpresionCubierta.val()) || 2; const cubiertaCaras = parseInt(this.carasImpresionCubierta.val()) || 2;
const papelGuardasId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3; const papelGuardasId = parseInt($('#papel-guardas option:selected').data('papel-id')) || 3;
@ -1689,6 +1692,11 @@ export default class PresupuestoWizard {
this.divTiradas.append(item.renderCol(this.divTiradas)); this.divTiradas.append(item.renderCol(this.divTiradas));
} }
if(data.lomo_cubierta) {
this.formData.lomoCubierta = data.lomo_cubierta;
this.#cacheFormData();
}
if (this.divTiradas.find('.tirada-card input[type="radio"]:checked').length === 0) { if (this.divTiradas.find('.tirada-card input[type="radio"]:checked').length === 0) {
this.divTiradas.find('.tirada-card input[type="radio"]').first().prop('checked', true).trigger('change'); this.divTiradas.find('.tirada-card input[type="radio"]').first().prop('checked', true).trigger('change');
this.formData.selectedTirada = this.divTiradas.find('.tirada-card input[type="radio"]').first().data('unidades') || data.tiradas[0]; this.formData.selectedTirada = this.divTiradas.find('.tirada-card input[type="radio"]').first().data('unidades') || data.tiradas[0];
@ -1897,15 +1905,6 @@ export default class PresupuestoWizard {
...result, ...result,
}; };
if (!this.formData.servicios.servicios.some(s => s.id === "marcapaginas") && result.precio > 0) {
this.formData.servicios.servicios.push({
id: "marcapaginas",
label: $(`label[for="marcapaginas"] .service-title`).text().trim(),
units: 1,
price: result.precio,
});
}
this.#cacheFormData(); this.#cacheFormData();
}); });

View File

@ -72,7 +72,7 @@ import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-ut
e.preventDefault(); e.preventDefault();
const id = $(this).data('id'); const id = $(this).data('id');
if (id) { if (id) {
window.location.href = '/presupuesto/view/' + id; window.open('/presupuesto/view/' + id, '_blank', 'noopener,noreferrer');
} }
}); });
@ -196,7 +196,7 @@ import { preguntarTipoPresupuesto, duplicar, reimprimir } from './presupuesto-ut
e.preventDefault(); e.preventDefault();
const id = $(this).data('id'); const id = $(this).data('id');
if (id) { if (id) {
window.location.href = '/presupuesto/edit/' + id; window.open('/presupuesto/edit/' + id, '_blank', 'noopener,noreferrer');
} }
}); });

View File

@ -55,7 +55,11 @@ export async function preguntarTipoPresupuesto() {
url: 'users/api/get-users', // ajusta a tu endpoint url: 'users/api/get-users', // ajusta a tu endpoint
dataType: 'json', dataType: 'json',
delay: 250, delay: 250,
data: (params) => ({ q: params.term }), data: (params) => ({
q: params.term || '',
page: params.page || 1,
size: 10,
}),
processResults: data => ({ processResults: data => ({
results: data.results, results: data.results,
pagination: data.pagination pagination: data.pagination

View File

@ -56,6 +56,8 @@
<h5 class="fs-14 text-truncate mb-1"> <h5 class="fs-14 text-truncate mb-1">
<span th:text="#{cart.item.presupuesto-numero}">Presupuesto #</span> <span th:text="#{cart.item.presupuesto-numero}">Presupuesto #</span>
<span th:text="${item.presupuestoId != null ? item.presupuestoId : ''}">#</span> <span th:text="${item.presupuestoId != null ? item.presupuestoId : ''}">#</span>
<a th:href="@{|/presupuesto/edit/${item.presupuestoId}|}"
th:text="#{pedido.view.view-presupuesto}" class="badge bg-secondary">Ver presupuesto</a>
</h5> </h5>
<ul class="list-unstyled text-muted mb-1 ps-0"> <ul class="list-unstyled text-muted mb-1 ps-0">

View File

@ -48,7 +48,7 @@
</div> </div>
<form th:action="@{/pagos/redsys/crear}" method="post"> <form th:action="@{/pagos/redsys/crear}" method="post">
<input type="hidden" name="amountCents" th:value="${summary.amountCents}" /> <input type="hidden" name="amountCents" th:value="${summary.amountCents}" />
<input type="hidden" name="method" value="card" /> <input type="hidden" name="method" th:value="${method} ?: 'card'" />
<input type="hidden" name="cartId" th:value="${summary.cartId}" /> <input type="hidden" name="cartId" th:value="${summary.cartId}" />
<input type="hidden" id="dirFactId" name="dirFactId" value="" /> <input type="hidden" id="dirFactId" name="dirFactId" value="" />
<button id="btn-checkout" type="submit" class="btn btn-secondary w-100 mt-2" <button id="btn-checkout" type="submit" class="btn btn-secondary w-100 mt-2"

View File

@ -50,6 +50,9 @@
<div> <div>
<h5 th:text="#{checkout.billing-address}" class="mb-3">Dirección de envío</h5> <h5 th:text="#{checkout.billing-address}" class="mb-3">Dirección de envío</h5>
<div id="dirFactWarning" class="alert alert-danger alert-shipment" role="alert"
th:text="#{cart.shipping.errors.fillBillingAddressItems}"></div>
<button type="button" class="btn btn-secondary mb-3" id="addBillingAddressBtn" <button type="button" class="btn btn-secondary mb-3" id="addBillingAddressBtn"
th:text="#{cart.shipping.add}">Añadir dirección th:text="#{cart.shipping.add}">Añadir dirección
</button> </button>

View File

@ -0,0 +1,61 @@
<!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>
</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">
<!-- CONTENIDO -->
<div class="row justify-content-center">
<div class="col-lg-7">
<div class="card shadow-sm">
<div class="card-body p-4 p-md-5 text-center">
<h1 class="display-4 mb-2" th:text="${status}">404</h1>
<h4 class="mb-3" th:text="${error}">Not Found</h4>
<p class="text-muted mb-4"
th:text="${message != null and !#strings.isEmpty(message)} ? ${message} : 'Ha ocurrido un error inesperado.'">
Ha ocurrido un error inesperado.
</p>
<div class="small text-muted mb-4">
<div><strong>Ruta:</strong> <span th:text="${path}">/logout</span></div>
<div><strong>Fecha:</strong> <span
th:text="${#temporals.format(timestamp, 'dd/MM/yyyy HH:mm:ss')}">--</span></div>
</div>
<div class="d-flex gap-2 justify-content-center">
<a class="btn btn-primary" th:href="@{/}">Volver al inicio</a>
<a class="btn btn-outline-secondary" href="javascript:history.back()">Atrás</a>
</div>
<!-- Opcional: bloque extra sólo para 404 -->
<div class="mt-4" th:if="${is404}">
<div class="alert alert-warning mb-0">
No se ha encontrado el recurso solicitado.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /CONTENIDO -->
</div>
</body>
</html>

View File

@ -37,6 +37,7 @@
<tr> <tr>
<th class="text-start" scope="col" th:text="#{pedido.table.id}">Num. Pedido</th> <th class="text-start" scope="col" th:text="#{pedido.table.id}">Num. Pedido</th>
<th class="text-start" scope="col" th:text="#{pedido.table.fecha}">Fecha</th> <th class="text-start" scope="col" th:text="#{pedido.table.fecha}">Fecha</th>
<th class="text-start" scope="col" th:text="#{pedido.table.titulos}">Títulos</th>
<th class="text-start" scope="col" th:text="#{pedido.table.importe}">Importe</th> <th class="text-start" scope="col" th:text="#{pedido.table.importe}">Importe</th>
<th class="text-start" scope="col" th:text="#{pedido.table.estado}">Estado</th> <th class="text-start" scope="col" th:text="#{pedido.table.estado}">Estado</th>
<th class="text-start" scope="col" th:text="#{pedido.table.acciones}">Acciones</th> <th class="text-start" scope="col" th:text="#{pedido.table.acciones}">Acciones</th>
@ -44,6 +45,7 @@
<tr> <tr>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="id" /></th> <th><input type="text" class="form-control form-control-sm input-filter" data-col="id" /></th>
<th></th> <th></th>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="titulos" /></th>
<th></th> <th></th>
<th> <th>
<select class="form-select form-select-sm input-filter" data-col="estado"> <select class="form-select form-select-sm input-filter" data-col="estado">

View File

@ -38,6 +38,7 @@
<th class="text-start" scope="col" th:text="#{pedido.table.id}">Num. Pedido</th> <th class="text-start" scope="col" th:text="#{pedido.table.id}">Num. Pedido</th>
<th class="text-start" scope="col" th:text="#{pedido.table.cliente}">Cliente</th> <th class="text-start" scope="col" th:text="#{pedido.table.cliente}">Cliente</th>
<th class="text-start" scope="col" th:text="#{pedido.table.fecha}">Fecha</th> <th class="text-start" scope="col" th:text="#{pedido.table.fecha}">Fecha</th>
<th class="text-start" scope="col" th:text="#{pedido.table.titulos}">Títulos</th>
<th class="text-start" scope="col" th:text="#{pedido.table.importe}">Importe</th> <th class="text-start" scope="col" th:text="#{pedido.table.importe}">Importe</th>
<th class="text-start" scope="col" th:text="#{pedido.table.estado}">Estado</th> <th class="text-start" scope="col" th:text="#{pedido.table.estado}">Estado</th>
<th class="text-start" scope="col" th:text="#{pedido.table.acciones}">Acciones</th> <th class="text-start" scope="col" th:text="#{pedido.table.acciones}">Acciones</th>
@ -46,6 +47,7 @@
<th><input type="text" class="form-control form-control-sm input-filter" data-col="id" /></th> <th><input type="text" class="form-control form-control-sm input-filter" data-col="id" /></th>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="createdBy.fullName" /></th> <th><input type="text" class="form-control form-control-sm input-filter" data-col="createdBy.fullName" /></th>
<th></th> <th></th>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="titulos" /></th>
<th></th> <th></th>
<th> <th>
<select class="form-select form-select-sm input-filter" data-col="estado"> <select class="form-select form-select-sm input-filter" data-col="estado">

View File

@ -302,19 +302,6 @@
</label> </label>
</div> </div>
</div> </div>
<div class="row justify-content-center mb-2">
<div class="col-sm-3 justify-content-center">
<label for="entregaTipo" class="form-label mt-2"
th:text="#{presupuesto.entrega}">Entrega</label>
<select class="form-select select2 datos-generales-data" id="entregaTipo" name="entregaTipo">
<option selected value="peninsula" th:text="#{presupuesto.entrega.peninsula}">Península
y
Baleares</option>
<option value="canarias" th:text="#{presupuesto.entrega.canarias}">Canarias</option>
<option value="paises_ue" th:text="#{presupuesto.entrega.paises-ue}">Países UE</option>
</select>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>