feat: enhance employee and symbol management with new features, improved UI components, and backend schema updates
This commit is contained in:
525
packages/backend/convex/menuPermissoes.ts
Normal file
525
packages/backend/convex/menuPermissoes.ts
Normal file
@@ -0,0 +1,525 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import type { Id } from "./_generated/dataModel";
|
||||
|
||||
/**
|
||||
* Lista de menus do sistema
|
||||
*/
|
||||
export const MENUS_SISTEMA = [
|
||||
{ path: "/recursos-humanos", nome: "Recursos Humanos", descricao: "Gestão de funcionários e símbolos" },
|
||||
{ path: "/recursos-humanos/funcionarios", nome: "Funcionários", descricao: "Cadastro e gestão de funcionários" },
|
||||
{ path: "/recursos-humanos/simbolos", nome: "Símbolos", descricao: "Cadastro e gestão de símbolos" },
|
||||
{ path: "/financeiro", nome: "Financeiro", descricao: "Gestão financeira" },
|
||||
{ path: "/controladoria", nome: "Controladoria", descricao: "Controle e auditoria" },
|
||||
{ path: "/licitacoes", nome: "Licitações", descricao: "Gestão de licitações" },
|
||||
{ path: "/compras", nome: "Compras", descricao: "Gestão de compras" },
|
||||
{ path: "/juridico", nome: "Jurídico", descricao: "Departamento jurídico" },
|
||||
{ path: "/comunicacao", nome: "Comunicação", descricao: "Gestão de comunicação" },
|
||||
{ path: "/programas-esportivos", nome: "Programas Esportivos", descricao: "Gestão de programas esportivos" },
|
||||
{ path: "/secretaria-executiva", nome: "Secretaria Executiva", descricao: "Secretaria executiva" },
|
||||
{ path: "/gestao-pessoas", nome: "Gestão de Pessoas", descricao: "Gestão de recursos humanos" },
|
||||
{ path: "/ti", nome: "Tecnologia da Informação", descricao: "TI e suporte técnico" },
|
||||
{ path: "/ti/painel-administrativo", nome: "Painel Administrativo TI", descricao: "Painel de administração do sistema" },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Listar todas as permissões de menu para uma role
|
||||
*/
|
||||
export const listarPorRole = query({
|
||||
args: { roleId: v.id("roles") },
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("menuPermissoes"),
|
||||
roleId: v.id("roles"),
|
||||
menuPath: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", args.roleId))
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Verificar se um usuário tem permissão para acessar um menu
|
||||
* Prioridade: Permissão personalizada > Permissão da role
|
||||
*/
|
||||
export const verificarAcesso = query({
|
||||
args: {
|
||||
usuarioId: v.id("usuarios"),
|
||||
menuPath: v.string(),
|
||||
},
|
||||
returns: v.object({
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
motivo: v.optional(v.string()),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar o usuário
|
||||
const usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return {
|
||||
podeAcessar: false,
|
||||
podeConsultar: false,
|
||||
podeGravar: false,
|
||||
motivo: "Usuário não encontrado",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se o usuário está ativo
|
||||
if (!usuario.ativo) {
|
||||
return {
|
||||
podeAcessar: false,
|
||||
podeConsultar: false,
|
||||
podeGravar: false,
|
||||
motivo: "Usuário inativo",
|
||||
};
|
||||
}
|
||||
|
||||
// Buscar a role do usuário
|
||||
const role = await ctx.db.get(usuario.roleId);
|
||||
if (!role) {
|
||||
return {
|
||||
podeAcessar: false,
|
||||
podeConsultar: false,
|
||||
podeGravar: false,
|
||||
motivo: "Role não encontrada",
|
||||
};
|
||||
}
|
||||
|
||||
// Admin (nível 0) e TI (nível 1) têm acesso total
|
||||
if (role.nivel <= 1) {
|
||||
return {
|
||||
podeAcessar: true,
|
||||
podeConsultar: true,
|
||||
podeGravar: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Dashboard e Solicitar Acesso são públicos
|
||||
if (args.menuPath === "/" || args.menuPath === "/solicitar-acesso") {
|
||||
return {
|
||||
podeAcessar: true,
|
||||
podeConsultar: true,
|
||||
podeGravar: false,
|
||||
};
|
||||
}
|
||||
|
||||
// 1. Verificar se existe permissão personalizada para este usuário
|
||||
const permissaoPersonalizada = await ctx.db
|
||||
.query("menuPermissoesPersonalizadas")
|
||||
.withIndex("by_usuario_and_menu", (q) =>
|
||||
q.eq("usuarioId", args.usuarioId).eq("menuPath", args.menuPath)
|
||||
)
|
||||
.first();
|
||||
|
||||
if (permissaoPersonalizada) {
|
||||
return {
|
||||
podeAcessar: permissaoPersonalizada.podeAcessar,
|
||||
podeConsultar: permissaoPersonalizada.podeConsultar,
|
||||
podeGravar: permissaoPersonalizada.podeGravar,
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Se não houver permissão personalizada, verificar permissão da role
|
||||
const permissaoRole = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role_and_menu", (q) =>
|
||||
q.eq("roleId", usuario.roleId).eq("menuPath", args.menuPath)
|
||||
)
|
||||
.first();
|
||||
|
||||
if (!permissaoRole) {
|
||||
return {
|
||||
podeAcessar: false,
|
||||
podeConsultar: false,
|
||||
podeGravar: false,
|
||||
motivo: "Sem permissão configurada para este menu",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
podeAcessar: permissaoRole.podeAcessar,
|
||||
podeConsultar: permissaoRole.podeConsultar,
|
||||
podeGravar: permissaoRole.podeGravar,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Atualizar ou criar permissão de menu para uma role
|
||||
*/
|
||||
export const atualizarPermissao = mutation({
|
||||
args: {
|
||||
roleId: v.id("roles"),
|
||||
menuPath: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
},
|
||||
returns: v.id("menuPermissoes"),
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar se já existe uma permissão
|
||||
const existente = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role_and_menu", (q) =>
|
||||
q.eq("roleId", args.roleId).eq("menuPath", args.menuPath)
|
||||
)
|
||||
.first();
|
||||
|
||||
if (existente) {
|
||||
// Atualizar permissão existente
|
||||
await ctx.db.patch(existente._id, {
|
||||
podeAcessar: args.podeAcessar,
|
||||
podeConsultar: args.podeConsultar,
|
||||
podeGravar: args.podeGravar,
|
||||
});
|
||||
return existente._id;
|
||||
} else {
|
||||
// Criar nova permissão
|
||||
return await ctx.db.insert("menuPermissoes", {
|
||||
roleId: args.roleId,
|
||||
menuPath: args.menuPath,
|
||||
podeAcessar: args.podeAcessar,
|
||||
podeConsultar: args.podeConsultar,
|
||||
podeGravar: args.podeGravar,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Remover permissão de menu
|
||||
*/
|
||||
export const removerPermissao = mutation({
|
||||
args: {
|
||||
permissaoId: v.id("menuPermissoes"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.delete(args.permissaoId);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Inicializar permissões padrão para uma role
|
||||
*/
|
||||
export const inicializarPermissoesRole = mutation({
|
||||
args: {
|
||||
roleId: v.id("roles"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar a role
|
||||
const role = await ctx.db.get(args.roleId);
|
||||
if (!role) {
|
||||
throw new Error("Role não encontrada");
|
||||
}
|
||||
|
||||
// Admin e TI não precisam de permissões específicas (acesso total)
|
||||
if (role.nivel <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Para outras roles, criar permissões básicas (apenas consulta)
|
||||
for (const menu of MENUS_SISTEMA) {
|
||||
// Verificar se já existe permissão
|
||||
const existente = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role_and_menu", (q) =>
|
||||
q.eq("roleId", args.roleId).eq("menuPath", menu.path)
|
||||
)
|
||||
.first();
|
||||
|
||||
if (!existente) {
|
||||
// Criar permissão padrão (sem acesso)
|
||||
await ctx.db.insert("menuPermissoes", {
|
||||
roleId: args.roleId,
|
||||
menuPath: menu.path,
|
||||
podeAcessar: false,
|
||||
podeConsultar: false,
|
||||
podeGravar: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar todos os menus do sistema
|
||||
*/
|
||||
export const listarMenus = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
path: v.string(),
|
||||
nome: v.string(),
|
||||
descricao: v.string(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
return MENUS_SISTEMA.map((menu) => ({
|
||||
path: menu.path,
|
||||
nome: menu.nome,
|
||||
descricao: menu.descricao,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter matriz de permissões (role x menu) para o painel de controle
|
||||
*/
|
||||
export const obterMatrizPermissoes = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
role: v.object({
|
||||
_id: v.id("roles"),
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
descricao: v.string(),
|
||||
}),
|
||||
permissoes: v.array(
|
||||
v.object({
|
||||
menuPath: v.string(),
|
||||
menuNome: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
permissaoId: v.optional(v.id("menuPermissoes")),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
// Buscar todas as roles (exceto Admin e TI que têm acesso total)
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
|
||||
const matriz = [];
|
||||
|
||||
for (const role of roles) {
|
||||
const permissoes = [];
|
||||
|
||||
for (const menu of MENUS_SISTEMA) {
|
||||
// Buscar permissão específica
|
||||
const permissao = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role_and_menu", (q) =>
|
||||
q.eq("roleId", role._id).eq("menuPath", menu.path)
|
||||
)
|
||||
.first();
|
||||
|
||||
// Admin e TI têm acesso total automático
|
||||
if (role.nivel <= 1) {
|
||||
permissoes.push({
|
||||
menuPath: menu.path,
|
||||
menuNome: menu.nome,
|
||||
podeAcessar: true,
|
||||
podeConsultar: true,
|
||||
podeGravar: true,
|
||||
permissaoId: permissao?._id,
|
||||
});
|
||||
} else {
|
||||
permissoes.push({
|
||||
menuPath: menu.path,
|
||||
menuNome: menu.nome,
|
||||
podeAcessar: permissao?.podeAcessar ?? false,
|
||||
podeConsultar: permissao?.podeConsultar ?? false,
|
||||
podeGravar: permissao?.podeGravar ?? false,
|
||||
permissaoId: permissao?._id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
matriz.push({
|
||||
role: {
|
||||
_id: role._id,
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
descricao: role.descricao,
|
||||
},
|
||||
permissoes,
|
||||
});
|
||||
}
|
||||
|
||||
return matriz;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Criar ou atualizar permissão personalizada por matrícula
|
||||
*/
|
||||
export const atualizarPermissaoPersonalizada = mutation({
|
||||
args: {
|
||||
matricula: v.string(),
|
||||
menuPath: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
},
|
||||
returns: v.union(v.id("menuPermissoesPersonalizadas"), v.null()),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar usuário pela matrícula
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.first();
|
||||
|
||||
if (!usuario) {
|
||||
throw new Error("Usuário não encontrado com esta matrícula");
|
||||
}
|
||||
|
||||
// Verificar se já existe permissão personalizada
|
||||
const existente = await ctx.db
|
||||
.query("menuPermissoesPersonalizadas")
|
||||
.withIndex("by_usuario_and_menu", (q) =>
|
||||
q.eq("usuarioId", usuario._id).eq("menuPath", args.menuPath)
|
||||
)
|
||||
.first();
|
||||
|
||||
if (existente) {
|
||||
// Atualizar permissão existente
|
||||
await ctx.db.patch(existente._id, {
|
||||
podeAcessar: args.podeAcessar,
|
||||
podeConsultar: args.podeConsultar,
|
||||
podeGravar: args.podeGravar,
|
||||
});
|
||||
return existente._id;
|
||||
} else {
|
||||
// Criar nova permissão
|
||||
return await ctx.db.insert("menuPermissoesPersonalizadas", {
|
||||
usuarioId: usuario._id,
|
||||
matricula: args.matricula,
|
||||
menuPath: args.menuPath,
|
||||
podeAcessar: args.podeAcessar,
|
||||
podeConsultar: args.podeConsultar,
|
||||
podeGravar: args.podeGravar,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Remover permissão personalizada
|
||||
*/
|
||||
export const removerPermissaoPersonalizada = mutation({
|
||||
args: {
|
||||
permissaoId: v.id("menuPermissoesPersonalizadas"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.delete(args.permissaoId);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar permissões personalizadas de um usuário por matrícula
|
||||
*/
|
||||
export const listarPermissoesPersonalizadas = query({
|
||||
args: {
|
||||
matricula: v.string(),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("menuPermissoesPersonalizadas"),
|
||||
menuPath: v.string(),
|
||||
menuNome: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar usuário
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.first();
|
||||
|
||||
if (!usuario) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Buscar permissões personalizadas
|
||||
const permissoes = await ctx.db
|
||||
.query("menuPermissoesPersonalizadas")
|
||||
.withIndex("by_usuario", (q) => q.eq("usuarioId", usuario._id))
|
||||
.collect();
|
||||
|
||||
// Mapear com nomes dos menus
|
||||
return permissoes.map((p) => {
|
||||
const menu = MENUS_SISTEMA.find((m) => m.path === p.menuPath);
|
||||
return {
|
||||
_id: p._id,
|
||||
menuPath: p.menuPath,
|
||||
menuNome: menu?.nome || p.menuPath,
|
||||
podeAcessar: p.podeAcessar,
|
||||
podeConsultar: p.podeConsultar,
|
||||
podeGravar: p.podeGravar,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Buscar usuário por matrícula para o painel de personalização
|
||||
*/
|
||||
export const buscarUsuarioPorMatricula = query({
|
||||
args: {
|
||||
matricula: v.string(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
role: v.object({
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
descricao: v.string(),
|
||||
}),
|
||||
ativo: v.boolean(),
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.first();
|
||||
|
||||
if (!usuario) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const role = await ctx.db.get(usuario.roleId);
|
||||
if (!role) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
_id: usuario._id,
|
||||
matricula: usuario.matricula,
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
role: {
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
descricao: role.descricao,
|
||||
},
|
||||
ativo: usuario.ativo,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user