diff --git a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte index 45230c9..529cbe4 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte @@ -117,6 +117,8 @@ }); let addingItem = $state(false); + let hasAppliedPrefill = $state(false); + // Edit SEI State let editingSei = $state(false); let seiValue = $state(''); @@ -139,6 +141,47 @@ selectedObjeto = null; } + $effect(() => { + if (hasAppliedPrefill) return; + if (objetosQuery.isLoading || acoesQuery.isLoading) return; + + const url = $page.url; + const obj = url.searchParams.get('obj'); + const qtdStr = url.searchParams.get('qtd'); + const mod = url.searchParams.get('mod') as Modalidade | null; + const acao = url.searchParams.get('acao'); + const ata = url.searchParams.get('ata'); + + if (!obj) return; + + const objeto = objetos.find((o) => o._id === obj); + if (!objeto) return; + + let quantidade = parseInt(qtdStr || '1', 10); + if (!Number.isFinite(quantidade) || quantidade <= 0) { + quantidade = 1; + } + + const modalidade: Modalidade = + mod === 'dispensa' || mod === 'inexgibilidade' || mod === 'adesao' || mod === 'consumo' + ? mod + : 'consumo'; + + showAddItem = true; + newItem = { + objetoId: obj, + valorEstimado: maskCurrencyBRL(objeto.valorEstimado || ''), + quantidade, + modalidade, + acaoId: acao || '', + ataId: ata || '' + }; + + void loadAtasForObjeto(obj); + + hasAppliedPrefill = true; + }); + async function handleAddItem() { if (!newItem.objetoId || !newItem.valorEstimado) return; addingItem = true; diff --git a/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte index 3416999..50cd90b 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte @@ -125,7 +125,11 @@ numeroSei?: string; status: string; criadoEm: number; - matchingItems?: { objetoId: Id<'objetos'>; quantidade: number }[]; + matchingItems?: { + objetoId: Id<'objetos'>; + modalidade: SelectedItem['modalidade']; + quantidade: number; + }[]; }[] >([]); let checking = $state(false); @@ -175,20 +179,64 @@ return `Contém: ${details}`; } + function getFirstMatchingSelectedItem(pedido: (typeof existingPedidos)[0]) { + if (!pedido.matchingItems || pedido.matchingItems.length === 0) return null; + + for (const match of pedido.matchingItems) { + const item = selectedItems.find( + (p) => p.objeto._id === match.objetoId && p.modalidade === match.modalidade + ); + if (item) { + return item; + } + } + + return null; + } + + function buildPedidoHref(pedido: (typeof existingPedidos)[0]) { + const matchedItem = getFirstMatchingSelectedItem(pedido); + + if (!matchedItem) { + return resolve(`/pedidos/${pedido._id}`); + } + + const params = new URLSearchParams(); + params.set('obj', matchedItem.objeto._id); + params.set('qtd', String(matchedItem.quantidade)); + params.set('mod', matchedItem.modalidade); + + if (matchedItem.acaoId) { + params.set('acao', matchedItem.acaoId); + } + + if (matchedItem.ataId) { + params.set('ata', matchedItem.ataId); + } + + return resolve(`/pedidos/${pedido._id}?${params.toString()}`); + } + async function checkExisting() { warning = null; existingPedidos = []; - const hasFilters = formData.numeroSei || selectedObjetoIds.length > 0; + const hasFilters = formData.numeroSei || selectedItems.length > 0; if (!hasFilters) return; checking = true; try { - // Note: checkExisting query might need update to handle item-level acaoId if we want to filter by it. - // Currently we only filter by numeroSei and objetoIds. + const itensFiltro = + selectedItems.length > 0 + ? selectedItems.map((item) => ({ + objetoId: item.objeto._id, + modalidade: item.modalidade + })) + : undefined; + const result = await client.query(api.pedidos.checkExisting, { numeroSei: formData.numeroSei || undefined, - objetoIds: selectedObjetoIds.length ? (selectedObjetoIds as Id<'objetos'>[]) : undefined + itensFiltro }); existingPedidos = result; @@ -407,7 +455,7 @@ {/if} Abrir diff --git a/packages/backend/convex/pedidos.ts b/packages/backend/convex/pedidos.ts index 3e571c7..bc1ddf1 100644 --- a/packages/backend/convex/pedidos.ts +++ b/packages/backend/convex/pedidos.ts @@ -145,9 +145,20 @@ export const getHistory = query({ export const checkExisting = query({ args: { - acaoId: v.optional(v.id('acoes')), // Used to filter items numeroSei: v.optional(v.string()), - objetoIds: v.optional(v.array(v.id('objetos'))) + itensFiltro: v.optional( + v.array( + v.object({ + objetoId: v.id('objetos'), + modalidade: v.union( + v.literal('dispensa'), + v.literal('inexgibilidade'), + v.literal('adesao'), + v.literal('consumo') + ) + }) + ) + ) }, returns: v.array( v.object({ @@ -170,6 +181,12 @@ export const checkExisting = query({ v.array( v.object({ objetoId: v.id('objetos'), + modalidade: v.union( + v.literal('dispensa'), + v.literal('inexgibilidade'), + v.literal('adesao'), + v.literal('consumo') + ), quantidade: v.number() }) ) @@ -200,38 +217,36 @@ export const checkExisting = query({ return true; }); - // 3) Filtro por acaoId (via items) - if (args.acaoId) { - // This is expensive, but for now we iterate. Better would be to query items by acaoId first. - // Optimization: Query items by acaoId and get unique pedidoIds. - const itemsComAcao = await ctx.db - .query('objetoItems') - .withIndex('by_acaoId', (q) => q.eq('acaoId', args.acaoId)) - .collect(); - - const pedidoIdsComAcao = new Set(itemsComAcao.map((i) => i.pedidoId)); - pedidosAbertos = pedidosAbertos.filter((p) => pedidoIdsComAcao.has(p._id)); - } - - // 4) Filtro por objetos (se informado) e coleta de matchingItems + // 3) Filtro por itens (objetoId + modalidade), se informado, e coleta de matchingItems const resultados = []; + const itensFiltro = args.itensFiltro ?? []; + for (const pedido of pedidosAbertos) { let include = true; - let matchingItems: { objetoId: Id<'objetos'>; quantidade: number }[] = []; + let matchingItems: { + objetoId: Id<'objetos'>; + modalidade: Doc<'objetoItems'>['modalidade']; + quantidade: number; + }[] = []; - // Se houver filtro de objetos, verificamos se o pedido tem ALGUM dos objetos - if (args.objetoIds && args.objetoIds.length > 0) { + // Se houver filtro de itens, verificamos se o pedido tem ALGUM dos itens (objetoId + modalidade) + if (itensFiltro.length > 0) { const items = await ctx.db .query('objetoItems') .withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedido._id)) .collect(); - const matching = items.filter((i) => args.objetoIds?.includes(i.objetoId)); + const matching = items.filter((i) => + itensFiltro.some( + (f) => f.objetoId === i.objetoId && f.modalidade === i.modalidade + ) + ); if (matching.length > 0) { matchingItems = matching.map((i) => ({ objetoId: i.objetoId, + modalidade: i.modalidade, quantidade: i.quantidade })); } else {