Allow production orders to return to prior stages
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m2s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m2s
This commit is contained in:
@@ -98,6 +98,21 @@ const getNextStage = (status: ProductionOrderStatus): { status: Exclude<Producti
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPreviousStage = (status: ProductionOrderStatus): { status: Exclude<ProductionOrderStatusTab, 'all'>; label: string; description: string } | null => {
|
||||||
|
switch (status) {
|
||||||
|
case 'cutting': return { status: 'open', label: 'Voltar para em aberto', description: 'Use quando a produção ainda não foi iniciada.' };
|
||||||
|
case 'sewing': return { status: 'cutting', label: 'Voltar para corte', description: 'Use quando esta OP precisa retornar à etapa de corte.' };
|
||||||
|
case 'review': return { status: 'sewing', label: 'Voltar para costura', description: 'Use quando a revisão identificar um ajuste na montagem.' };
|
||||||
|
case 'finished': return { status: 'review', label: 'Reabrir em revisão', description: 'Use para corrigir uma OP finalizada antes de encerrá-la novamente.' };
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canReturnToCutPlan = (order: ProductionOrderItem) => (
|
||||||
|
!['finished', 'canceled'].includes(order.status)
|
||||||
|
&& (order.integrationStatus || '').toLowerCase() === 'local'
|
||||||
|
);
|
||||||
|
|
||||||
const ProductionOrdersSkeleton = () => (
|
const ProductionOrdersSkeleton = () => (
|
||||||
<div className="space-y-6" aria-label="Carregando ordens de produção">
|
<div className="space-y-6" aria-label="Carregando ordens de produção">
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||||
@@ -229,7 +244,8 @@ const ProductionOrders = () => {
|
|||||||
const finishedQuantity = summary.orders.filter(order => order.status === 'finished').reduce((total, order) => total + (order.finishedQuantity ?? order.quantity), 0);
|
const finishedQuantity = summary.orders.filter(order => order.status === 'finished').reduce((total, order) => total + (order.finishedQuantity ?? order.quantity), 0);
|
||||||
const selectedOrder = summary.orders.find(order => order.id === selectedOrderId) || null;
|
const selectedOrder = summary.orders.find(order => order.id === selectedOrderId) || null;
|
||||||
const selectedOrderNextStage = selectedOrder ? getNextStage(selectedOrder.status) : null;
|
const selectedOrderNextStage = selectedOrder ? getNextStage(selectedOrder.status) : null;
|
||||||
const selectedOrderCanReturnToCutPlan = Boolean(selectedOrder && selectedOrder.status === 'open' && (selectedOrder.integrationStatus || '').toLowerCase() === 'local');
|
const selectedOrderPreviousStage = selectedOrder ? getPreviousStage(selectedOrder.status) : null;
|
||||||
|
const selectedOrderCanReturnToCutPlan = Boolean(selectedOrder && canReturnToCutPlan(selectedOrder));
|
||||||
|
|
||||||
const handleManualRefresh = () => {
|
const handleManualRefresh = () => {
|
||||||
void loadProductionOrders({ force: true });
|
void loadProductionOrders({ force: true });
|
||||||
@@ -254,7 +270,7 @@ const ProductionOrders = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const returnOrderToCutPlan = async (order: ProductionOrderItem) => {
|
const returnOrderToCutPlan = async (order: ProductionOrderItem) => {
|
||||||
if (order.status !== 'open' || (order.integrationStatus || '').toLowerCase() !== 'local') return;
|
if (!canReturnToCutPlan(order)) return;
|
||||||
const orderNumber = order.number || `#${order.id}`;
|
const orderNumber = order.number || `#${order.id}`;
|
||||||
if (!window.confirm(`Devolver a OP ${orderNumber} ao Plano de Corte?\n\nA OP será cancelada, preservando o histórico. A necessidade voltará para o Plano de Corte e poderá ser gerada novamente quando você decidir.`)) return;
|
if (!window.confirm(`Devolver a OP ${orderNumber} ao Plano de Corte?\n\nA OP será cancelada, preservando o histórico. A necessidade voltará para o Plano de Corte e poderá ser gerada novamente quando você decidir.`)) return;
|
||||||
|
|
||||||
@@ -744,7 +760,7 @@ const ProductionOrders = () => {
|
|||||||
</header>
|
</header>
|
||||||
<div className="overflow-y-auto p-5">
|
<div className="overflow-y-auto p-5">
|
||||||
{statusMessage && <div className="mb-4 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-sm font-bold text-dark-muted">{statusMessage}</div>}
|
{statusMessage && <div className="mb-4 rounded-xl border border-dark-border bg-dark-input px-3 py-2 text-sm font-bold text-dark-muted">{statusMessage}</div>}
|
||||||
{selectedOrderNextStage ? <section className="flex flex-col gap-3 rounded-xl border border-brand-primary/25 bg-brand-primary/[0.055] p-4 sm:flex-row sm:items-center sm:justify-between"><div><p className="text-[10px] font-bold uppercase tracking-wider text-brand-primary">Próxima ação</p><p className="mt-1 text-sm font-bold text-dark-text">{selectedOrderNextStage.label}</p><p className="mt-1 text-xs font-semibold text-dark-muted">{selectedOrderNextStage.description}</p></div><button type="button" onClick={() => void handleStatusChange(selectedOrder, selectedOrderNextStage.status)} disabled={busyStatusOrderId === selectedOrder.id} className="inline-flex h-10 shrink-0 items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer">{busyStatusOrderId === selectedOrder.id ? 'Atualizando...' : selectedOrderNextStage.label}<ChevronDown className="h-4 w-4 -rotate-90" /></button></section> : <section className="rounded-xl border border-dark-border bg-dark-input/45 px-4 py-3 text-sm font-semibold text-dark-muted">{selectedOrder.status === 'finished' ? 'Esta OP foi finalizada. Os registros abaixo permanecem disponíveis para consulta.' : 'Esta OP foi cancelada e não possui próxima etapa operacional.'}</section>}
|
{selectedOrderNextStage || selectedOrderPreviousStage ? <section className="flex flex-col gap-3 rounded-xl border border-brand-primary/25 bg-brand-primary/[0.055] p-4 sm:flex-row sm:items-center sm:justify-between"><div><p className="text-[10px] font-bold uppercase tracking-wider text-brand-primary">Fluxo de produção</p><p className="mt-1 text-sm font-bold text-dark-text">{selectedOrderNextStage?.label || selectedOrderPreviousStage?.label}</p><p className="mt-1 text-xs font-semibold text-dark-muted">{selectedOrderNextStage?.description || selectedOrderPreviousStage?.description}</p></div><div className="flex shrink-0 flex-wrap gap-2"><button type="button" onClick={() => selectedOrderPreviousStage && void handleStatusChange(selectedOrder, selectedOrderPreviousStage.status)} disabled={!selectedOrderPreviousStage || busyStatusOrderId === selectedOrder.id} className="inline-flex h-10 items-center justify-center gap-2 rounded-xl border border-dark-border bg-dark-input px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer"><Undo2 className="h-4 w-4" />{busyStatusOrderId === selectedOrder.id ? 'Atualizando...' : selectedOrderPreviousStage?.label || 'Sem etapa anterior'}</button>{selectedOrderNextStage && <button type="button" onClick={() => void handleStatusChange(selectedOrder, selectedOrderNextStage.status)} disabled={busyStatusOrderId === selectedOrder.id} className="inline-flex h-10 items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer">{busyStatusOrderId === selectedOrder.id ? 'Atualizando...' : selectedOrderNextStage.label}<ChevronDown className="h-4 w-4 -rotate-90" /></button>}</div></section> : <section className="rounded-xl border border-dark-border bg-dark-input/45 px-4 py-3 text-sm font-semibold text-dark-muted">Esta OP foi cancelada e não possui próxima etapa operacional.</section>}
|
||||||
<div className="mt-5">{renderOrderDetails(selectedOrder)}</div>
|
<div className="mt-5">{renderOrderDetails(selectedOrder)}</div>
|
||||||
</div>
|
</div>
|
||||||
<footer className="flex flex-col-reverse gap-2 border-t border-dark-border px-5 py-4 sm:flex-row sm:items-center sm:justify-between"><div>{selectedOrderCanReturnToCutPlan && <button type="button" onClick={() => void returnOrderToCutPlan(selectedOrder)} disabled={busyStatusOrderId === selectedOrder.id} className="inline-flex h-10 items-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 text-sm font-bold text-dark-muted transition-colors hover:border-amber-400/45 hover:text-amber-200 disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer"><Undo2 className="h-4 w-4" />Devolver ao Plano de Corte</button>}</div><button type="button" onClick={() => setSelectedOrderId(null)} disabled={busyStatusOrderId === selectedOrder.id} className="h-10 rounded-xl border border-dark-border bg-dark-input px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer">Fechar</button></footer>
|
<footer className="flex flex-col-reverse gap-2 border-t border-dark-border px-5 py-4 sm:flex-row sm:items-center sm:justify-between"><div>{selectedOrderCanReturnToCutPlan && <button type="button" onClick={() => void returnOrderToCutPlan(selectedOrder)} disabled={busyStatusOrderId === selectedOrder.id} className="inline-flex h-10 items-center gap-2 rounded-xl border border-dark-border bg-dark-input px-3 text-sm font-bold text-dark-muted transition-colors hover:border-amber-400/45 hover:text-amber-200 disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer"><Undo2 className="h-4 w-4" />Devolver ao Plano de Corte</button>}</div><button type="button" onClick={() => setSelectedOrderId(null)} disabled={busyStatusOrderId === selectedOrder.id} className="h-10 rounded-xl border border-dark-border bg-dark-input px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer">Fechar</button></footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user