663 lines
19 KiB
TypeScript
663 lines
19 KiB
TypeScript
import { v } from 'convex/values';
|
|
import type { Doc, Id } from './_generated/dataModel';
|
|
import { mutation, query } from './_generated/server';
|
|
import { getCurrentUserFunction } from './auth';
|
|
|
|
async function getUsuarioAutenticado(ctx: Parameters<typeof getCurrentUserFunction>[0]) {
|
|
const user = await getCurrentUserFunction(ctx);
|
|
if (!user) throw new Error('Unauthorized');
|
|
return user;
|
|
}
|
|
|
|
function normalizeOptionalString(value: string | undefined): string | undefined {
|
|
const trimmed = value?.trim();
|
|
return trimmed ? trimmed : undefined;
|
|
}
|
|
|
|
// ========== QUERIES ==========
|
|
|
|
export const list = query({
|
|
args: {
|
|
status: v.optional(v.union(v.literal('rascunho'), v.literal('gerado'), v.literal('cancelado'))),
|
|
statuses: v.optional(
|
|
v.array(v.union(v.literal('rascunho'), v.literal('gerado'), v.literal('cancelado')))
|
|
),
|
|
responsavelId: v.optional(v.id('funcionarios')),
|
|
acaoId: v.optional(v.id('acoes')),
|
|
texto: v.optional(v.string()),
|
|
periodoInicio: v.optional(v.number()),
|
|
periodoFim: v.optional(v.number())
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const { periodoInicio, periodoFim, texto } = args;
|
|
|
|
let base = await ctx.db.query('planejamentosPedidos').collect();
|
|
|
|
// Filtros em memória (devido à complexidade de múltiplos índices)
|
|
if (args.responsavelId) {
|
|
base = base.filter((p) => p.responsavelId === args.responsavelId);
|
|
}
|
|
if (args.acaoId) {
|
|
base = base.filter((p) => p.acaoId === args.acaoId);
|
|
}
|
|
|
|
// Status simples ou múltiplo
|
|
if (args.statuses && args.statuses.length > 0) {
|
|
base = base.filter((p) => args.statuses!.includes(p.status));
|
|
} else if (args.status) {
|
|
base = base.filter((p) => p.status === args.status);
|
|
}
|
|
|
|
if (periodoInicio) {
|
|
base = base.filter((p) => p.data >= new Date(periodoInicio).toISOString().split('T')[0]);
|
|
}
|
|
if (periodoFim) {
|
|
base = base.filter((p) => p.data <= new Date(periodoFim).toISOString().split('T')[0]);
|
|
}
|
|
|
|
if (texto) {
|
|
const t = texto.toLowerCase();
|
|
base = base.filter(
|
|
(p) => p.titulo.toLowerCase().includes(t) || p.descricao.toLowerCase().includes(t)
|
|
);
|
|
}
|
|
|
|
base.sort((a, b) => b.criadoEm - a.criadoEm);
|
|
|
|
return await Promise.all(
|
|
base.map(async (p) => {
|
|
const [responsavel, acao] = await Promise.all([
|
|
ctx.db.get(p.responsavelId),
|
|
p.acaoId ? ctx.db.get(p.acaoId) : Promise.resolve(null)
|
|
]);
|
|
return {
|
|
...p,
|
|
responsavelNome: responsavel?.nome ?? 'Desconhecido',
|
|
acaoNome: acao?.nome ?? undefined
|
|
};
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
export const gerarRelatorio = query({
|
|
args: {
|
|
statuses: v.optional(
|
|
v.array(v.union(v.literal('rascunho'), v.literal('gerado'), v.literal('cancelado')))
|
|
),
|
|
responsavelId: v.optional(v.id('funcionarios')),
|
|
acaoId: v.optional(v.id('acoes')),
|
|
texto: v.optional(v.string()),
|
|
periodoInicio: v.optional(v.number()),
|
|
periodoFim: v.optional(v.number())
|
|
},
|
|
handler: async (ctx, args) => {
|
|
// Reutilizar lógica de filtro
|
|
let base = await ctx.db.query('planejamentosPedidos').collect();
|
|
|
|
if (args.responsavelId) {
|
|
base = base.filter((p) => p.responsavelId === args.responsavelId);
|
|
}
|
|
if (args.acaoId) {
|
|
base = base.filter((p) => p.acaoId === args.acaoId);
|
|
}
|
|
if (args.statuses && args.statuses.length > 0) {
|
|
base = base.filter((p) => args.statuses!.includes(p.status));
|
|
}
|
|
if (args.periodoInicio) {
|
|
base = base.filter(
|
|
(p) => p.data >= new Date(args.periodoInicio!).toISOString().split('T')[0]
|
|
);
|
|
}
|
|
if (args.periodoFim) {
|
|
base = base.filter((p) => p.data <= new Date(args.periodoFim!).toISOString().split('T')[0]);
|
|
}
|
|
if (args.texto) {
|
|
const t = args.texto.toLowerCase();
|
|
base = base.filter(
|
|
(p) => p.titulo.toLowerCase().includes(t) || p.descricao.toLowerCase().includes(t)
|
|
);
|
|
}
|
|
|
|
base.sort((a, b) => b.criadoEm - a.criadoEm);
|
|
|
|
// Enriquecer dados
|
|
const planejamentosEnriquecidos = await Promise.all(
|
|
base.map(async (p) => {
|
|
const [responsavel, acao, itens] = await Promise.all([
|
|
ctx.db.get(p.responsavelId),
|
|
p.acaoId ? ctx.db.get(p.acaoId) : Promise.resolve(null),
|
|
ctx.db
|
|
.query('planejamentoItens')
|
|
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', p._id))
|
|
.collect()
|
|
]);
|
|
|
|
let valorEstimadoTotal = 0;
|
|
for (const item of itens) {
|
|
// Corrigir string '1.000,00' -> number
|
|
const val = parseFloat(
|
|
item.valorEstimado.replace(/\./g, '').replace(',', '.').replace('R$', '').trim()
|
|
);
|
|
if (!isNaN(val)) valorEstimadoTotal += val * item.quantidade;
|
|
}
|
|
|
|
return {
|
|
...p,
|
|
responsavelNome: responsavel?.nome ?? 'Desconhecido',
|
|
acaoNome: acao?.nome ?? undefined,
|
|
itensCount: itens.length,
|
|
valorEstimadoTotal
|
|
};
|
|
})
|
|
);
|
|
|
|
// Calcular resumo
|
|
const totalPlanejamentos = base.length;
|
|
const totalValorEstimado = planejamentosEnriquecidos.reduce(
|
|
(acc, curr) => acc + curr.valorEstimadoTotal,
|
|
0
|
|
);
|
|
|
|
const totalPorStatus = [
|
|
{ status: 'rascunho', count: 0 },
|
|
{ status: 'gerado', count: 0 },
|
|
{ status: 'cancelado', count: 0 }
|
|
];
|
|
|
|
base.forEach((p) => {
|
|
const st = totalPorStatus.find((s) => s.status === p.status);
|
|
if (st) st.count++;
|
|
});
|
|
|
|
return {
|
|
filtros: args,
|
|
resumo: {
|
|
totalPlanejamentos,
|
|
totalValorEstimado,
|
|
totalPorStatus
|
|
},
|
|
planejamentos: planejamentosEnriquecidos
|
|
};
|
|
}
|
|
});
|
|
|
|
export const get = query({
|
|
args: { id: v.id('planejamentosPedidos') },
|
|
handler: async (ctx, args) => {
|
|
const p = await ctx.db.get(args.id);
|
|
if (!p) return null;
|
|
|
|
const [responsavel, acao] = await Promise.all([
|
|
ctx.db.get(p.responsavelId),
|
|
p.acaoId ? ctx.db.get(p.acaoId) : Promise.resolve(null)
|
|
]);
|
|
|
|
return {
|
|
...p,
|
|
responsavelNome: responsavel?.nome ?? 'Desconhecido',
|
|
acaoNome: acao?.nome ?? undefined
|
|
};
|
|
}
|
|
});
|
|
|
|
export const listItems = query({
|
|
args: { planejamentoId: v.id('planejamentosPedidos') },
|
|
handler: async (ctx, args) => {
|
|
const items = await ctx.db
|
|
.query('planejamentoItens')
|
|
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', args.planejamentoId))
|
|
.collect();
|
|
|
|
// Ordenação útil: primeiro sem pedido, depois por numeroDfd, depois por criadoEm
|
|
items.sort((a, b) => {
|
|
const ap = a.pedidoId ? 1 : 0;
|
|
const bp = b.pedidoId ? 1 : 0;
|
|
if (ap !== bp) return ap - bp;
|
|
const ad = (a.numeroDfd ?? '').localeCompare(b.numeroDfd ?? '');
|
|
if (ad !== 0) return ad;
|
|
return a.criadoEm - b.criadoEm;
|
|
});
|
|
|
|
return await Promise.all(
|
|
items.map(async (it) => {
|
|
const [objeto, pedido] = await Promise.all([
|
|
ctx.db.get(it.objetoId),
|
|
it.pedidoId ? ctx.db.get(it.pedidoId) : Promise.resolve(null)
|
|
]);
|
|
|
|
return {
|
|
...it,
|
|
objetoNome: objeto?.nome ?? 'Objeto desconhecido',
|
|
objetoUnidade: objeto?.unidade ?? '',
|
|
pedidoNumeroSei: pedido?.numeroSei ?? undefined,
|
|
pedidoStatus: pedido?.status ?? undefined
|
|
};
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
export const listPedidos = query({
|
|
args: { planejamentoId: v.id('planejamentosPedidos') },
|
|
handler: async (ctx, args) => {
|
|
const links = await ctx.db
|
|
.query('planejamentoPedidosLinks')
|
|
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', args.planejamentoId))
|
|
.collect();
|
|
|
|
links.sort((a, b) => a.numeroDfd.localeCompare(b.numeroDfd));
|
|
|
|
return await Promise.all(
|
|
links.map(async (link) => {
|
|
const pedido = await ctx.db.get(link.pedidoId);
|
|
if (!pedido) {
|
|
return {
|
|
...link,
|
|
pedido: null,
|
|
lastHistory: []
|
|
};
|
|
}
|
|
|
|
const history = await ctx.db
|
|
.query('historicoPedidos')
|
|
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', link.pedidoId))
|
|
.order('desc')
|
|
.take(3);
|
|
|
|
const historyWithNames = await Promise.all(
|
|
history.map(async (h) => {
|
|
const usuario = await ctx.db.get(h.usuarioId);
|
|
return {
|
|
...h,
|
|
usuarioNome: usuario?.nome ?? 'Desconhecido'
|
|
};
|
|
})
|
|
);
|
|
|
|
return {
|
|
...link,
|
|
pedido: {
|
|
_id: pedido._id,
|
|
numeroSei: pedido.numeroSei,
|
|
numeroDfd: pedido.numeroDfd,
|
|
status: pedido.status,
|
|
criadoEm: pedido.criadoEm,
|
|
atualizadoEm: pedido.atualizadoEm
|
|
},
|
|
lastHistory: historyWithNames
|
|
};
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
// ========== MUTATIONS ==========
|
|
|
|
export const create = mutation({
|
|
args: {
|
|
titulo: v.string(),
|
|
descricao: v.string(),
|
|
data: v.string(),
|
|
responsavelId: v.id('funcionarios'),
|
|
acaoId: v.optional(v.id('acoes')),
|
|
sourcePlanningId: v.optional(v.id('planejamentosPedidos'))
|
|
},
|
|
returns: v.id('planejamentosPedidos'),
|
|
handler: async (ctx, args) => {
|
|
const user = await getUsuarioAutenticado(ctx);
|
|
const now = Date.now();
|
|
|
|
const titulo = args.titulo.trim();
|
|
const descricao = args.descricao.trim();
|
|
const data = args.data.trim();
|
|
|
|
if (!titulo) throw new Error('Informe um título.');
|
|
if (!descricao) throw new Error('Informe uma descrição.');
|
|
if (!data) throw new Error('Informe uma data.');
|
|
|
|
const newItemId = await ctx.db.insert('planejamentosPedidos', {
|
|
titulo,
|
|
descricao,
|
|
data,
|
|
responsavelId: args.responsavelId,
|
|
acaoId: args.acaoId,
|
|
status: 'rascunho',
|
|
criadoPor: user._id,
|
|
criadoEm: now,
|
|
atualizadoEm: now
|
|
});
|
|
|
|
const sourcePlanningId = args.sourcePlanningId;
|
|
|
|
if (sourcePlanningId) {
|
|
const sourceItems = await ctx.db
|
|
.query('planejamentoItens')
|
|
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', sourcePlanningId))
|
|
.collect();
|
|
|
|
for (const item of sourceItems) {
|
|
await ctx.db.insert('planejamentoItens', {
|
|
planejamentoId: newItemId,
|
|
objetoId: item.objetoId,
|
|
quantidade: item.quantidade,
|
|
valorEstimado: item.valorEstimado,
|
|
numeroDfd: item.numeroDfd,
|
|
// Não copiamos o pedidoId pois é um novo planejamento
|
|
criadoEm: now,
|
|
atualizadoEm: now
|
|
});
|
|
}
|
|
}
|
|
|
|
return newItemId;
|
|
}
|
|
});
|
|
|
|
export const update = mutation({
|
|
args: {
|
|
id: v.id('planejamentosPedidos'),
|
|
titulo: v.optional(v.string()),
|
|
descricao: v.optional(v.string()),
|
|
data: v.optional(v.string()),
|
|
responsavelId: v.optional(v.id('funcionarios')),
|
|
acaoId: v.optional(v.union(v.id('acoes'), v.null()))
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
await getUsuarioAutenticado(ctx);
|
|
const p = await ctx.db.get(args.id);
|
|
if (!p) throw new Error('Planejamento não encontrado.');
|
|
if (p.status !== 'rascunho')
|
|
throw new Error('Apenas planejamentos em rascunho podem ser editados.');
|
|
|
|
const patch: Partial<Doc<'planejamentosPedidos'>> & { acaoId?: Id<'acoes'> | undefined } = {};
|
|
|
|
if (args.titulo !== undefined) {
|
|
const t = args.titulo.trim();
|
|
if (!t) throw new Error('Título não pode ficar vazio.');
|
|
patch.titulo = t;
|
|
}
|
|
if (args.descricao !== undefined) {
|
|
const d = args.descricao.trim();
|
|
if (!d) throw new Error('Descrição não pode ficar vazia.');
|
|
patch.descricao = d;
|
|
}
|
|
if (args.data !== undefined) {
|
|
const dt = args.data.trim();
|
|
if (!dt) throw new Error('Data não pode ficar vazia.');
|
|
patch.data = dt;
|
|
}
|
|
if (args.responsavelId !== undefined) {
|
|
patch.responsavelId = args.responsavelId;
|
|
}
|
|
if (args.acaoId !== undefined) {
|
|
patch.acaoId = args.acaoId === null ? undefined : args.acaoId;
|
|
}
|
|
|
|
patch.atualizadoEm = Date.now();
|
|
await ctx.db.patch(args.id, patch);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
export const addItem = mutation({
|
|
args: {
|
|
planejamentoId: v.id('planejamentosPedidos'),
|
|
objetoId: v.id('objetos'),
|
|
quantidade: v.number(),
|
|
valorEstimado: v.string(),
|
|
numeroDfd: v.optional(v.string())
|
|
},
|
|
returns: v.id('planejamentoItens'),
|
|
handler: async (ctx, args) => {
|
|
await getUsuarioAutenticado(ctx);
|
|
const p = await ctx.db.get(args.planejamentoId);
|
|
if (!p) throw new Error('Planejamento não encontrado.');
|
|
if (p.status !== 'rascunho')
|
|
throw new Error('Apenas planejamentos em rascunho podem ser editados.');
|
|
|
|
if (!Number.isFinite(args.quantidade) || args.quantidade <= 0) {
|
|
throw new Error('Quantidade inválida.');
|
|
}
|
|
|
|
const now = Date.now();
|
|
const numeroDfd = normalizeOptionalString(args.numeroDfd);
|
|
const valorEstimado = args.valorEstimado.trim();
|
|
if (!valorEstimado) throw new Error('Valor estimado inválido.');
|
|
|
|
const itemId = await ctx.db.insert('planejamentoItens', {
|
|
planejamentoId: args.planejamentoId,
|
|
numeroDfd,
|
|
objetoId: args.objetoId,
|
|
quantidade: args.quantidade,
|
|
valorEstimado,
|
|
criadoEm: now,
|
|
atualizadoEm: now
|
|
});
|
|
await ctx.db.patch(args.planejamentoId, { atualizadoEm: Date.now() });
|
|
return itemId;
|
|
}
|
|
});
|
|
|
|
export const updateItem = mutation({
|
|
args: {
|
|
itemId: v.id('planejamentoItens'),
|
|
numeroDfd: v.optional(v.union(v.string(), v.null())),
|
|
quantidade: v.optional(v.number()),
|
|
valorEstimado: v.optional(v.string())
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
await getUsuarioAutenticado(ctx);
|
|
const it = await ctx.db.get(args.itemId);
|
|
if (!it) throw new Error('Item não encontrado.');
|
|
|
|
const p = await ctx.db.get(it.planejamentoId);
|
|
if (!p) throw new Error('Planejamento não encontrado.');
|
|
if (p.status !== 'rascunho')
|
|
throw new Error('Apenas planejamentos em rascunho podem ser editados.');
|
|
|
|
const patch: Partial<Doc<'planejamentoItens'>> = { atualizadoEm: Date.now() };
|
|
|
|
if (args.numeroDfd !== undefined) {
|
|
patch.numeroDfd =
|
|
args.numeroDfd === null
|
|
? undefined
|
|
: (normalizeOptionalString(args.numeroDfd) ?? undefined);
|
|
}
|
|
if (args.quantidade !== undefined) {
|
|
if (!Number.isFinite(args.quantidade) || args.quantidade <= 0) {
|
|
throw new Error('Quantidade inválida.');
|
|
}
|
|
patch.quantidade = args.quantidade;
|
|
}
|
|
if (args.valorEstimado !== undefined) {
|
|
const vEst = args.valorEstimado.trim();
|
|
if (!vEst) throw new Error('Valor estimado inválido.');
|
|
patch.valorEstimado = vEst;
|
|
}
|
|
|
|
await ctx.db.patch(args.itemId, patch);
|
|
await ctx.db.patch(it.planejamentoId, { atualizadoEm: Date.now() });
|
|
return null;
|
|
}
|
|
});
|
|
|
|
export const removeItem = mutation({
|
|
args: { itemId: v.id('planejamentoItens') },
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
await getUsuarioAutenticado(ctx);
|
|
const it = await ctx.db.get(args.itemId);
|
|
if (!it) return null;
|
|
const p = await ctx.db.get(it.planejamentoId);
|
|
if (!p) throw new Error('Planejamento não encontrado.');
|
|
if (p.status !== 'rascunho')
|
|
throw new Error('Apenas planejamentos em rascunho podem ser editados.');
|
|
await ctx.db.delete(args.itemId);
|
|
await ctx.db.patch(it.planejamentoId, { atualizadoEm: Date.now() });
|
|
return null;
|
|
}
|
|
});
|
|
|
|
export const gerarPedidosPorDfd = mutation({
|
|
args: {
|
|
planejamentoId: v.id('planejamentosPedidos'),
|
|
dfds: v.array(
|
|
v.object({
|
|
numeroDfd: v.string(),
|
|
numeroSei: v.string()
|
|
})
|
|
)
|
|
},
|
|
returns: v.array(v.id('pedidos')),
|
|
handler: async (ctx, args) => {
|
|
const user = await getUsuarioAutenticado(ctx);
|
|
if (!user.funcionarioId) {
|
|
throw new Error('Usuário não vinculado a um funcionário.');
|
|
}
|
|
|
|
const planejamento = await ctx.db.get(args.planejamentoId);
|
|
if (!planejamento) throw new Error('Planejamento não encontrado.');
|
|
if (planejamento.status !== 'rascunho') {
|
|
throw new Error('Este planejamento não está em rascunho.');
|
|
}
|
|
|
|
const items = await ctx.db
|
|
.query('planejamentoItens')
|
|
.withIndex('by_planejamentoId', (q) => q.eq('planejamentoId', args.planejamentoId))
|
|
.collect();
|
|
|
|
if (items.length === 0) {
|
|
throw new Error('Adicione ao menos um item antes de gerar pedidos.');
|
|
}
|
|
|
|
const itensSemDfd = items.filter((i) => !i.numeroDfd || !i.numeroDfd.trim());
|
|
if (itensSemDfd.length > 0) {
|
|
throw new Error(
|
|
`Existem ${itensSemDfd.length} item(ns) sem DFD. Atribua um DFD a todos os itens antes de gerar pedidos.`
|
|
);
|
|
}
|
|
|
|
const dfdsPayload = args.dfds.map((d) => ({
|
|
numeroDfd: d.numeroDfd.trim(),
|
|
numeroSei: d.numeroSei.trim()
|
|
}));
|
|
|
|
if (dfdsPayload.length === 0) {
|
|
throw new Error('Informe ao menos um DFD para gerar.');
|
|
}
|
|
|
|
for (const d of dfdsPayload) {
|
|
if (!d.numeroDfd) throw new Error('DFD inválido.');
|
|
if (!d.numeroSei) throw new Error(`Informe o número SEI para o DFD ${d.numeroDfd}.`);
|
|
}
|
|
|
|
// Validar que todos os DFDs existem nos itens
|
|
const dfdsFromItems = new Set(items.map((i) => (i.numeroDfd as string).trim()));
|
|
for (const d of dfdsPayload) {
|
|
if (!dfdsFromItems.has(d.numeroDfd)) {
|
|
throw new Error(`DFD ${d.numeroDfd} não existe nos itens do planejamento.`);
|
|
}
|
|
}
|
|
|
|
// Evitar duplicidade de DFD no payload
|
|
const payloadSet = new Set<string>();
|
|
for (const d of dfdsPayload) {
|
|
if (payloadSet.has(d.numeroDfd)) throw new Error(`DFD duplicado no envio: ${d.numeroDfd}.`);
|
|
payloadSet.add(d.numeroDfd);
|
|
}
|
|
|
|
// Garantir que será gerado 1 pedido para CADA DFD existente nos itens
|
|
if (payloadSet.size !== dfdsFromItems.size) {
|
|
const missing = [...dfdsFromItems].filter((d) => !payloadSet.has(d));
|
|
throw new Error(`Informe o número SEI para todos os DFDs. Faltando: ${missing.join(', ')}.`);
|
|
}
|
|
|
|
// Não permitir gerar se algum item já tiver sido movido para pedido
|
|
const jaMovidos = items.filter((i) => i.pedidoId);
|
|
if (jaMovidos.length > 0) {
|
|
throw new Error('Este planejamento já possui itens vinculados a pedidos.');
|
|
}
|
|
|
|
const now = Date.now();
|
|
const pedidoIds: Id<'pedidos'>[] = [];
|
|
|
|
for (const dfd of dfdsPayload) {
|
|
// Criar pedido em rascunho (similar a pedidos.create)
|
|
const pedidoId = await ctx.db.insert('pedidos', {
|
|
numeroSei: dfd.numeroSei,
|
|
numeroDfd: dfd.numeroDfd,
|
|
status: 'em_rascunho',
|
|
criadoPor: user._id,
|
|
criadoEm: now,
|
|
atualizadoEm: now
|
|
});
|
|
|
|
pedidoIds.push(pedidoId);
|
|
|
|
await ctx.db.insert('historicoPedidos', {
|
|
pedidoId,
|
|
usuarioId: user._id,
|
|
acao: 'criacao',
|
|
detalhes: JSON.stringify({ numeroSei: dfd.numeroSei, numeroDfd: dfd.numeroDfd }),
|
|
data: now
|
|
});
|
|
|
|
await ctx.db.insert('historicoPedidos', {
|
|
pedidoId,
|
|
usuarioId: user._id,
|
|
acao: 'gerado_de_planejamento',
|
|
detalhes: JSON.stringify({ planejamentoId: args.planejamentoId, numeroDfd: dfd.numeroDfd }),
|
|
data: now
|
|
});
|
|
|
|
await ctx.db.insert('planejamentoPedidosLinks', {
|
|
planejamentoId: args.planejamentoId,
|
|
numeroDfd: dfd.numeroDfd,
|
|
pedidoId,
|
|
criadoEm: now
|
|
});
|
|
|
|
// Mover itens deste DFD para o pedido
|
|
const itensDfd = items.filter((i) => (i.numeroDfd as string).trim() === dfd.numeroDfd);
|
|
for (const it of itensDfd) {
|
|
// Criar item real diretamente no pedido (sem etapa de conversão)
|
|
await ctx.db.insert('objetoItems', {
|
|
pedidoId,
|
|
objetoId: it.objetoId,
|
|
ataId: undefined,
|
|
acaoId: undefined,
|
|
valorEstimado: it.valorEstimado,
|
|
quantidade: it.quantidade,
|
|
adicionadoPor: user.funcionarioId,
|
|
criadoEm: now
|
|
});
|
|
|
|
await ctx.db.insert('historicoPedidos', {
|
|
pedidoId,
|
|
usuarioId: user._id,
|
|
acao: 'adicao_item',
|
|
detalhes: JSON.stringify({
|
|
objetoId: it.objetoId,
|
|
valor: it.valorEstimado,
|
|
quantidade: it.quantidade,
|
|
acaoId: null,
|
|
ataId: null,
|
|
modalidade: null,
|
|
origem: { planejamentoId: args.planejamentoId }
|
|
}),
|
|
data: now
|
|
});
|
|
|
|
await ctx.db.patch(it._id, { pedidoId, atualizadoEm: Date.now() });
|
|
}
|
|
}
|
|
|
|
await ctx.db.patch(args.planejamentoId, { status: 'gerado', atualizadoEm: Date.now() });
|
|
|
|
return pedidoIds;
|
|
}
|
|
});
|