feat: Implement item request/approval workflow for pedidos in analysis mode, adding conditional item modifications and new request management APIs.
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
const objetosQuery = $derived.by(() => useQuery(api.objetos.list, {}));
|
||||
const acoesQuery = $derived.by(() => useQuery(api.acoes.list, {}));
|
||||
const permissionsQuery = $derived.by(() => useQuery(api.pedidos.getPermissions, { pedidoId }));
|
||||
const requestsQuery = $derived.by(() => useQuery(api.pedidos.getItemRequests, { pedidoId }));
|
||||
|
||||
// Derived state
|
||||
let pedido = $derived(pedidoQuery.data);
|
||||
@@ -39,6 +40,7 @@
|
||||
let objetos = $derived(objetosQuery.data || []);
|
||||
let acoes = $derived(acoesQuery.data || []);
|
||||
let permissions = $derived(permissionsQuery.data);
|
||||
let requests = $derived(requestsQuery.data || []);
|
||||
|
||||
type Modalidade = 'dispensa' | 'inexgibilidade' | 'adesao' | 'consumo';
|
||||
|
||||
@@ -116,8 +118,10 @@
|
||||
itemsQuery.isLoading ||
|
||||
historyQuery.isLoading ||
|
||||
objetosQuery.isLoading ||
|
||||
objetosQuery.isLoading ||
|
||||
acoesQuery.isLoading ||
|
||||
permissionsQuery.isLoading
|
||||
permissionsQuery.isLoading ||
|
||||
requestsQuery.isLoading
|
||||
);
|
||||
|
||||
let error = $derived(
|
||||
@@ -207,7 +211,7 @@
|
||||
});
|
||||
|
||||
async function handleAddItem() {
|
||||
if (!newItem.objetoId || !newItem.valorEstimado) return;
|
||||
if (!pedido || !newItem.objetoId || !newItem.valorEstimado) return;
|
||||
addingItem = true;
|
||||
try {
|
||||
await client.mutation(api.pedidos.addItem, {
|
||||
@@ -228,6 +232,9 @@
|
||||
ataId: ''
|
||||
};
|
||||
showAddItem = false;
|
||||
if (pedido.status === 'em_analise') {
|
||||
alert('Solicitação de adição enviada para análise.');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Erro ao adicionar item: ' + (e as Error).message);
|
||||
} finally {
|
||||
@@ -236,6 +243,8 @@
|
||||
}
|
||||
|
||||
async function handleUpdateQuantity(itemId: Id<'objetoItems'>, novaQuantidade: number) {
|
||||
if (!pedido) return;
|
||||
|
||||
if (novaQuantidade < 1) {
|
||||
alert('Quantidade deve ser pelo menos 1.');
|
||||
return;
|
||||
@@ -245,15 +254,22 @@
|
||||
itemId,
|
||||
novaQuantidade
|
||||
});
|
||||
if (pedido.status === 'em_analise') {
|
||||
alert('Solicitação de alteração de quantidade enviada para análise.');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Erro ao atualizar quantidade: ' + (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveItem(itemId: Id<'objetoItems'>) {
|
||||
if (!pedido) return;
|
||||
if (!confirm('Remover este item?')) return;
|
||||
try {
|
||||
await client.mutation(api.pedidos.removeItem, { itemId });
|
||||
if (pedido.status === 'em_analise') {
|
||||
alert('Solicitação de remoção enviada para análise.');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Erro ao remover item: ' + (e as Error).message);
|
||||
}
|
||||
@@ -625,6 +641,32 @@
|
||||
alert('Erro: ' + (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApproveRequest(requestId: Id<'solicitacoesItens'>) {
|
||||
if (!confirm('Aprovar esta solicitação?')) return;
|
||||
try {
|
||||
await client.mutation(api.pedidos.approveItemRequest, { requestId });
|
||||
} catch (e) {
|
||||
alert('Erro ao aprovar: ' + (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRejectRequest(requestId: Id<'solicitacoesItens'>) {
|
||||
if (!confirm('Rejeitar esta solicitação?')) return;
|
||||
try {
|
||||
await client.mutation(api.pedidos.rejectItemRequest, { requestId });
|
||||
} catch (e) {
|
||||
alert('Erro ao rejeitar: ' + (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
function parseRequestData(json: string) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-6">
|
||||
@@ -752,6 +794,87 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Requests Section -->
|
||||
{#if requests.length > 0}
|
||||
<div class="mb-6 overflow-hidden rounded-lg border-l-4 border-yellow-400 bg-white shadow-md">
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-gray-200 bg-yellow-50 px-6 py-4"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-yellow-800">Solicitações Pendentes</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm text-gray-500">
|
||||
<thead class="bg-gray-50 text-xs font-medium text-gray-500 uppercase">
|
||||
<tr>
|
||||
<th class="px-4 py-2">Tipo</th>
|
||||
<th class="px-4 py-2">Solicitante</th>
|
||||
<th class="px-4 py-2">Detalhes</th>
|
||||
<th class="px-4 py-2 text-right">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{#each requests as req (req._id)}
|
||||
{@const data = parseRequestData(req.dados)}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-2 font-medium">
|
||||
{#if req.tipo === 'adicao'}
|
||||
<span class="text-green-600">Adição</span>
|
||||
{:else if req.tipo === 'alteracao_quantidade'}
|
||||
<span class="text-blue-600">Alteração Qtd</span>
|
||||
{:else if req.tipo === 'exclusao'}
|
||||
<span class="text-red-600">Exclusão</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-4 py-2">{req.solicitadoPorNome}</td>
|
||||
<td class="px-4 py-2">
|
||||
{#if req.tipo === 'adicao'}
|
||||
{getObjetoName(data.objetoId)} - {data.quantidade}x ({data.modalidade})
|
||||
{:else if req.tipo === 'alteracao_quantidade'}
|
||||
{#if data.itemId}
|
||||
{@const item = items.find((i) => i._id === data.itemId)}
|
||||
{item ? getObjetoName(item.objetoId) : 'Item desconhecido'} (Nova Qtd: {data.novaQuantidade})
|
||||
{:else}
|
||||
Qtd: {data.novaQuantidade}
|
||||
{/if}
|
||||
{:else if req.tipo === 'exclusao'}
|
||||
{#if data.itemId}
|
||||
{@const item = items.find((i) => i._id === data.itemId)}
|
||||
Remover: {item ? getObjetoName(item.objetoId) : 'Item desconhecido'}
|
||||
{:else}
|
||||
Remover Item
|
||||
{/if}
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-right">
|
||||
{#if permissions?.canManageRequests}
|
||||
<button
|
||||
onclick={() => handleApproveRequest(req._id)}
|
||||
class="mr-2 rounded bg-green-100 p-1 text-green-700 hover:bg-green-200"
|
||||
title="Aprovar"
|
||||
>
|
||||
<CheckCircle size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleRejectRequest(req._id)}
|
||||
class="rounded bg-red-100 p-1 text-red-700 hover:bg-red-200"
|
||||
title="Rejeitar"
|
||||
>
|
||||
<XCircle size={16} />
|
||||
</button>
|
||||
{:else}
|
||||
<span class="text-xs text-gray-400">Aguardando Análise</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Items Section -->
|
||||
<div class="mb-6 overflow-hidden rounded-lg bg-white shadow-md">
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-6 py-4">
|
||||
@@ -998,7 +1121,7 @@
|
||||
{/if}
|
||||
<td class="px-6 py-4 whitespace-nowrap">{getObjetoName(item.objetoId)}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes'}
|
||||
{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise'}
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -1110,7 +1233,7 @@
|
||||
>
|
||||
<Eye size={18} />
|
||||
</button>
|
||||
{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes'}
|
||||
{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise'}
|
||||
<button
|
||||
onclick={() => handleRemoveItem(item._id)}
|
||||
class="text-red-600 hover:text-red-900"
|
||||
|
||||
Reference in New Issue
Block a user