feat: Implement order acceptance and analysis workflows with new pages, sidebar navigation, and backend queries for filtering and permissions.
This commit is contained in:
@@ -270,6 +270,233 @@ export const checkExisting = query({
|
||||
}
|
||||
});
|
||||
|
||||
export const listForAcceptance = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.string(),
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
|
||||
// Security Check: Must be in Compras Sector
|
||||
const config = await ctx.db.query('config').first();
|
||||
if (!config || !config.comprasSetorId) return [];
|
||||
|
||||
if (!user.funcionarioId) return [];
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
if (!isInSector) return [];
|
||||
|
||||
// Fetch orders waiting for acceptance
|
||||
const orders = await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_status', (q) => q.eq('status', 'aguardando_aceite'))
|
||||
.collect();
|
||||
|
||||
// Enrich with creator name
|
||||
return await Promise.all(
|
||||
orders.map(async (o) => {
|
||||
const creator = await ctx.db.get(o.criadoPor);
|
||||
return {
|
||||
_id: o._id,
|
||||
_creationTime: o._creationTime,
|
||||
numeroSei: o.numeroSei,
|
||||
status: o.status,
|
||||
criadoPor: o.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
criadoEm: o.criadoEm,
|
||||
atualizadoEm: o.atualizadoEm
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const listMyAnalysis = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.string(),
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
|
||||
// Security Check: Must be in Compras Sector
|
||||
const config = await ctx.db.query('config').first();
|
||||
if (!config || !config.comprasSetorId) return [];
|
||||
|
||||
if (!user.funcionarioId) return [];
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
if (!isInSector) return [];
|
||||
|
||||
// Fetch orders accepted by this user
|
||||
// We don't have an index on aceitoPor yet, but we can filter or add index.
|
||||
// Ideally we should add an index, but for now let's filter since volume might be low per user.
|
||||
// Wait, we can't filter efficiently without index if table is huge.
|
||||
// Let's assume we should add index or filter in memory if small.
|
||||
// Given the schema change was just adding the field, let's filter.
|
||||
// Actually, let's add the index in the schema update if possible, but I already did that step.
|
||||
// I'll filter for now.
|
||||
|
||||
const orders = await ctx.db
|
||||
.query('pedidos')
|
||||
.filter((q) => q.eq(q.field('aceitoPor'), user.funcionarioId))
|
||||
.collect();
|
||||
|
||||
return await Promise.all(
|
||||
orders.map(async (o) => {
|
||||
const creator = await ctx.db.get(o.criadoPor);
|
||||
return {
|
||||
_id: o._id,
|
||||
_creationTime: o._creationTime,
|
||||
numeroSei: o.numeroSei,
|
||||
status: o.status,
|
||||
criadoPor: o.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
aceitoPor: o.aceitoPor,
|
||||
criadoEm: o.criadoEm,
|
||||
atualizadoEm: o.atualizadoEm
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const listByItemCreator = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.string(),
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
if (!user.funcionarioId) return [];
|
||||
|
||||
// Find all items added by this user
|
||||
const myItems = await ctx.db
|
||||
.query('objetoItems')
|
||||
.withIndex('by_adicionadoPor', (q) => q.eq('adicionadoPor', user.funcionarioId!))
|
||||
.collect();
|
||||
|
||||
// Get unique pedidoIds
|
||||
const pedidoIds = [...new Set(myItems.map((i) => i.pedidoId))];
|
||||
|
||||
// Fetch orders
|
||||
const orders = await Promise.all(pedidoIds.map((id) => ctx.db.get(id)));
|
||||
|
||||
// Filter out nulls and enrich
|
||||
const validOrders = orders.filter((o) => o !== null);
|
||||
|
||||
return await Promise.all(
|
||||
validOrders.map(async (o) => {
|
||||
const creator = await ctx.db.get(o!.criadoPor);
|
||||
return {
|
||||
_id: o!._id,
|
||||
_creationTime: o!._creationTime,
|
||||
numeroSei: o!.numeroSei,
|
||||
status: o!.status,
|
||||
criadoPor: o!.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
criadoEm: o!.criadoEm,
|
||||
atualizadoEm: o!.atualizadoEm
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const acceptOrder = mutation({
|
||||
args: {
|
||||
pedidoId: v.id('pedidos')
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
|
||||
// Security Check: Must be in Compras Sector
|
||||
const config = await ctx.db.query('config').first();
|
||||
if (!config || !config.comprasSetorId) {
|
||||
throw new Error('Setor de compras não configurado.');
|
||||
}
|
||||
|
||||
if (!user.funcionarioId) {
|
||||
throw new Error('Usuário sem funcionário vinculado.');
|
||||
}
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
if (!isInSector) {
|
||||
throw new Error('Você não tem permissão para aceitar pedidos (Setor inválido).');
|
||||
}
|
||||
|
||||
const pedido = await ctx.db.get(args.pedidoId);
|
||||
if (!pedido) throw new Error('Pedido não encontrado.');
|
||||
|
||||
if (pedido.status !== 'aguardando_aceite') {
|
||||
throw new Error('Este pedido não está aguardando aceite.');
|
||||
}
|
||||
|
||||
if (pedido.aceitoPor) {
|
||||
throw new Error('Este pedido já foi aceito por outro funcionário.');
|
||||
}
|
||||
|
||||
await ctx.db.patch(args.pedidoId, {
|
||||
status: 'em_analise',
|
||||
aceitoPor: user.funcionarioId,
|
||||
atualizadoEm: Date.now()
|
||||
});
|
||||
|
||||
await ctx.db.insert('historicoPedidos', {
|
||||
pedidoId: args.pedidoId,
|
||||
usuarioId: user._id,
|
||||
acao: 'aceite_pedido',
|
||||
detalhes: JSON.stringify({ aceitoPor: user.funcionarioId }),
|
||||
data: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== MUTATIONS ==========
|
||||
|
||||
export const create = mutation({
|
||||
|
||||
Reference in New Issue
Block a user