mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
318 lines
13 KiB
Java
318 lines
13 KiB
Java
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<Map<String, Object>> 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<String> searchable = List.of(
|
|
"id",
|
|
"cliente", "alias",
|
|
"att", "direccion", "cp", "ciudad", "provincia", "pais");
|
|
|
|
List<String> orderable = List.of(
|
|
"id",
|
|
"cliente", "alias",
|
|
"att", "direccion", "cp", "ciudad", "provincia", "pais");
|
|
|
|
// Filtro base por rol (ROLE_USER solo ve sus direcciones)
|
|
Specification<Direccion> base = (root, query, cb) -> {
|
|
List<Predicate> 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 -> """
|
|
<div class="hstack gap-3 flex-wrap">
|
|
<a href="javascript:void(0);" data-id="%d" class="link-success btn-edit-direccion fs-15">
|
|
<i class="ri-edit-2-line"></i>
|
|
</a>
|
|
<a href="javascript:void(0);" data-id="%d" class="link-danger btn-delete-direccion fs-15">
|
|
<i class="ri-delete-bin-5-line"></i>
|
|
</a>
|
|
</div>
|
|
""".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))));
|
|
}
|
|
*/
|
|
}
|