This commit is contained in:
2026-02-15 12:53:19 +01:00
parent 1a04d5ace1
commit a9cdc6d5d6
8 changed files with 87 additions and 6 deletions

View File

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

View File

@ -69,7 +69,7 @@ public class PedidoService {
}
public PedidoDireccion getPedidoDireccionFacturacionByPedidoId(Long pedidoId) {
return pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId);
return pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
}
@Transactional
@ -206,7 +206,7 @@ public class PedidoService {
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if (pedido != null) {
PedidoDireccion direccionPedido = pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId);
PedidoDireccion direccionPedido = pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
if (direccionPedido == null) {
// crear
@ -253,7 +253,7 @@ public class PedidoService {
}
public PedidoDireccion getDireccionFacturacionPedido(Long pedidoId) {
return pedidoDireccionRepository.findByPedidoIdAndFacturacionTrue(pedidoId);
return pedidoDireccionRepository.findFirstByPedidoIdAndFacturacionTrue(pedidoId);
}
public List<PedidoDireccion> getDireccionesEntregaPedidoLinea(Long pedidoLineaId) {

View File

@ -12,6 +12,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.MessageSource;
import org.springframework.core.io.ByteArrayResource;
@ -33,11 +34,14 @@ import com.imprimelibros.erp.users.UserDao;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Subquery;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.PostMapping;
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
@Controller
@RequestMapping("/pedidos")
public class PedidosController {
@ -106,6 +110,7 @@ public class PedidosController {
"id",
"createdBy.fullName",
"createdAt",
"titulos",
"total",
"estado");
@ -113,8 +118,10 @@ public class PedidosController {
if (!isAdmin) {
base = base.and((root, query, cb) -> cb.equal(root.get("createdBy").get("id"), currentUserId));
}
String clientSearch = dt.getColumnSearch("cliente");
String estadoSearch = dt.getColumnSearch("estado");
String titulosSearch = dt.getColumnSearch("titulos");
// 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification
if (clientSearch != null) {
@ -146,11 +153,45 @@ public class PedidosController {
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);
return DataTable
.of(repoPedido, Pedido.class, dt, searchable)
.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("created_at", pedido -> Utils.formatInstant(pedido.getCreatedAt(), locale))
.add("cliente", pedido -> {
@ -166,6 +207,38 @@ public class PedidosController {
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 -> {
List<PedidoLinea> lineas = repoPedidoLinea.findByPedidoId(pedido.getId());
if (lineas.isEmpty()) {

View File

@ -47,6 +47,8 @@ pedido.prueba=Prueba
pedido.table.id=Num. Pedido
pedido.table.cliente=Cliente
pedido.table.fecha=Fecha
pedido.table.titulos=Títulos
pedido.table.titulos.and-more=y {0} m\u00E1s
pedido.table.importe=Importe
pedido.table.estado=Estado
pedido.table.acciones=Acciones

View File

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

View File

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

View File

@ -37,6 +37,7 @@
<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.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.estado}">Estado</th>
<th class="text-start" scope="col" th:text="#{pedido.table.acciones}">Acciones</th>
@ -44,6 +45,7 @@
<tr>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="id" /></th>
<th></th>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="titulos" /></th>
<th></th>
<th>
<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.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.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.estado}">Estado</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="createdBy.fullName" /></th>
<th></th>
<th><input type="text" class="form-control form-control-sm input-filter" data-col="titulos" /></th>
<th></th>
<th>
<select class="form-select form-select-sm input-filter" data-col="estado">