implementado el soft-delete

This commit is contained in:
Jaime Jiménez
2025-09-29 15:35:41 +02:00
parent 865b1573b9
commit 656bb5bad2
11 changed files with 522 additions and 241 deletions

View File

@ -13,6 +13,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
@ -33,6 +34,7 @@ import com.imprimelibros.erp.datatables.DataTable;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Locale;
import org.springframework.web.bind.annotation.RequestParam;
@ -66,18 +68,17 @@ public class UserController {
public String list(Model model, Authentication authentication, Locale locale) {
List<String> keys = List.of(
"usuarios.delete.title",
"usuarios.delete.text",
"usuarios.eliminar",
"usuarios.delete.button",
"app.yes",
"app.cancelar",
"usuarios.delete.ok.title",
"usuarios.delete.ok.text"
);
"usuarios.delete.title",
"usuarios.delete.text",
"usuarios.eliminar",
"usuarios.delete.button",
"app.yes",
"app.cancelar",
"usuarios.delete.ok.title",
"usuarios.delete.ok.text");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
return "imprimelibros/users/users-list";
}
@ -86,7 +87,8 @@ public class UserController {
// método con @ResponseBody.
@GetMapping(value = "/datatable", produces = "application/json")
@ResponseBody
public DataTablesResponse<Map<String, Object>> datatable(HttpServletRequest request, Locale locale) {
public DataTablesResponse<Map<String, Object>> datatable(HttpServletRequest request, Authentication authentication,
Locale locale) {
DataTablesRequest dt = DataTablesParser.from(request); //
@ -94,8 +96,9 @@ public class UserController {
// Si 'role' es relación, sácalo de aquí:
List<String> searchable = List.of("fullName", "userName", "enabled", "rolesConcat"); // <- busca por roles de
// verdad
List<String> orderable = List.of("id", "fullName", "userName", "enabled", "roleRank"); // <- permite ordenar por estas
// columnas
List<String> orderable = List.of("id", "fullName", "userName", "enabled", "roleRank"); // <- permite ordenar por
// estas
// columnas
Specification<User> base = (root, query, cb) -> cb.conjunction();
long total = repo.count();
@ -121,12 +124,27 @@ public class UserController {
messageSource.getMessage("usuarios.rol." + rol, null, locale) + "</span>")
.collect(Collectors.joining(" ")))
.add("actions", (user) -> {
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n" +
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
+ "\" class=\"link-danger btn-delete-user fs-15\"><i class=\"user-delete ri-delete-bin-line\"></i></a>\n" +
" </div>";
boolean isSuperAdmin = authentication.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_SUPERADMIN"));
if (!isSuperAdmin) {
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
+
" </div>";
} else {
// Admin editando otro admin o usuario normal: puede editarse y eliminarse
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
+
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
+ "\" class=\"link-danger btn-delete-user fs-15\"><i class=\"user-delete ri-delete-bin-line\"></i></a>\n"
+
" </div>";
}
})
.where(base)
// Filtros custom:
@ -295,28 +313,38 @@ public class UserController {
}
@DeleteMapping("/{id}")
public ResponseEntity<?> delete(@PathVariable Long id, Authentication authentication, Locale locale) {
@Transactional
public ResponseEntity<?> delete(@PathVariable Long id, Authentication auth, Locale locale) {
return repo.findById(id).map(u -> {
if (authentication != null && u.getUserName().equalsIgnoreCase(authentication.getName())) {
if (auth != null && u.getUserName().equalsIgnoreCase(auth.getName())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Map.of("message", messageSource.getMessage("usuarios.error.delete-self", null, locale)));
}
try {
repo.delete(u);
return ResponseEntity.status(HttpStatus.OK).body(
Map.of("message", messageSource.getMessage("usuarios.exito.eliminado", null, locale))
);
} catch (DataIntegrityViolationException dive) {
// Restricción FK / dependencias
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(Map.of("message", messageSource.getMessage("usuarios.error.delete-relational-data", null, locale)));
Long currentUserId = null;
if (auth != null && auth.getPrincipal() instanceof UserDetailsImpl udi) {
currentUserId = udi.getId();
} else if (auth != null) {
currentUserId = repo.findIdByUserNameIgnoreCase(auth.getName()).orElse(null); // fallback
}
u.setDeleted(true);
u.setDeletedAt(LocalDateTime.now());
u.setDeletedBy(currentUserId);
// Soft-delete de los vínculos (si usas cascade REMOVE + @SQLDelete en UserRole,
// podrías omitir este foreach y dejar que JPA lo haga)
u.getRolesLink().forEach(UserRole::softDelete);
repo.save(u); // ← NO delete(); guardamos el soft delete con deleted_by relleno
return ResponseEntity.ok(Map.of("message",
messageSource.getMessage("usuarios.exito.eliminado", null, locale)));
} catch (Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message", messageSource.getMessage("usuarios.error.delete-internal-error", null, locale)));
.body(Map.of("message",
messageSource.getMessage("usuarios.error.delete-internal-error", null, locale)));
}
}).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("message", messageSource.getMessage("usuarios.error.delete-not-found", null, locale))));
.body(Map.of("message", messageSource.getMessage("usuarios.error.not-found", null, locale))));
}
}