Merge remote-tracking branch 'origin/master' into feat-ausencia

This commit is contained in:
2025-11-04 15:10:35 -03:00
12 changed files with 1252 additions and 589 deletions

View File

@@ -294,12 +294,19 @@ export const login = mutation({
timestamp: agora,
});
// Obter matrícula do funcionário se houver
let matricula: string | undefined = undefined;
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula;
}
return {
sucesso: true as const,
token,
usuario: {
_id: usuario._id,
matricula: usuario.matricula,
matricula: matricula || "",
nome: usuario.nome,
email: usuario.email,
funcionarioId: usuario.funcionarioId,
@@ -568,12 +575,19 @@ export const loginComIP = internalMutation({
timestamp: agora,
});
// Obter matrícula do funcionário se houver
let matricula: string | undefined = undefined;
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula;
}
return {
sucesso: true as const,
token,
usuario: {
_id: usuario._id,
matricula: usuario.matricula,
matricula: matricula || "",
nome: usuario.nome,
email: usuario.email,
funcionarioId: usuario.funcionarioId,
@@ -688,11 +702,18 @@ export const verificarSessao = query({
return { valido: false as const, motivo: "Role não encontrada" };
}
// Obter matrícula do funcionário se houver
let matricula: string | undefined = undefined;
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula;
}
return {
valido: true as const,
usuario: {
_id: usuario._id,
matricula: usuario.matricula,
matricula: matricula || "",
nome: usuario.nome,
email: usuario.email,
funcionarioId: usuario.funcionarioId,

View File

@@ -94,7 +94,9 @@ export const criarConversa = mutation({
conversaId,
remetenteId: usuarioAtual._id,
titulo: "Adicionado a grupo",
descricao: `Você foi adicionado ao grupo "${args.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
descricao: `Você foi adicionado ao grupo "${
args.nome || "Sem nome"
}" por ${usuarioAtual.nome}`,
lida: false,
criadaEm: Date.now(),
});
@@ -226,8 +228,9 @@ export const enviarMensagem = mutation({
for (const participanteId of conversa.participantes) {
// ✅ MODIFICADO: Permite notificação para si mesmo se flag estiver ativa
const ehOMesmoUsuario = participanteId === usuarioAtual._id;
const deveCriarNotificacao = !ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo;
const deveCriarNotificacao =
!ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo;
if (deveCriarNotificacao) {
const tipoNotificacao = args.mencoes?.includes(participanteId)
? "mencao"
@@ -318,7 +321,10 @@ export const cancelarMensagemAgendada = mutation({
}
if (mensagem.remetenteId !== usuarioAtual._id) {
return { sucesso: false, erro: "Você só pode cancelar suas próprias mensagens" };
return {
sucesso: false,
erro: "Você só pode cancelar suas próprias mensagens",
};
}
if (!mensagem.agendadaPara) {
@@ -611,16 +617,20 @@ export const listarConversas = query({
// Para conversas individuais, pegar o outro usuário
let outroUsuario = null;
if (conversa.tipo === "individual") {
const outroUsuarioRaw = participantes.find((p) => p?._id !== usuarioAtual._id);
const outroUsuarioRaw = participantes.find(
(p) => p?._id !== usuarioAtual._id
);
if (outroUsuarioRaw) {
// 🔄 BUSCAR DADOS ATUALIZADOS DO USUÁRIO (não usar snapshot)
const usuarioAtualizado = await ctx.db.get(outroUsuarioRaw._id);
if (usuarioAtualizado) {
// Adicionar URL da foto de perfil
let fotoPerfilUrl = null;
if (usuarioAtualizado.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtualizado.fotoPerfil);
fotoPerfilUrl = await ctx.storage.getUrl(
usuarioAtualizado.fotoPerfil
);
}
outroUsuario = {
...usuarioAtualizado,
@@ -643,7 +653,7 @@ export const listarConversas = query({
.query("mensagens")
.withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id))
.collect();
const mensagens = todasMensagens.filter((m) => !m.agendadaPara);
let naoLidas = 0;
@@ -756,7 +766,16 @@ export const obterMensagensAgendadas = query({
*/
export const listarAgendamentosChat = query({
args: {},
handler: async (ctx): Promise<Array<Doc<"mensagens"> & { conversaInfo: Doc<"conversas"> | null; destinatarioInfo: Doc<"usuarios"> | null }>> => {
handler: async (
ctx
): Promise<
Array<
Doc<"mensagens"> & {
conversaInfo: Doc<"conversas"> | null;
destinatarioInfo: Doc<"usuarios"> | null;
}
>
> => {
const usuarioAtual = await getUsuarioAutenticado(ctx);
if (!usuarioAtual) {
return [];
@@ -912,20 +931,31 @@ export const listarTodosUsuarios = query({
.withIndex("by_ativo", (q) => q.eq("ativo", true))
.collect();
// Excluir o usuário atual
return usuarios
.filter((u) => u._id !== usuarioAtual._id)
.map((u) => ({
_id: u._id,
nome: u.nome,
email: u.email,
matricula: u.matricula,
avatar: u.avatar,
fotoPerfil: u.fotoPerfil,
statusPresenca: u.statusPresenca,
statusMensagem: u.statusMensagem,
setor: u.setor,
}));
// Excluir o usuário atual e buscar matrículas
const usuariosComMatricula = await Promise.all(
usuarios
.filter((u) => u._id !== usuarioAtual._id)
.map(async (u) => {
let matricula: string | undefined = undefined;
if (u.funcionarioId) {
const funcionario = await ctx.db.get(u.funcionarioId);
matricula = funcionario?.matricula;
}
return {
_id: u._id,
nome: u.nome,
email: u.email,
matricula,
avatar: u.avatar,
fotoPerfil: u.fotoPerfil,
statusPresenca: u.statusPresenca,
statusMensagem: u.statusMensagem,
setor: u.setor,
};
})
);
return usuariosComMatricula;
},
});

View File

@@ -88,9 +88,14 @@ export const listar = query({
if (log.usuarioId) {
const user = await ctx.db.get(log.usuarioId);
if (user) {
let matricula: string | undefined = undefined;
if (user.funcionarioId) {
const funcionario = await ctx.db.get(user.funcionarioId);
matricula = funcionario?.matricula;
}
usuario = {
_id: user._id,
matricula: user.matricula,
matricula: matricula || "",
nome: user.nome,
};
}

View File

@@ -78,10 +78,15 @@ export const listarAtividades = query({
const atividadesComUsuarios = await Promise.all(
atividades.map(async (atividade) => {
const usuario = await ctx.db.get(atividade.usuarioId);
let matricula = "N/A";
if (usuario?.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula || "N/A";
}
return {
...atividade,
usuarioNome: usuario?.nome || "Usuário Desconhecido",
usuarioMatricula: usuario?.matricula || "N/A",
usuarioMatricula: matricula,
};
})
);
@@ -157,10 +162,15 @@ export const obterHistoricoRecurso = query({
const atividadesComUsuarios = await Promise.all(
atividades.map(async (atividade) => {
const usuario = await ctx.db.get(atividade.usuarioId);
let matricula = "N/A";
if (usuario?.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula || "N/A";
}
return {
...atividade,
usuarioNome: usuario?.nome || "Usuário Desconhecido",
usuarioMatricula: usuario?.matricula || "N/A",
usuarioMatricula: matricula,
};
})
);

View File

@@ -30,18 +30,19 @@ export default defineSchema({
simboloId: v.id("simbolos"),
simboloTipo: simboloTipo,
gestorId: v.optional(v.id("usuarios")),
statusFerias: v.optional(v.union(
v.literal("ativo"),
v.literal("em_ferias")
)),
statusFerias: v.optional(
v.union(v.literal("ativo"), v.literal("em_ferias"))
),
// Regime de trabalho (para cálculo correto de férias)
regimeTrabalho: v.optional(v.union(
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
v.literal("estatutario_federal"), // Servidor Público Federal
v.literal("estatutario_municipal") // Servidor Público Municipal
)),
regimeTrabalho: v.optional(
v.union(
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
v.literal("estatutario_federal"), // Servidor Público Federal
v.literal("estatutario_municipal") // Servidor Público Municipal
)
),
// Dados Pessoais Adicionais (opcionais)
nomePai: v.optional(v.string()),
@@ -191,10 +192,7 @@ export default defineSchema({
licencas: defineTable({
funcionarioId: v.id("funcionarios"),
tipo: v.union(
v.literal("maternidade"),
v.literal("paternidade")
),
tipo: v.union(v.literal("maternidade"), v.literal("paternidade")),
dataInicio: v.string(),
dataFim: v.string(),
documentoId: v.optional(v.id("_storage")),
@@ -237,11 +235,15 @@ export default defineSchema({
data: v.number(),
usuarioId: v.id("usuarios"),
acao: v.string(),
periodosAnteriores: v.optional(v.array(v.object({
dataInicio: v.string(),
dataFim: v.string(),
diasCorridos: v.number(),
}))),
periodosAnteriores: v.optional(
v.array(
v.object({
dataInicio: v.string(),
dataFim: v.string(),
diasCorridos: v.number(),
})
)
),
})
)
),
@@ -379,7 +381,6 @@ export default defineSchema({
// Sistema de Autenticação e Controle de Acesso
usuarios: defineTable({
matricula: v.string(),
senhaHash: v.string(), // Senha criptografada com bcrypt
nome: v.string(),
email: v.string(),
@@ -416,7 +417,6 @@ export default defineSchema({
notificacoesAtivadas: v.optional(v.boolean()),
somNotificacao: v.optional(v.boolean()),
})
.index("by_matricula", ["matricula"])
.index("by_email", ["email"])
.index("by_role", ["roleId"])
.index("by_ativo", ["ativo"])
@@ -536,7 +536,7 @@ export default defineSchema({
.index("by_data_inicio", ["dataInicio"]),
// Perfis Customizados
// Templates de Mensagens
templatesMensagens: defineTable({
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
@@ -699,8 +699,7 @@ export default defineSchema({
mensagensPorMinuto: v.optional(v.number()),
tempoRespostaMedio: v.optional(v.number()),
errosCount: v.optional(v.number()),
})
.index("by_timestamp", ["timestamp"]),
}).index("by_timestamp", ["timestamp"]),
alertConfigurations: defineTable({
metricName: v.string(),
@@ -717,8 +716,7 @@ export default defineSchema({
notifyByChat: v.boolean(),
createdBy: v.id("usuarios"),
lastModified: v.number(),
})
.index("by_enabled", ["enabled"]),
}).index("by_enabled", ["enabled"]),
alertHistory: defineTable({
configId: v.id("alertConfigurations"),

View File

@@ -316,7 +316,6 @@ export const seedDatabase = internalMutation({
const senhaInicial = await hashPassword("Mudar@123");
await ctx.db.insert("usuarios", {
matricula: funcionario.matricula,
senhaHash: senhaInicial,
nome: funcionario.nome,
email: funcionario.email,

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,

View File

@@ -3,7 +3,7 @@ import { v } from "convex/values";
import { Id, Doc } from "./_generated/dataModel";
/**
* Verificar duplicatas de matrícula
* Verificar duplicatas de matrícula (agora busca do funcionário associado)
*/
export const verificarDuplicatas = query({
args: {},
@@ -23,18 +23,27 @@ export const verificarDuplicatas = query({
handler: async (ctx) => {
const usuarios = await ctx.db.query("usuarios").collect();
// Agrupar por matrícula
const gruposPorMatricula = usuarios.reduce((acc, usuario) => {
if (!acc[usuario.matricula]) {
acc[usuario.matricula] = [];
// Agrupar por matrícula do funcionário associado
const gruposPorMatricula: Record<string, Array<{ _id: Id<"usuarios">; nome: string; email: string }>> = {};
for (const usuario of usuarios) {
let matricula: string | undefined = undefined;
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula;
}
acc[usuario.matricula].push({
_id: usuario._id,
nome: usuario.nome,
email: usuario.email || "",
});
return acc;
}, {} as Record<string, Array<{ _id: Id<"usuarios">; nome: string; email: string }>>);
if (matricula) {
if (!gruposPorMatricula[matricula]) {
gruposPorMatricula[matricula] = [];
}
gruposPorMatricula[matricula].push({
_id: usuario._id,
nome: usuario.nome,
email: usuario.email || "",
});
}
}
// Filtrar apenas duplicatas
const duplicatas = Object.entries(gruposPorMatricula)
@@ -50,7 +59,7 @@ export const verificarDuplicatas = query({
});
/**
* Remover duplicatas mantendo apenas o mais recente
* Remover duplicatas mantendo apenas o mais recente (agora busca do funcionário associado)
*/
export const removerDuplicatas = internalMutation({
args: {},
@@ -61,14 +70,23 @@ export const removerDuplicatas = internalMutation({
handler: async (ctx) => {
const usuarios = await ctx.db.query("usuarios").collect();
// Agrupar por matrícula
const gruposPorMatricula = usuarios.reduce((acc, usuario) => {
if (!acc[usuario.matricula]) {
acc[usuario.matricula] = [];
// Agrupar por matrícula do funcionário associado
const gruposPorMatricula: Record<string, Doc<"usuarios">[]> = {};
for (const usuario of usuarios) {
let matricula: string | undefined = undefined;
if (usuario.funcionarioId) {
const funcionario = await ctx.db.get(usuario.funcionarioId);
matricula = funcionario?.matricula;
}
acc[usuario.matricula].push(usuario);
return acc;
}, {} as Record<string, Doc<"usuarios">[]>);
if (matricula) {
if (!gruposPorMatricula[matricula]) {
gruposPorMatricula[matricula] = [];
}
gruposPorMatricula[matricula].push(usuario);
}
}
let removidos = 0;
const matriculasDuplicadas: string[] = [];