feat: implement filtering and document management features in dashboard components, enhancing user experience with improved query capabilities and UI for managing documents in pedidos and compras
This commit is contained in:
@@ -47,6 +47,61 @@ async function ensurePedidoModalidadeAtaConsistency(
|
||||
}
|
||||
}
|
||||
|
||||
async function isFuncionarioInComprasSector(
|
||||
ctx: QueryCtx | MutationCtx,
|
||||
funcionarioId: Id<'funcionarios'>
|
||||
) {
|
||||
const config = await ctx.db.query('config').first();
|
||||
if (!config || !config.comprasSetorId) return false;
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionarioId))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
return !!isInSector;
|
||||
}
|
||||
|
||||
async function isUsuarioEnvolvidoNoPedido(
|
||||
ctx: QueryCtx | MutationCtx,
|
||||
pedidoId: Id<'pedidos'>,
|
||||
user: Awaited<ReturnType<typeof getUsuarioAutenticado>>
|
||||
) {
|
||||
const pedido = await ctx.db.get(pedidoId);
|
||||
if (!pedido) return false;
|
||||
|
||||
// Criador do pedido (por usuarioId)
|
||||
if (pedido.criadoPor === user._id) return true;
|
||||
|
||||
// Envolvimento por itens (requer funcionarioId)
|
||||
if (!user.funcionarioId) return false;
|
||||
|
||||
const hasItem = await ctx.db
|
||||
.query('objetoItems')
|
||||
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedidoId))
|
||||
.filter((q) => q.eq(q.field('adicionadoPor'), user.funcionarioId))
|
||||
.first();
|
||||
|
||||
return !!hasItem;
|
||||
}
|
||||
|
||||
async function assertPodeGerenciarDocumentosDoPedido(
|
||||
ctx: QueryCtx | MutationCtx,
|
||||
pedidoId: Id<'pedidos'>,
|
||||
user: Awaited<ReturnType<typeof getUsuarioAutenticado>>
|
||||
) {
|
||||
const isEnvolvido = await isUsuarioEnvolvidoNoPedido(ctx, pedidoId, user);
|
||||
if (isEnvolvido) return;
|
||||
|
||||
if (user.funcionarioId) {
|
||||
const isCompras = await isFuncionarioInComprasSector(ctx, user.funcionarioId);
|
||||
if (isCompras) return;
|
||||
}
|
||||
|
||||
throw new Error('Acesso negado.');
|
||||
}
|
||||
|
||||
// ========== QUERIES ==========
|
||||
|
||||
export const list = query({
|
||||
@@ -1652,6 +1707,8 @@ export const approveItemRequest = mutation({
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
const funcionarioId = user.funcionarioId;
|
||||
if (!funcionarioId) throw new Error('Usuário sem funcionário vinculado.');
|
||||
|
||||
const request = await ctx.db.get(args.requestId);
|
||||
if (!request) throw new Error('Solicitação não encontrada.');
|
||||
@@ -1666,12 +1723,18 @@ export const approveItemRequest = mutation({
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionarioId))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
if (!isInSector) throw new Error('Acesso negado.');
|
||||
|
||||
// Documentos anexados à solicitação (se houver) devem migrar para o pedido ao aprovar.
|
||||
const solicitacaoDocs = await ctx.db
|
||||
.query('solicitacoesItensDocumentos')
|
||||
.withIndex('by_requestId', (q) => q.eq('requestId', request._id))
|
||||
.collect();
|
||||
|
||||
// Apply the change
|
||||
const data = JSON.parse(request.dados);
|
||||
|
||||
@@ -1790,15 +1853,37 @@ export const approveItemRequest = mutation({
|
||||
// I'll delete it to keep table clean as requested.
|
||||
await ctx.db.delete(request._id);
|
||||
|
||||
// Migrar docs: cria em pedidoDocumentos e remove os registros da solicitacao
|
||||
const documentosMigrados: Array<{ nome: string; descricao: string }> = [];
|
||||
if (solicitacaoDocs.length > 0) {
|
||||
for (const doc of solicitacaoDocs) {
|
||||
await ctx.db.insert('pedidoDocumentos', {
|
||||
pedidoId: request.pedidoId,
|
||||
descricao: doc.descricao,
|
||||
nome: doc.nome,
|
||||
storageId: doc.storageId,
|
||||
tipo: doc.tipo,
|
||||
tamanho: doc.tamanho,
|
||||
criadoPor: doc.criadoPor,
|
||||
criadoEm: doc.criadoEm,
|
||||
origemSolicitacaoId: request._id
|
||||
});
|
||||
documentosMigrados.push({ nome: doc.nome, descricao: doc.descricao });
|
||||
await ctx.db.delete(doc._id);
|
||||
}
|
||||
}
|
||||
|
||||
// History
|
||||
await ctx.db.insert('historicoPedidos', {
|
||||
pedidoId: request.pedidoId,
|
||||
usuarioId: user._id,
|
||||
acao: 'aprovacao_solicitacao',
|
||||
detalhes: JSON.stringify({
|
||||
requestId: request._id,
|
||||
tipo: request.tipo,
|
||||
solicitadoPor: request.solicitadoPor,
|
||||
dados: data
|
||||
dados: data,
|
||||
documentosMigrados
|
||||
}),
|
||||
data: Date.now()
|
||||
});
|
||||
@@ -1814,6 +1899,8 @@ export const rejectItemRequest = mutation({
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
const funcionarioId = user.funcionarioId;
|
||||
if (!funcionarioId) throw new Error('Usuário sem funcionário vinculado.');
|
||||
const request = await ctx.db.get(args.requestId);
|
||||
if (!request) throw new Error('Solicitação não encontrada.');
|
||||
|
||||
@@ -1822,12 +1909,25 @@ export const rejectItemRequest = mutation({
|
||||
|
||||
const isInSector = await ctx.db
|
||||
.query('funcionarioSetores')
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
|
||||
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionarioId))
|
||||
.filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
|
||||
.first();
|
||||
|
||||
if (!isInSector) throw new Error('Acesso negado.');
|
||||
|
||||
// Remover documentos anexados à solicitação (para não deixar órfãos no storage)
|
||||
const solicitacaoDocs = await ctx.db
|
||||
.query('solicitacoesItensDocumentos')
|
||||
.withIndex('by_requestId', (q) => q.eq('requestId', request._id))
|
||||
.collect();
|
||||
|
||||
const documentosRemovidos: Array<{ nome: string; descricao: string }> = [];
|
||||
for (const doc of solicitacaoDocs) {
|
||||
documentosRemovidos.push({ nome: doc.nome, descricao: doc.descricao });
|
||||
await ctx.storage.delete(doc.storageId);
|
||||
await ctx.db.delete(doc._id);
|
||||
}
|
||||
|
||||
// Delete request
|
||||
await ctx.db.delete(args.requestId);
|
||||
|
||||
@@ -1837,10 +1937,230 @@ export const rejectItemRequest = mutation({
|
||||
usuarioId: user._id,
|
||||
acao: 'rejeicao_solicitacao',
|
||||
detalhes: JSON.stringify({
|
||||
requestId: request._id,
|
||||
tipo: request.tipo,
|
||||
solicitadoPor: request.solicitadoPor
|
||||
solicitadoPor: request.solicitadoPor,
|
||||
documentosRemovidos
|
||||
}),
|
||||
data: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== DOCUMENTOS (PEDIDO / SOLICITAÇÃO) ==========
|
||||
|
||||
export const generatePedidoUploadUrl = mutation({
|
||||
args: { pedidoId: v.id('pedidos') },
|
||||
returns: v.string(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
await assertPodeGerenciarDocumentosDoPedido(ctx, args.pedidoId, user);
|
||||
return await ctx.storage.generateUploadUrl();
|
||||
}
|
||||
});
|
||||
|
||||
export const addPedidoDocumento = mutation({
|
||||
args: {
|
||||
pedidoId: v.id('pedidos'),
|
||||
descricao: v.string(),
|
||||
nome: v.string(),
|
||||
storageId: v.id('_storage'),
|
||||
tipo: v.string(),
|
||||
tamanho: v.number(),
|
||||
origemSolicitacaoId: v.optional(v.id('solicitacoesItens'))
|
||||
},
|
||||
returns: v.id('pedidoDocumentos'),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
await assertPodeGerenciarDocumentosDoPedido(ctx, args.pedidoId, user);
|
||||
|
||||
if (!user.funcionarioId) {
|
||||
throw new Error('Usuário sem funcionário vinculado.');
|
||||
}
|
||||
|
||||
// Garantir que o pedido existe
|
||||
const pedido = await ctx.db.get(args.pedidoId);
|
||||
if (!pedido) throw new Error('Pedido não encontrado.');
|
||||
|
||||
return await ctx.db.insert('pedidoDocumentos', {
|
||||
pedidoId: args.pedidoId,
|
||||
descricao: args.descricao,
|
||||
nome: args.nome,
|
||||
storageId: args.storageId,
|
||||
tipo: args.tipo,
|
||||
tamanho: args.tamanho,
|
||||
criadoPor: user.funcionarioId,
|
||||
criadoEm: Date.now(),
|
||||
origemSolicitacaoId: args.origemSolicitacaoId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const listPedidoDocumentos = query({
|
||||
args: { pedidoId: v.id('pedidos') },
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
await assertPodeGerenciarDocumentosDoPedido(ctx, args.pedidoId, user);
|
||||
|
||||
const docs = await ctx.db
|
||||
.query('pedidoDocumentos')
|
||||
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
|
||||
.order('desc')
|
||||
.collect();
|
||||
|
||||
return await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const url = await ctx.storage.getUrl(doc.storageId);
|
||||
const func = await ctx.db.get(doc.criadoPor);
|
||||
return {
|
||||
...doc,
|
||||
criadoPorNome: func?.nome ?? 'Desconhecido',
|
||||
url
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const removePedidoDocumento = mutation({
|
||||
args: { id: v.id('pedidoDocumentos') },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
|
||||
const doc = await ctx.db.get(args.id);
|
||||
if (!doc) throw new Error('Documento não encontrado.');
|
||||
|
||||
// Pode remover se for o autor OU se for envolvido/compras
|
||||
if (!user.funcionarioId || doc.criadoPor !== user.funcionarioId) {
|
||||
await assertPodeGerenciarDocumentosDoPedido(ctx, doc.pedidoId, user);
|
||||
}
|
||||
|
||||
await ctx.storage.delete(doc.storageId);
|
||||
await ctx.db.delete(doc._id);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
export const generateSolicitacaoUploadUrl = mutation({
|
||||
args: { requestId: v.id('solicitacoesItens') },
|
||||
returns: v.string(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
if (!user.funcionarioId) throw new Error('Usuário sem funcionário vinculado.');
|
||||
|
||||
const request = await ctx.db.get(args.requestId);
|
||||
if (!request) throw new Error('Solicitação não encontrada.');
|
||||
if (request.tipo !== 'adicao') {
|
||||
throw new Error('Apenas solicitações de adição permitem anexar documentos.');
|
||||
}
|
||||
if (request.status !== 'pendente') {
|
||||
throw new Error('Não é possível anexar documentos em uma solicitação não pendente.');
|
||||
}
|
||||
if (request.solicitadoPor !== user.funcionarioId) {
|
||||
throw new Error('Apenas quem criou a solicitação pode anexar documentos.');
|
||||
}
|
||||
|
||||
return await ctx.storage.generateUploadUrl();
|
||||
}
|
||||
});
|
||||
|
||||
export const addSolicitacaoDocumento = mutation({
|
||||
args: {
|
||||
requestId: v.id('solicitacoesItens'),
|
||||
descricao: v.string(),
|
||||
nome: v.string(),
|
||||
storageId: v.id('_storage'),
|
||||
tipo: v.string(),
|
||||
tamanho: v.number()
|
||||
},
|
||||
returns: v.id('solicitacoesItensDocumentos'),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
if (!user.funcionarioId) throw new Error('Usuário sem funcionário vinculado.');
|
||||
|
||||
const request = await ctx.db.get(args.requestId);
|
||||
if (!request) throw new Error('Solicitação não encontrada.');
|
||||
if (request.tipo !== 'adicao') {
|
||||
throw new Error('Apenas solicitações de adição permitem anexar documentos.');
|
||||
}
|
||||
if (request.status !== 'pendente') {
|
||||
throw new Error('Não é possível anexar documentos em uma solicitação não pendente.');
|
||||
}
|
||||
if (request.solicitadoPor !== user.funcionarioId) {
|
||||
throw new Error('Apenas quem criou a solicitação pode anexar documentos.');
|
||||
}
|
||||
|
||||
return await ctx.db.insert('solicitacoesItensDocumentos', {
|
||||
requestId: args.requestId,
|
||||
pedidoId: request.pedidoId,
|
||||
descricao: args.descricao,
|
||||
nome: args.nome,
|
||||
storageId: args.storageId,
|
||||
tipo: args.tipo,
|
||||
tamanho: args.tamanho,
|
||||
criadoPor: user.funcionarioId,
|
||||
criadoEm: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const listSolicitacaoDocumentos = query({
|
||||
args: { requestId: v.id('solicitacoesItens') },
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
const request = await ctx.db.get(args.requestId);
|
||||
if (!request) return [];
|
||||
|
||||
await assertPodeGerenciarDocumentosDoPedido(ctx, request.pedidoId, user);
|
||||
|
||||
const docs = await ctx.db
|
||||
.query('solicitacoesItensDocumentos')
|
||||
.withIndex('by_requestId', (q) => q.eq('requestId', args.requestId))
|
||||
.order('desc')
|
||||
.collect();
|
||||
|
||||
return await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const url = await ctx.storage.getUrl(doc.storageId);
|
||||
const func = await ctx.db.get(doc.criadoPor);
|
||||
return {
|
||||
...doc,
|
||||
criadoPorNome: func?.nome ?? 'Desconhecido',
|
||||
url
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const removeSolicitacaoDocumento = mutation({
|
||||
args: { id: v.id('solicitacoesItensDocumentos') },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await getUsuarioAutenticado(ctx);
|
||||
if (!user.funcionarioId) throw new Error('Usuário sem funcionário vinculado.');
|
||||
|
||||
const doc = await ctx.db.get(args.id);
|
||||
if (!doc) throw new Error('Documento não encontrado.');
|
||||
|
||||
const request = await ctx.db.get(doc.requestId);
|
||||
if (!request) throw new Error('Solicitação não encontrada.');
|
||||
if (request.tipo !== 'adicao') {
|
||||
throw new Error('Apenas solicitações de adição permitem documentos.');
|
||||
}
|
||||
if (request.status !== 'pendente') {
|
||||
throw new Error('Não é possível remover documentos de uma solicitação não pendente.');
|
||||
}
|
||||
|
||||
if (doc.criadoPor !== user.funcionarioId || request.solicitadoPor !== user.funcionarioId) {
|
||||
throw new Error('Apenas quem criou a solicitação pode remover documentos.');
|
||||
}
|
||||
|
||||
await ctx.storage.delete(doc.storageId);
|
||||
await ctx.db.delete(doc._id);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user