feat: implement requisition approval and rejection functionality in 'Almoxarifado', including stock verification, modal confirmations, and improved error handling for better inventory management

This commit is contained in:
2025-12-22 13:47:05 -03:00
parent b1db926ab4
commit 7ccca5c233
8 changed files with 783 additions and 23 deletions

View File

@@ -1132,6 +1132,53 @@ export const criarRequisicao = mutation({
}
});
export const verificarEstoqueRequisicao = query({
args: { id: v.id('requisicoesMaterial') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'almoxarifado',
acao: 'aprovar_requisicao'
});
const requisicao = await ctx.db.get(args.id);
if (!requisicao) throw new Error('Requisição não encontrada');
// Buscar itens da requisição
const itens = await ctx.db
.query('requisicaoItens')
.withIndex('by_requisicaoId', (q) => q.eq('requisicaoId', args.id))
.collect();
const problemasEstoque: Array<{
materialId: Id<'materiais'>;
materialNome: string;
quantidadeSolicitada: number;
estoqueAtual: number;
falta: number;
}> = [];
for (const item of itens) {
const material = await ctx.db.get(item.materialId);
if (!material) continue;
if (material.estoqueAtual < item.quantidadeSolicitada) {
problemasEstoque.push({
materialId: material._id,
materialNome: material.nome,
quantidadeSolicitada: item.quantidadeSolicitada,
estoqueAtual: material.estoqueAtual,
falta: item.quantidadeSolicitada - material.estoqueAtual
});
}
}
return {
temEstoqueSuficiente: problemasEstoque.length === 0,
problemasEstoque
};
}
});
export const aprovarRequisicao = mutation({
args: {
id: v.id('requisicoesMaterial'),
@@ -1149,16 +1196,55 @@ export const aprovarRequisicao = mutation({
throw new Error('Apenas requisições pendentes podem ser aprovadas');
}
// Verificar estoque antes de aprovar
const itens = await ctx.db
.query('requisicaoItens')
.withIndex('by_requisicaoId', (q) => q.eq('requisicaoId', args.id))
.collect();
const problemasEstoque: Array<{
materialNome: string;
quantidadeSolicitada: number;
estoqueAtual: number;
}> = [];
for (const item of itens) {
const material = await ctx.db.get(item.materialId);
if (!material) continue;
if (material.estoqueAtual < item.quantidadeSolicitada) {
problemasEstoque.push({
materialNome: material.nome,
quantidadeSolicitada: item.quantidadeSolicitada,
estoqueAtual: material.estoqueAtual
});
}
}
if (problemasEstoque.length > 0) {
const mensagem = `Estoque insuficiente para os seguintes materiais:\n${problemasEstoque.map(p => `- ${p.materialNome}: solicitado ${p.quantidadeSolicitada}, disponível ${p.estoqueAtual}`).join('\n')}`;
throw new Error(mensagem);
}
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) throw new Error('Usuário não autenticado');
// Buscar funcionário do usuário
const funcionario = await ctx.db
.query('funcionarios')
.filter((q) => q.eq(q.field('email'), usuario.email))
.first();
// Buscar funcionário do usuário - primeiro tenta por funcionarioId, depois por email
let funcionario;
if (usuario.funcionarioId) {
funcionario = await ctx.db.get(usuario.funcionarioId);
}
if (!funcionario) {
funcionario = await ctx.db
.query('funcionarios')
.filter((q) => q.eq(q.field('email'), usuario.email))
.first();
}
if (!funcionario) throw new Error('Funcionário não encontrado para o usuário');
if (!funcionario) {
throw new Error('Funcionário não encontrado para o usuário');
}
await ctx.db.patch(args.id, {
status: 'aprovada',
@@ -1176,6 +1262,64 @@ export const aprovarRequisicao = mutation({
}
});
export const reprovarRequisicao = mutation({
args: {
id: v.id('requisicoesMaterial'),
motivoReprovacao: v.string()
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'almoxarifado',
acao: 'aprovar_requisicao'
});
const requisicao = await ctx.db.get(args.id);
if (!requisicao) throw new Error('Requisição não encontrada');
if (requisicao.status !== 'pendente') {
throw new Error('Apenas requisições pendentes podem ser reprovadas');
}
if (!args.motivoReprovacao.trim()) {
throw new Error('É necessário informar o motivo da reprovação');
}
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) throw new Error('Usuário não autenticado');
// Buscar funcionário do usuário - primeiro tenta por funcionarioId, depois por email
let funcionario;
if (usuario.funcionarioId) {
funcionario = await ctx.db.get(usuario.funcionarioId);
}
if (!funcionario) {
funcionario = await ctx.db
.query('funcionarios')
.filter((q) => q.eq(q.field('email'), usuario.email))
.first();
}
if (!funcionario) {
throw new Error('Funcionário não encontrado para o usuário');
}
await ctx.db.patch(args.id, {
status: 'rejeitada',
reprovadoPor: funcionario._id,
dataReprovacao: Date.now(),
motivoReprovacao: args.motivoReprovacao.trim(),
atualizadoEm: Date.now()
});
// Registrar histórico
await registrarHistorico(ctx, 'requisicao', args.id.toString(), 'edicao', requisicao, {
status: 'rejeitada',
reprovadoPor: funcionario._id,
motivoReprovacao: args.motivoReprovacao.trim()
});
}
});
export const atenderRequisicao = mutation({
args: {
id: v.id('requisicoesMaterial')