mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
529 lines
22 KiB
Java
529 lines
22 KiB
Java
package com.imprimelibros.erp.direcciones;
|
|
|
|
import java.security.Principal;
|
|
import java.time.Instant;
|
|
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.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.security.core.Authentication;
|
|
import org.springframework.stereotype.Controller;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.ui.Model;
|
|
import org.springframework.validation.BindingResult;
|
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
|
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.cart.CartService;
|
|
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.i18n.TranslationService;
|
|
import com.imprimelibros.erp.paises.PaisesService;
|
|
import com.imprimelibros.erp.users.User;
|
|
import com.imprimelibros.erp.users.UserDao;
|
|
import com.imprimelibros.erp.users.UserDetailsImpl;
|
|
|
|
import jakarta.persistence.criteria.Predicate;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
import jakarta.validation.Valid;
|
|
|
|
@Controller
|
|
@RequestMapping("/direcciones")
|
|
public class DireccionController {
|
|
|
|
private final DireccionService direccionService;
|
|
|
|
protected final DireccionRepository repo;
|
|
protected final PaisesService paisesService;
|
|
protected final MessageSource messageSource;
|
|
protected final UserDao userRepo;
|
|
protected final TranslationService translationService;
|
|
protected final CartService cartService;
|
|
|
|
public DireccionController(DireccionRepository repo, PaisesService paisesService,
|
|
MessageSource messageSource, UserDao userRepo, TranslationService translationService,
|
|
DireccionService direccionService, CartService cartService) {
|
|
this.repo = repo;
|
|
this.paisesService = paisesService;
|
|
this.messageSource = messageSource;
|
|
this.userRepo = userRepo;
|
|
this.translationService = translationService;
|
|
this.direccionService = direccionService;
|
|
this.cartService = cartService;
|
|
}
|
|
|
|
@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);
|
|
|
|
List<String> keys = List.of(
|
|
"direcciones.delete.title",
|
|
"direcciones.delete.text",
|
|
"direcciones.eliminar",
|
|
"direcciones.delete.button",
|
|
"app.yes",
|
|
"app.cancelar",
|
|
"direcciones.delete.ok.title",
|
|
"direcciones.delete.ok.text",
|
|
"direcciones.btn.edit",
|
|
"direcciones.btn.delete",
|
|
"direcciones.telefono", "direcciones.isFacturacionShort");
|
|
|
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
|
model.addAttribute("languageBundle", translations);
|
|
|
|
if (isUser)
|
|
return "imprimelibros/direcciones/direccion-list-cliente";
|
|
else
|
|
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(base);
|
|
|
|
// 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(value = "/datatableDirecciones", produces = "application/json")
|
|
@ResponseBody
|
|
public DataTablesResponse<Map<String, Object>> datatableCliente(
|
|
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",
|
|
"alias",
|
|
"att", "direccion", "cp", "ciudad", "provincia", "pais", "telefono", "is_facturacion", "razonSocial",
|
|
"identificacionFiscal");
|
|
|
|
List<String> orderable = List.of(
|
|
"id",
|
|
"cliente", "alias",
|
|
"att", "direccion", "cp", "ciudad", "provincia", "pais", "telefono");
|
|
|
|
// 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(base);
|
|
|
|
// 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())
|
|
.edit("telefono", d -> d.getTelefono())
|
|
.edit("is_facturacion", d -> d.isDireccionFacturacion())
|
|
.edit("razon_social", d -> d.getRazonSocial())
|
|
.edit("tipo_identificacion_fiscal", d -> d.getTipoIdentificacionFiscal())
|
|
.edit("identificacion_fiscal", d -> d.getIdentificacionFiscal())
|
|
|
|
// 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);
|
|
})
|
|
// WHERE dinámico (spec base)
|
|
.where(base)
|
|
.toJson(total);
|
|
}
|
|
|
|
@GetMapping("form")
|
|
public String getForm(@RequestParam(required = false) Long id,
|
|
Direccion direccion,
|
|
BindingResult binding,
|
|
Model model,
|
|
HttpServletResponse response,
|
|
Authentication auth,
|
|
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("dirForm", opt.get());
|
|
model.addAttribute("action", "/direcciones/" + id);
|
|
} else {
|
|
|
|
Direccion newDireccion = new Direccion();
|
|
boolean isUser = auth != null && auth.getAuthorities().stream()
|
|
.anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
|
|
if (isUser) {
|
|
User user = direccion.getUser() != null ? direccion.getUser() : null;
|
|
if (user != null) {
|
|
newDireccion.setUser(user);
|
|
}
|
|
}
|
|
model.addAttribute("dirForm", newDireccion);
|
|
model.addAttribute("action", "/direcciones");
|
|
}
|
|
return "imprimelibros/direcciones/direccion-form :: direccionForm";
|
|
}
|
|
|
|
@GetMapping("direction-form")
|
|
public String getForm(@RequestParam(required = false) Long id,
|
|
Direccion direccion,
|
|
BindingResult binding,
|
|
Model model,
|
|
HttpServletResponse response,
|
|
Principal principal,
|
|
Locale locale) {
|
|
|
|
model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results"));
|
|
|
|
Direccion newDireccion = new Direccion();
|
|
|
|
User user = null;
|
|
if (principal instanceof UserDetailsImpl udi) {
|
|
user = new User();
|
|
user.setId(udi.getId());
|
|
} else if (principal instanceof User u && u.getId() != null) {
|
|
user = u;
|
|
}
|
|
newDireccion.setUser(user);
|
|
model.addAttribute("dirForm", newDireccion);
|
|
model.addAttribute("action", "/direcciones/add");
|
|
|
|
return "imprimelibros/direcciones/direccion-form-fixed-user :: direccionForm";
|
|
}
|
|
|
|
@PostMapping
|
|
public String create(
|
|
@Valid @ModelAttribute("dirForm") Direccion direccion,
|
|
BindingResult binding,
|
|
Model model,
|
|
HttpServletResponse response,
|
|
Authentication auth,
|
|
Locale locale) {
|
|
|
|
boolean isUser = auth != null && auth.getAuthorities().stream()
|
|
.anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
|
|
|
|
if (isUser) {
|
|
User current = userRepo.findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(auth.getName()).orElse(null);
|
|
direccion.setUser(current); // ignora lo que venga del hidden
|
|
}
|
|
|
|
if (binding.hasErrors()) {
|
|
response.setStatus(422);
|
|
model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results"));
|
|
model.addAttribute("action", "/direcciones");
|
|
model.addAttribute("dirForm", direccion);
|
|
return "imprimelibros/direcciones/direccion-form :: direccionForm";
|
|
}
|
|
|
|
var data = direccion;
|
|
|
|
repo.save(data);
|
|
response.setStatus(201);
|
|
return null;
|
|
}
|
|
|
|
// para el formulario modal en checkout
|
|
@PostMapping("/add")
|
|
public String create2(
|
|
@Valid @ModelAttribute("dirForm") Direccion direccion,
|
|
BindingResult binding,
|
|
Model model,
|
|
HttpServletResponse response,
|
|
Authentication auth,
|
|
Locale locale) {
|
|
|
|
User current = userRepo.findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(auth.getName()).orElse(null);
|
|
direccion.setUser(current);
|
|
|
|
if (binding.hasErrors()) {
|
|
response.setStatus(422);
|
|
model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results"));
|
|
model.addAttribute("action", "/direcciones/add");
|
|
model.addAttribute("dirForm", direccion);
|
|
return "imprimelibros/direcciones/direccion-form-fixed-user :: direccionForm";
|
|
}
|
|
|
|
var data = direccion;
|
|
|
|
repo.save(data);
|
|
response.setStatus(201);
|
|
return null;
|
|
}
|
|
|
|
@PostMapping("/{id}")
|
|
public String update(
|
|
@PathVariable Long id,
|
|
@Valid @ModelAttribute("dirForm") Direccion direccion, // <- nombre distinto
|
|
BindingResult binding,
|
|
Model model,
|
|
Authentication auth,
|
|
HttpServletResponse response,
|
|
Locale locale) {
|
|
|
|
var opt = repo.findById(id);
|
|
if (opt.isEmpty()) {
|
|
binding.reject("direcciones.error.noEncontrado",
|
|
messageSource.getMessage("direcciones.error.noEncontrado", null, locale));
|
|
response.setStatus(404);
|
|
model.addAttribute("dirForm", direccion); // por si re-renderiza
|
|
model.addAttribute("action", "/direcciones/" + id);
|
|
return "imprimelibros/direcciones/direccion-form :: direccionForm";
|
|
}
|
|
|
|
Long ownerId = opt.get().getUser() != null ? opt.get().getUser().getId() : null;
|
|
if (!isOwnerOrAdmin(auth, ownerId)) {
|
|
binding.reject("direcciones.error.sinPermiso",
|
|
messageSource.getMessage("direcciones.error.sinPermiso", null, locale));
|
|
response.setStatus(403);
|
|
model.addAttribute("dirForm", direccion); // por si re-renderiza
|
|
model.addAttribute("action", "/direcciones/" + id);
|
|
return "imprimelibros/direcciones/direccion-form :: direccionForm";
|
|
}
|
|
|
|
if (binding.hasErrors()) {
|
|
response.setStatus(422);
|
|
model.addAttribute("dirForm", direccion); // <- importante
|
|
model.addAttribute("paises", paisesService.getForSelect("", "", locale).get("results"));
|
|
model.addAttribute("action", "/direcciones/" + id);
|
|
return "imprimelibros/direcciones/direccion-form :: direccionForm";
|
|
}
|
|
|
|
repo.save(direccion);
|
|
response.setStatus(200);
|
|
return null;
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
@Transactional
|
|
public ResponseEntity<?> delete(@PathVariable Long id, Authentication auth, Locale locale) {
|
|
|
|
Direccion direccion = repo.findById(id).orElse(null);
|
|
if (direccion == null) {
|
|
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
.body(Map.of("message", messageSource.getMessage("direcciones.error.noEncontrado", null, locale)));
|
|
}
|
|
|
|
boolean isUser = auth != null && auth.getAuthorities().stream()
|
|
.anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
|
|
|
|
Long ownerId = direccion.getUser() != null ? direccion.getUser().getId() : null;
|
|
Boolean isOwner = this.isOwnerOrAdmin(auth, ownerId);
|
|
|
|
if (isUser && !isOwner) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
|
.body(Map.of("message",
|
|
messageSource.getMessage("direcciones.error.sinPermiso", null, locale)));
|
|
}
|
|
|
|
try {
|
|
|
|
direccion.setDeleted(true);
|
|
direccion.setDeletedAt(Instant.now());
|
|
|
|
if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) {
|
|
direccion.setDeletedBy(userRepo.getReferenceById(udi.getId()));
|
|
} else if (auth != null) {
|
|
userRepo.findByUserNameIgnoreCase(auth.getName()).ifPresent(direccion::setDeletedBy);
|
|
}
|
|
repo.saveAndFlush(direccion);
|
|
|
|
// eliminar referencias en carritos activos
|
|
cartService.deleteCartDireccionesByDireccionId(direccion.getId());
|
|
|
|
return ResponseEntity.ok(Map.of("message",
|
|
messageSource.getMessage("direcciones.exito.eliminado", null, locale)));
|
|
|
|
} catch (Exception ex) {
|
|
// Devuelve SIEMPRE algo en el catch
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
.body(Map.of("message",
|
|
messageSource.getMessage("direcciones.error.delete-internal-error", null, locale),
|
|
"detail",
|
|
ex.getClass().getSimpleName() + ": " + (ex.getMessage() != null ? ex.getMessage() : "")));
|
|
}
|
|
}
|
|
|
|
@GetMapping(value = "/select2", produces = "application/json")
|
|
@ResponseBody
|
|
public Map<String, Object> getSelect2(
|
|
@RequestParam(value = "q", required = false) String q1,
|
|
@RequestParam(value = "term", required = false) String q2,
|
|
@RequestParam(value = "presupuestoId", required = false) Long presupuestoId,
|
|
Authentication auth) {
|
|
|
|
boolean isAdmin = auth.getAuthorities().stream()
|
|
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN") || a.getAuthority().equals("ROLE_SUPERADMIN"));
|
|
|
|
Long currentUserId = null;
|
|
if (!isAdmin) {
|
|
if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) {
|
|
currentUserId = udi.getId();
|
|
} else if (auth != null) {
|
|
currentUserId = userRepo.findIdByUserNameIgnoreCase(auth.getName()).orElse(null);
|
|
}
|
|
}
|
|
|
|
return direccionService.getForSelect(q1, q2, isAdmin ? null : currentUserId);
|
|
|
|
}
|
|
|
|
private boolean isOwnerOrAdmin(Authentication auth, Long ownerId) {
|
|
if (auth == null) {
|
|
return false;
|
|
}
|
|
boolean isAdmin = auth.getAuthorities().stream()
|
|
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN") || a.getAuthority().equals("ROLE_SUPERADMIN"));
|
|
if (isAdmin) {
|
|
return true;
|
|
}
|
|
// Aquí deberías obtener el ID del usuario actual desde tu servicio de usuarios
|
|
Long currentUserId = null;
|
|
if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) {
|
|
currentUserId = udi.getId();
|
|
} else if (auth != null) {
|
|
currentUserId = userRepo.findIdByUserNameIgnoreCase(auth.getName()).orElse(null);
|
|
}
|
|
return currentUserId != null && currentUserId.equals(ownerId);
|
|
}
|
|
|
|
}
|