From 94373c6b942f40173504181b889d9b4580920c31 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Thu, 18 Dec 2025 08:48:40 -0300 Subject: [PATCH] refactor: simplify pedidos item management by removing modalidade from item configuration and validation, ensuring all items use the same ata while enhancing code clarity and maintainability --- .../(dashboard)/pedidos/[id]/+page.svelte | 42 +--- .../(dashboard)/pedidos/novo/+page.svelte | 213 +----------------- packages/backend/convex/pedidos.ts | 131 ++++++----- 3 files changed, 91 insertions(+), 295 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte index 7bdba57..c2735e0 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte @@ -514,30 +514,23 @@ if (!pedido || !newItem.objetoId || !newItem.valorEstimado) return; // Validação no front: garantir que todos os itens existentes do pedido - // utilizem a mesma combinação de modalidade e ata (quando houver). + // utilizem a mesma ata (quando houver). if (items.length > 0) { const referenceItem = items[0]; - - const referenceModalidade = (referenceItem.modalidade as Modalidade | undefined) ?? undefined; const referenceAtaId = (('ataId' in referenceItem ? referenceItem.ataId : undefined) ?? null) as string | null; const newAtaId = newItem.ataId || null; - - const sameModalidade = !referenceModalidade || newItem.modalidade === referenceModalidade; const sameAta = referenceAtaId === newAtaId; - if (!sameModalidade || !sameAta) { - const refModalidadeLabel = referenceModalidade - ? formatModalidade(referenceModalidade) - : 'Não definida'; + if (!sameAta) { const refAtaLabel = referenceAtaId === null ? 'sem Ata vinculada' : 'com uma Ata específica'; toast.error( - `Não é possível adicionar este item com esta combinação de modalidade e ata.\n\n` + - `Este pedido já está utilizando Modalidade: ${refModalidadeLabel} e está ${refAtaLabel}.\n` + - `Todos os itens do pedido devem usar a mesma modalidade e a mesma ata (quando houver).` + `Não é possível adicionar este item com esta ata.\n\n` + + `Este pedido já está vinculado a: ${refAtaLabel}.\n` + + `Todos os itens do pedido devem usar a mesma ata (quando houver).` ); return; } @@ -550,7 +543,6 @@ objetoId: newItem.objetoId as Id<'objetos'>, valorEstimado: newItem.valorEstimado, quantidade: newItem.quantidade, - modalidade: newItem.modalidade, acaoId: newItem.acaoId ? (newItem.acaoId as Id<'acoes'>) : undefined, ataId: newItem.ataId ? (newItem.ataId as Id<'atas'>) : undefined }); @@ -1664,22 +1656,8 @@ placeholder="R$ 0,00" /> -
- - -
- {#if newItem.objetoId} + + {#if newItem.objetoId && permissions?.canEditAta}
- {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} + {#if permissions?.canEditModalidade} {:else} - {item.modalidade} + {formatModalidade(item.modalidade as Modalidade) || '-'} {/if} @@ -1942,7 +1920,7 @@ {/if} - {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} + {#if permissions?.canEditAta} - - - - - -
- - {#if availableAtas.length > 0} -
-
- - {availableAtas.length} - {availableAtas.length === 1 ? 'Ata' : 'Atas'} - - disponível para este objeto -
- - -
- {/if} -
diff --git a/packages/backend/convex/pedidos.ts b/packages/backend/convex/pedidos.ts index 8781225..f7159d5 100644 --- a/packages/backend/convex/pedidos.ts +++ b/packages/backend/convex/pedidos.ts @@ -159,12 +159,11 @@ async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) { return user; } -// Garante que todos os itens de um pedido utilizem a mesma -// combinação de modalidade e ata (quando houver). -async function ensurePedidoModalidadeAtaConsistency( +// Garante que todos os itens de um pedido utilizem a mesma ata (quando houver). +// Nota: Modalidade não é mais validada aqui, pois é definida apenas pelo Setor de Compras. +async function ensurePedidoAtaConsistency( ctx: MutationCtx, pedidoId: Id<'pedidos'>, - modalidade: Doc<'objetoItems'>['modalidade'], ataId: Id<'atas'> | undefined, ignoreItemId?: Id<'objetoItems'> ) { @@ -185,12 +184,11 @@ async function ensurePedidoModalidadeAtaConsistency( const normalizedItemAtaId = (('ataId' in item ? item.ataId : undefined) ?? null) as Id<'atas'> | null; - const modalidadeMismatch = !!item.modalidade && !!modalidade && item.modalidade !== modalidade; const ataMismatch = normalizedItemAtaId !== normalizedNewAtaId; - if (modalidadeMismatch || ataMismatch) { + if (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.' + 'Todos os itens do pedido devem usar a mesma ata (quando houver). Ajuste os itens existentes ou crie um novo pedido para a nova ata.' ); } } @@ -580,16 +578,11 @@ export const getHistory = query({ export const checkExisting = query({ args: { numeroSei: v.optional(v.string()), + // Modalidade removida do filtro - agora busca apenas por objetoId 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') - ) + objetoId: v.id('objetos') }) ) ) @@ -608,7 +601,6 @@ export const checkExisting = query({ v.literal('cancelado'), v.literal('concluido') ), - // acaoId removed criadoPor: v.id('usuarios'), aceitoPor: v.optional(v.id('funcionarios')), descricaoAjuste: v.optional(v.string()), @@ -618,12 +610,6 @@ 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() }) ) @@ -654,7 +640,7 @@ export const checkExisting = query({ return true; }); - // 3) Filtro por itens (objetoId + modalidade), se informado, e coleta de matchingItems + // 3) Filtro por itens (apenas objetoId), se informado, e coleta de matchingItems const resultados = []; const itensFiltro = args.itensFiltro ?? []; @@ -663,31 +649,21 @@ export const checkExisting = query({ let include = true; let matchingItems: { objetoId: Id<'objetos'>; - modalidade: NonNullable['modalidade']>; quantidade: number; }[] = []; - // Se houver filtro de itens, verificamos se o pedido tem ALGUM dos itens (objetoId + modalidade) + // Se houver filtro de itens, verificamos se o pedido tem ALGUM dos itens (apenas objetoId) 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) => - itensFiltro.some( - (f) => f.objetoId === i.objetoId && f.modalidade === (i.modalidade ?? 'consumo') - ) - ); + const matching = items.filter((i) => itensFiltro.some((f) => f.objetoId === i.objetoId)); if (matching.length > 0) { matchingItems = matching.map((i) => ({ objetoId: i.objetoId, - modalidade: (i.modalidade ?? 'consumo') as - | 'dispensa' - | 'inexgibilidade' - | 'adesao' - | 'consumo', quantidade: i.quantidade })); } else { @@ -1442,9 +1418,27 @@ export const addItem = mutation({ 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); + // Regra global: todos os itens do pedido devem ter a mesma ata (quando houver). + await ensurePedidoAtaConsistency(ctx, args.pedidoId, args.ataId); + + // Bloqueia ataId se não for Compras em análise + if (args.ataId) { + const config = await ctx.db.query('config').first(); + let isInComprasSector = false; + if (config?.comprasSetorId) { + const funcionarioSetores = await ctx.db + .query('funcionarioSetores') + .withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!)) + .filter((q) => q.eq(q.field('setorId'), config.comprasSetorId)) + .first(); + isInComprasSector = !!funcionarioSetores; + } + if (!(pedido.status === 'em_analise' && isInComprasSector)) { + throw new Error( + 'Apenas funcionários do Setor de Compras podem vincular uma Ata, e somente quando o pedido está em análise.' + ); + } + } // --- CHECK ANALYSIS MODE --- // Em pedidos em análise, a inclusão de itens deve passar por fluxo de aprovação. @@ -1846,9 +1840,35 @@ export const updateItem = mutation({ const pedido = await ctx.db.get(item.pedidoId); if (!pedido) throw new Error('Pedido não encontrado.'); - // Apenas quem adicionou o item pode editá-lo + // Apenas quem adicionou o item pode editá-lo (outras propriedades) const isOwner = item.adicionadoPor === user.funcionarioId; - if (!isOwner) { + + // Verificar permissão para editar modalidade e ata + // Somente Setor de Compras pode editar modalidade e ata, e apenas quando em análise + const config = await ctx.db.query('config').first(); + let isInComprasSector = false; + if (config?.comprasSetorId) { + const funcionarioSetores = await ctx.db + .query('funcionarioSetores') + .withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!)) + .filter((q) => q.eq(q.field('setorId'), config.comprasSetorId)) + .first(); + isInComprasSector = !!funcionarioSetores; + } + + const canEditComprasFields = pedido.status === 'em_analise' && isInComprasSector; + + // Se está tentando editar a modalidade ou a ata, verificar permissão + const isChangingModalidade = item.modalidade !== args.modalidade; + const isChangingAta = (item.ataId ?? null) !== (args.ataId ?? null); + if ((isChangingModalidade || isChangingAta) && !canEditComprasFields) { + throw new Error( + 'Apenas funcionários do Setor de Compras podem editar a modalidade ou a ata, e somente quando o pedido está em análise.' + ); + } + + // Para outras propriedades, apenas o dono do item pode editar + if (!isOwner && !canEditComprasFields) { throw new Error('Apenas quem adicionou este item pode editá-lo.'); } @@ -1860,14 +1880,8 @@ export const updateItem = mutation({ }; // Regra global: ao alterar um item, garantir que os demais itens do pedido - // continuem (ou passem a estar) com a mesma modalidade e ata. - await ensurePedidoModalidadeAtaConsistency( - ctx, - item.pedidoId, - args.modalidade, - args.ataId, - args.itemId - ); + // continuem (ou passem a estar) com a mesma ata. + await ensurePedidoAtaConsistency(ctx, item.pedidoId, args.ataId, args.itemId); // Em pedidos em análise ou aguardando aceite, geramos uma solicitação em vez de alterar diretamente if (pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite') { @@ -1954,6 +1968,8 @@ export const getPermissions = query({ canCancel: false, canCompleteAdjustments: false, canManageRequests: false, + canEditModalidade: false, + canEditAta: false, currentFuncionarioId: user?.funcionarioId ?? null }; } @@ -2004,6 +2020,9 @@ export const getPermissions = query({ isCreator && hasOnlyCreatorItems, canManageRequests: pedido.status === 'em_analise' && isInComprasSector, + // Somente Setor de Compras pode editar modalidade e ata, e apenas quando o pedido está em análise + canEditModalidade: pedido.status === 'em_analise' && isInComprasSector, + canEditAta: pedido.status === 'em_analise' && isInComprasSector, currentFuncionarioId: user.funcionarioId }; } @@ -2524,14 +2543,8 @@ export const approveItemRequest = mutation({ // We trust the request data structure matches addItem args const newItem = data; - // Regra global: todos os itens do pedido devem ter a mesma - // modalidade e ata (quando houver). - await ensurePedidoModalidadeAtaConsistency( - ctx, - request.pedidoId, - newItem.modalidade, - newItem.ataId - ); + // Regra global: todos os itens do pedido devem ter a mesma ata (quando houver). + await ensurePedidoAtaConsistency(ctx, request.pedidoId, newItem.ataId); // Note: We MUST use the original requester's ID (request.solicitadoPor) as addedBy? // Or should we attribute it to the requester? YES. // BUT `addItem` logic usually checks if `existingItem.adicionadoPor === user`. @@ -2625,13 +2638,7 @@ export const approveItemRequest = mutation({ const item = await ctx.db.get(itemId); if (item) { // Regra global também se aplica na alteração de detalhes aprovada - await ensurePedidoModalidadeAtaConsistency( - ctx, - item.pedidoId, - para.modalidade, - para.ataId, - itemId - ); + await ensurePedidoAtaConsistency(ctx, item.pedidoId, para.ataId, itemId); const oldAtaId = ('ataId' in item ? item.ataId : undefined) ?? undefined; const newAtaId = para.ataId;