confirm actions
This commit is contained in:
@@ -11,6 +11,7 @@ import { Link, useParams } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "../../auth/AuthProvider";
|
||||
import { api, ApiError } from "../../lib/api";
|
||||
import { ConfirmActionDialog } from "../../components/ConfirmActionDialog";
|
||||
import { emptyInventoryTransactionInput, inventoryTransactionOptions } from "./config";
|
||||
import { InventoryAttachmentsPanel } from "./InventoryAttachmentsPanel";
|
||||
import { InventoryStatusBadge } from "./InventoryStatusBadge";
|
||||
@@ -48,6 +49,19 @@ export function InventoryDetailPage() {
|
||||
const [isSavingTransfer, setIsSavingTransfer] = useState(false);
|
||||
const [isSavingReservation, setIsSavingReservation] = useState(false);
|
||||
const [status, setStatus] = useState("Loading inventory item...");
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState<
|
||||
| {
|
||||
kind: "transaction" | "transfer" | "reservation";
|
||||
title: string;
|
||||
description: string;
|
||||
impact: string;
|
||||
recovery: string;
|
||||
confirmLabel: string;
|
||||
confirmationLabel?: string;
|
||||
confirmationValue?: string;
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
|
||||
const canManage = user?.permissions.includes(permissions.inventoryWrite) ?? false;
|
||||
|
||||
@@ -100,8 +114,7 @@ export function InventoryDetailPage() {
|
||||
setTransferForm((current) => ({ ...current, [key]: value }));
|
||||
}
|
||||
|
||||
async function handleTransactionSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function submitTransaction() {
|
||||
if (!token || !itemId) {
|
||||
return;
|
||||
}
|
||||
@@ -112,7 +125,7 @@ export function InventoryDetailPage() {
|
||||
try {
|
||||
const nextItem = await api.createInventoryTransaction(token, itemId, transactionForm);
|
||||
setItem(nextItem);
|
||||
setTransactionStatus("Stock transaction recorded.");
|
||||
setTransactionStatus("Stock transaction recorded. If this was posted in error, create an offsetting stock entry and verify the result in Recent Movements.");
|
||||
setTransactionForm((current) => ({
|
||||
...emptyInventoryTransactionInput,
|
||||
transactionType: current.transactionType,
|
||||
@@ -127,8 +140,7 @@ export function InventoryDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTransferSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function submitTransfer() {
|
||||
if (!token || !itemId) {
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +151,7 @@ export function InventoryDetailPage() {
|
||||
try {
|
||||
const nextItem = await api.createInventoryTransfer(token, itemId, transferForm);
|
||||
setItem(nextItem);
|
||||
setTransferStatus("Transfer recorded.");
|
||||
setTransferStatus("Transfer recorded. Review stock balances on both locations and post a return transfer if this movement was entered incorrectly.");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to save transfer.";
|
||||
setTransferStatus(message);
|
||||
@@ -148,8 +160,7 @@ export function InventoryDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReservationSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function submitReservation() {
|
||||
if (!token || !itemId) {
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +171,7 @@ export function InventoryDetailPage() {
|
||||
try {
|
||||
const nextItem = await api.createInventoryReservation(token, itemId, reservationForm);
|
||||
setItem(nextItem);
|
||||
setReservationStatus("Reservation recorded.");
|
||||
setReservationStatus("Reservation recorded. Verify available stock and add a compensating reservation change if this demand hold was entered incorrectly.");
|
||||
setReservationForm((current) => ({ ...current, quantity: 1, notes: "" }));
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to save reservation.";
|
||||
@@ -170,6 +181,64 @@ export function InventoryDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleTransactionSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionLabel = inventoryTransactionOptions.find((option) => option.value === transactionForm.transactionType)?.label ?? "transaction";
|
||||
setPendingConfirmation({
|
||||
kind: "transaction",
|
||||
title: `Post ${transactionLabel.toLowerCase()}`,
|
||||
description: `Post a ${transactionLabel.toLowerCase()} of ${transactionForm.quantity} units for ${item.sku} at the selected stock location.`,
|
||||
impact:
|
||||
transactionForm.transactionType === "ISSUE" || transactionForm.transactionType === "ADJUSTMENT_OUT"
|
||||
? "This reduces available inventory immediately and affects downstream shortage and readiness calculations."
|
||||
: "This updates the stock ledger immediately and becomes part of the item transaction history.",
|
||||
recovery: "If this is incorrect, post an explicit offsetting transaction instead of editing history.",
|
||||
confirmLabel: `Post ${transactionLabel.toLowerCase()}`,
|
||||
confirmationLabel:
|
||||
transactionForm.transactionType === "ISSUE" || transactionForm.transactionType === "ADJUSTMENT_OUT"
|
||||
? "Type item SKU to confirm:"
|
||||
: undefined,
|
||||
confirmationValue:
|
||||
transactionForm.transactionType === "ISSUE" || transactionForm.transactionType === "ADJUSTMENT_OUT"
|
||||
? item.sku
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function handleTransferSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
setPendingConfirmation({
|
||||
kind: "transfer",
|
||||
title: "Post inventory transfer",
|
||||
description: `Move ${transferForm.quantity} units of ${item.sku} between the selected source and destination locations.`,
|
||||
impact: "This creates paired stock movement entries and changes both source and destination availability immediately.",
|
||||
recovery: "If the move was entered incorrectly, post a reversing transfer back to the original location.",
|
||||
confirmLabel: "Post transfer",
|
||||
});
|
||||
}
|
||||
|
||||
function handleReservationSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
setPendingConfirmation({
|
||||
kind: "reservation",
|
||||
title: "Create manual reservation",
|
||||
description: `Reserve ${reservationForm.quantity} units of ${item.sku}${reservationForm.locationId ? " at the selected location" : ""}.`,
|
||||
impact: "This reduces available quantity used by planning, purchasing, manufacturing, and readiness views.",
|
||||
recovery: "Add the correcting reservation entry if this hold should be reduced or removed.",
|
||||
confirmLabel: "Create reservation",
|
||||
});
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
return <div className="rounded-[28px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">{status}</div>;
|
||||
}
|
||||
@@ -530,6 +599,41 @@ export function InventoryDetailPage() {
|
||||
</section>
|
||||
|
||||
<InventoryAttachmentsPanel itemId={item.id} />
|
||||
<ConfirmActionDialog
|
||||
open={pendingConfirmation != null}
|
||||
title={pendingConfirmation?.title ?? "Confirm inventory action"}
|
||||
description={pendingConfirmation?.description ?? ""}
|
||||
impact={pendingConfirmation?.impact}
|
||||
recovery={pendingConfirmation?.recovery}
|
||||
confirmLabel={pendingConfirmation?.confirmLabel ?? "Confirm"}
|
||||
confirmationLabel={pendingConfirmation?.confirmationLabel}
|
||||
confirmationValue={pendingConfirmation?.confirmationValue}
|
||||
isConfirming={
|
||||
(pendingConfirmation?.kind === "transaction" && isSavingTransaction) ||
|
||||
(pendingConfirmation?.kind === "transfer" && isSavingTransfer) ||
|
||||
(pendingConfirmation?.kind === "reservation" && isSavingReservation)
|
||||
}
|
||||
onClose={() => {
|
||||
if (!isSavingTransaction && !isSavingTransfer && !isSavingReservation) {
|
||||
setPendingConfirmation(null);
|
||||
}
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!pendingConfirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingConfirmation.kind === "transaction") {
|
||||
await submitTransaction();
|
||||
} else if (pendingConfirmation.kind === "transfer") {
|
||||
await submitTransfer();
|
||||
} else {
|
||||
await submitReservation();
|
||||
}
|
||||
|
||||
setPendingConfirmation(null);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user