feat: implement almoxarifado features including new category in recursos-humanos, configuration options in TI, and backend support for inventory management, enhancing user navigation and system functionality
This commit is contained in:
2
packages/backend/convex/_generated/api.d.ts
vendored
2
packages/backend/convex/_generated/api.d.ts
vendored
@@ -62,6 +62,7 @@ import type * as security from "../security.js";
|
||||
import type * as seed from "../seed.js";
|
||||
import type * as setores from "../setores.js";
|
||||
import type * as simbolos from "../simbolos.js";
|
||||
import type * as tables_almoxarifado from "../tables/almoxarifado.js";
|
||||
import type * as tables_atas from "../tables/atas.js";
|
||||
import type * as tables_atestados from "../tables/atestados.js";
|
||||
import type * as tables_ausencias from "../tables/ausencias.js";
|
||||
@@ -156,6 +157,7 @@ declare const fullApi: ApiFromModules<{
|
||||
seed: typeof seed;
|
||||
setores: typeof setores;
|
||||
simbolos: typeof simbolos;
|
||||
"tables/almoxarifado": typeof tables_almoxarifado;
|
||||
"tables/atas": typeof tables_atas;
|
||||
"tables/atestados": typeof tables_atestados;
|
||||
"tables/ausencias": typeof tables_ausencias;
|
||||
|
||||
1179
packages/backend/convex/almoxarifado.ts
Normal file
1179
packages/backend/convex/almoxarifado.ts
Normal file
File diff suppressed because it is too large
Load Diff
165
packages/backend/convex/configuracaoAlmoxarifado.ts
Normal file
165
packages/backend/convex/configuracaoAlmoxarifado.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { v } from 'convex/values';
|
||||
import { internal, internalQuery, mutation, query } from './_generated/server';
|
||||
import { getCurrentUserFunction } from './auth';
|
||||
|
||||
export const obterConfiguracao = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
// Verificar se usuário tem permissão de TI
|
||||
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'configurar'
|
||||
});
|
||||
|
||||
const config = await ctx.db
|
||||
.query('configuracoesAlmoxarifado')
|
||||
.filter((q) => q.eq(q.field('ativo'), true))
|
||||
.first();
|
||||
|
||||
// Se não existe configuração, retornar valores padrão
|
||||
if (!config) {
|
||||
return {
|
||||
estoqueMinimoPadrao: 10,
|
||||
diasAntecedenciaAlerta: 7,
|
||||
permitirEstoqueNegativo: false,
|
||||
requerAprovacaoRequisicao: true,
|
||||
rolesAprovacao: [],
|
||||
emailAlertasAtivo: false,
|
||||
emailsDestinatarios: [],
|
||||
periodicidadeInventario: 30,
|
||||
ultimoInventario: undefined,
|
||||
ativo: true
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
});
|
||||
|
||||
export const atualizarConfiguracao = mutation({
|
||||
args: {
|
||||
estoqueMinimoPadrao: v.optional(v.number()),
|
||||
diasAntecedenciaAlerta: v.optional(v.number()),
|
||||
permitirEstoqueNegativo: v.optional(v.boolean()),
|
||||
requerAprovacaoRequisicao: v.optional(v.boolean()),
|
||||
rolesAprovacao: v.optional(v.array(v.string())),
|
||||
emailAlertasAtivo: v.optional(v.boolean()),
|
||||
emailsDestinatarios: v.optional(v.array(v.string())),
|
||||
periodicidadeInventario: v.optional(v.number()),
|
||||
ultimoInventario: v.optional(v.number())
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar se usuário tem permissão de TI_MASTER
|
||||
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'configurar'
|
||||
});
|
||||
|
||||
const usuario = await getCurrentUserFunction(ctx);
|
||||
if (!usuario) throw new Error('Usuário não autenticado');
|
||||
|
||||
// Buscar configuração existente
|
||||
let config = await ctx.db
|
||||
.query('configuracoesAlmoxarifado')
|
||||
.filter((q) => q.eq(q.field('ativo'), true))
|
||||
.first();
|
||||
|
||||
const dadosAnteriores = config ? { ...config } : undefined;
|
||||
|
||||
if (config) {
|
||||
// Desativar configuração antiga
|
||||
await ctx.db.patch(config._id, { ativo: false });
|
||||
|
||||
// Criar nova configuração
|
||||
const dadosNovos = {
|
||||
...config,
|
||||
...args,
|
||||
ativo: true,
|
||||
atualizadoPor: usuario._id,
|
||||
atualizadoEm: Date.now()
|
||||
};
|
||||
|
||||
const novaConfigId = await ctx.db.insert('configuracoesAlmoxarifado', dadosNovos);
|
||||
|
||||
// Registrar histórico
|
||||
if (usuario) {
|
||||
await ctx.db.insert('historicoAlteracoes', {
|
||||
tipoEntidade: 'configuracao',
|
||||
entidadeId: novaConfigId,
|
||||
acao: 'edicao',
|
||||
usuarioId: usuario._id,
|
||||
dadosAnteriores: dadosAnteriores ? JSON.stringify(dadosAnteriores) : undefined,
|
||||
dadosNovos: JSON.stringify(dadosNovos),
|
||||
timestamp: Date.now(),
|
||||
observacoes: 'Atualização de configurações do almoxarifado'
|
||||
});
|
||||
}
|
||||
|
||||
return novaConfigId;
|
||||
} else {
|
||||
// Criar primeira configuração
|
||||
const dadosNovos = {
|
||||
estoqueMinimoPadrao: args.estoqueMinimoPadrao ?? 10,
|
||||
diasAntecedenciaAlerta: args.diasAntecedenciaAlerta ?? 7,
|
||||
permitirEstoqueNegativo: args.permitirEstoqueNegativo ?? false,
|
||||
requerAprovacaoRequisicao: args.requerAprovacaoRequisicao ?? true,
|
||||
rolesAprovacao: args.rolesAprovacao ?? [],
|
||||
emailAlertasAtivo: args.emailAlertasAtivo ?? false,
|
||||
emailsDestinatarios: args.emailsDestinatarios ?? [],
|
||||
periodicidadeInventario: args.periodicidadeInventario ?? 30,
|
||||
ultimoInventario: args.ultimoInventario,
|
||||
ativo: true,
|
||||
atualizadoPor: usuario._id,
|
||||
atualizadoEm: Date.now()
|
||||
};
|
||||
|
||||
const novaConfigId = await ctx.db.insert('configuracoesAlmoxarifado', dadosNovos);
|
||||
|
||||
// Registrar histórico
|
||||
if (usuario) {
|
||||
await ctx.db.insert('historicoAlteracoes', {
|
||||
tipoEntidade: 'configuracao',
|
||||
entidadeId: novaConfigId,
|
||||
acao: 'criacao',
|
||||
usuarioId: usuario._id,
|
||||
dadosAnteriores: undefined,
|
||||
dadosNovos: JSON.stringify(dadosNovos),
|
||||
timestamp: Date.now(),
|
||||
observacoes: 'Criação de configurações do almoxarifado'
|
||||
});
|
||||
}
|
||||
|
||||
return novaConfigId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ========== INTERNAL QUERIES ==========
|
||||
|
||||
export const obterConfiguracaoInterno = internalQuery({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const config = await ctx.db
|
||||
.query('configuracoesAlmoxarifado')
|
||||
.filter((q) => q.eq(q.field('ativo'), true))
|
||||
.first();
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
estoqueMinimoPadrao: 10,
|
||||
diasAntecedenciaAlerta: 7,
|
||||
permitirEstoqueNegativo: false,
|
||||
requerAprovacaoRequisicao: true,
|
||||
rolesAprovacao: [],
|
||||
emailAlertasAtivo: false,
|
||||
emailsDestinatarios: [],
|
||||
periodicidadeInventario: 30,
|
||||
ultimoInventario: undefined,
|
||||
ativo: true
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,4 +58,12 @@ crons.interval(
|
||||
{}
|
||||
);
|
||||
|
||||
// Verificar alertas de estoque do almoxarifado diariamente
|
||||
crons.interval(
|
||||
'verificar-alertas-almoxarifado',
|
||||
{ hours: 24 },
|
||||
internal.almoxarifado.verificarAlertasAutomatico,
|
||||
{}
|
||||
);
|
||||
|
||||
export default crons;
|
||||
|
||||
@@ -735,6 +735,49 @@ const PERMISSOES_BASE = {
|
||||
recurso: 'config',
|
||||
acao: 'gerenciar_compras',
|
||||
descricao: 'Gerenciar configurações de compras'
|
||||
},
|
||||
// Almoxarifado
|
||||
{
|
||||
nome: 'almoxarifado.listar',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'listar',
|
||||
descricao: 'Listar materiais e movimentações'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.criar_material',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'criar_material',
|
||||
descricao: 'Cadastrar novos materiais'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.editar_material',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'editar_material',
|
||||
descricao: 'Editar materiais existentes'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.registrar_movimentacao',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'registrar_movimentacao',
|
||||
descricao: 'Registrar entradas e saídas'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.ajustar_estoque',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'ajustar_estoque',
|
||||
descricao: 'Realizar ajustes manuais de estoque'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.aprovar_requisicao',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'aprovar_requisicao',
|
||||
descricao: 'Aprovar requisições de material'
|
||||
},
|
||||
{
|
||||
nome: 'almoxarifado.configurar',
|
||||
recurso: 'almoxarifado',
|
||||
acao: 'configurar',
|
||||
descricao: 'Configurar sistema de almoxarifado (apenas TI)'
|
||||
}
|
||||
]
|
||||
} as const;
|
||||
|
||||
@@ -22,6 +22,7 @@ import { systemTables } from './tables/system';
|
||||
import { ticketsTables } from './tables/tickets';
|
||||
import { timesTables } from './tables/times';
|
||||
import { lgpdTables } from './tables/lgpdTables';
|
||||
import { almoxarifadoTables } from './tables/almoxarifado';
|
||||
|
||||
export default defineSchema({
|
||||
...setoresTables,
|
||||
@@ -46,5 +47,6 @@ export default defineSchema({
|
||||
...planejamentosTables,
|
||||
...objetosTables,
|
||||
...atasTables,
|
||||
...lgpdTables
|
||||
...lgpdTables,
|
||||
...almoxarifadoTables
|
||||
});
|
||||
|
||||
149
packages/backend/convex/tables/almoxarifado.ts
Normal file
149
packages/backend/convex/tables/almoxarifado.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { defineTable } from 'convex/server';
|
||||
import { type Infer, v } from 'convex/values';
|
||||
|
||||
export const movimentacaoTipo = v.union(
|
||||
v.literal('entrada'),
|
||||
v.literal('saida'),
|
||||
v.literal('ajuste'),
|
||||
v.literal('transferencia')
|
||||
);
|
||||
export type MovimentacaoTipo = Infer<typeof movimentacaoTipo>;
|
||||
|
||||
export const requisicaoStatus = v.union(
|
||||
v.literal('pendente'),
|
||||
v.literal('aprovada'),
|
||||
v.literal('rejeitada'),
|
||||
v.literal('atendida'),
|
||||
v.literal('cancelada')
|
||||
);
|
||||
export type RequisicaoStatus = Infer<typeof requisicaoStatus>;
|
||||
|
||||
export const alertaTipo = v.union(
|
||||
v.literal('estoque_minimo'),
|
||||
v.literal('estoque_zerado'),
|
||||
v.literal('reposicao_necessaria')
|
||||
);
|
||||
export type AlertaTipo = Infer<typeof alertaTipo>;
|
||||
|
||||
export const alertaStatus = v.union(
|
||||
v.literal('ativo'),
|
||||
v.literal('resolvido'),
|
||||
v.literal('ignorado')
|
||||
);
|
||||
export type AlertaStatus = Infer<typeof alertaStatus>;
|
||||
|
||||
export const almoxarifadoTables = {
|
||||
materiais: defineTable({
|
||||
codigo: v.string(),
|
||||
nome: v.string(),
|
||||
descricao: v.optional(v.string()),
|
||||
categoria: v.string(),
|
||||
unidadeMedida: v.string(),
|
||||
estoqueMinimo: v.number(),
|
||||
estoqueMaximo: v.optional(v.number()),
|
||||
estoqueAtual: v.number(),
|
||||
localizacao: v.optional(v.string()),
|
||||
fornecedor: v.optional(v.string()),
|
||||
ativo: v.boolean(),
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
.index('by_codigo', ['codigo'])
|
||||
.index('by_categoria', ['categoria'])
|
||||
.index('by_ativo', ['ativo'])
|
||||
.index('by_estoqueAtual', ['estoqueAtual']),
|
||||
|
||||
movimentacoesEstoque: defineTable({
|
||||
materialId: v.id('materiais'),
|
||||
tipo: movimentacaoTipo,
|
||||
quantidade: v.number(),
|
||||
quantidadeAnterior: v.number(),
|
||||
quantidadeNova: v.number(),
|
||||
motivo: v.string(),
|
||||
documento: v.optional(v.string()),
|
||||
funcionarioId: v.optional(v.id('funcionarios')),
|
||||
setorId: v.optional(v.id('setores')),
|
||||
usuarioId: v.id('usuarios'),
|
||||
data: v.number(),
|
||||
observacoes: v.optional(v.string())
|
||||
})
|
||||
.index('by_materialId', ['materialId'])
|
||||
.index('by_tipo', ['tipo'])
|
||||
.index('by_data', ['data'])
|
||||
.index('by_funcionarioId', ['funcionarioId'])
|
||||
.index('by_usuarioId', ['usuarioId']),
|
||||
|
||||
requisicoesMaterial: defineTable({
|
||||
numero: v.string(),
|
||||
solicitanteId: v.id('funcionarios'),
|
||||
setorId: v.id('setores'),
|
||||
status: requisicaoStatus,
|
||||
aprovadoPor: v.optional(v.id('funcionarios')),
|
||||
dataAprovacao: v.optional(v.number()),
|
||||
observacoes: v.optional(v.string()),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
.index('by_status', ['status'])
|
||||
.index('by_solicitanteId', ['solicitanteId'])
|
||||
.index('by_setorId', ['setorId'])
|
||||
.index('by_numero', ['numero']),
|
||||
|
||||
requisicaoItens: defineTable({
|
||||
requisicaoId: v.id('requisicoesMaterial'),
|
||||
materialId: v.id('materiais'),
|
||||
quantidadeSolicitada: v.number(),
|
||||
quantidadeAtendida: v.optional(v.number()),
|
||||
observacoes: v.optional(v.string())
|
||||
})
|
||||
.index('by_requisicaoId', ['requisicaoId'])
|
||||
.index('by_materialId', ['materialId']),
|
||||
|
||||
historicoAlteracoes: defineTable({
|
||||
tipoEntidade: v.string(),
|
||||
entidadeId: v.string(),
|
||||
acao: v.string(),
|
||||
usuarioId: v.id('usuarios'),
|
||||
dadosAnteriores: v.optional(v.string()),
|
||||
dadosNovos: v.optional(v.string()),
|
||||
timestamp: v.number(),
|
||||
ipAddress: v.optional(v.string()),
|
||||
observacoes: v.optional(v.string())
|
||||
})
|
||||
.index('by_tipoEntidade', ['tipoEntidade'])
|
||||
.index('by_entidadeId', ['entidadeId'])
|
||||
.index('by_usuarioId', ['usuarioId'])
|
||||
.index('by_timestamp', ['timestamp']),
|
||||
|
||||
alertasEstoque: defineTable({
|
||||
materialId: v.id('materiais'),
|
||||
tipo: alertaTipo,
|
||||
quantidadeAtual: v.number(),
|
||||
quantidadeMinima: v.number(),
|
||||
status: alertaStatus,
|
||||
criadoEm: v.number(),
|
||||
resolvidoEm: v.optional(v.number()),
|
||||
resolvidoPor: v.optional(v.id('usuarios'))
|
||||
})
|
||||
.index('by_materialId', ['materialId'])
|
||||
.index('by_status', ['status'])
|
||||
.index('by_tipo', ['tipo']),
|
||||
|
||||
configuracoesAlmoxarifado: defineTable({
|
||||
estoqueMinimoPadrao: v.number(),
|
||||
diasAntecedenciaAlerta: v.number(),
|
||||
permitirEstoqueNegativo: v.boolean(),
|
||||
requerAprovacaoRequisicao: v.boolean(),
|
||||
rolesAprovacao: v.array(v.string()),
|
||||
emailAlertasAtivo: v.boolean(),
|
||||
emailsDestinatarios: v.array(v.string()),
|
||||
periodicidadeInventario: v.number(),
|
||||
ultimoInventario: v.optional(v.number()),
|
||||
ativo: v.boolean(),
|
||||
atualizadoPor: v.id('usuarios'),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user