package com.imprimelibros.erp.pedidos; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import java.security.Principal; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.springframework.context.MessageSource; import org.springframework.core.io.ByteArrayResource; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.datatables.DataTable; import com.imprimelibros.erp.datatables.DataTablesParser; import com.imprimelibros.erp.datatables.DataTablesRequest; import com.imprimelibros.erp.datatables.DataTablesResponse; import com.imprimelibros.erp.facturacion.service.FacturacionService; import com.imprimelibros.erp.i18n.TranslationService; import com.imprimelibros.erp.paises.PaisesService; import com.imprimelibros.erp.presupuesto.service.PresupuestoService; import com.imprimelibros.erp.users.UserDao; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @Controller @RequestMapping("/pedidos") public class PedidosController { private final PresupuestoService presupuestoService; private final PedidoRepository repoPedido; private final PedidoService pedidoService; private final UserDao repoUser; private final MessageSource messageSource; private final PedidoLineaRepository repoPedidoLinea; private final PaisesService paisesService; private final TranslationService translationService; private final FacturacionService facturacionService; public PedidosController(PedidoRepository repoPedido, PedidoService pedidoService, UserDao repoUser, MessageSource messageSource, TranslationService translationService, PedidoLineaRepository repoPedidoLinea, PaisesService paisesService, FacturacionService facturacionService, PresupuestoService presupuestoService) { this.repoPedido = repoPedido; this.pedidoService = pedidoService; this.repoUser = repoUser; this.messageSource = messageSource; this.translationService = translationService; this.repoPedidoLinea = repoPedidoLinea; this.paisesService = paisesService; this.facturacionService = facturacionService; this.presupuestoService = presupuestoService; } @GetMapping public String listarPedidos(Model model, Locale locale) { List keys = List.of( "app.cancelar", "app.seleccionar", "app.yes", "checkout.payment.card", "checkout.payment.bizum", "checkout.payment.bank-transfer", "checkout.error.select-method"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); if (Utils.isCurrentUserAdmin()) { return "imprimelibros/pedidos/pedidos-list"; } return "imprimelibros/pedidos/pedidos-list-cliente"; } @GetMapping(value = "datatable", produces = "application/json") @ResponseBody public DataTablesResponse> getDatatable( HttpServletRequest request, Principal principal, Locale locale) { DataTablesRequest dt = DataTablesParser.from(request); Boolean isAdmin = Utils.isCurrentUserAdmin(); Long currentUserId = Utils.currentUserId(principal); List searchable = List.of( "id"); // Campos ordenables List orderable = List.of( "id", "createdBy.fullName", "createdAt", "total", "estado"); Specification base = (root, query, cb) -> cb.conjunction(); 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"); // 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification if (clientSearch != null) { List userIds = repoUser.findIdsByFullNameLike(clientSearch.trim()); if (userIds.isEmpty()) { // Ningún usuario coincide → forzamos 0 resultados base = base.and((root, query, cb) -> cb.disjunction()); } else { base = base.and((root, query, cb) -> root.get("createdBy").in(userIds)); } } if (estadoSearch != null && !estadoSearch.isBlank()) { try { PedidoLinea.Estado estadoEnum = PedidoLinea.Estado.valueOf(estadoSearch.trim()); base = base.and((root, query, cb) -> { // Evitar duplicados de pedidos si el provider usa joins if (Pedido.class.equals(query.getResultType())) { query.distinct(true); } Join lineas = root.join("lineas", JoinType.INNER); return cb.equal(lineas.get("estado"), estadoEnum); }); } catch (IllegalArgumentException ex) { // Valor de estado no válido → forzamos 0 resultados base = base.and((root, query, cb) -> cb.disjunction()); } } Long total = repoPedido.count(base); return DataTable .of(repoPedido, Pedido.class, dt, searchable) .orderable(orderable) .add("id", Pedido::getId) .add("created_at", pedido -> Utils.formatInstant(pedido.getCreatedAt(), locale)) .add("cliente", pedido -> { if (pedido.getCreatedBy() != null) { return pedido.getCreatedBy().getFullName(); } return ""; }) .add("total", pedido -> { if (pedido.getTotal() != null) { return Utils.formatCurrency(pedido.getTotal(), locale); } else { return ""; } }) .add("estado", pedido -> { List lineas = repoPedidoLinea.findByPedidoId(pedido.getId()); if (lineas.isEmpty()) { return ""; } // concatenar los estados de las líneas, ordenados por prioridad StringBuilder sb = new StringBuilder(); lineas.stream() .map(PedidoLinea::getEstado) .distinct() .sorted(Comparator.comparingInt(PedidoLinea.Estado::getPriority)) .forEach(estado -> { if (sb.length() > 0) { sb.append(", "); } sb.append(messageSource.getMessage(estado.getMessageKey(), null, locale)); }); String text = sb.toString(); return text; }) .add("actions", pedido -> { String data = "" + messageSource.getMessage("app.view", null, locale) + ""; List lineas = repoPedidoLinea.findByPedidoId(pedido.getId()); boolean hasDenegadoPago = lineas.stream() .anyMatch(linea -> PedidoLinea.Estado.denegado_pago.equals(linea.getEstado())); if (hasDenegadoPago) { data += " " + messageSource.getMessage("app.pay", null, locale) + ""; } return data; }) .where(base) .toJson(total); } @GetMapping("/view/{id}") public String verPedido( @PathVariable(name = "id", required = true) Long id, Model model, Locale locale) { List keys = List.of( "app.cancelar", "app.yes", "pedido.view.cancel-title", "pedido.view.cancel-text"); Map translations = translationService.getTranslations(locale, keys); model.addAttribute("languageBundle", translations); Boolean isAdmin = Utils.isCurrentUserAdmin(); if (isAdmin) { model.addAttribute("isAdmin", true); } else { model.addAttribute("isAdmin", false); } PedidoDireccion direccionFacturacion = pedidoService.getDireccionFacturacionPedido(id); if (direccionFacturacion != null) { String paisNombre = paisesService.getPaisNombrePorCode3(direccionFacturacion.getPaisCode3(), locale); direccionFacturacion.setPaisNombre(paisNombre); } model.addAttribute("direccionFacturacion", direccionFacturacion); Boolean showCancel = false; Boolean showDownloadFactura = true; List> lineas = pedidoService.getLineas(id, locale); for (Map linea : lineas) { PedidoLinea pedidoLinea = repoPedidoLinea.findById( ((Number) linea.get("lineaId")).longValue()).orElse(null); if (pedidoLinea != null) { Map buttons = new HashMap<>(); if (pedidoLinea.getEstado() != PedidoLinea.Estado.enviado) { showDownloadFactura = false; } if (pedidoLinea.getEstado().getPriority() >= PedidoLinea.Estado.esperando_aceptacion_ferro.getPriority() && pedidoLinea.getEstado().getPriority() <= PedidoLinea.Estado.produccion.getPriority()) { if (pedidoLinea.getEstado() == PedidoLinea.Estado.esperando_aceptacion_ferro) { buttons.put("aceptar_ferro", true); } else { buttons.put("aceptar_ferro", false); } Map filesType = pedidoService.getFilesType(pedidoLinea.getId(), locale); if (filesType == null || filesType.get("error") != null) { throw new RuntimeException( messageSource.getMessage("pedido.errors.update-server-error", null, locale)); } for (String key : filesType.keySet()) { buttons.put(key, (Integer) filesType.get(key) == 1 ? true : false); } linea.put("buttons", buttons); } if (pedidoLinea.getEstado() != PedidoLinea.Estado.cancelado && pedidoLinea.getEstado() != PedidoLinea.Estado.terminado && pedidoLinea.getEstado() != PedidoLinea.Estado.enviado) { showCancel = true; } } List dirEntrega = pedidoService.getDireccionesEntregaPedidoLinea( ((Number) linea.get("lineaId")).longValue()); if (dirEntrega != null && !dirEntrega.isEmpty()) { for (PedidoDireccion direccion : dirEntrega) { String paisNombre = paisesService.getPaisNombrePorCode3(direccion.getPaisCode3(), locale); direccion.setPaisNombre(paisNombre); } } linea.put("direccionesEntrega", dirEntrega); } Long facturaId = null; if (showDownloadFactura) { facturaId = facturacionService.getFacturaIdFromPedidoId(id); } model.addAttribute("lineas", lineas); model.addAttribute("showCancel", showCancel); if (showDownloadFactura && facturaId != null) { model.addAttribute("facturaId", facturaId); model.addAttribute("showDownloadFactura", showDownloadFactura); } model.addAttribute("id", id); return "imprimelibros/pedidos/pedidos-view"; } @PostMapping("/cancel/{id}") @ResponseBody public Map cancelPedido( @PathVariable(name = "id", required = true) Long id, Locale locale) { Boolean result = pedidoService.cancelarPedido(id); if (result) { String successMsg = messageSource.getMessage("pedido.success.pedido-cancelado", null, locale); return Map.of( "success", true, "message", successMsg); } else { String errorMsg = messageSource.getMessage("pedido.errors.cancel-pedido", null, locale); return Map.of( "success", false, "message", errorMsg); } } // ------------------------------------- // Acciones sobre las lineas de pedido // ------------------------------------- @PostMapping("/linea/{id}/update-status") @ResponseBody public Map updateStatus( @PathVariable(name = "id", required = true) Long id, Locale locale) { Map result = pedidoService.actualizarEstado(id, locale); return result; } @PostMapping("/linea/{id}/update-maquetacion") @ResponseBody public Map updateMaquetacion( @PathVariable(name = "id", required = true) Long id, Locale locale) { PedidoLinea entity = repoPedidoLinea.findById(id).orElse(null); if (entity == null) { String errorMsg = messageSource.getMessage("pedido.errors.linea-not-found", null, locale); return Map.of( "success", false, "message", errorMsg); } if (entity.getEstado() != PedidoLinea.Estado.maquetacion) { String errorMsg = messageSource.getMessage("pedido.errors.state-error", null, locale); return Map.of( "success", false, "message", errorMsg); } entity.setEstado(PedidoLinea.Estado.haciendo_ferro); repoPedidoLinea.save(entity); String successMsg = messageSource.getMessage("pedido.success.estado-actualizado", null, locale); return Map.of( "success", true, "message", successMsg, "state", messageSource.getMessage(entity.getEstado().getMessageKey(), null, locale)); } @GetMapping("/linea/{id}/download-ferro") public ResponseEntity downloadFerro(@PathVariable(name = "id", required = true) Long id, Locale locale) { byte[] ferroFileContent = pedidoService.getFerroFileContent(id, locale); if (ferroFileContent == null) { return ResponseEntity.notFound().build(); } ByteArrayResource resource = new ByteArrayResource(ferroFileContent); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=ferro_" + id + ".pdf") .contentType(MediaType.APPLICATION_PDF) .body(resource); } @GetMapping("/linea/{id}/download-cub") public ResponseEntity downloadCubierta(@PathVariable(name = "id", required = true) Long id, Locale locale) { byte[] cubFileContent = pedidoService.getCubiertaFileContent(id, locale); if (cubFileContent == null) { return ResponseEntity.notFound().build(); } ByteArrayResource resource = new ByteArrayResource(cubFileContent); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=cubierta_" + id + ".pdf") .contentType(MediaType.APPLICATION_PDF) .body(resource); } @GetMapping("/linea/{id}/download-tapa") public ResponseEntity downloadTapa(@PathVariable(name = "id", required = true) Long id, Locale locale) { byte[] tapaFileContent = pedidoService.getTapaFileContent(id, locale); if (tapaFileContent == null) { return ResponseEntity.notFound().build(); } ByteArrayResource resource = new ByteArrayResource(tapaFileContent); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=tapa_" + id + ".pdf") .contentType(MediaType.APPLICATION_PDF) .body(resource); } @PostMapping("/linea/{id}/aceptar-ferro") @ResponseBody public Map aceptarFerro(@PathVariable(name = "id", required = true) Long id, Locale locale) { PedidoLinea entity = repoPedidoLinea.findById(id).orElse(null); if (entity == null) { String errorMsg = messageSource.getMessage("pedido.errors.linea-not-found", null, locale); return Map.of( "success", false, "message", errorMsg); } if (entity.getEstado() != PedidoLinea.Estado.esperando_aceptacion_ferro) { String errorMsg = messageSource.getMessage("pedido.errors.state-error", null, locale); return Map.of( "success", false, "message", errorMsg); } Boolean result = pedidoService.aceptarFerro(id, locale); if (result) { String successMsg = messageSource.getMessage("pedido.success.estado-actualizado", null, locale); return Map.of( "success", true, "message", successMsg, "state", messageSource.getMessage(entity.getEstado().getMessageKey(), null, locale)); } else { String errorMsg = messageSource.getMessage("pedido.errors.update-server-error", null, locale); return Map.of( "success", false, "message", errorMsg); } } }