feat: Add prefill functionality for pedidos and enhance item matching logic with modalidade support

This commit is contained in:
2025-12-03 11:31:33 -03:00
parent d86d7d8dbb
commit fb78866a0e
3 changed files with 132 additions and 26 deletions

View File

@@ -117,6 +117,8 @@
}); });
let addingItem = $state(false); let addingItem = $state(false);
let hasAppliedPrefill = $state(false);
// Edit SEI State // Edit SEI State
let editingSei = $state(false); let editingSei = $state(false);
let seiValue = $state(''); let seiValue = $state('');
@@ -139,6 +141,47 @@
selectedObjeto = null; 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() { async function handleAddItem() {
if (!newItem.objetoId || !newItem.valorEstimado) return; if (!newItem.objetoId || !newItem.valorEstimado) return;
addingItem = true; addingItem = true;

View File

@@ -125,7 +125,11 @@
numeroSei?: string; numeroSei?: string;
status: string; status: string;
criadoEm: number; criadoEm: number;
matchingItems?: { objetoId: Id<'objetos'>; quantidade: number }[]; matchingItems?: {
objetoId: Id<'objetos'>;
modalidade: SelectedItem['modalidade'];
quantidade: number;
}[];
}[] }[]
>([]); >([]);
let checking = $state(false); let checking = $state(false);
@@ -175,20 +179,64 @@
return `Contém: ${details}`; 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() { async function checkExisting() {
warning = null; warning = null;
existingPedidos = []; existingPedidos = [];
const hasFilters = formData.numeroSei || selectedObjetoIds.length > 0; const hasFilters = formData.numeroSei || selectedItems.length > 0;
if (!hasFilters) return; if (!hasFilters) return;
checking = true; checking = true;
try { try {
// Note: checkExisting query might need update to handle item-level acaoId if we want to filter by it. const itensFiltro =
// Currently we only filter by numeroSei and objetoIds. selectedItems.length > 0
? selectedItems.map((item) => ({
objetoId: item.objeto._id,
modalidade: item.modalidade
}))
: undefined;
const result = await client.query(api.pedidos.checkExisting, { const result = await client.query(api.pedidos.checkExisting, {
numeroSei: formData.numeroSei || undefined, numeroSei: formData.numeroSei || undefined,
objetoIds: selectedObjetoIds.length ? (selectedObjetoIds as Id<'objetos'>[]) : undefined itensFiltro
}); });
existingPedidos = result; existingPedidos = result;
@@ -407,7 +455,7 @@
{/if} {/if}
</div> </div>
<a <a
href={resolve(`/pedidos/${pedido._id}`)} href={buildPedidoHref(pedido)}
class="text-sm font-medium text-blue-600 hover:text-blue-800" class="text-sm font-medium text-blue-600 hover:text-blue-800"
> >
Abrir Abrir

View File

@@ -145,9 +145,20 @@ export const getHistory = query({
export const checkExisting = query({ export const checkExisting = query({
args: { args: {
acaoId: v.optional(v.id('acoes')), // Used to filter items
numeroSei: v.optional(v.string()), 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( returns: v.array(
v.object({ v.object({
@@ -170,6 +181,12 @@ export const checkExisting = query({
v.array( v.array(
v.object({ v.object({
objetoId: v.id('objetos'), objetoId: v.id('objetos'),
modalidade: v.union(
v.literal('dispensa'),
v.literal('inexgibilidade'),
v.literal('adesao'),
v.literal('consumo')
),
quantidade: v.number() quantidade: v.number()
}) })
) )
@@ -200,38 +217,36 @@ export const checkExisting = query({
return true; return true;
}); });
// 3) Filtro por acaoId (via items) // 3) Filtro por itens (objetoId + modalidade), se informado, e coleta de matchingItems
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
const resultados = []; const resultados = [];
const itensFiltro = args.itensFiltro ?? [];
for (const pedido of pedidosAbertos) { for (const pedido of pedidosAbertos) {
let include = true; 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 // Se houver filtro de itens, verificamos se o pedido tem ALGUM dos itens (objetoId + modalidade)
if (args.objetoIds && args.objetoIds.length > 0) { if (itensFiltro.length > 0) {
const items = await ctx.db const items = await ctx.db
.query('objetoItems') .query('objetoItems')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedido._id)) .withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedido._id))
.collect(); .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) { if (matching.length > 0) {
matchingItems = matching.map((i) => ({ matchingItems = matching.map((i) => ({
objetoId: i.objetoId, objetoId: i.objetoId,
modalidade: i.modalidade,
quantidade: i.quantidade quantidade: i.quantidade
})); }));
} else { } else {