package com.imprimelibros.erp.users; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import java.util.Set; import org.hibernate.annotations.Formula; import java.time.LocalDateTime; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.hibernate.annotations.SQLRestriction; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @Entity @Table(name = "users", uniqueConstraints = { @UniqueConstraint(name = "uk_users_username", columnNames = "username") }) @SQLRestriction("deleted = false") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "fullname") @NotBlank(message = "{validation.required}") private String fullName; @Column(name = "username", nullable = false, length = 190) @Email(message = "{validation.email}") @NotBlank(message = "{validation.required}") private String userName; @Column(name = "password") @NotBlank(message = "{validation.required}") private String password; @Column(name = "enabled") private boolean enabled; @Column(name = "deleted", nullable = false) private boolean deleted = false; @Column(name = "deleted_at") private LocalDateTime deletedAt; @Column(name = "deleted_by") private Long deletedBy; @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true) @SQLRestriction("deleted = false") @JsonIgnore private Set rolesLink = new HashSet<>(); // SUPERADMIN=3, ADMIN=2, USER=1 (ajusta a tus nombres reales) @Formula(""" ( select coalesce(max( case r.name when 'SUPERADMIN' then 3 when 'ADMIN' then 2 else 1 end ), 0) from users_roles ur join roles r on r.id = ur.role_id where ur.user_id = id ) """) private Integer roleRank; @Formula(""" (select group_concat(lower(r.name) order by r.name separator ', ') from users_roles ur join roles r on r.id = ur.role_id where ur.user_id = id) """) private String rolesConcat; /* Constructors */ public User() { } public User(String fullName, String userName, String password, boolean enabled) { this.fullName = fullName; this.userName = userName; this.password = password; this.enabled = enabled; } public User(String fullName, String userName, String password, boolean enabled, Set roles) { this.fullName = fullName; this.userName = userName; this.password = password; this.enabled = enabled; this.rolesLink = roles; } /* Getters and Setters */ public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } @Transient public Set getRoles() { return rolesLink.stream() .filter(ur -> !ur.isDeleted()) .map(UserRole::getRole) .collect(Collectors.toSet()); } @JsonProperty("roles") public List getRoleNames() { return this.getRoles().stream() .map(Role::getName) .filter(java.util.Objects::nonNull) .map(String::trim) .toList(); } public void setRoles(Set desired) { if (desired == null) desired = Collections.emptySet(); // 1) ids deseados Set desiredIds = desired.stream() .map(Role::getId) .collect(Collectors.toSet()); // 2) Soft-delete de vínculos activos que ya no se desean this.rolesLink.stream() .filter(ur -> !ur.isDeleted() && !desiredIds.contains(ur.getRole().getId())) .forEach(UserRole::softDelete); // 3) Para cada rol deseado: si hay vínculo borrado => reactivar; si no existe // => crear for (Role role : desired) { // ya activo boolean activeExists = this.rolesLink.stream() .anyMatch(ur -> !ur.isDeleted() && ur.getRole().getId().equals(role.getId())); if (activeExists) continue; // existe borrado => reactivar Optional deletedLink = this.rolesLink.stream() .filter(ur -> ur.isDeleted() && ur.getRole().getId().equals(role.getId())) .findFirst(); if (deletedLink.isPresent()) { UserRole ur = deletedLink.get(); ur.setDeleted(false); ur.setDeletedAt(null); } else { // crear nuevo vínculo UserRole ur = new UserRole(this, role); this.rolesLink.add(ur); // si tienes la colección inversa en Role: role.getUsersLink().add(ur); } } } public Integer getRoleRank() { return roleRank; } public String getRolesConcat() { return rolesConcat; } public boolean isDeleted() { return deleted; } public void setDeleted(boolean deleted) { this.deleted = deleted; } public LocalDateTime getDeletedAt() { return deletedAt; } public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; } public Long getDeletedBy() { return deletedBy; } public void setDeletedBy(Long deletedBy) { this.deletedBy = deletedBy; } public Set getRolesLink() { return rolesLink; } public void setRolesLink(Set rolesLink) { this.rolesLink = rolesLink; } @Override public String toString() { return "User{" + "id=" + id + ", fullName='" + fullName + '\'' + ", userName='" + userName + '\'' + ", enabled=" + enabled + '}'; } }