guardando presupuestos anonimos

This commit is contained in:
2025-10-11 14:14:47 +02:00
parent d4d83fe118
commit a1359f37b0
28 changed files with 697 additions and 232 deletions

View File

@ -255,7 +255,6 @@ public class User {
", fullName='" + fullName + '\'' +
", userName='" + userName + '\'' +
", enabled=" + enabled +
", roles=" + getRoles() +
'}';
}

View File

@ -8,8 +8,10 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.MessageSource;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
@ -25,6 +27,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.imprimelibros.erp.datatables.DataTablesRequest;
import com.imprimelibros.erp.datatables.DataTablesParser;
import com.imprimelibros.erp.config.Sanitizer;
@ -34,6 +39,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import org.springframework.web.bind.annotation.RequestParam;
@ -52,6 +58,7 @@ public class UserController {
private Sanitizer sanitizer;
private PasswordEncoder passwordEncoder;
private TranslationService translationService;
private UserService userService;
public UserController(UserDao repo, UserService userService, MessageSource messageSource, Sanitizer sanitizer,
PasswordEncoder passwordEncoder, RoleDao roleRepo, TranslationService translationService) {
@ -61,6 +68,7 @@ public class UserController {
this.roleRepo = roleRepo;
this.passwordEncoder = passwordEncoder;
this.translationService = translationService;
this.userService = userService;
}
@GetMapping
@ -346,4 +354,35 @@ public class UserController {
}).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("message", messageSource.getMessage("usuarios.error.not-found", null, locale))));
}
@ResponseBody
@GetMapping(value = "api/get-users", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> getUsers(
@RequestParam(required = false) String role, // puede venir ausente
@RequestParam(required = false) String q,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(Math.max(0, page - 1), size);
Page<User> users = userService.findByRoleAndSearch(role, q, pageable);
boolean more = users.hasNext();
List<Map<String, Object>> results = users.getContent().stream()
.map(u -> {
Map<String, Object> m = new HashMap<>();
m.put("id", u.getId());
m.put("text", (u.getFullName() != null && !u.getFullName().isBlank())
? u.getFullName()
: u.getUserName());
return m;
})
.collect(Collectors.toList());
return Map.of(
"results", results,
"pagination", Map.of("more", more));
}
}

View File

@ -19,39 +19,60 @@ import org.springframework.lang.Nullable;
@Repository
public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
// Aplicamos EntityGraph a la versión con Specification+Pageable
@Override
@EntityGraph(attributePaths = { "rolesLink", "rolesLink.role" })
@NonNull
Page<User> findAll(@Nullable Specification<User> spec, @NonNull Pageable pageable);
// Aplicamos EntityGraph a la versión con Specification+Pageable
@Override
@EntityGraph(attributePaths = { "rolesLink", "rolesLink.role" })
@NonNull
Page<User> findAll(@Nullable Specification<User> spec, @NonNull Pageable pageable);
Optional<User> findByUserNameIgnoreCase(String userName);
Optional<User> findByUserNameIgnoreCase(String userName);
boolean existsByUserNameIgnoreCase(String userName);
boolean existsByUserNameIgnoreCase(String userName);
// Para comprobar si existe al hacer signup
@Query(value = """
SELECT id, deleted, enabled
FROM users
WHERE LOWER(username) = LOWER(:userName)
LIMIT 1
""", nativeQuery = true)
Optional<UserLite> findLiteByUserNameIgnoreCase(@Param("userName") String userName);
// Para comprobar si existe al hacer signup
@Query(value = """
SELECT id, deleted, enabled
FROM users
WHERE LOWER(username) = LOWER(:userName)
LIMIT 1
""", nativeQuery = true)
Optional<UserLite> findLiteByUserNameIgnoreCase(@Param("userName") String userName);
boolean existsByUserNameIgnoreCaseAndIdNot(String userName, Long id);
boolean existsByUserNameIgnoreCaseAndIdNot(String userName, Long id);
// Nuevo: para login/negocio "activo"
@EntityGraph(attributePaths = { "rolesLink", "rolesLink.role" })
Optional<User> findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(String userName);
// Nuevo: para login/negocio "activo"
@EntityGraph(attributePaths = { "rolesLink", "rolesLink.role" })
Optional<User> findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(String userName);
// Para poder restaurar, necesitas leer ignorando @Where (native):
@Query(value = "SELECT * FROM users WHERE id = :id", nativeQuery = true)
Optional<User> findByIdIncludingDeleted(@Param("id") Long id);
// Para poder restaurar, necesitas leer ignorando @Where (native):
@Query(value = "SELECT * FROM users WHERE id = :id", nativeQuery = true)
Optional<User> findByIdIncludingDeleted(@Param("id") Long id);
@Query(value = "SELECT * FROM users WHERE deleted = TRUE", nativeQuery = true)
List<User> findAllDeleted();
@Query(value = "SELECT * FROM users WHERE deleted = TRUE", nativeQuery = true)
List<User> findAllDeleted();
@Query("select u.id from User u where lower(u.userName) = lower(:userName)")
Optional<Long> findIdByUserNameIgnoreCase(@Param("userName") String userName);
@Query("select u.id from User u where lower(u.userName) = lower(:userName)")
Optional<Long> findIdByUserNameIgnoreCase(@Param("userName") String userName);
@Query(value = """
SELECT DISTINCT u
FROM User u
JOIN u.rolesLink rl
JOIN rl.role r
WHERE (:role IS NULL OR r.name = :role)
AND (:q IS NULL OR LOWER(u.fullName) LIKE LOWER(CONCAT('%', :q, '%'))
OR LOWER(u.userName) LIKE LOWER(CONCAT('%', :q, '%')))
""", countQuery = """
SELECT COUNT(DISTINCT u.id)
FROM User u
JOIN u.rolesLink rl
JOIN rl.role r
WHERE (:role IS NULL OR r.name = :role)
AND (:q IS NULL OR LOWER(u.fullName) LIKE LOWER(CONCAT('%', :q, '%'))
OR LOWER(u.userName) LIKE LOWER(CONCAT('%', :q, '%')))
""")
Page<User> searchUsers(@Param("role") String role,
@Param("q") String q,
Pageable pageable);
}

View File

@ -1,7 +1,19 @@
package com.imprimelibros.erp.users;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface UserService extends UserDetailsService {
/**
* Busca usuarios por rol y texto libre (en username, email o fullName),
* con paginación.
*
* @param role nombre del rol (ej. "ROL_USER")
* @param query texto de búsqueda (puede ser null o vacío)
* @param pageable paginación
* @return página de usuarios
*/
Page<User> findByRoleAndSearch(String role, String query, Pageable pageable);
}

View File

@ -2,6 +2,8 @@ package com.imprimelibros.erp.users;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
@ -13,10 +15,19 @@ public class UserServiceImpl implements UserService {
this.userDao = userDao;
}
@Override
@Override
public UserDetails loadUserByUsername(String username) {
User user = userDao.findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(username)
.orElseThrow(() -> new UsernameNotFoundException("No existe usuario activo: " + username));
return new UserDetailsImpl(user);
}
// ===== Búsqueda para Select2 =====
@Override
public Page<User> findByRoleAndSearch(String role, String query, Pageable pageable) {
if (query == null || query.isBlank()) query = null;
return userDao.searchUsers(role, query, pageable);
}
}