feat: enhance pedidos functionality by adding new submenu options for creating and planning orders, improving user navigation and access control in the sidebar; also implement URL-based prefill for adding items, ensuring a smoother user experience when creating pedidos

This commit is contained in:
2025-12-17 21:42:35 -03:00
parent 551a2fed00
commit 69914170bf
12 changed files with 1896 additions and 97 deletions

View File

@@ -185,7 +185,10 @@ async function ensurePedidoModalidadeAtaConsistency(
const normalizedItemAtaId = (('ataId' in item ? item.ataId : undefined) ??
null) as Id<'atas'> | null;
if (item.modalidade !== modalidade || normalizedItemAtaId !== normalizedNewAtaId) {
const modalidadeMismatch = !!item.modalidade && !!modalidade && item.modalidade !== modalidade;
const ataMismatch = normalizedItemAtaId !== normalizedNewAtaId;
if (modalidadeMismatch || ataMismatch) {
throw new Error(
'Todos os itens do pedido devem usar a mesma modalidade e a mesma ata (quando houver). Ajuste os itens existentes ou crie um novo pedido para a nova combinação.'
);
@@ -529,6 +532,12 @@ export const getItems = query({
const funcionario = await ctx.db.get(item.adicionadoPor);
return {
...item,
// Se a modalidade ainda não foi definida, expõe como "consumo" para manter compatibilidade com UI existente
modalidade: (item.modalidade ?? 'consumo') as
| 'dispensa'
| 'inexgibilidade'
| 'adesao'
| 'consumo',
adicionadoPorNome: funcionario?.nome || 'Desconhecido'
};
})
@@ -654,7 +663,7 @@ export const checkExisting = query({
let include = true;
let matchingItems: {
objetoId: Id<'objetos'>;
modalidade: Doc<'objetoItems'>['modalidade'];
modalidade: NonNullable<Doc<'objetoItems'>['modalidade']>;
quantidade: number;
}[] = [];
@@ -666,13 +675,19 @@ export const checkExisting = query({
.collect();
const matching = items.filter((i) =>
itensFiltro.some((f) => f.objetoId === i.objetoId && f.modalidade === i.modalidade)
itensFiltro.some(
(f) => f.objetoId === i.objetoId && f.modalidade === (i.modalidade ?? 'consumo')
)
);
if (matching.length > 0) {
matchingItems = matching.map((i) => ({
objetoId: i.objetoId,
modalidade: i.modalidade,
modalidade: (i.modalidade ?? 'consumo') as
| 'dispensa'
| 'inexgibilidade'
| 'adesao'
| 'consumo',
quantidade: i.quantidade
}));
} else {
@@ -959,11 +974,13 @@ export const gerarRelatorio = query({
ataNumero: v.optional(v.string()),
acaoId: v.optional(v.id('acoes')),
acaoNome: v.optional(v.string()),
modalidade: v.union(
v.literal('dispensa'),
v.literal('inexgibilidade'),
v.literal('adesao'),
v.literal('consumo')
modalidade: v.optional(
v.union(
v.literal('dispensa'),
v.literal('inexgibilidade'),
v.literal('adesao'),
v.literal('consumo')
)
),
quantidade: v.number(),
valorEstimado: v.string(),
@@ -1125,7 +1142,11 @@ export const gerarRelatorio = query({
ataNumero: ata?.numero ?? undefined,
acaoId: it.acaoId,
acaoNome: acao?.nome ?? undefined,
modalidade: it.modalidade,
modalidade: (it.modalidade ?? 'consumo') as
| 'dispensa'
| 'inexgibilidade'
| 'adesao'
| 'consumo',
quantidade: it.quantidade,
valorEstimado: it.valorEstimado,
valorReal: it.valorReal,
@@ -1380,11 +1401,14 @@ export const addItem = mutation({
objetoId: v.id('objetos'),
ataId: v.optional(v.id('atas')),
acaoId: v.optional(v.id('acoes')),
modalidade: v.union(
v.literal('dispensa'),
v.literal('inexgibilidade'),
v.literal('adesao'),
v.literal('consumo')
// Opcional: permite criar itens sem definir modalidade upfront (ex: geração via planejamento).
modalidade: v.optional(
v.union(
v.literal('dispensa'),
v.literal('inexgibilidade'),
v.literal('adesao'),
v.literal('consumo')
)
),
valorEstimado: v.string(),
quantidade: v.number()
@@ -1401,30 +1425,6 @@ export const addItem = mutation({
const pedido = await ctx.db.get(args.pedidoId);
if (!pedido) throw new Error('Pedido não encontrado.');
// Regra global: todos os itens do pedido devem ter a mesma
// modalidade e a mesma ata (quando houver).
await ensurePedidoModalidadeAtaConsistency(ctx, args.pedidoId, args.modalidade, args.ataId);
// --- CHECK ANALYSIS MODE ---
// Em pedidos em análise, a inclusão de itens deve passar por fluxo de aprovação.
// Em rascunho ou aguardando aceite, a inclusão é direta, sem necessidade de aprovação.
if (pedido.status === 'em_analise') {
if (args.ataId) {
// Não altera consumo aqui (ainda é só solicitação), mas valida limite/configuração.
await assertAtaObjetoCanConsume(ctx, args.ataId, args.objetoId, args.quantidade);
}
await ctx.db.insert('solicitacoesItens', {
pedidoId: args.pedidoId,
tipo: 'adicao',
dados: JSON.stringify(args),
status: 'pendente',
solicitadoPor: user.funcionarioId,
criadoEm: Date.now()
});
return;
}
// --- CHECK DUPLICATES (Same Product + User, Different Config) ---
// Get all items of this product added by this user in this order
const userProductItems = await ctx.db
.query('objetoItems')
@@ -1437,8 +1437,37 @@ export const addItem = mutation({
)
.collect();
// Se não foi informada, tenta inferir a modalidade a partir de itens já adicionados por este usuário
// (evita conflito de "mesmo produto com outra combinação" quando modalidade estiver vazia).
const modalidade =
args.modalidade ?? userProductItems.find((i) => !!i.modalidade)?.modalidade ?? undefined;
// Regra global: todos os itens do pedido devem ter a mesma
// modalidade e a mesma ata (quando houver).
await ensurePedidoModalidadeAtaConsistency(ctx, args.pedidoId, modalidade, args.ataId);
// --- CHECK ANALYSIS MODE ---
// Em pedidos em análise, a inclusão de itens deve passar por fluxo de aprovação.
// Em rascunho ou aguardando aceite, a inclusão é direta, sem necessidade de aprovação.
if (pedido.status === 'em_analise') {
if (args.ataId) {
// Não altera consumo aqui (ainda é só solicitação), mas valida limite/configuração.
await assertAtaObjetoCanConsume(ctx, args.ataId, args.objetoId, args.quantidade);
}
await ctx.db.insert('solicitacoesItens', {
pedidoId: args.pedidoId,
tipo: 'adicao',
dados: JSON.stringify({ ...args, modalidade }),
status: 'pendente',
solicitadoPor: user.funcionarioId,
criadoEm: Date.now()
});
return;
}
// --- CHECK DUPLICATES (Same Product + User, Different Config) ---
const conflict = userProductItems.find(
(i) => i.modalidade !== args.modalidade || i.ataId !== args.ataId
(i) => i.modalidade !== modalidade || i.ataId !== args.ataId
);
if (conflict) {
@@ -1449,7 +1478,7 @@ export const addItem = mutation({
// Check if item already exists with SAME parameters (exact match) to increment
const existingItem = userProductItems.find(
(i) => i.acaoId === args.acaoId && i.ataId === args.ataId && i.modalidade === args.modalidade
(i) => i.acaoId === args.acaoId && i.ataId === args.ataId && i.modalidade === modalidade
);
if (existingItem) {
@@ -1481,7 +1510,7 @@ export const addItem = mutation({
objetoId: args.objetoId,
ataId: args.ataId,
acaoId: args.acaoId,
modalidade: args.modalidade,
...(modalidade ? { modalidade } : {}),
valorEstimado: args.valorEstimado,
quantidade: args.quantidade,
adicionadoPor: user.funcionarioId,
@@ -1498,7 +1527,7 @@ export const addItem = mutation({
quantidade: args.quantidade,
acaoId: args.acaoId,
ataId: args.ataId,
modalidade: args.modalidade
modalidade: modalidade ?? null
}),
data: Date.now()
});