confirm actions
This commit is contained in:
@@ -7,6 +7,7 @@ import { Link, useParams } from "react-router-dom";
|
||||
import { FileAttachmentsPanel } from "../../components/FileAttachmentsPanel";
|
||||
import { useAuth } from "../../auth/AuthProvider";
|
||||
import { api, ApiError } from "../../lib/api";
|
||||
import { ConfirmActionDialog } from "../../components/ConfirmActionDialog";
|
||||
import { emptyCompletionInput, emptyMaterialIssueInput, workOrderStatusOptions } from "./config";
|
||||
import { WorkOrderStatusBadge } from "./WorkOrderStatusBadge";
|
||||
|
||||
@@ -21,6 +22,20 @@ export function WorkOrderDetailPage() {
|
||||
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
||||
const [isPostingIssue, setIsPostingIssue] = useState(false);
|
||||
const [isPostingCompletion, setIsPostingCompletion] = useState(false);
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState<
|
||||
| {
|
||||
kind: "status" | "issue" | "completion";
|
||||
title: string;
|
||||
description: string;
|
||||
impact: string;
|
||||
recovery: string;
|
||||
confirmLabel: string;
|
||||
confirmationLabel?: string;
|
||||
confirmationValue?: string;
|
||||
nextStatus?: WorkOrderStatus;
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
|
||||
const canManage = user?.permissions.includes(permissions.manufacturingWrite) ?? false;
|
||||
|
||||
@@ -56,7 +71,7 @@ export function WorkOrderDetailPage() {
|
||||
[issueForm.warehouseId, locationOptions]
|
||||
);
|
||||
|
||||
async function handleStatusChange(nextStatus: WorkOrderStatus) {
|
||||
async function applyStatusChange(nextStatus: WorkOrderStatus) {
|
||||
if (!token || !workOrder) {
|
||||
return;
|
||||
}
|
||||
@@ -66,7 +81,7 @@ export function WorkOrderDetailPage() {
|
||||
try {
|
||||
const nextWorkOrder = await api.updateWorkOrderStatus(token, workOrder.id, nextStatus);
|
||||
setWorkOrder(nextWorkOrder);
|
||||
setStatus("Work-order status updated.");
|
||||
setStatus("Work-order status updated. Review downstream planning and shipment readiness if this change affects execution timing.");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to update work-order status.";
|
||||
setStatus(message);
|
||||
@@ -75,8 +90,7 @@ export function WorkOrderDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleIssueSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function submitIssue() {
|
||||
if (!token || !workOrder) {
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +105,7 @@ export function WorkOrderDetailPage() {
|
||||
warehouseId: nextWorkOrder.warehouseId,
|
||||
locationId: nextWorkOrder.locationId,
|
||||
});
|
||||
setStatus("Material issue posted.");
|
||||
setStatus("Material issue posted. This consumed inventory immediately; post a correcting stock movement if the issue quantity was wrong.");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to post material issue.";
|
||||
setStatus(message);
|
||||
@@ -100,8 +114,7 @@ export function WorkOrderDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCompletionSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function submitCompletion() {
|
||||
if (!token || !workOrder) {
|
||||
return;
|
||||
}
|
||||
@@ -115,7 +128,7 @@ export function WorkOrderDetailPage() {
|
||||
...emptyCompletionInput,
|
||||
quantity: Math.max(nextWorkOrder.dueQuantity, 1),
|
||||
});
|
||||
setStatus("Completion posted.");
|
||||
setStatus("Completion posted. Finished-goods stock has been received; verify the remaining quantity and post a correcting transaction if this completion was overstated.");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to post completion.";
|
||||
setStatus(message);
|
||||
@@ -124,6 +137,64 @@ export function WorkOrderDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleStatusChange(nextStatus: WorkOrderStatus) {
|
||||
if (!workOrder) {
|
||||
return;
|
||||
}
|
||||
const option = workOrderStatusOptions.find((entry) => entry.value === nextStatus);
|
||||
setPendingConfirmation({
|
||||
kind: "status",
|
||||
title: `Change status to ${option?.label ?? nextStatus}`,
|
||||
description: `Update work order ${workOrder.workOrderNumber} from ${workOrder.status} to ${nextStatus}.`,
|
||||
impact:
|
||||
nextStatus === "CANCELLED"
|
||||
? "Cancelling a work order can invalidate planning assumptions, reservations, and operator expectations."
|
||||
: nextStatus === "COMPLETE"
|
||||
? "Completing the work order signals execution closure and can change readiness views across the system."
|
||||
: "This changes the execution state used by planning, dashboards, and downstream operational review.",
|
||||
recovery: "If this status was selected in error, set the work order back to the correct state immediately after review.",
|
||||
confirmLabel: `Set ${option?.label ?? nextStatus}`,
|
||||
confirmationLabel: nextStatus === "CANCELLED" ? "Type work-order number to confirm:" : undefined,
|
||||
confirmationValue: nextStatus === "CANCELLED" ? workOrder.workOrderNumber : undefined,
|
||||
nextStatus,
|
||||
});
|
||||
}
|
||||
|
||||
function handleIssueSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!workOrder) {
|
||||
return;
|
||||
}
|
||||
const component = workOrder.materialRequirements.find((requirement) => requirement.componentItemId === issueForm.componentItemId);
|
||||
setPendingConfirmation({
|
||||
kind: "issue",
|
||||
title: "Post material issue",
|
||||
description: `Issue ${issueForm.quantity} units of ${component?.componentSku ?? "the selected component"} to work order ${workOrder.workOrderNumber}.`,
|
||||
impact: "This consumes component inventory immediately and updates work-order material history.",
|
||||
recovery: "If the wrong quantity was issued, post a correcting stock transaction and note the reason on the work order.",
|
||||
confirmLabel: "Post issue",
|
||||
confirmationLabel: "Type work-order number to confirm:",
|
||||
confirmationValue: workOrder.workOrderNumber,
|
||||
});
|
||||
}
|
||||
|
||||
function handleCompletionSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!workOrder) {
|
||||
return;
|
||||
}
|
||||
setPendingConfirmation({
|
||||
kind: "completion",
|
||||
title: "Post production completion",
|
||||
description: `Receive ${completionForm.quantity} finished units into ${workOrder.warehouseCode} / ${workOrder.locationCode}.`,
|
||||
impact: "This increases finished-goods inventory immediately and advances the execution history for this work order.",
|
||||
recovery: "If the completion quantity is wrong, post the correcting inventory movement and verify the work-order remaining quantity.",
|
||||
confirmLabel: "Post completion",
|
||||
confirmationLabel: completionForm.quantity >= workOrder.dueQuantity ? "Type work-order number to confirm:" : undefined,
|
||||
confirmationValue: completionForm.quantity >= workOrder.dueQuantity ? workOrder.workOrderNumber : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (!workOrder) {
|
||||
return <div className="rounded-[28px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">{status}</div>;
|
||||
}
|
||||
@@ -375,6 +446,41 @@ export function WorkOrderDetailPage() {
|
||||
emptyMessage="No manufacturing attachments have been uploaded for this work order yet."
|
||||
/>
|
||||
<div className="rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted">{status}</div>
|
||||
<ConfirmActionDialog
|
||||
open={pendingConfirmation != null}
|
||||
title={pendingConfirmation?.title ?? "Confirm manufacturing action"}
|
||||
description={pendingConfirmation?.description ?? ""}
|
||||
impact={pendingConfirmation?.impact}
|
||||
recovery={pendingConfirmation?.recovery}
|
||||
confirmLabel={pendingConfirmation?.confirmLabel ?? "Confirm"}
|
||||
confirmationLabel={pendingConfirmation?.confirmationLabel}
|
||||
confirmationValue={pendingConfirmation?.confirmationValue}
|
||||
isConfirming={
|
||||
(pendingConfirmation?.kind === "status" && isUpdatingStatus) ||
|
||||
(pendingConfirmation?.kind === "issue" && isPostingIssue) ||
|
||||
(pendingConfirmation?.kind === "completion" && isPostingCompletion)
|
||||
}
|
||||
onClose={() => {
|
||||
if (!isUpdatingStatus && !isPostingIssue && !isPostingCompletion) {
|
||||
setPendingConfirmation(null);
|
||||
}
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!pendingConfirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingConfirmation.kind === "status" && pendingConfirmation.nextStatus) {
|
||||
await applyStatusChange(pendingConfirmation.nextStatus);
|
||||
} else if (pendingConfirmation.kind === "issue") {
|
||||
await submitIssue();
|
||||
} else if (pendingConfirmation.kind === "completion") {
|
||||
await submitCompletion();
|
||||
}
|
||||
|
||||
setPendingConfirmation(null);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user