diff --git a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte
index 76263d2..f9bbafb 100644
--- a/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/pedidos/[id]/+page.svelte
@@ -286,15 +286,6 @@
}
}
- async function handleSolicitarAjustes() {
- if (!confirm('Solicitar ajustes?')) return;
- try {
- await client.mutation(api.pedidos.solicitarAjustes, { pedidoId });
- } catch (e) {
- alert('Erro: ' + (e as Error).message);
- }
- }
-
async function handleCancelar() {
if (!confirm('Cancelar pedido?')) return;
try {
@@ -600,6 +591,40 @@
alert('Erro ao dividir pedido: ' + (e as Error).message);
}
}
+
+ // Adjustment Modal State
+ let showRequestAdjustmentsModal = $state(false);
+ let adjustmentDescription = $state('');
+
+ function openRequestAdjustmentsModal() {
+ adjustmentDescription = '';
+ showRequestAdjustmentsModal = true;
+ }
+
+ async function confirmRequestAdjustments() {
+ if (!adjustmentDescription.trim()) {
+ alert('Por favor, informe a descrição dos ajustes necessários.');
+ return;
+ }
+ try {
+ await client.mutation(api.pedidos.solicitarAjustes, {
+ pedidoId,
+ descricao: adjustmentDescription
+ });
+ showRequestAdjustmentsModal = false;
+ } catch (e) {
+ alert('Erro: ' + (e as Error).message);
+ }
+ }
+
+ async function handleConcluirAjustes() {
+ if (!confirm('Concluir ajustes e retornar para análise?')) return;
+ try {
+ await client.mutation(api.pedidos.concluirAjustes, { pedidoId });
+ } catch (e) {
+ alert('Erro: ' + (e as Error).message);
+ }
+ }
@@ -660,6 +685,14 @@
⚠️ Este pedido não possui número SEI. Adicione um número SEI quando disponível.
{/if}
+ {#if pedido.status === 'precisa_ajustes' && pedido.descricaoAjuste}
+
+
+ Ajustes Solicitados:
+
+
{pedido.descricaoAjuste}
+
+ {/if}
@@ -692,13 +725,22 @@
{#if permissions?.canRequestAdjustments}
Solicitar Ajustes
{/if}
+ {#if permissions?.canCompleteAdjustments}
+
+ Concluir Ajustes
+
+ {/if}
+
{#if permissions?.canCancel}
{/if}
+
+ {#if showRequestAdjustmentsModal}
+
+
+
Solicitar Ajustes
+
+ Descreva os ajustes necessários para este pedido. O status será alterado para "Precisa de
+ Ajustes".
+
+
+
+ (showRequestAdjustmentsModal = false)}
+ class="rounded bg-gray-200 px-4 py-2 text-gray-700 hover:bg-gray-300"
+ >
+ Cancelar
+
+
+ Confirmar Solicitação
+
+
+
+
+ {/if}
diff --git a/packages/backend/convex/pedidos.ts b/packages/backend/convex/pedidos.ts
index 581daf9..2630a35 100644
--- a/packages/backend/convex/pedidos.ts
+++ b/packages/backend/convex/pedidos.ts
@@ -32,6 +32,8 @@ export const list = query({
),
// acaoId removed from return
criadoPor: v.id('usuarios'),
+ aceitoPor: v.optional(v.id('funcionarios')),
+ descricaoAjuste: v.optional(v.string()),
criadoEm: v.number(),
atualizadoEm: v.number()
})
@@ -58,6 +60,8 @@ export const get = query({
),
acaoId: v.optional(v.id('acoes')),
criadoPor: v.id('usuarios'),
+ aceitoPor: v.optional(v.id('funcionarios')),
+ descricaoAjuste: v.optional(v.string()),
criadoEm: v.number(),
atualizadoEm: v.number()
}),
@@ -175,6 +179,8 @@ export const checkExisting = query({
),
// acaoId removed
criadoPor: v.id('usuarios'),
+ aceitoPor: v.optional(v.id('funcionarios')),
+ descricaoAjuste: v.optional(v.string()),
criadoEm: v.number(),
atualizadoEm: v.number(),
matchingItems: v.optional(
@@ -259,6 +265,8 @@ export const checkExisting = query({
numeroSei: pedido.numeroSei,
status: pedido.status,
criadoPor: pedido.criadoPor,
+ aceitoPor: pedido.aceitoPor,
+ descricaoAjuste: pedido.descricaoAjuste,
criadoEm: pedido.criadoEm,
atualizadoEm: pedido.atualizadoEm,
matchingItems: matchingItems.length > 0 ? matchingItems : undefined
@@ -318,6 +326,8 @@ export const listForAcceptance = query({
status: o.status,
criadoPor: o.criadoPor,
criadoPorNome: creator?.nome || 'Desconhecido',
+ aceitoPor: o.aceitoPor,
+ descricaoAjuste: o.descricaoAjuste,
criadoEm: o.criadoEm,
atualizadoEm: o.atualizadoEm
};
@@ -337,6 +347,7 @@ export const listMyAnalysis = query({
criadoPor: v.id('usuarios'),
criadoPorNome: v.string(),
aceitoPor: v.optional(v.id('funcionarios')),
+ descricaoAjuste: v.optional(v.string()),
criadoEm: v.number(),
atualizadoEm: v.number()
})
@@ -383,6 +394,7 @@ export const listMyAnalysis = query({
criadoPor: o.criadoPor,
criadoPorNome: creator?.nome || 'Desconhecido',
aceitoPor: o.aceitoPor,
+ descricaoAjuste: o.descricaoAjuste,
criadoEm: o.criadoEm,
atualizadoEm: o.atualizadoEm
};
@@ -434,6 +446,8 @@ export const listByItemCreator = query({
status: o!.status,
criadoPor: o!.criadoPor,
criadoPorNome: creator?.nome || 'Desconhecido',
+ aceitoPor: o!.aceitoPor,
+ descricaoAjuste: o!.descricaoAjuste,
criadoEm: o!.criadoEm,
atualizadoEm: o!.atualizadoEm
};
@@ -1000,7 +1014,11 @@ export const getPermissions = query({
(pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes') && hasAddedItems,
canStartAnalysis: pedido.status === 'aguardando_aceite' && isInComprasSector,
canConclude: pedido.status === 'em_analise' && isInComprasSector,
- canRequestAdjustments: pedido.status === 'em_analise' && isInComprasSector,
+ canRequestAdjustments:
+ pedido.status === 'em_analise' &&
+ isInComprasSector &&
+ pedido.aceitoPor === user.funcionarioId,
+ canCompleteAdjustments: pedido.status === 'precisa_ajustes' && hasAddedItems,
canCancel:
pedido.status !== 'cancelado' &&
pedido.status !== 'concluido' &&
@@ -1161,7 +1179,10 @@ export const concluirPedido = mutation({
});
export const solicitarAjustes = mutation({
- args: { pedidoId: v.id('pedidos') },
+ args: {
+ pedidoId: v.id('pedidos'),
+ descricao: v.string()
+ },
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
@@ -1172,32 +1193,29 @@ export const solicitarAjustes = mutation({
throw new Error('O pedido deve estar em análise para solicitar ajustes.');
}
- // Security Check: Must be in Compras Sector
- const config = await ctx.db.query('config').first();
- if (!config || !config.comprasSetorId) throw new Error('Setor de compras não configurado.');
-
- const isInSector = await ctx.db
- .query('funcionarioSetores')
- .withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', user.funcionarioId!))
- .filter((q) => q.eq(q.field('setorId'), config.comprasSetorId))
- .first();
-
- if (!isInSector) throw new Error('Acesso não autorizado (Setor de Compras).');
+ // Security Check: Only the user who accepted the order can request adjustments
+ if (pedido.aceitoPor !== user.funcionarioId) {
+ throw new Error('Apenas o funcionário que aceitou o pedido pode solicitar ajustes.');
+ }
const oldStatus = pedido.status;
const newStatus = 'precisa_ajustes';
await ctx.db.patch(args.pedidoId, {
status: newStatus,
- aceitoPor: undefined, // Clear accepted by since it's back to adjustments
+ descricaoAjuste: args.descricao,
atualizadoEm: Date.now()
});
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: user._id,
- acao: 'alteracao_status',
- detalhes: JSON.stringify({ de: oldStatus, para: newStatus }),
+ acao: 'solicitacao_ajuste',
+ detalhes: JSON.stringify({
+ de: oldStatus,
+ para: newStatus,
+ descricao: args.descricao
+ }),
data: Date.now()
});
@@ -1205,7 +1223,65 @@ export const solicitarAjustes = mutation({
pedidoId: args.pedidoId,
oldStatus,
newStatus,
- actorId: user._id
+ actorId: user._id,
+ customMessage: args.descricao
+ });
+ }
+});
+
+export const concluirAjustes = mutation({
+ args: { pedidoId: v.id('pedidos') },
+ 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 pedido = await ctx.db.get(args.pedidoId);
+ if (!pedido) throw new Error('Pedido não encontrado.');
+
+ if (pedido.status !== 'precisa_ajustes') {
+ throw new Error('O pedido não está aguardando ajustes.');
+ }
+
+ // Security Check: Must have items in the order
+ const requestorItem = await ctx.db
+ .query('objetoItems')
+ .withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
+ .filter((q) => q.eq(q.field('adicionadoPor'), user.funcionarioId))
+ .first();
+
+ if (!requestorItem) {
+ throw new Error('Você deve ter itens no pedido para concluir os ajustes.');
+ }
+
+ const oldStatus = pedido.status;
+ const newStatus = 'em_analise';
+ const descricaoAnterior = pedido.descricaoAjuste;
+
+ await ctx.db.patch(args.pedidoId, {
+ status: newStatus,
+ descricaoAjuste: undefined, // Clear the adjustment description from the active field
+ atualizadoEm: Date.now()
+ });
+
+ await ctx.db.insert('historicoPedidos', {
+ pedidoId: args.pedidoId,
+ usuarioId: user._id,
+ acao: 'conclusao_ajustes',
+ detalhes: JSON.stringify({
+ de: oldStatus,
+ para: newStatus,
+ descricaoResolvida: descricaoAnterior
+ }),
+ data: Date.now()
+ });
+
+ await ctx.scheduler.runAfter(0, internal.pedidos.notifyStatusChange, {
+ pedidoId: args.pedidoId,
+ oldStatus,
+ newStatus,
+ actorId: user._id,
+ customMessage: 'Ajustes concluídos. O pedido retornou para análise.'
});
}
});
@@ -1273,7 +1349,8 @@ export const notifyStatusChange = internalMutation({
pedidoId: v.id('pedidos'),
oldStatus: v.string(),
newStatus: v.string(),
- actorId: v.id('usuarios')
+ actorId: v.id('usuarios'),
+ customMessage: v.optional(v.string())
},
returns: v.null(),
handler: async (ctx, args) => {
@@ -1330,6 +1407,11 @@ export const notifyStatusChange = internalMutation({
}
}
+ let description = `Status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`;
+ if (args.customMessage) {
+ description += `\n\nDetalhes: ${args.customMessage}`;
+ }
+
// Send Notifications
for (const recipientId of recipients) {
const recipientIdTyped = recipientId as Id<'usuarios'>;
@@ -1339,7 +1421,7 @@ export const notifyStatusChange = internalMutation({
usuarioId: recipientIdTyped,
tipo: 'alerta_seguranca', // Using alerta_seguranca as the closest match for system notifications
titulo: `Pedido ${pedido.numeroSei || 'sem número SEI'} atualizado`,
- descricao: `Status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
+ descricao: description,
lida: false,
criadaEm: Date.now(),
remetenteId: args.actorId
@@ -1353,7 +1435,7 @@ export const notifyStatusChange = internalMutation({
destinatario: recipientUser.email,
destinatarioId: recipientIdTyped,
assunto: `Atualização no Pedido ${pedido.numeroSei || 'sem número SEI'}`,
- corpo: `O pedido ${pedido.numeroSei || 'sem número SEI'} teve seu status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
+ corpo: `O pedido ${pedido.numeroSei || 'sem número SEI'} teve seu status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}. ${args.customMessage ? `Detalhes: ${args.customMessage}` : ''}`,
enviadoPor: args.actorId
});
}
diff --git a/packages/backend/convex/tables/pedidos.ts b/packages/backend/convex/tables/pedidos.ts
index f5862c2..c50f586 100644
--- a/packages/backend/convex/tables/pedidos.ts
+++ b/packages/backend/convex/tables/pedidos.ts
@@ -15,6 +15,7 @@ export const pedidosTables = {
// acaoId removed
criadoPor: v.id('usuarios'),
aceitoPor: v.optional(v.id('funcionarios')),
+ descricaoAjuste: v.optional(v.string()), // Required when status is 'precisa_ajustes'
criadoEm: v.number(),
atualizadoEm: v.number()
})