diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index ea4bfea..3c40c08 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -2,6 +2,7 @@ import { api } from '@sgse-app/backend/convex/_generated/api'; import { useConvexClient, useQuery } from 'convex-svelte'; import type { FunctionReference } from 'convex/server'; + import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { Home, User, @@ -73,7 +74,7 @@ } // Estrutura do menu definida no frontend - const MENU_STRUCTURE: MenuItem[] = [ + const MENU_STRUCTURE = [ { label: 'Dashboard', icon: 'Home', @@ -83,12 +84,44 @@ label: 'Gestão de Pessoas', icon: 'Users', link: '/recursos-humanos', - permission: { recurso: 'funcionarios', acao: 'ver' }, + permission: { recurso: 'gestao_pessoas', acao: 'ver' }, submenus: [ { label: 'Funcionários', link: '/recursos-humanos/funcionarios', - permission: { recurso: 'funcionarios', acao: 'listar' } + permission: { recurso: 'funcionarios', acao: 'listar' }, + exact: true + }, + { + label: 'Cadastro de Funcionários', + link: '/recursos-humanos/funcionarios/cadastro', + permission: { recurso: 'funcionarios', acao: 'criar' } + }, + { + label: 'Exclusão de Funcionários', + link: '/recursos-humanos/funcionarios/excluir', + permission: { recurso: 'funcionarios', acao: 'excluir' } + }, + { + label: 'Férias', + link: '/recursos-humanos/ferias', + permission: { recurso: 'ferias', acao: 'dashboard' } + }, + { + label: 'Atestados de Licenças', + link: '/recursos-humanos/atestados-licencas', + permission: { recurso: 'atestados_licencas', acao: 'listar' } + }, + { + label: 'Controle de Ponto', + link: '/recursos-humanos/controle-ponto', + permission: { recurso: 'ponto', acao: 'ver' }, + exact: true + }, + { + label: 'Banco de Horas', + link: '/recursos-humanos/controle-ponto/banco-horas', + permission: { recurso: 'banco_horas', acao: 'ver' } }, { label: 'Registro de Ponto', @@ -129,25 +162,25 @@ { label: 'Objetos', icon: 'Tag', - link: resolve('/compras/objetos'), + link: '/compras/objetos', permission: { recurso: 'objetos', acao: 'listar' } }, { label: 'Atas de Registro', icon: 'FileText', - link: resolve('/compras/atas'), + link: '/compras/atas', permission: { recurso: 'atas', acao: 'listar' } }, { label: 'Contratos', icon: 'FileText', - link: resolve('/licitacoes/contratos'), + link: '/licitacoes/contratos', permission: { recurso: 'contratos', acao: 'listar' } }, { label: 'Empresas', icon: 'Briefcase', - link: resolve('/licitacoes/empresas'), + link: '/licitacoes/empresas', permission: { recurso: 'empresas', acao: 'listar' } }, { @@ -174,7 +207,7 @@ link: '/ti', permission: { recurso: 'ti_painel_administrativo', acao: 'ver' } } - ]; + ] as const satisfies readonly MenuItem[]; type IconType = typeof Home; @@ -192,6 +225,20 @@ const permissionsQuery = useQuery(api.menu.getUserPermissions as FunctionReference<'query'>, {}); // Filtrar menu baseado nas permissões do usuário + function filterSubmenusByPermissions( + items: SubMenuItem[], + isMaster: boolean, + permissionsSet: Set + ): SubMenuItem[] { + if (isMaster) return items; + + return items.filter((item) => { + if (!item.permission) return true; + const key = `${item.permission.recurso}.${item.permission.acao}`; + return permissionsSet.has(key); + }); + } + function filterMenuByPermissions( items: MenuItem[], isMaster: boolean, @@ -199,30 +246,32 @@ ): MenuItem[] { if (isMaster) return items; - return items - .map((item) => { - // Verifica permissão do item atual - let hasPermission = true; - if (item.permission) { - const key = `${item.permission.recurso}.${item.permission.acao}`; - hasPermission = permissionsSet.has(key); - } + const filtered: MenuItem[] = []; - // Se não tem permissão, não mostra - if (!hasPermission) return null; + for (const item of items) { + // Verifica permissão do item atual + let hasPermission = true; + if (item.permission) { + const key = `${item.permission.recurso}.${item.permission.acao}`; + hasPermission = permissionsSet.has(key); + } - // Se tiver submenus, filtra eles recursivamente - let filteredSubmenus: MenuItem[] | undefined = undefined; - if (item.submenus) { - filteredSubmenus = filterMenuByPermissions(item.submenus, isMaster, permissionsSet); - } + if (!hasPermission) continue; - return { - ...item, - submenus: filteredSubmenus && filteredSubmenus.length > 0 ? filteredSubmenus : undefined - }; - }) - .filter((item): item is MenuItem => item !== null); + // Se tiver submenus, filtra e só mantém se sobrar algo + let filteredSubmenus: SubMenuItem[] | undefined = undefined; + if (item.submenus) { + const subs = filterSubmenusByPermissions(item.submenus, isMaster, permissionsSet); + filteredSubmenus = subs.length > 0 ? subs : undefined; + } + + filtered.push({ + ...item, + submenus: filteredSubmenus + }); + } + + return filtered; } // Menu filtrado reativo @@ -230,7 +279,7 @@ const data = permissionsQuery.data; if (!data) return []; - const permissionsSet = new Set(data.permissions); + const permissionsSet = new Set((data.permissions ?? []) as string[]); return filterMenuByPermissions(MENU_STRUCTURE, data.isMaster, permissionsSet); }); @@ -438,7 +487,11 @@ } // Buscar o usuário no Convex usando getCurrentUser - const usuario = await convexClient.query(api.auth.getCurrentUser, {}); + // (o typesafe FunctionReference pode falhar aqui; tipamos o retorno e mantemos a chamada) + const usuario = (await convexClient.query( + api.auth.getCurrentUser as unknown as FunctionReference<'query'>, + {} + )) as { _id?: Id<'usuarios'> } | null; if (usuario && usuario._id) { await convexClient.mutation(api.logsLogin.registrarTentativaLogin, { diff --git a/bun.lock b/bun.lock index 9c6d4fd..d892394 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "sgse-app", @@ -20,7 +21,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.7.1", "svelte-dnd-action": "^0.9.67", - "turbo": "^2.5.8", + "turbo": "^2.6.3", "typescript-eslint": "^8.46.3", }, }, @@ -1475,19 +1476,19 @@ "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="], - "turbo": ["turbo@2.5.8", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.8", "turbo-darwin-arm64": "2.5.8", "turbo-linux-64": "2.5.8", "turbo-linux-arm64": "2.5.8", "turbo-windows-64": "2.5.8", "turbo-windows-arm64": "2.5.8" }, "bin": { "turbo": "bin/turbo" } }, "sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w=="], + "turbo": ["turbo@2.6.3", "", { "optionalDependencies": { "turbo-darwin-64": "2.6.3", "turbo-darwin-arm64": "2.6.3", "turbo-linux-64": "2.6.3", "turbo-linux-arm64": "2.6.3", "turbo-windows-64": "2.6.3", "turbo-windows-arm64": "2.6.3" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bf6YKUv11l5Xfcmg76PyWoy/e2vbkkxFNBGJSnfdSXQC33ZiUfutYh6IXidc5MhsnrFkWfdNNLyaRk+kHMLlwA=="], - "turbo-darwin-64": ["turbo-darwin-64@2.5.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh5bCACiHO8rUXZLpKw+m3FiHtAp2CkanSyJre+SInEvEr5kIxjGvCK/8MFX8SFRjQuhjtvpIvYYZJB4AGCxNQ=="], + "turbo-darwin-64": ["turbo-darwin-64@2.6.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-f1H/tQC9px7+hmXn6Kx/w8Jd/FneIUnvLlcI/7RGHunxfOkKJKvsoiNzySkoHQ8uq1pJnhJ0xNGTlYM48ZaJOQ=="], + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.6.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MwVt7rBKiOK7zdYerenfCRTypefw4kZCue35IJga9CH1+S50+KTiCkT6LBqo0hHeoH2iKuI0ldTF2a0aB72z3w=="], - "turbo-linux-64": ["turbo-linux-64@2.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-hMyvc7w7yadBlZBGl/bnR6O+dJTx3XkTeyTTH4zEjERO6ChEs0SrN8jTFj1lueNXKIHh1SnALmy6VctKMGnWfw=="], + "turbo-linux-64": ["turbo-linux-64@2.6.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cqpcw+dXxbnPtNnzeeSyWprjmuFVpHJqKcs7Jym5oXlu/ZcovEASUIUZVN3OGEM6Y/OTyyw0z09tOHNt5yBAVg=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-LQELGa7bAqV2f+3rTMRPnj5G/OHAe2U+0N9BwsZvfMvHSUbsQ3bBMWdSQaYNicok7wOZcHjz2TkESn1hYK6xIQ=="], + "turbo-linux-arm64": ["turbo-linux-arm64@2.6.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-MterpZQmjXyr4uM7zOgFSFL3oRdNKeflY7nsjxJb2TklsYqiu3Z9pQ4zRVFFH8n0mLGna7MbQMZuKoWqqHb45w=="], - "turbo-windows-64": ["turbo-windows-64@2.5.8", "", { "os": "win32", "cpu": "x64" }, "sha512-3YdcaW34TrN1AWwqgYL9gUqmZsMT4T7g8Y5Azz+uwwEJW+4sgcJkIi9pYFyU4ZBSjBvkfuPZkGgfStir5BBDJQ=="], + "turbo-windows-64": ["turbo-windows-64@2.6.3", "", { "os": "win32", "cpu": "x64" }, "sha512-biDU70v9dLwnBdLf+daoDlNJVvqOOP8YEjqNipBHzgclbQlXbsi6Gqqelp5er81Qo3BiRgmTNx79oaZQTPb07Q=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.5.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-eFC5XzLmgXJfnAK3UMTmVECCwuBcORrWdewoiXBnUm934DY6QN8YowC/srhNnROMpaKaqNeRpoB5FxCww3eteQ=="], + "turbo-windows-arm64": ["turbo-windows-arm64@2.6.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDHVKpSeukah3VsI/xMEKeTnV9V9cjlpFSUs4bmsUiLu3Yv2ENlgVEZv65wxbeE0bh0jjpmElDT+P1KaCxArQQ=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], diff --git a/package.json b/package.json index e1b92d2..ec4e9c1 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.7.1", "svelte-dnd-action": "^0.9.67", - "turbo": "^2.5.8", + "turbo": "^2.6.3", "typescript-eslint": "^8.46.3" }, "dependencies": { diff --git a/packages/backend/convex/acoes.ts b/packages/backend/convex/acoes.ts index 3f515f1..1f23c1c 100644 --- a/packages/backend/convex/acoes.ts +++ b/packages/backend/convex/acoes.ts @@ -1,10 +1,15 @@ import { v } from 'convex/values'; import { mutation, query } from './_generated/server'; import { getCurrentUserFunction } from './auth'; +import { internal } from './_generated/api'; export const list = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'acoes', + acao: 'listar' + }); return await ctx.db.query('acoes').collect(); } }); @@ -15,6 +20,11 @@ export const create = mutation({ tipo: v.union(v.literal('projeto'), v.literal('lei')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'acoes', + acao: 'criar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -33,6 +43,11 @@ export const update = mutation({ tipo: v.union(v.literal('projeto'), v.literal('lei')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'acoes', + acao: 'editar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -48,6 +63,11 @@ export const remove = mutation({ id: v.id('acoes') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'acoes', + acao: 'excluir' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); diff --git a/packages/backend/convex/atas.ts b/packages/backend/convex/atas.ts index e599c7a..70d53bb 100644 --- a/packages/backend/convex/atas.ts +++ b/packages/backend/convex/atas.ts @@ -2,10 +2,15 @@ import { v } from 'convex/values'; import { mutation, query } from './_generated/server'; import type { Id } from './_generated/dataModel'; import { getCurrentUserFunction } from './auth'; +import { internal } from './_generated/api'; export const list = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'listar' + }); return await ctx.db.query('atas').collect(); } }); @@ -13,6 +18,10 @@ export const list = query({ export const get = query({ args: { id: v.id('atas') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'ver' + }); return await ctx.db.get(args.id); } }); @@ -20,6 +29,10 @@ export const get = query({ export const getObjetos = query({ args: { id: v.id('atas') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'ver' + }); const links = await ctx.db .query('atasObjetos') .withIndex('by_ataId', (q) => q.eq('ataId', args.id)) @@ -35,6 +48,10 @@ export const listByObjetoIds = query({ objetoIds: v.array(v.id('objetos')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'listar' + }); if (args.objetoIds.length === 0) return []; // Buscar todos os vínculos ata-objeto para os objetos informados @@ -66,6 +83,11 @@ export const create = mutation({ objetosIds: v.array(v.id('objetos')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'criar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -103,6 +125,11 @@ export const update = mutation({ objetosIds: v.array(v.id('objetos')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'editar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -139,6 +166,11 @@ export const update = mutation({ export const remove = mutation({ args: { id: v.id('atas') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'excluir' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -170,6 +202,10 @@ export const remove = mutation({ export const generateUploadUrl = mutation({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'editar' + }); return await ctx.storage.generateUploadUrl(); } }); @@ -183,6 +219,11 @@ export const saveDocumento = mutation({ tamanho: v.number() }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'editar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -201,6 +242,11 @@ export const saveDocumento = mutation({ export const removeDocumento = mutation({ args: { id: v.id('atasDocumentos') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'editar' + }); + const user = await getCurrentUserFunction(ctx); if (!user) throw new Error('Unauthorized'); @@ -215,6 +261,11 @@ export const removeDocumento = mutation({ export const getDocumentos = query({ args: { ataId: v.id('atas') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atas', + acao: 'ver' + }); + const docs = await ctx.db .query('atasDocumentos') .withIndex('by_ataId', (q) => q.eq('ataId', args.ataId)) diff --git a/packages/backend/convex/atestadosLicencas.ts b/packages/backend/convex/atestadosLicencas.ts index 1b6fcf9..a96042f 100644 --- a/packages/backend/convex/atestadosLicencas.ts +++ b/packages/backend/convex/atestadosLicencas.ts @@ -147,6 +147,11 @@ export async function verificarLicencaAtiva( export const listarTodos = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'listar' + }); + try { const [atestados, licencas] = await Promise.all([ ctx.db.query('atestados').collect(), @@ -258,6 +263,11 @@ export const listarTodos = query({ export const listarPorFuncionario = query({ args: { funcionarioId: v.id('funcionarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'listar' + }); + const [atestados, licencas] = await Promise.all([ ctx.db .query('atestados') @@ -285,6 +295,11 @@ export const listarPorPeriodo = query({ dataFim: v.string() }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'listar' + }); + const dataInicioObj = new Date(args.dataInicio); const dataFimObj = new Date(args.dataFim); @@ -327,6 +342,10 @@ export const verificarStatusLicenca = query({ }, returns: v.boolean(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'listar' + }); return await verificarLicencaAtiva(ctx, args.funcionarioId); } }); @@ -339,6 +358,11 @@ export const obterDadosGraficos = query({ periodo: v.optional(v.number()) // dias (padrão: 30) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'dashboard' + }); + try { const dias = args.periodo || 30; const dataLimite = Date.now() - dias * 24 * 60 * 60 * 1000; @@ -588,6 +612,11 @@ export const obterDadosGraficos = query({ export const obterEstatisticas = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'dashboard' + }); + const hoje = new Date(); hoje.setHours(0, 0, 0, 0); const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1); @@ -657,6 +686,11 @@ export const obterEventosCalendario = query({ ) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'dashboard' + }); + const eventos: Array<{ id: string; title: string; @@ -837,6 +871,11 @@ export const generateUploadUrl = mutation({ args: {}, returns: v.string(), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'criar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -853,6 +892,11 @@ export const obterUrlDocumento = query({ }, returns: v.union(v.string(), v.null()), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'listar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -874,6 +918,11 @@ export const criarAtestadoMedico = mutation({ }, returns: v.id('atestados'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'criar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -932,6 +981,11 @@ export const criarDeclaracaoComparecimento = mutation({ }, returns: v.id('atestados'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'criar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -990,6 +1044,11 @@ export const criarLicencaMaternidade = mutation({ }, returns: v.id('licencas'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'criar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -1054,6 +1113,11 @@ export const criarLicencaPaternidade = mutation({ }, returns: v.id('licencas'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'criar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -1112,6 +1176,11 @@ export const prorrogarLicencaMaternidade = mutation({ }, returns: v.id('licencas'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'editar' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -1169,6 +1238,11 @@ export const excluirAtestado = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'excluir' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); @@ -1215,6 +1289,11 @@ export const excluirLicenca = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'atestados_licencas', + acao: 'excluir' + }); + const usuario = await getUsuarioAutenticado(ctx); if (!usuario) throw new Error('Não autenticado'); diff --git a/packages/backend/convex/ausencias.ts b/packages/backend/convex/ausencias.ts index 8f9f220..2155289 100644 --- a/packages/backend/convex/ausencias.ts +++ b/packages/backend/convex/ausencias.ts @@ -11,6 +11,11 @@ import { getCurrentUserFunction } from './auth'; export const listarTodas = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'listar' + }); + const solicitacoes = await ctx.db.query('solicitacoesAusencias').collect(); const solicitacoesComDetalhes = await Promise.all( @@ -65,6 +70,11 @@ export const listarMinhasSolicitacoes = query({ _refresh: v.optional(v.number()) // Parâmetro para forçar atualização no frontend }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'listar' + }); + const solicitacoes = await ctx.db .query('solicitacoesAusencias') .withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId)) @@ -104,6 +114,11 @@ export const listarMinhasSolicitacoes = query({ export const listarSolicitacoesSubordinados = query({ args: { gestorId: v.id('usuarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'aprovar' + }); + // Buscar times onde o usuário é gestor const timesGestor = await ctx.db .query('times') @@ -170,6 +185,11 @@ export const listarSolicitacoesSubordinados = query({ export const obterDetalhes = query({ args: { solicitacaoId: v.id('solicitacoesAusencias') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'listar' + }); + const solicitacao = await ctx.db.get(args.solicitacaoId); if (!solicitacao) return null; @@ -237,6 +257,11 @@ export const obterDetalhes = query({ export const obterNotificacoesNaoLidas = query({ args: { usuarioId: v.id('usuarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'listar' + }); + const notificacoes = await ctx.db .query('notificacoesAusencias') .withIndex('by_destinatario_and_lida', (q) => @@ -253,6 +278,11 @@ export const obterNotificacoesNaoLidas = query({ export const contarPendentesGestor = query({ args: { gestorId: v.id('usuarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'aprovar' + }); + // Buscar times onde o usuário é gestor const timesGestor = await ctx.db .query('times') @@ -359,6 +389,11 @@ export const criarSolicitacao = mutation({ }, returns: v.id('solicitacoesAusencias'), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'criar' + }); + // Validações if (args.motivo.trim().length < 10) { throw new Error('O motivo deve ter no mínimo 10 caracteres'); @@ -547,6 +582,11 @@ export const aprovar = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'aprovar' + }); + const solicitacao = await ctx.db.get(args.solicitacaoId); if (!solicitacao) { throw new Error('Solicitação não encontrada'); @@ -710,6 +750,11 @@ export const reprovar = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'reprovar' + }); + const solicitacao = await ctx.db.get(args.solicitacaoId); if (!solicitacao) { throw new Error('Solicitação não encontrada'); @@ -866,6 +911,11 @@ export const marcarComoLida = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ausencias', + acao: 'listar' + }); + await ctx.db.patch(args.notificacaoId, { lida: true }); diff --git a/packages/backend/convex/ferias.ts b/packages/backend/convex/ferias.ts index 60c07dd..862fa1a 100644 --- a/packages/backend/convex/ferias.ts +++ b/packages/backend/convex/ferias.ts @@ -76,6 +76,11 @@ function agruparPorSolicitacao(registros: Array>): Array<{ export const listarTodas = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'listar' + }); + const todasFerias = await ctx.db.query('ferias').collect(); const periodosComDetalhes = await Promise.all( @@ -146,6 +151,11 @@ export const listarMinhasSolicitacoes = query({ _refresh: v.optional(v.number()) // Parâmetro para forçar atualização no frontend }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'dashboard' + }); + const todasFerias = await ctx.db .query('ferias') .withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId)) @@ -180,6 +190,11 @@ export const listarMinhasSolicitacoes = query({ export const listarSolicitacoesSubordinados = query({ args: { gestorId: v.id('usuarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'dashboard' + }); + // Buscar times onde o usuário é gestor const timesGestor = await ctx.db .query('times') @@ -259,6 +274,11 @@ export const obterDetalhes = query({ feriasId: v.id('ferias') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'ver' + }); + const ferias = await ctx.db.get(args.feriasId); if (!ferias) return null; @@ -333,6 +353,11 @@ export const criarSolicitacao = mutation({ }, returns: v.array(v.id('ferias')), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'criar' + }); + if (args.periodos.length === 0) { throw new Error('É necessário adicionar pelo menos 1 período'); } @@ -394,6 +419,11 @@ export const aprovar = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'aprovar' + }); + // Buscar o registro específico const registro = await ctx.db.get(args.feriasId); @@ -503,6 +533,11 @@ export const reprovar = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'reprovar' + }); + // Buscar o registro específico const registro = await ctx.db.get(args.feriasId); @@ -565,6 +600,11 @@ export const ajustarEAprovar = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'aprovar' + }); + // Buscar o registro específico const registroAntigo = await ctx.db.get(args.feriasId); @@ -676,6 +716,11 @@ export const verificarStatusFerias = query({ args: { funcionarioId: v.id('funcionarios') }, returns: v.union(v.literal('ativo'), v.literal('em_ferias')), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'ver' + }); + const hoje = new Date(); hoje.setHours(0, 0, 0, 0); @@ -721,6 +766,11 @@ export const verificarStatusFerias = query({ export const obterNotificacoesNaoLidas = query({ args: { usuarioId: v.id('usuarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'dashboard' + }); + return await ctx.db .query('notificacoesFerias') .withIndex('by_destinatario_and_lida', (q) => @@ -735,6 +785,10 @@ export const marcarComoLida = mutation({ args: { notificacaoId: v.id('notificacoesFerias') }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'dashboard' + }); await ctx.db.patch(args.notificacaoId, { lida: true }); return null; } @@ -755,6 +809,11 @@ export const atualizarStatus = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'editar_status' + }); + // Buscar o registro específico const registro = await ctx.db.get(args.feriasId); @@ -1184,6 +1243,11 @@ export const atualizarMeuStatus = mutation({ args: {}, returns: v.null(), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ferias', + acao: 'atualizar_status' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario || !usuario.funcionarioId) { throw new Error('Usuário não encontrado ou não possui funcionário associado'); diff --git a/packages/backend/convex/permissoesAcoes.ts b/packages/backend/convex/permissoesAcoes.ts index 7eef7d8..d2f1f21 100644 --- a/packages/backend/convex/permissoesAcoes.ts +++ b/packages/backend/convex/permissoesAcoes.ts @@ -8,15 +8,9 @@ const PERMISSOES_BASE = { permissoes: [ // Funcionários { - nome: 'funcionarios.dashboard', + nome: 'funcionarios.detalhar', recurso: 'funcionarios', - acao: 'dashboard', - descricao: 'Acessar o painel de funcionários' - }, - { - nome: 'funcionarios.ver', - recurso: 'funcionarios', - acao: 'ver', + acao: 'detalhar', descricao: 'Visualizar detalhes de funcionários' }, { @@ -25,6 +19,12 @@ const PERMISSOES_BASE = { acao: 'listar', descricao: 'Listar funcionários' }, + { + nome: 'funcionarios.ver', + recurso: 'funcionarios', + acao: 'ver', + descricao: 'Visualizar dados completos de funcionários' + }, { nome: 'funcionarios.criar', recurso: 'funcionarios', @@ -50,11 +50,152 @@ const PERMISSOES_BASE = { descricao: 'Aprovar ausências de funcionários' }, { - nome: 'funcionarios.aprovar_ferias', - recurso: 'funcionarios', - acao: 'aprovar_ferias', + nome: 'ferias.aprovar', + recurso: 'ferias', + acao: 'aprovar', descricao: 'Aprovar férias de funcionários' }, + { + nome: 'ferias.reprovar', + recurso: 'ferias', + acao: 'reprovar', + descricao: 'Reprovar férias de funcionários' + }, + { + nome: 'ferias.dashboard', + recurso: 'ferias', + acao: 'dashboard', + descricao: 'Acessar o painel de férias de funcionários' + }, + { + nome: 'ferias.listar', + recurso: 'ferias', + acao: 'listar', + descricao: 'Listar solicitações e períodos de férias' + }, + { + nome: 'ferias.criar', + recurso: 'ferias', + acao: 'criar', + descricao: 'Criar solicitações de férias' + }, + { + nome: 'ferias.editar_status', + recurso: 'ferias', + acao: 'editar_status', + descricao: 'Editar/cancelar status de solicitações de férias' + }, + { + nome: 'ferias.atualizar_status', + recurso: 'ferias', + acao: 'atualizar_status', + descricao: 'Atualizar status automático de férias (administração)' + }, + { + nome: 'ferias.ver', + recurso: 'ferias', + acao: 'ver', + descricao: 'Visualizar detalhes de uma solicitação/período de férias' + }, + // Atestados e Licenças + { + nome: 'atestados_licencas.listar', + recurso: 'atestados_licencas', + acao: 'listar', + descricao: 'Listar atestados e licenças' + }, + { + nome: 'atestados_licencas.criar', + recurso: 'atestados_licencas', + acao: 'criar', + descricao: 'Registrar novos atestados ou licenças' + }, + { + nome: 'atestados_licencas.editar', + recurso: 'atestados_licencas', + acao: 'editar', + descricao: 'Editar atestados ou licenças' + }, + { + nome: 'atestados_licencas.excluir', + recurso: 'atestados_licencas', + acao: 'excluir', + descricao: 'Excluir atestados ou licenças' + }, + { + nome: 'atestados_licencas.dashboard', + recurso: 'atestados_licencas', + acao: 'dashboard', + descricao: 'Acessar painel e gráficos de atestados e licenças' + }, + // Ausências + { + nome: 'ausencias.listar', + recurso: 'ausencias', + acao: 'listar', + descricao: 'Listar solicitações de ausência' + }, + { + nome: 'ausencias.criar', + recurso: 'ausencias', + acao: 'criar', + descricao: 'Criar solicitações de ausência' + }, + { + nome: 'ausencias.aprovar', + recurso: 'ausencias', + acao: 'aprovar', + descricao: 'Aprovar solicitações de ausência' + }, + { + nome: 'ausencias.reprovar', + recurso: 'ausencias', + acao: 'reprovar', + descricao: 'Reprovar solicitações de ausência' + }, + { + nome: 'ausencias.excluir', + recurso: 'ausencias', + acao: 'excluir', + descricao: 'Excluir solicitações de ausência' + }, + // Ponto e Banco de Horas + { + nome: 'ponto.ver', + recurso: 'ponto', + acao: 'ver', + descricao: 'Visualizar telas e relatórios de ponto' + }, + { + nome: 'ponto.registrar', + recurso: 'ponto', + acao: 'registrar', + descricao: 'Registrar batidas de ponto' + }, + { + nome: 'ponto.editar', + recurso: 'ponto', + acao: 'editar', + descricao: 'Editar registros de ponto (homologação)' + }, + { + nome: 'banco_horas.ver', + recurso: 'banco_horas', + acao: 'ver', + descricao: 'Visualizar saldo e extrato de banco de horas' + }, + { + nome: 'banco_horas.ajustar', + recurso: 'banco_horas', + acao: 'ajustar', + descricao: 'Criar e aprovar ajustes de banco de horas' + }, + { + nome: 'banco_horas.configurar', + recurso: 'banco_horas', + acao: 'configurar', + descricao: 'Configurar regras e alertas de banco de horas' + }, // Símbolos { nome: 'simbolos.dashboard', diff --git a/packages/backend/convex/pontos.ts b/packages/backend/convex/pontos.ts index a0685d1..889342e 100644 --- a/packages/backend/convex/pontos.ts +++ b/packages/backend/convex/pontos.ts @@ -321,6 +321,11 @@ function validarAcelerometro( export const generateUploadUrl = mutation({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'registrar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -543,6 +548,11 @@ export const registrarPonto = mutation({ justificativa: v.optional(v.string()) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'registrar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -921,6 +931,15 @@ export const listarRegistrosDia = query({ _refresh: v.optional(v.number()) // Parâmetro usado pelo frontend para forçar refresh }, handler: async (ctx, args) => { + try { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + } catch { + return []; + } + const usuario = await getCurrentUserFunction(ctx); if (!usuario || !usuario.funcionarioId) { return []; @@ -967,6 +986,11 @@ export const obterSaldoDiario = query({ data: v.string() // YYYY-MM-DD }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + // Buscar banco de horas do dia const bancoHoras = await ctx.db .query('bancoHoras') @@ -1007,15 +1031,22 @@ export const listarRegistrosPeriodo = query({ dataFim: v.string() // YYYY-MM-DD }, handler: async (ctx, args) => { + try { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + } catch { + return []; + } + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { console.warn('[listarRegistrosPeriodo] Usuário não autenticado'); return []; } - // Verificar permissão (RH ou TI) - // Por enquanto, permitir se tiver funcionarioId ou for admin - // TODO: Implementar verificação de permissão adequada + // Permissão já verificada acima (ponto.ver) // Validar formato das datas if (!args.dataInicio || !args.dataFim) { @@ -1185,6 +1216,22 @@ export const obterEstatisticas = query({ funcionarioId: v.optional(v.id('funcionarios')) }, handler: async (ctx, args) => { + try { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + } catch { + return { + totalRegistros: 0, + dentroDoPrazo: 0, + foraDoPrazo: 0, + totalFuncionarios: 0, + funcionariosDentroPrazo: 0, + funcionariosForaPrazo: 0 + }; + } + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { // Retornar estatísticas zeradas quando não autenticado @@ -1198,7 +1245,7 @@ export const obterEstatisticas = query({ }; } - // TODO: Verificar permissão (RH ou TI) + // Permissão já verificada acima (ponto.ver) let registros = await ctx.db .query('registrosPonto') @@ -1245,6 +1292,11 @@ export const obterRegistro = query({ registroId: v.id('registrosPonto') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -1255,11 +1307,7 @@ export const obterRegistro = query({ throw new Error('Registro não encontrado'); } - // Verificar se o usuário tem permissão (próprio registro ou RH/TI) - if (registro.funcionarioId !== usuario.funcionarioId) { - // TODO: Verificar se é RH ou TI - // Por enquanto, permitir - } + // Permissão já verificada acima (ponto.ver) const funcionario = await ctx.db.get(registro.funcionarioId); let simbolo = null; @@ -1813,6 +1861,20 @@ export const obterHistoricoESaldoDia = query({ _refresh: v.optional(v.number()) // Parâmetro usado pelo frontend para forçar refresh }, handler: async (ctx, args) => { + try { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + } catch { + return { + registros: [], + cargaHorariaDiaria: 0, + horasTrabalhadas: 0, + saldoMinutos: 0 + }; + } + const usuario = await getCurrentUserFunction(ctx); if (!usuario || !usuario.funcionarioId) { console.warn('[obterHistoricoESaldoDia] Usuário não autenticado ou sem funcionarioId'); @@ -1825,10 +1887,7 @@ export const obterHistoricoESaldoDia = query({ }; } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (ponto.ver) // Buscar registros do dia const registros = await ctx.db @@ -1890,15 +1949,17 @@ export const obterBancoHorasFuncionario = query({ funcionarioId: v.id('funcionarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) // Buscar todos os registros de banco de horas do funcionário const bancosHoras = await ctx.db @@ -2056,15 +2117,17 @@ export const obterBancoHorasMensal = query({ mes: v.string() // YYYY-MM }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) const bancoMensal = await ctx.db .query('bancoHorasMensal') @@ -2119,15 +2182,17 @@ export const listarHistoricoMensal = query({ mesFim: v.optional(v.string()) // YYYY-MM }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) let query = ctx.db .query('bancoHorasMensal') @@ -2242,15 +2307,17 @@ export const verificarAlertasBancoHoras = query({ funcionarioId: v.id('funcionarios') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) // Buscar banco de horas mensal mais recente const hoje = new Date(); @@ -2356,6 +2423,11 @@ export const editarRegistroPonto = mutation({ observacoes: v.optional(v.string()) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2441,6 +2513,11 @@ export const ajustarBancoHoras = mutation({ ajusteMinutos: v.number() }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ajustar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2564,6 +2641,11 @@ export const listarHomologacoes = query({ funcionarioId: v.optional(v.id('funcionarios')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2650,6 +2732,11 @@ export const excluirHomologacao = mutation({ homologacaoId: v.id('homologacoesPonto') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2694,6 +2781,11 @@ export const excluirHomologacao = mutation({ export const obterMotivosAtestados = query({ args: {}, handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + // Buscar tipos de atestados e declarações const atestados = await ctx.db.query('atestados').collect(); const tiposUnicos = new Set(); @@ -2733,6 +2825,11 @@ export const criarDispensaRegistro = mutation({ isento: v.boolean() }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2780,6 +2877,11 @@ export const removerDispensaRegistro = mutation({ dispensaId: v.id('dispensasRegistro') }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2814,6 +2916,11 @@ export const listarDispensas = query({ apenasAtivas: v.optional(v.boolean()) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'editar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -2917,12 +3024,17 @@ export const obterEstatisticasBancoHorasGerencial = query({ funcionarioId: v.optional(v.id('funcionarios')) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de RH/TI + // Permissão já verificada acima (banco_horas.ver) // Buscar todos os bancos de horas do mês let bancosMensais = await ctx.db @@ -2997,15 +3109,17 @@ export const listarHistoricoAlteracoesBancoHoras = query({ mes: v.optional(v.string()) // YYYY-MM - se fornecido, filtra por mês }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) // Buscar homologações do funcionário let homologacoes = await ctx.db @@ -3089,6 +3203,11 @@ export const verificarDispensaAtiva = query({ minuto: v.optional(v.number()) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'ponto', + acao: 'ver' + }); + const dispensas = await ctx.db .query('dispensasRegistro') .withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId)) @@ -3217,15 +3336,17 @@ export const obterBancoHorasCompleto = query({ ) }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) const bancoHoras = await ctx.db .query('bancoHoras') @@ -3314,15 +3435,17 @@ export const listarAjustesBancoHoras = query({ }) ), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) let query = ctx.db .query('ajustesBancoHoras') @@ -3402,6 +3525,11 @@ export const listarInconsistenciasBancoHoras = query({ }) ), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -3492,12 +3620,17 @@ export const obterConfiguracaoBancoHoras = query({ v.null() ), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de TI + // Permissão já verificada acima (banco_horas.configurar) const config = await ctx.db.query('configuracaoBancoHoras').order('desc').first(); @@ -3540,12 +3673,17 @@ export const obterAlertasConfigurados = query({ }) ), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de TI + // Permissão já verificada acima (banco_horas.configurar) // Retornar todos os alertas (ativos e inativos) para permitir edição const alertas = await ctx.db.query('alertasBancoHoras').collect(); @@ -3591,15 +3729,17 @@ export const verificarInconsistencias = query({ }) ), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ver' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // Verificar se é o próprio funcionário ou tem permissão - if (usuario.funcionarioId !== args.funcionarioId) { - // TODO: Verificar permissão de RH - } + // Permissão já verificada acima (banco_horas.ver) let query = ctx.db .query('inconsistenciasBancoHoras') @@ -3650,6 +3790,11 @@ export const criarAjusteBancoHoras = mutation({ success: v.boolean() }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ajustar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -3745,6 +3890,11 @@ export const resolverInconsistencia = mutation({ success: v.boolean() }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'ajustar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -3794,12 +3944,17 @@ export const atualizarConfiguracaoBancoHoras = mutation({ configId: v.id('configuracaoBancoHoras') }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de TI + // Permissão já verificada acima (banco_horas.configurar) // Buscar configuração existente ou criar nova const configExistente = await ctx.db.query('configuracaoBancoHoras').order('desc').first(); @@ -3854,12 +4009,17 @@ export const criarAlertaBancoHoras = mutation({ alertaId: v.id('alertasBancoHoras') }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de TI + // Permissão já verificada acima (banco_horas.configurar) // Verificar se já existe alerta do mesmo tipo const alertaExistente = await ctx.db @@ -3907,12 +4067,17 @@ export const atualizarConfiguracaoAlerta = mutation({ success: v.boolean() }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } - // TODO: Verificar permissão de TI + // Permissão já verificada acima (banco_horas.configurar) const alerta = await ctx.db.get(args.alertaId); if (!alerta) { @@ -3952,6 +4117,11 @@ export const recalcularBancoHoras = mutation({ diasRecalculados: v.number() }), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); @@ -4194,7 +4364,7 @@ export const detectarEEnviarAlertasBancoHoras = internalMutation({ } break; - case 'dias_sem_registro': + case 'dias_sem_registro': { // Verificar últimos 7 dias const ultimos7Dias: string[] = []; for (let i = 0; i < 7; i++) { @@ -4221,6 +4391,7 @@ export const detectarEEnviarAlertasBancoHoras = internalMutation({ mensagem = `O funcionário não possui registro de ponto em ${diasSemRegistro.length} dos últimos 7 dias.`; } break; + } case 'limite_saldo_excedido': if (bancoMensal) { @@ -4329,6 +4500,11 @@ export const inicializarAlertasPadrao = mutation({ alertasCriados: v.number() }), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'banco_horas', + acao: 'configurar' + }); + const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); diff --git a/packages/backend/convex/simbolos.ts b/packages/backend/convex/simbolos.ts index 2f299be..8d5d4e2 100644 --- a/packages/backend/convex/simbolos.ts +++ b/packages/backend/convex/simbolos.ts @@ -1,5 +1,6 @@ import { v } from 'convex/values'; import { mutation, query } from './_generated/server'; +import { internal } from './_generated/api'; import { simboloTipo } from './tables/funcionarios'; export const getAll = query({ @@ -17,6 +18,10 @@ export const getAll = query({ }) ), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'listar' + }); return await ctx.db.query('simbolos').collect(); } }); @@ -39,6 +44,10 @@ export const getById = query({ v.null() ), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'ver' + }); return await ctx.db.get(args.id); } }); @@ -53,6 +62,11 @@ export const create = mutation({ valor: v.optional(v.string()) }, handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'criar' + }); + let refValor = args.refValor; let vencValor = args.vencValor; let valor = args.valor ?? ''; @@ -90,6 +104,10 @@ export const remove = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'excluir' + }); await ctx.db.delete(args.id); return null; } @@ -107,6 +125,11 @@ export const update = mutation({ }, returns: v.null(), handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'editar' + }); + let refValor = args.refValor; let vencValor = args.vencValor; let valor = args.valor ?? ''; @@ -149,6 +172,11 @@ export const removerDuplicados = mutation({ mantidos: v.number() }), handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: 'simbolos', + acao: 'excluir' + }); + const todosSimbolos = await ctx.db.query('simbolos').collect(); // Agrupar símbolos por nome