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

This commit is contained in:
2025-12-18 08:48:40 -03:00
parent 69914170bf
commit 94373c6b94
3 changed files with 91 additions and 295 deletions

View File

@@ -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<Doc<'objetoItems'>['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;