feat: Implement batch item removal and pedido splitting for pedidos, and add document management for atas.

This commit is contained in:
2025-12-03 23:37:26 -03:00
parent fb78866a0e
commit 7746dce25a
6 changed files with 796 additions and 100 deletions

View File

@@ -47,9 +47,7 @@ export const listByObjetoIds = query({
links.push(...partial);
}
const ataIds = Array.from(
new Set(links.map((l) => l.ataId as Id<'atas'>))
);
const ataIds = Array.from(new Set(links.map((l) => l.ataId as Id<'atas'>)));
if (ataIds.length === 0) return [];
@@ -64,9 +62,9 @@ export const create = mutation({
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
empresaId: v.id('empresas'),
pdf: v.optional(v.string()),
pdf: v.optional(v.id('_storage')),
numeroSei: v.string(),
objetosIds: v.optional(v.array(v.id('objetos')))
objetosIds: v.array(v.id('objetos'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
@@ -74,23 +72,22 @@ export const create = mutation({
const ataId = await ctx.db.insert('atas', {
numero: args.numero,
numeroSei: args.numeroSei,
empresaId: args.empresaId,
dataInicio: args.dataInicio,
dataFim: args.dataFim,
empresaId: args.empresaId,
pdf: args.pdf,
numeroSei: args.numeroSei,
criadoPor: user._id,
criadoEm: Date.now(),
atualizadoEm: Date.now()
});
if (args.objetosIds) {
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId,
objetoId
});
}
// Vincular objetos
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId,
objetoId
});
}
return ataId;
@@ -101,12 +98,12 @@ export const update = mutation({
args: {
id: v.id('atas'),
numero: v.string(),
numeroSei: v.string(),
empresaId: v.id('empresas'),
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
empresaId: v.id('empresas'),
pdf: v.optional(v.string()),
numeroSei: v.string(),
objetosIds: v.optional(v.array(v.id('objetos')))
pdf: v.optional(v.id('_storage')),
objetosIds: v.array(v.id('objetos'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
@@ -114,45 +111,42 @@ export const update = mutation({
await ctx.db.patch(args.id, {
numero: args.numero,
numeroSei: args.numeroSei,
empresaId: args.empresaId,
dataInicio: args.dataInicio,
dataFim: args.dataFim,
empresaId: args.empresaId,
pdf: args.pdf,
numeroSei: args.numeroSei,
atualizadoEm: Date.now()
});
if (args.objetosIds !== undefined) {
// Remove existing links
const existingLinks = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
// 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();
for (const link of existingLinks) {
await ctx.db.delete(link._id);
}
for (const link of existingLinks) {
await ctx.db.delete(link._id);
}
// Add new links
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId: args.id,
objetoId
});
}
// Adiciona os novos vínculos
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId: args.id,
objetoId
});
}
}
});
export const remove = mutation({
args: {
id: v.id('atas')
},
args: { id: v.id('atas') },
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
// Remove linked objects
// Remover vínculos com objetos
const links = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
@@ -162,6 +156,79 @@ export const remove = mutation({
await ctx.db.delete(link._id);
}
// Remover documentos vinculados
const docs = await ctx.db
.query('atasDocumentos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
for (const doc of docs) {
await ctx.storage.delete(doc.storageId);
await ctx.db.delete(doc._id);
}
await ctx.db.delete(args.id);
}
});
export const generateUploadUrl = mutation({
args: {},
handler: async (ctx) => {
return await ctx.storage.generateUploadUrl();
}
});
export const saveDocumento = mutation({
args: {
ataId: v.id('atas'),
nome: v.string(),
storageId: v.id('_storage'),
tipo: v.string(),
tamanho: v.number()
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
return await ctx.db.insert('atasDocumentos', {
ataId: args.ataId,
nome: args.nome,
storageId: args.storageId,
tipo: args.tipo,
tamanho: args.tamanho,
criadoPor: user._id,
criadoEm: Date.now()
});
}
});
export const removeDocumento = mutation({
args: { id: v.id('atasDocumentos') },
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
const doc = await ctx.db.get(args.id);
if (!doc) throw new Error('Documento não encontrado');
await ctx.storage.delete(doc.storageId);
await ctx.db.delete(args.id);
}
});
export const getDocumentos = query({
args: { ataId: v.id('atas') },
handler: async (ctx, args) => {
const docs = await ctx.db
.query('atasDocumentos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.ataId))
.collect();
return await Promise.all(
docs.map(async (doc) => ({
...doc,
url: await ctx.storage.getUrl(doc.storageId)
}))
);
}
});

View File

@@ -238,9 +238,7 @@ export const checkExisting = query({
.collect();
const matching = items.filter((i) =>
itensFiltro.some(
(f) => f.objetoId === i.objetoId && f.modalidade === i.modalidade
)
itensFiltro.some((f) => f.objetoId === i.objetoId && f.modalidade === i.modalidade)
);
if (matching.length > 0) {
@@ -513,6 +511,159 @@ export const removeItem = mutation({
}
});
export const removeItemsBatch = mutation({
args: {
itemIds: v.array(v.id('objetoItems'))
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
if (!user.funcionarioId) {
throw new Error('Usuário não vinculado a um funcionário.');
}
if (args.itemIds.length === 0) {
return null;
}
const firstItem = await ctx.db.get(args.itemIds[0]);
if (!firstItem) {
throw new Error('Item não encontrado.');
}
const pedidoId = firstItem.pedidoId;
const pedido = await ctx.db.get(pedidoId);
if (!pedido) {
throw new Error('Pedido não encontrado.');
}
if (pedido.status !== 'em_rascunho' && pedido.status !== 'precisa_ajustes') {
throw new Error(
'Só é possível remover itens em pedidos em rascunho ou que precisam de ajustes.'
);
}
for (const itemId of args.itemIds) {
const item = await ctx.db.get(itemId);
if (!item) continue;
if (item.pedidoId !== pedidoId) {
throw new Error('Todos os itens devem pertencer ao mesmo pedido.');
}
if (item.adicionadoPor !== user.funcionarioId) {
throw new Error('Você só pode remover itens que você adicionou.');
}
await ctx.db.delete(itemId);
await ctx.db.insert('historicoPedidos', {
pedidoId,
usuarioId: user._id,
acao: 'remocao_item',
detalhes: JSON.stringify({
objetoId: item.objetoId,
valor: item.valorEstimado
}),
data: Date.now()
});
}
await ctx.db.patch(pedidoId, { atualizadoEm: Date.now() });
return null;
}
});
export const splitPedido = mutation({
args: {
pedidoId: v.id('pedidos'),
itemIds: v.array(v.id('objetoItems')),
numeroSei: v.optional(v.string())
},
returns: 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.');
}
if (args.itemIds.length === 0) {
throw new Error('Selecione ao menos um item para dividir o pedido.');
}
const pedidoOriginal = await ctx.db.get(args.pedidoId);
if (!pedidoOriginal) {
throw new Error('Pedido não encontrado.');
}
if (pedidoOriginal.status !== 'em_rascunho' && pedidoOriginal.status !== 'precisa_ajustes') {
throw new Error('Só é possível dividir pedidos em rascunho ou que precisam de ajustes.');
}
const itens = [];
for (const itemId of args.itemIds) {
const item = await ctx.db.get(itemId);
if (!item) {
continue;
}
if (item.pedidoId !== args.pedidoId) {
throw new Error('Todos os itens devem pertencer ao mesmo pedido.');
}
if (item.adicionadoPor !== user.funcionarioId) {
throw new Error('Você só pode mover itens que você adicionou.');
}
itens.push(item);
}
if (itens.length === 0) {
throw new Error('Nenhum dos itens selecionados pôde ser usado para divisão.');
}
const novoPedidoId = await ctx.db.insert('pedidos', {
numeroSei: args.numeroSei,
status: 'em_rascunho',
criadoPor: user._id,
criadoEm: Date.now(),
atualizadoEm: Date.now()
});
for (const item of itens) {
await ctx.db.patch(item._id, {
pedidoId: novoPedidoId
});
}
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: user._id,
acao: 'divisao_pedido_origem',
detalhes: JSON.stringify({
itensMovidos: itens.map((i) => i._id),
novoPedidoId
}),
data: Date.now()
});
await ctx.db.insert('historicoPedidos', {
pedidoId: novoPedidoId,
usuarioId: user._id,
acao: 'divisao_pedido_destino',
detalhes: JSON.stringify({
pedidoOriginalId: args.pedidoId,
itensRecebidos: itens.map((i) => i._id)
}),
data: Date.now()
});
await ctx.db.patch(args.pedidoId, { atualizadoEm: Date.now() });
return novoPedidoId;
}
});
export const updateItem = mutation({
args: {
itemId: v.id('objetoItems'),

View File

@@ -7,7 +7,7 @@ export const atasTables = {
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
empresaId: v.id('empresas'),
pdf: v.optional(v.string()), // storage ID
pdf: v.optional(v.id('_storage')),
numeroSei: v.string(),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
@@ -22,5 +22,15 @@ export const atasTables = {
objetoId: v.id('objetos')
})
.index('by_ataId', ['ataId'])
.index('by_objetoId', ['objetoId'])
.index('by_objetoId', ['objetoId']),
atasDocumentos: defineTable({
ataId: v.id('atas'),
nome: v.string(),
storageId: v.id('_storage'),
tipo: v.string(), // MIME type
tamanho: v.number(), // bytes
criadoPor: v.id('usuarios'),
criadoEm: v.number()
}).index('by_ataId', ['ataId'])
};