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:
@@ -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')
|
||||
|
||||
@@ -38,7 +38,7 @@ export const createAuth = (
|
||||
logger: {
|
||||
disabled: optionsOnly
|
||||
},
|
||||
trustedOrigins: ['https://vite.kilder.dev'],
|
||||
trustedOrigins: ['https://vite.kilder.dev', 'http://localhost:5173', 'http://127.0.0.1:5173'],
|
||||
baseURL: siteUrl,
|
||||
database: authComponent.adapter(ctx),
|
||||
// Configure simple, non-verified email/password to get started
|
||||
|
||||
@@ -85,6 +85,9 @@ export const almoxarifadoTables = {
|
||||
status: requisicaoStatus,
|
||||
aprovadoPor: v.optional(v.id('funcionarios')),
|
||||
dataAprovacao: v.optional(v.number()),
|
||||
reprovadoPor: v.optional(v.id('funcionarios')),
|
||||
dataReprovacao: v.optional(v.number()),
|
||||
motivoReprovacao: v.optional(v.string()),
|
||||
observacoes: v.optional(v.string()),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
|
||||
Reference in New Issue
Block a user