feat: enhance ata and objeto management by adding configuration options for quantity limits and usage tracking, improving data integrity and user feedback in pedidos

This commit is contained in:
2025-12-16 14:20:31 -03:00
parent fd2669aa4f
commit f90b27648f
8 changed files with 618 additions and 55 deletions

View File

@@ -4,6 +4,21 @@ import type { Id } from './_generated/dataModel';
import { getCurrentUserFunction } from './auth';
import { internal } from './_generated/api';
function normalizeLimitePercentual(value: number | undefined): number {
const fallback = 50;
if (value === undefined) return fallback;
if (!Number.isFinite(value)) return fallback;
if (value < 0) return 0;
if (value > 100) return 100;
return value;
}
function assertQuantidadeTotalValida(value: number) {
if (!Number.isFinite(value) || value <= 0) {
throw new Error('Quantidade do produto na ata deve ser maior que zero.');
}
}
export const list = query({
args: {
periodoInicio: v.optional(v.string()),
@@ -71,6 +86,34 @@ export const getObjetos = query({
}
});
export const getObjetosConfig = query({
args: { id: v.id('atas') },
returns: v.array(
v.object({
objetoId: v.id('objetos'),
quantidadeTotal: v.union(v.number(), v.null()),
limitePercentual: v.union(v.number(), v.null())
})
),
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'ver'
});
const links = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
return links.map((l) => ({
objetoId: l.objetoId,
quantidadeTotal: l.quantidadeTotal ?? null,
limitePercentual: l.limitePercentual ?? null
}));
}
});
export const listByObjetoIds = query({
args: {
objetoIds: v.array(v.id('objetos'))
@@ -108,7 +151,13 @@ export const create = mutation({
dataFim: v.optional(v.string()),
empresaId: v.id('empresas'),
numeroSei: v.string(),
objetosIds: v.array(v.id('objetos'))
objetos: v.array(
v.object({
objetoId: v.id('objetos'),
quantidadeTotal: v.number(),
limitePercentual: v.optional(v.number())
})
)
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
@@ -131,10 +180,16 @@ export const create = mutation({
});
// Vincular objetos
for (const objetoId of args.objetosIds) {
for (const cfg of args.objetos) {
assertQuantidadeTotalValida(cfg.quantidadeTotal);
const limitePercentual = normalizeLimitePercentual(cfg.limitePercentual);
await ctx.db.insert('atasObjetos', {
ataId,
objetoId
objetoId: cfg.objetoId,
quantidadeTotal: cfg.quantidadeTotal,
limitePercentual,
quantidadeUsada: 0
});
}
@@ -150,7 +205,13 @@ export const update = mutation({
empresaId: v.id('empresas'),
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
objetosIds: v.array(v.id('objetos'))
objetos: v.array(
v.object({
objetoId: v.id('objetos'),
quantidadeTotal: v.number(),
limitePercentual: v.optional(v.number())
})
)
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
@@ -171,22 +232,71 @@ export const update = mutation({
});
// Atualizar objetos vinculados
// Primeiro remove todos os vínculos existentes
const existingLinks = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
const existingByObjeto = new Map<Id<'objetos'>, (typeof existingLinks)[number]>();
for (const link of existingLinks) {
await ctx.db.delete(link._id);
existingByObjeto.set(link.objetoId, link);
}
// Adiciona os novos vínculos
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId: args.id,
objetoId
});
const desiredObjetoIds = new Set<Id<'objetos'>>(args.objetos.map((o) => o.objetoId));
// Upsert dos vínculos desejados (preserva quantidadeUsada quando já existe)
for (const cfg of args.objetos) {
assertQuantidadeTotalValida(cfg.quantidadeTotal);
const limitePercentual = normalizeLimitePercentual(cfg.limitePercentual);
const existing = existingByObjeto.get(cfg.objetoId);
if (existing) {
await ctx.db.patch(existing._id, {
quantidadeTotal: cfg.quantidadeTotal,
limitePercentual
});
} else {
await ctx.db.insert('atasObjetos', {
ataId: args.id,
objetoId: cfg.objetoId,
quantidadeTotal: cfg.quantidadeTotal,
limitePercentual,
quantidadeUsada: 0
});
}
}
// Remoção de vínculos não selecionados (somente se não houver uso em pedidos não-cancelados)
for (const link of existingLinks) {
if (desiredObjetoIds.has(link.objetoId)) continue;
const items = await ctx.db
.query('objetoItems')
.withIndex('by_ataId_and_objetoId', (q) =>
q.eq('ataId', args.id).eq('objetoId', link.objetoId)
)
.collect();
// Se existe qualquer item em pedido não cancelado, bloquear remoção do vínculo
let inUse = false;
const seenPedidos = new Set<Id<'pedidos'>>();
for (const item of items) {
if (seenPedidos.has(item.pedidoId)) continue;
seenPedidos.add(item.pedidoId);
const pedido = await ctx.db.get(item.pedidoId);
if (pedido && pedido.status !== 'cancelado') {
inUse = true;
break;
}
}
if (inUse) {
throw new Error(
'Não é possível remover este objeto da ata porque já existe uso em pedidos não cancelados.'
);
}
await ctx.db.delete(link._id);
}
}
});