feat: implement advanced filtering and reporting features for pedidos, including status selection, date range filtering, and export options for PDF and XLSX formats
This commit is contained in:
@@ -7,6 +7,151 @@ import { getCurrentUserFunction } from './auth';
|
||||
|
||||
// ========== HELPERS ==========
|
||||
|
||||
const pedidoStatusValidator = v.union(
|
||||
v.literal('em_rascunho'),
|
||||
v.literal('aguardando_aceite'),
|
||||
v.literal('em_analise'),
|
||||
v.literal('precisa_ajustes'),
|
||||
v.literal('cancelado'),
|
||||
v.literal('concluido')
|
||||
);
|
||||
|
||||
type PedidoStatus = Doc<'pedidos'>['status'];
|
||||
|
||||
type PedidoListFilters = {
|
||||
statuses?: PedidoStatus[];
|
||||
numeroSei?: string;
|
||||
criadoPor?: Id<'usuarios'>;
|
||||
aceitoPor?: Id<'funcionarios'>;
|
||||
periodoInicio?: number;
|
||||
periodoFim?: number;
|
||||
};
|
||||
|
||||
function inRange(ts: number, inicio?: number, fim?: number): boolean {
|
||||
if (inicio !== undefined && ts < inicio) return false;
|
||||
if (fim !== undefined && ts > fim) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function matchesPeriodo(p: Doc<'pedidos'>, inicio?: number, fim?: number): boolean {
|
||||
if (inicio === undefined && fim === undefined) return true;
|
||||
if (inRange(p.criadoEm, inicio, fim)) return true;
|
||||
if (p.concluidoEm !== undefined && inRange(p.concluidoEm, inicio, fim)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeNumeroSeiQuery(q: string | undefined): string | undefined {
|
||||
const trimmed = q?.trim();
|
||||
return trimmed ? trimmed.toLowerCase() : undefined;
|
||||
}
|
||||
|
||||
function applyPedidoFilters(pedidos: Doc<'pedidos'>[], args: PedidoListFilters): Doc<'pedidos'>[] {
|
||||
const numeroSeiQuery = normalizeNumeroSeiQuery(args.numeroSei);
|
||||
const statusesSet =
|
||||
args.statuses && args.statuses.length > 0 ? new Set<PedidoStatus>(args.statuses) : null;
|
||||
|
||||
const filtered = pedidos.filter((p) => {
|
||||
if (statusesSet && !statusesSet.has(p.status)) return false;
|
||||
if (args.criadoPor && p.criadoPor !== args.criadoPor) return false;
|
||||
if (args.aceitoPor && p.aceitoPor !== args.aceitoPor) return false;
|
||||
|
||||
if (!matchesPeriodo(p, args.periodoInicio, args.periodoFim)) return false;
|
||||
|
||||
if (numeroSeiQuery) {
|
||||
const sei = (p.numeroSei ?? '').toLowerCase();
|
||||
if (!sei.includes(numeroSeiQuery)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Ordem mais útil para listagem: mais recentes primeiro
|
||||
filtered.sort((a, b) => b.criadoEm - a.criadoEm);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function dedupePedidos(pedidos: Doc<'pedidos'>[]): Doc<'pedidos'>[] {
|
||||
const map = new Map<string, Doc<'pedidos'>>();
|
||||
for (const p of pedidos) map.set(String(p._id), p);
|
||||
return [...map.values()];
|
||||
}
|
||||
|
||||
async function fetchPedidosBase(ctx: QueryCtx, args: PedidoListFilters): Promise<Doc<'pedidos'>[]> {
|
||||
// 1) Se há período, buscamos por índices de criadoEm e concluidoEm e unimos (OR)
|
||||
if (args.periodoInicio !== undefined || args.periodoFim !== undefined) {
|
||||
const inicio = args.periodoInicio;
|
||||
const fim = args.periodoFim;
|
||||
|
||||
const byCriado =
|
||||
inicio !== undefined && fim !== undefined
|
||||
? await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_criadoEm', (q) => q.gte('criadoEm', inicio).lte('criadoEm', fim))
|
||||
.collect()
|
||||
: inicio !== undefined
|
||||
? await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_criadoEm', (q) => q.gte('criadoEm', inicio))
|
||||
.collect()
|
||||
: await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_criadoEm', (q) => q.lte('criadoEm', fim as number))
|
||||
.collect();
|
||||
|
||||
const byConcluido =
|
||||
inicio !== undefined && fim !== undefined
|
||||
? await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_concluidoEm', (q) =>
|
||||
q.gte('concluidoEm', inicio).lte('concluidoEm', fim)
|
||||
)
|
||||
.collect()
|
||||
: inicio !== undefined
|
||||
? await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_concluidoEm', (q) => q.gte('concluidoEm', inicio))
|
||||
.collect()
|
||||
: await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_concluidoEm', (q) => q.lte('concluidoEm', fim as number))
|
||||
.collect();
|
||||
|
||||
return dedupePedidos(byCriado.concat(byConcluido));
|
||||
}
|
||||
|
||||
// 2) Se há statuses selecionados, usar o índice by_status
|
||||
if (args.statuses && args.statuses.length > 0) {
|
||||
let out: Doc<'pedidos'>[] = [];
|
||||
for (const status of args.statuses) {
|
||||
const part = await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_status', (q) => q.eq('status', status))
|
||||
.collect();
|
||||
out = out.concat(part);
|
||||
}
|
||||
return dedupePedidos(out);
|
||||
}
|
||||
|
||||
// 3) Se há criador, usar by_criadoPor
|
||||
if (args.criadoPor) {
|
||||
return await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_criadoPor', (q) => q.eq('criadoPor', args.criadoPor!))
|
||||
.collect();
|
||||
}
|
||||
|
||||
// 4) Se há aceitoPor, usar by_aceitoPor
|
||||
if (args.aceitoPor) {
|
||||
return await ctx.db
|
||||
.query('pedidos')
|
||||
.withIndex('by_aceitoPor', (q) => q.eq('aceitoPor', args.aceitoPor!))
|
||||
.collect();
|
||||
}
|
||||
|
||||
// 5) fallback: varrer (caso sem índices úteis)
|
||||
return await ctx.db.query('pedidos').collect();
|
||||
}
|
||||
|
||||
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
||||
const user = await getCurrentUserFunction(ctx);
|
||||
if (!user) throw new Error('Unauthorized');
|
||||
@@ -105,30 +250,55 @@ async function assertPodeGerenciarDocumentosDoPedido(
|
||||
// ========== QUERIES ==========
|
||||
|
||||
export const list = query({
|
||||
args: {},
|
||||
args: {
|
||||
statuses: v.optional(v.array(pedidoStatusValidator)),
|
||||
numeroSei: v.optional(v.string()),
|
||||
criadoPor: v.optional(v.id('usuarios')),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
periodoInicio: v.optional(v.number()),
|
||||
periodoFim: v.optional(v.number())
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.union(
|
||||
v.literal('em_rascunho'),
|
||||
v.literal('aguardando_aceite'),
|
||||
v.literal('em_analise'),
|
||||
v.literal('precisa_ajustes'),
|
||||
v.literal('cancelado'),
|
||||
v.literal('concluido')
|
||||
),
|
||||
status: pedidoStatusValidator,
|
||||
// acaoId removed from return
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
aceitoPorNome: v.optional(v.string()),
|
||||
descricaoAjuste: v.optional(v.string()),
|
||||
concluidoEm: v.optional(v.number()),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
return await ctx.db.query('pedidos').collect();
|
||||
handler: async (ctx, args) => {
|
||||
const base = await fetchPedidosBase(ctx, args);
|
||||
const pedidos = applyPedidoFilters(base, args);
|
||||
|
||||
return await Promise.all(
|
||||
pedidos.map(async (p) => {
|
||||
const creator = await ctx.db.get(p.criadoPor);
|
||||
const aceito = p.aceitoPor ? await ctx.db.get(p.aceitoPor) : null;
|
||||
return {
|
||||
_id: p._id,
|
||||
_creationTime: p._creationTime,
|
||||
numeroSei: p.numeroSei,
|
||||
status: p.status,
|
||||
criadoPor: p.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
aceitoPor: p.aceitoPor,
|
||||
aceitoPorNome: aceito?.nome || undefined,
|
||||
descricaoAjuste: p.descricaoAjuste,
|
||||
concluidoEm: p.concluidoEm,
|
||||
criadoEm: p.criadoEm,
|
||||
atualizadoEm: p.atualizadoEm
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,18 +309,12 @@ export const get = query({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.union(
|
||||
v.literal('em_rascunho'),
|
||||
v.literal('aguardando_aceite'),
|
||||
v.literal('em_analise'),
|
||||
v.literal('precisa_ajustes'),
|
||||
v.literal('cancelado'),
|
||||
v.literal('concluido')
|
||||
),
|
||||
status: pedidoStatusValidator,
|
||||
acaoId: v.optional(v.id('acoes')),
|
||||
criadoPor: v.id('usuarios'),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
descricaoAjuste: v.optional(v.string()),
|
||||
concluidoEm: v.optional(v.number()),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
}),
|
||||
@@ -493,21 +657,31 @@ export const listMyAnalysis = query({
|
||||
});
|
||||
|
||||
export const listByItemCreator = query({
|
||||
args: {},
|
||||
args: {
|
||||
statuses: v.optional(v.array(pedidoStatusValidator)),
|
||||
numeroSei: v.optional(v.string()),
|
||||
criadoPor: v.optional(v.id('usuarios')),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
periodoInicio: v.optional(v.number()),
|
||||
periodoFim: v.optional(v.number())
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: v.string(),
|
||||
status: pedidoStatusValidator,
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
aceitoPorNome: v.optional(v.string()),
|
||||
descricaoAjuste: v.optional(v.string()),
|
||||
concluidoEm: v.optional(v.number()),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
if (!user.funcionarioId) return [];
|
||||
|
||||
@@ -525,27 +699,320 @@ export const listByItemCreator = query({
|
||||
|
||||
// Filter out nulls and enrich
|
||||
const validOrders = orders.filter((o) => o !== null);
|
||||
const filtered = applyPedidoFilters(validOrders as Doc<'pedidos'>[], args);
|
||||
|
||||
return await Promise.all(
|
||||
validOrders.map(async (o) => {
|
||||
const creator = await ctx.db.get(o!.criadoPor);
|
||||
filtered.map(async (o) => {
|
||||
const creator = await ctx.db.get(o.criadoPor);
|
||||
const aceito = o.aceitoPor ? await ctx.db.get(o.aceitoPor) : null;
|
||||
return {
|
||||
_id: o!._id,
|
||||
_creationTime: o!._creationTime,
|
||||
numeroSei: o!.numeroSei,
|
||||
status: o!.status,
|
||||
criadoPor: o!.criadoPor,
|
||||
_id: o._id,
|
||||
_creationTime: o._creationTime,
|
||||
numeroSei: o.numeroSei,
|
||||
status: o.status,
|
||||
criadoPor: o.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
aceitoPor: o!.aceitoPor,
|
||||
descricaoAjuste: o!.descricaoAjuste,
|
||||
criadoEm: o!.criadoEm,
|
||||
atualizadoEm: o!.atualizadoEm
|
||||
aceitoPor: o.aceitoPor,
|
||||
aceitoPorNome: aceito?.nome || undefined,
|
||||
descricaoAjuste: o.descricaoAjuste,
|
||||
concluidoEm: o.concluidoEm,
|
||||
criadoEm: o.criadoEm,
|
||||
atualizadoEm: o.atualizadoEm
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const gerarRelatorio = query({
|
||||
args: {
|
||||
statuses: v.optional(v.array(pedidoStatusValidator)),
|
||||
numeroSei: v.optional(v.string()),
|
||||
criadoPor: v.optional(v.id('usuarios')),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
periodoInicio: v.optional(v.number()),
|
||||
periodoFim: v.optional(v.number())
|
||||
},
|
||||
returns: v.object({
|
||||
filtros: v.object({
|
||||
statuses: v.optional(v.array(pedidoStatusValidator)),
|
||||
numeroSei: v.optional(v.string()),
|
||||
criadoPor: v.optional(v.id('usuarios')),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
periodoInicio: v.optional(v.number()),
|
||||
periodoFim: v.optional(v.number())
|
||||
}),
|
||||
resumo: v.object({
|
||||
totalPedidos: v.number(),
|
||||
totalItens: v.number(),
|
||||
totalDocumentos: v.number(),
|
||||
totalPorStatus: v.array(v.object({ status: pedidoStatusValidator, count: v.number() })),
|
||||
totalValorEstimado: v.number(),
|
||||
totalValorReal: v.number()
|
||||
}),
|
||||
pedidos: v.array(
|
||||
v.object({
|
||||
_id: v.id('pedidos'),
|
||||
_creationTime: v.number(),
|
||||
numeroSei: v.optional(v.string()),
|
||||
status: pedidoStatusValidator,
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoPorNome: v.string(),
|
||||
aceitoPor: v.optional(v.id('funcionarios')),
|
||||
aceitoPorNome: v.optional(v.string()),
|
||||
descricaoAjuste: v.optional(v.string()),
|
||||
criadoEm: v.number(),
|
||||
concluidoEm: v.optional(v.number()),
|
||||
atualizadoEm: v.number(),
|
||||
itensCount: v.number(),
|
||||
documentosCount: v.number(),
|
||||
valorEstimadoTotal: v.number(),
|
||||
valorRealTotal: v.number()
|
||||
})
|
||||
),
|
||||
itens: v.array(
|
||||
v.object({
|
||||
_id: v.id('objetoItems'),
|
||||
pedidoId: v.id('pedidos'),
|
||||
pedidoNumeroSei: v.optional(v.string()),
|
||||
pedidoStatus: pedidoStatusValidator,
|
||||
objetoId: v.id('objetos'),
|
||||
objetoNome: v.optional(v.string()),
|
||||
ataId: v.optional(v.id('atas')),
|
||||
ataNumero: v.optional(v.string()),
|
||||
acaoId: v.optional(v.id('acoes')),
|
||||
acaoNome: v.optional(v.string()),
|
||||
modalidade: v.union(
|
||||
v.literal('dispensa'),
|
||||
v.literal('inexgibilidade'),
|
||||
v.literal('adesao'),
|
||||
v.literal('consumo')
|
||||
),
|
||||
quantidade: v.number(),
|
||||
valorEstimado: v.string(),
|
||||
valorReal: v.optional(v.string()),
|
||||
adicionadoPor: v.id('funcionarios'),
|
||||
adicionadoPorNome: v.string(),
|
||||
criadoEm: v.number()
|
||||
})
|
||||
)
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
// Para relatório "por período", exigimos pelo menos um limite (início ou fim)
|
||||
if (args.periodoInicio === undefined && args.periodoFim === undefined) {
|
||||
throw new Error('Informe um período (início e/ou fim) para gerar o relatório.');
|
||||
}
|
||||
|
||||
const base = await fetchPedidosBase(ctx, args);
|
||||
const pedidosFiltrados = applyPedidoFilters(base, args);
|
||||
|
||||
// Guardrail para evitar timeouts/relatórios gigantescos
|
||||
if (pedidosFiltrados.length > 500) {
|
||||
throw new Error(
|
||||
`Relatório muito grande (${pedidosFiltrados.length} pedidos). Reduza o período/filtros e tente novamente.`
|
||||
);
|
||||
}
|
||||
|
||||
// Cache para evitar múltiplos gets repetidos
|
||||
const cacheUsuarios = new Map<string, Doc<'usuarios'> | null>();
|
||||
const cacheFuncionarios = new Map<string, Doc<'funcionarios'> | null>();
|
||||
const cacheObjetos = new Map<string, Doc<'objetos'> | null>();
|
||||
const cacheAtas = new Map<string, Doc<'atas'> | null>();
|
||||
const cacheAcoes = new Map<string, Doc<'acoes'> | null>();
|
||||
|
||||
async function getUsuario(id: Id<'usuarios'>): Promise<Doc<'usuarios'> | null> {
|
||||
const k = String(id);
|
||||
if (cacheUsuarios.has(k)) return cacheUsuarios.get(k) ?? null;
|
||||
const doc = await ctx.db.get(id);
|
||||
cacheUsuarios.set(k, doc);
|
||||
return doc;
|
||||
}
|
||||
async function getFuncionario(id: Id<'funcionarios'>): Promise<Doc<'funcionarios'> | null> {
|
||||
const k = String(id);
|
||||
if (cacheFuncionarios.has(k)) return cacheFuncionarios.get(k) ?? null;
|
||||
const doc = await ctx.db.get(id);
|
||||
cacheFuncionarios.set(k, doc);
|
||||
return doc;
|
||||
}
|
||||
async function getObjeto(id: Id<'objetos'>): Promise<Doc<'objetos'> | null> {
|
||||
const k = String(id);
|
||||
if (cacheObjetos.has(k)) return cacheObjetos.get(k) ?? null;
|
||||
const doc = await ctx.db.get(id);
|
||||
cacheObjetos.set(k, doc);
|
||||
return doc;
|
||||
}
|
||||
async function getAta(id: Id<'atas'>): Promise<Doc<'atas'> | null> {
|
||||
const k = String(id);
|
||||
if (cacheAtas.has(k)) return cacheAtas.get(k) ?? null;
|
||||
const doc = await ctx.db.get(id);
|
||||
cacheAtas.set(k, doc);
|
||||
return doc;
|
||||
}
|
||||
async function getAcao(id: Id<'acoes'>): Promise<Doc<'acoes'> | null> {
|
||||
const k = String(id);
|
||||
if (cacheAcoes.has(k)) return cacheAcoes.get(k) ?? null;
|
||||
const doc = await ctx.db.get(id);
|
||||
cacheAcoes.set(k, doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
function parseValorMoeda(input: string | undefined): number {
|
||||
if (!input) return 0;
|
||||
const s = input
|
||||
.replace(/\s/g, '')
|
||||
.replace(/[Rr]\$?/g, '')
|
||||
.replace(/\./g, '')
|
||||
.replace(',', '.')
|
||||
.replace(/[^0-9.-]/g, '');
|
||||
const n = Number(s);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
const itensOut: Array<{
|
||||
_id: Id<'objetoItems'>;
|
||||
pedidoId: Id<'pedidos'>;
|
||||
pedidoNumeroSei?: string;
|
||||
pedidoStatus: PedidoStatus;
|
||||
objetoId: Id<'objetos'>;
|
||||
objetoNome?: string;
|
||||
ataId?: Id<'atas'>;
|
||||
ataNumero?: string;
|
||||
acaoId?: Id<'acoes'>;
|
||||
acaoNome?: string;
|
||||
modalidade: Doc<'objetoItems'>['modalidade'];
|
||||
quantidade: number;
|
||||
valorEstimado: string;
|
||||
valorReal?: string;
|
||||
adicionadoPor: Id<'funcionarios'>;
|
||||
adicionadoPorNome: string;
|
||||
criadoEm: number;
|
||||
}> = [];
|
||||
|
||||
let totalItens = 0;
|
||||
let totalDocumentos = 0;
|
||||
let totalValorEstimado = 0;
|
||||
let totalValorReal = 0;
|
||||
|
||||
const statusCounts = new Map<PedidoStatus, number>();
|
||||
for (const s of [
|
||||
'em_rascunho',
|
||||
'aguardando_aceite',
|
||||
'em_analise',
|
||||
'precisa_ajustes',
|
||||
'cancelado',
|
||||
'concluido'
|
||||
] as PedidoStatus[]) {
|
||||
statusCounts.set(s, 0);
|
||||
}
|
||||
|
||||
const pedidosOut = await Promise.all(
|
||||
pedidosFiltrados.map(async (p) => {
|
||||
statusCounts.set(p.status, (statusCounts.get(p.status) ?? 0) + 1);
|
||||
|
||||
const creator = await getUsuario(p.criadoPor);
|
||||
const aceito = p.aceitoPor ? await getFuncionario(p.aceitoPor) : null;
|
||||
|
||||
const itens = await ctx.db
|
||||
.query('objetoItems')
|
||||
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', p._id))
|
||||
.collect();
|
||||
|
||||
const docs = await ctx.db
|
||||
.query('pedidoDocumentos')
|
||||
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', p._id))
|
||||
.collect();
|
||||
|
||||
totalItens += itens.length;
|
||||
totalDocumentos += docs.length;
|
||||
|
||||
let valorEstimadoTotal = 0;
|
||||
let valorRealTotal = 0;
|
||||
|
||||
for (const it of itens) {
|
||||
valorEstimadoTotal += parseValorMoeda(it.valorEstimado);
|
||||
valorRealTotal += parseValorMoeda(it.valorReal);
|
||||
|
||||
const funcionario = await getFuncionario(it.adicionadoPor);
|
||||
const objeto = await getObjeto(it.objetoId);
|
||||
const ata = it.ataId ? await getAta(it.ataId) : null;
|
||||
const acao = it.acaoId ? await getAcao(it.acaoId) : null;
|
||||
|
||||
itensOut.push({
|
||||
_id: it._id,
|
||||
pedidoId: it.pedidoId,
|
||||
pedidoNumeroSei: p.numeroSei,
|
||||
pedidoStatus: p.status,
|
||||
objetoId: it.objetoId,
|
||||
objetoNome: objeto?.nome ?? undefined,
|
||||
ataId: it.ataId,
|
||||
ataNumero: ata?.numero ?? undefined,
|
||||
acaoId: it.acaoId,
|
||||
acaoNome: acao?.nome ?? undefined,
|
||||
modalidade: it.modalidade,
|
||||
quantidade: it.quantidade,
|
||||
valorEstimado: it.valorEstimado,
|
||||
valorReal: it.valorReal,
|
||||
adicionadoPor: it.adicionadoPor,
|
||||
adicionadoPorNome: funcionario?.nome || 'Desconhecido',
|
||||
criadoEm: it.criadoEm
|
||||
});
|
||||
}
|
||||
|
||||
totalValorEstimado += valorEstimadoTotal;
|
||||
totalValorReal += valorRealTotal;
|
||||
|
||||
return {
|
||||
_id: p._id,
|
||||
_creationTime: p._creationTime,
|
||||
numeroSei: p.numeroSei,
|
||||
status: p.status,
|
||||
criadoPor: p.criadoPor,
|
||||
criadoPorNome: creator?.nome || 'Desconhecido',
|
||||
aceitoPor: p.aceitoPor,
|
||||
aceitoPorNome: aceito?.nome || undefined,
|
||||
descricaoAjuste: p.descricaoAjuste,
|
||||
criadoEm: p.criadoEm,
|
||||
concluidoEm: p.concluidoEm,
|
||||
atualizadoEm: p.atualizadoEm,
|
||||
itensCount: itens.length,
|
||||
documentosCount: docs.length,
|
||||
valorEstimadoTotal,
|
||||
valorRealTotal
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Ordenar itens por data (mais recente primeiro)
|
||||
itensOut.sort((a, b) => b.criadoEm - a.criadoEm);
|
||||
|
||||
const totalPorStatus = [...statusCounts.entries()].map(([status, count]) => ({
|
||||
status,
|
||||
count
|
||||
}));
|
||||
|
||||
return {
|
||||
filtros: {
|
||||
statuses: args.statuses,
|
||||
numeroSei: args.numeroSei,
|
||||
criadoPor: args.criadoPor,
|
||||
aceitoPor: args.aceitoPor,
|
||||
periodoInicio: args.periodoInicio,
|
||||
periodoFim: args.periodoFim
|
||||
},
|
||||
resumo: {
|
||||
totalPedidos: pedidosFiltrados.length,
|
||||
totalItens,
|
||||
totalDocumentos,
|
||||
totalPorStatus,
|
||||
totalValorEstimado,
|
||||
totalValorReal
|
||||
},
|
||||
pedidos: pedidosOut,
|
||||
itens: itensOut
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const acceptOrder = mutation({
|
||||
args: {
|
||||
pedidoId: v.id('pedidos')
|
||||
@@ -1380,6 +1847,7 @@ export const concluirPedido = mutation({
|
||||
|
||||
await ctx.db.patch(args.pedidoId, {
|
||||
status: newStatus,
|
||||
concluidoEm: Date.now(),
|
||||
atualizadoEm: Date.now()
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user