package com.imprimelibros.erp.direcciones; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import org.springframework.context.MessageSource; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; 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.paises.PaisesService; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Controller @RequestMapping("/direcciones") public class DireccionController { protected final DireccionRepository repo; protected final PaisesService paisesService; protected final MessageSource messageSource; public DireccionController(DireccionRepository repo, PaisesService paisesService, MessageSource messageSource) { this.repo = repo; this.paisesService = paisesService; this.messageSource = messageSource; } @GetMapping() public String viewDirecciones(Model model, Authentication auth, Locale locale) { boolean isUser = auth != null && auth.getAuthorities().stream() .anyMatch(a -> a.getAuthority().equals("ROLE_USER")); model.addAttribute("isUser", isUser ? 1 : 0); return "imprimelibros/direcciones/direccion-list"; } @GetMapping(value = "/datatable", produces = "application/json") @ResponseBody public DataTablesResponse> datatable( HttpServletRequest request, Authentication authentication, Locale locale) { DataTablesRequest dt = DataTablesParser.from(request); // Columnas visibles / lógicas para el DataTable en el frontend: // id, cliente (nombre de usuario), alias, att, direccion, cp, ciudad, // provincia, pais List searchable = List.of( "id", "cliente", "alias", "att", "direccion", "cp", "ciudad", "provincia", "pais"); List orderable = List.of( "id", "cliente", "alias", "att", "direccion", "cp", "ciudad", "provincia", "pais"); // Filtro base por rol (ROLE_USER solo ve sus direcciones) Specification base = (root, query, cb) -> { List predicates = new ArrayList<>(); if (authentication != null && authentication.getAuthorities().stream() .anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) { String username = authentication.getName(); predicates.add(cb.equal(root.get("user").get("username"), username)); } return cb.and(predicates.toArray(new Predicate[0])); }; long total = repo.count(); // total sin filtro global // Construcción del datatable con entity + spec return DataTable .of(repo, Direccion.class, dt, searchable) .orderable(orderable) // Columnas "crudas" (las que existen tal cual): .edit("id", d -> d.getId()) .edit("alias", d -> d.getAlias()) .edit("att", d -> d.getAtt()) .edit("direccion", d -> d.getDireccion()) .edit("cp", d -> d.getCp()) .edit("ciudad", d -> d.getCiudad()) .edit("provincia", d -> d.getProvincia()) // Columnas calculadas: // cliente = nombre del usuario (o username si no tienes name) .add("cliente", d -> { var u = d.getUser(); return (u != null && u.getFullName() != null && !u.getFullName().isBlank()) ? u.getFullName() : ""; }) // pais = nombre localizado desde MessageSource usando el keyword del país .add("pais", d -> { // si tienes la relación read-only a Pais (d.getPais()) con .getKeyword() String keyword = (d.getPais() != null) ? d.getPais().getKeyword() : null; if (keyword == null || keyword.isBlank()) return d.getPaisCode3(); return messageSource.getMessage("paises." + keyword, null, keyword, locale); }) // Ejemplo de columna de acciones: .add("actions", d -> """ """.formatted(d.getId(), d.getId())) // WHERE dinámico (spec base) .where(base) // Si tu DataTable helper soporta “join/alias” para buscar/ordenar por campos // relacionados: // .searchAlias("cliente", (root, cb) -> root.join("user").get("name")) // .orderAlias("cliente", (root) -> root.join("user").get("name")) // .searchAlias("pais", (root, cb) -> root.join("pais", // JoinType.LEFT).get("keyword")) // .orderAlias("pais", (root) -> root.join("pais", // JoinType.LEFT).get("keyword")) .toJson(total); } @GetMapping("form") public String getForm(@RequestParam(required = false) Long id, Direccion direccion, BindingResult binding, Model model, HttpServletResponse response, Locale locale) { model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results")); if (id != null) { var opt = repo.findByIdWithPaisAndUser(id); if (opt == null) { binding.reject("direcciones.error.noEncontrado", messageSource.getMessage("direcciones.error.noEncontrado", null, locale)); response.setStatus(404); model.addAttribute("action", "/direcciones/" + id); return "imprimelibros/direcciones/direccion-form :: direccionForm"; } model.addAttribute("direccion", opt.get()); model.addAttribute("action", "/direcciones/" + id); } else { model.addAttribute("direccion", new Direccion()); model.addAttribute("action", "/direcciones"); } return "imprimelibros/direcciones/direccion-form :: direccionForm"; } @PostMapping public String create( Direccion direccion, BindingResult binding, Model model, HttpServletResponse response, Locale locale) { if (binding.hasErrors()) { response.setStatus(422); model.addAttribute("action", "/direcciones/"); return "imprimelibros/direcciones/direccion-form :: direccionForm"; } var data = direccion; try { repo.save(data); } catch (jakarta.validation.ConstraintViolationException vex) { // Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap) vex.getConstraintViolations().forEach(v -> { // intenta asignar al campo si existe, si no, error global String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null; String code = v.getMessage() != null ? v.getMessage().trim() : ""; if (code.startsWith("{") && code.endsWith("}")) { code = code.substring(1, code.length() - 1); // -> "validation.required" } if (path != null && binding.getFieldError(path) == null) { binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale)); } else { binding.reject("validation", messageSource.getMessage(code, null, locale)); } }); response.setStatus(422); model.addAttribute("action", "/direcciones/"); return "imprimelibros/direcciones/direccion-form :: direccionForm"; } response.setStatus(201); return null; } /* @PutMapping("/{id}") public String edit( @PathVariable Long id, MargenPresupuesto form, BindingResult binding, Model model, HttpServletResponse response, Locale locale) { var uOpt = repo.findById(id); if (uOpt.isEmpty()) { binding.reject("usuarios.error.noEncontrado", messageSource.getMessage("usuarios.error.noEncontrado", null, locale)); } if (binding.hasErrors()) { response.setStatus(422); model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; } var entity = uOpt.get(); // 3) Copiar solamente campos editables entity.setImporteMin(form.getImporteMin()); entity.setImporteMax(form.getImporteMax()); entity.setMargenMax(form.getMargenMax()); entity.setMargenMin(form.getMargenMin()); try { repo.saveAndFlush(entity); } catch (jakarta.validation.ConstraintViolationException vex) { // Errores de Bean Validation disparados al flush (incluye tu @NoRangeOverlap) vex.getConstraintViolations().forEach(v -> { // intenta asignar al campo si existe, si no, error global String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : null; String code = v.getMessage() != null ? v.getMessage().trim() : ""; if (code.startsWith("{") && code.endsWith("}")) { code = code.substring(1, code.length() - 1); // -> "validation.required" } if (path != null && binding.getFieldError(path) == null) { binding.rejectValue(path, "validation", messageSource.getMessage(code, null, locale)); } else { binding.reject("validation", messageSource.getMessage(code, null, locale)); } }); response.setStatus(422); model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; } catch (org.springframework.dao.DataIntegrityViolationException dex) { // Uniques, FKs, checks… mensajes de la BD String msg = dex.getMostSpecificCause() != null ? dex.getMostSpecificCause().getMessage() : dex.getMessage(); binding.reject("db.error", messageSource.getMessage(msg, null, locale)); response.setStatus(422); model.addAttribute("action", "/configuracion/margenes-presupuesto/" + id); return "imprimelibros/configuracion/margenes-presupuesto/margenes-presupuesto-form :: margenesPresupuestoForm"; } response.setStatus(204); return null; } @DeleteMapping("/{id}") @Transactional public ResponseEntity delete(@PathVariable Long id, Authentication auth, Locale locale) { return repo.findById(id).map(u -> { try { u.setDeleted(true); u.setDeletedAt(LocalDateTime.now()); repo.save(u); // ← NO delete(); guardamos el soft delete con deleted_by relleno return ResponseEntity.ok(Map.of("message", messageSource.getMessage("margenes-presupuesto.exito.eliminado", null, locale))); } catch (Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("message", messageSource.getMessage("margenes-presupuesto.error.delete-internal-error", null, locale))); } }).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Map.of("message", messageSource.getMessage("margenes-presupuesto.error.not-found", null, locale)))); } */ }