feat: enhance user management with matricula retrieval and validation

- Updated user-related queries and mutations to retrieve the matricula from associated funcionario records, improving data accuracy.
- Refactored user creation and listing functionalities to ensure matricula is correctly handled and displayed.
- Enhanced error handling and validation for user operations, ensuring a more robust user management experience.
- Improved the overall structure of user-related code for better maintainability and clarity.
This commit is contained in:
2025-11-04 14:37:28 -03:00
parent d0692c3608
commit fbec5c46c2
10 changed files with 1250 additions and 587 deletions

View File

@@ -4,6 +4,21 @@ import { hashPassword, generateToken } from "./auth/utils";
import { registrarAtividade } from "./logsAtividades";
import { Id, Doc } from "./_generated/dataModel";
import { api } from "./_generated/api";
import type { QueryCtx } from "./_generated/server";
/**
* Helper para obter a matrícula do usuário (do funcionário se houver)
*/
async function obterMatriculaUsuario(
ctx: QueryCtx,
usuario: Doc<"usuarios">
): Promise<string | undefined> {
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
return funcionario?.matricula;
}
return undefined;
}
/**
* Associar funcionário a um usuário
@@ -30,8 +45,11 @@ export const associarFuncionario = mutation({
.first();
if (usuarioExistente && usuarioExistente._id !== args.usuarioId) {
const matricula = await obterMatriculaUsuario(ctx, usuarioExistente);
throw new Error(
`Este funcionário já está associado ao usuário: ${usuarioExistente.nome} (${usuarioExistente.matricula})`
`Este funcionário já está associado ao usuário: ${
usuarioExistente.nome
}${matricula ? ` (${matricula})` : ""}`
);
}
@@ -66,7 +84,6 @@ export const desassociarFuncionario = mutation({
*/
export const criar = mutation({
args: {
matricula: v.string(),
nome: v.string(),
email: v.string(),
roleId: v.id("roles"),
@@ -78,16 +95,6 @@ export const criar = mutation({
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args) => {
// Verificar se matrícula já existe
const existente = await ctx.db
.query("usuarios")
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
.first();
if (existente) {
return { sucesso: false as const, erro: "Matrícula já cadastrada" };
}
// Verificar se email já existe
const emailExistente = await ctx.db
.query("usuarios")
@@ -103,7 +110,6 @@ export const criar = mutation({
// Criar usuário
const usuarioId = await ctx.db.insert("usuarios", {
matricula: args.matricula,
senhaHash,
nome: args.nome,
email: args.email,
@@ -194,9 +200,17 @@ export const listar = query({
handler: async (ctx, args) => {
let usuarios = await ctx.db.query("usuarios").collect();
// Filtrar por matrícula
// Filtrar por matrícula (buscar no funcionário)
if (args.matricula) {
usuarios = usuarios.filter((u) => u.matricula.includes(args.matricula!));
const usuariosComMatricula = await Promise.all(
usuarios.map(async (u) => {
const matricula = await obterMatriculaUsuario(ctx, u);
return { usuario: u, matricula };
})
);
usuarios = usuariosComMatricula
.filter(({ matricula }) => matricula?.includes(args.matricula!))
.map(({ usuario }) => usuario);
}
// Filtrar por ativo
@@ -206,20 +220,25 @@ export const listar = query({
// Buscar roles e funcionários
const resultado = [];
const usuariosSemRole: Array<{ nome: string; matricula: string; roleId: Id<"roles"> }> = [];
const usuariosSemRole: Array<{
nome: string;
matricula: string;
roleId: Id<"roles">;
}> = [];
for (const usuario of usuarios) {
try {
const role = await ctx.db.get(usuario.roleId);
// Se a role não existe, criar uma role de erro mas ainda incluir o usuário
if (!role) {
const matricula = await obterMatriculaUsuario(ctx, usuario);
usuariosSemRole.push({
nome: usuario.nome,
matricula: usuario.matricula,
matricula: matricula || "N/A",
roleId: usuario.roleId,
});
// Filtrar por setor - se filtro está ativo e role não existe, pular
if (args.setor) {
continue;
@@ -240,14 +259,19 @@ export const listar = query({
};
}
} catch (error) {
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
console.error(
`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`,
error
);
}
}
const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario);
// Criar role de erro (sem _creationTime pois a role não existe)
resultado.push({
_id: usuario._id,
matricula: usuario.matricula,
matricula: matriculaUsuario,
nome: usuario.nome,
email: usuario.email,
ativo: usuario.ativo,
@@ -294,7 +318,10 @@ export const listar = query({
};
}
} catch (error) {
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
console.error(
`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`,
error
);
}
}
@@ -305,14 +332,18 @@ export const listar = query({
nome: role.nome,
nivel: role.nivel,
...(role.criadoPor !== undefined && { criadoPor: role.criadoPor }),
...(role.customizado !== undefined && { customizado: role.customizado }),
...(role.customizado !== undefined && {
customizado: role.customizado,
}),
...(role.editavel !== undefined && { editavel: role.editavel }),
...(role.setor !== undefined && { setor: role.setor }),
};
const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario);
resultado.push({
_id: usuario._id,
matricula: usuario.matricula,
matricula: matriculaUsuario,
nome: usuario.nome,
email: usuario.email,
ativo: usuario.ativo,
@@ -334,7 +365,12 @@ export const listar = query({
if (usuariosSemRole.length > 0) {
console.warn(
`⚠️ Encontrados ${usuariosSemRole.length} usuário(s) com perfil ausente:`,
usuariosSemRole.map((u) => `${u.nome} (${u.matricula}) - RoleID: ${u.roleId}`)
usuariosSemRole.map(
(u) =>
`${u.nome}${
u.matricula !== "N/A" ? ` (${u.matricula})` : ""
} - RoleID: ${u.roleId}`
)
);
}
@@ -559,7 +595,9 @@ export const atualizarPerfil = mutation({
}
// Atualizar apenas os campos fornecidos
const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = { atualizadoEm: Date.now() };
const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = {
atualizadoEm: Date.now(),
};
if (args.avatar !== undefined) updates.avatar = args.avatar;
if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil;
@@ -591,7 +629,7 @@ export const obterPerfil = query({
_id: v.id("usuarios"),
nome: v.string(),
email: v.string(),
matricula: v.string(),
matricula: v.optional(v.string()),
funcionarioId: v.optional(v.id("funcionarios")),
avatar: v.optional(v.string()),
fotoPerfil: v.optional(v.id("_storage")),
@@ -675,11 +713,13 @@ export const obterPerfil = query({
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtual.fotoPerfil);
}
const matricula = await obterMatriculaUsuario(ctx, usuarioAtual);
return {
_id: usuarioAtual._id,
nome: usuarioAtual.nome,
email: usuarioAtual.email,
matricula: usuarioAtual.matricula,
matricula: matricula || undefined,
funcionarioId: usuarioAtual.funcionarioId,
avatar: usuarioAtual.avatar,
fotoPerfil: usuarioAtual.fotoPerfil,
@@ -735,11 +775,13 @@ export const listarParaChat = query({
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
const matricula = await obterMatriculaUsuario(ctx, usuario);
return {
_id: usuario._id,
nome: usuario.nome,
email: usuario.email,
matricula: usuario.matricula || undefined,
matricula: matricula || undefined,
avatar: usuario.avatar,
fotoPerfil: usuario.fotoPerfil,
fotoPerfilUrl,
@@ -1035,7 +1077,6 @@ export const editarUsuario = mutation({
*/
export const criarAdminMaster = mutation({
args: {
matricula: v.string(),
nome: v.string(),
email: v.string(),
senha: v.optional(v.string()),
@@ -1074,32 +1115,9 @@ export const criarAdminMaster = mutation({
};
}
// Se já existir usuário por matrícula, promove/atualiza
const existentePorMatricula = await ctx.db
.query("usuarios")
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
.first();
const senhaTemporaria = args.senha || gerarSenhaTemporaria();
const senhaHash = await hashPassword(senhaTemporaria);
if (existentePorMatricula) {
await ctx.db.patch(existentePorMatricula._id, {
nome: args.nome,
email: args.email,
senhaHash,
roleId: roleTIMaster._id,
ativo: true,
primeiroAcesso: true,
atualizadoEm: Date.now(),
});
return {
sucesso: true as const,
usuarioId: existentePorMatricula._id,
senhaTemporaria,
};
}
// Verificar se email já existe
const existentePorEmail = await ctx.db
.query("usuarios")
@@ -1108,7 +1126,6 @@ export const criarAdminMaster = mutation({
if (existentePorEmail) {
// Promove usuário existente por email
await ctx.db.patch(existentePorEmail._id, {
matricula: args.matricula,
nome: args.nome,
senhaHash,
roleId: roleTIMaster._id,
@@ -1125,7 +1142,6 @@ export const criarAdminMaster = mutation({
// Criar novo usuário TI Master
const usuarioId = await ctx.db.insert("usuarios", {
matricula: args.matricula,
senhaHash,
nome: args.nome,
email: args.email,
@@ -1194,7 +1210,6 @@ export const excluirUsuarioLogico = mutation({
*/
export const criarUsuarioCompleto = mutation({
args: {
matricula: v.string(),
nome: v.string(),
email: v.string(),
roleId: v.id("roles"),
@@ -1212,16 +1227,6 @@ export const criarUsuarioCompleto = mutation({
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args) => {
// Verificar se matrícula já existe
const existente = await ctx.db
.query("usuarios")
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
.first();
if (existente) {
return { sucesso: false as const, erro: "Matrícula já cadastrada" };
}
// Verificar se email já existe
const emailExistente = await ctx.db
.query("usuarios")
@@ -1238,7 +1243,6 @@ export const criarUsuarioCompleto = mutation({
// Criar usuário
const usuarioId = await ctx.db.insert("usuarios", {
matricula: args.matricula,
senhaHash,
nome: args.nome,
email: args.email,
@@ -1256,7 +1260,7 @@ export const criarUsuarioCompleto = mutation({
args.criadoPorId,
"criar",
"usuarios",
JSON.stringify({ usuarioId, matricula: args.matricula, nome: args.nome }),
JSON.stringify({ usuarioId, nome: args.nome }),
usuarioId
);
@@ -1272,7 +1276,6 @@ export const criarUsuarioCompleto = mutation({
*/
export const criarAdminPadrao = mutation({
args: {
matricula: v.optional(v.string()),
nome: v.optional(v.string()),
email: v.optional(v.string()),
senha: v.optional(v.string()),
@@ -1282,7 +1285,6 @@ export const criarAdminPadrao = mutation({
usuarioId: v.optional(v.id("usuarios")),
}),
handler: async (ctx, args) => {
const matricula = args.matricula ?? "0000";
const nome = args.nome ?? "Administrador Geral";
const email = args.email ?? "admin@sgse.pe.gov.br";
const senha = args.senha ?? "Admin@123";
@@ -1306,12 +1308,7 @@ export const criarAdminPadrao = mutation({
if (!roleAdmin) return { sucesso: false };
// Verificar se já existe por matrícula ou email
const existentePorMatricula = await ctx.db
.query("usuarios")
.withIndex("by_matricula", (q) => q.eq("matricula", matricula))
.first();
// Verificar se já existe por email
const existentePorEmail = await ctx.db
.query("usuarios")
.withIndex("by_email", (q) => q.eq("email", email))
@@ -1319,10 +1316,8 @@ export const criarAdminPadrao = mutation({
const senhaHash = await hashPassword(senha);
if (existentePorMatricula || existentePorEmail) {
const alvo = existentePorMatricula ?? existentePorEmail!;
await ctx.db.patch(alvo._id, {
matricula,
if (existentePorEmail) {
await ctx.db.patch(existentePorEmail._id, {
nome,
email,
senhaHash,
@@ -1331,11 +1326,10 @@ export const criarAdminPadrao = mutation({
primeiroAcesso: false,
atualizadoEm: Date.now(),
});
return { sucesso: true, usuarioId: alvo._id };
return { sucesso: true, usuarioId: existentePorEmail._id };
}
const usuarioId = await ctx.db.insert("usuarios", {
matricula,
senhaHash,
nome,
email,