import { permissions } from "@mrp/shared"; import type { SalesDocumentDetailDto, SalesDocumentStatus, SalesOrderPlanningDto, SalesOrderPlanningNodeDto } from "@mrp/shared/dist/sales/types.js"; import type { ShipmentSummaryDto } from "@mrp/shared/dist/shipping/types.js"; import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { salesConfigs, salesStatusOptions, type SalesDocumentEntity } from "./config"; import { SalesStatusBadge } from "./SalesStatusBadge"; import { ShipmentStatusBadge } from "../shipping/ShipmentStatusBadge"; function PlanningNodeCard({ node }: { node: SalesOrderPlanningNodeDto }) { return (
{node.itemSku} {node.itemName}
Demand {node.grossDemand} {node.unitOfMeasure} · Type {node.itemType} {node.bomQuantityPerParent !== null ? ` · Qty/parent ${node.bomQuantityPerParent}` : ""}
Linked WO {node.linkedWorkOrderSupply}
Linked PO {node.linkedPurchaseSupply}
Stock {node.supplyFromStock}
Open WO {node.supplyFromOpenWorkOrders}
Open PO {node.supplyFromOpenPurchaseOrders}
Build {node.recommendedBuildQuantity}
Buy {node.recommendedPurchaseQuantity}
{node.uncoveredQuantity > 0 ?
Uncovered {node.uncoveredQuantity}
: null}
{node.children.length > 0 ? (
{node.children.map((child) => ( ))}
) : null}
); } export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) { const { token, user } = useAuth(); const navigate = useNavigate(); const { quoteId, orderId } = useParams(); const config = salesConfigs[entity]; const documentId = entity === "quote" ? quoteId : orderId; const [document, setDocument] = useState(null); const [status, setStatus] = useState(`Loading ${config.singularLabel.toLowerCase()}...`); const [isUpdatingStatus, setIsUpdatingStatus] = useState(false); const [isConverting, setIsConverting] = useState(false); const [isOpeningPdf, setIsOpeningPdf] = useState(false); const [isApproving, setIsApproving] = useState(false); const [shipments, setShipments] = useState([]); const [planning, setPlanning] = useState(null); const canManage = user?.permissions.includes(permissions.salesWrite) ?? false; const canManageShipping = user?.permissions.includes(permissions.shippingWrite) ?? false; const canReadShipping = user?.permissions.includes(permissions.shippingRead) ?? false; const canManageManufacturing = user?.permissions.includes(permissions.manufacturingWrite) ?? false; const canManagePurchasing = user?.permissions.includes(permissions.purchasingWrite) ?? false; useEffect(() => { if (!token || !documentId) { return; } const loader = entity === "quote" ? api.getQuote(token, documentId) : api.getSalesOrder(token, documentId); const planningLoader = entity === "order" ? api.getSalesOrderPlanning(token, documentId) : Promise.resolve(null); Promise.all([loader, planningLoader]) .then(([nextDocument, nextPlanning]) => { setDocument(nextDocument); setPlanning(nextPlanning); setStatus(`${config.singularLabel} loaded.`); if (entity === "order" && canReadShipping) { api.getShipments(token, { salesOrderId: nextDocument.id }).then(setShipments).catch(() => setShipments([])); } }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : `Unable to load ${config.singularLabel.toLowerCase()}.`; setStatus(message); }); }, [canReadShipping, config.singularLabel, documentId, entity, token]); if (!document) { return
{status}
; } const activeDocument = document; function buildWorkOrderRecommendationLink(itemId: string, quantity: number) { const params = new URLSearchParams({ itemId, salesOrderId: activeDocument.id, quantity: quantity.toString(), status: "DRAFT", notes: `Generated from sales order ${activeDocument.documentNumber} demand planning.`, }); return `/manufacturing/work-orders/new?${params.toString()}`; } function buildPurchaseRecommendationLink(itemId?: string, vendorId?: string | null) { const params = new URLSearchParams(); params.set("planningOrderId", activeDocument.id); if (itemId) { params.set("itemId", itemId); } if (vendorId) { params.set("vendorId", vendorId); } return `/purchasing/orders/new?${params.toString()}`; } async function handleStatusChange(nextStatus: SalesDocumentStatus) { if (!token) { return; } setIsUpdatingStatus(true); setStatus(`Updating ${config.singularLabel.toLowerCase()} status...`); try { const nextDocument = entity === "quote" ? await api.updateQuoteStatus(token, activeDocument.id, nextStatus) : await api.updateSalesOrderStatus(token, activeDocument.id, nextStatus); setDocument(nextDocument); setStatus(`${config.singularLabel} status updated.`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : `Unable to update ${config.singularLabel.toLowerCase()} status.`; setStatus(message); } finally { setIsUpdatingStatus(false); } } async function handleConvert() { if (!token || entity !== "quote") { return; } setIsConverting(true); setStatus("Converting quote to sales order..."); try { const order = await api.convertQuoteToSalesOrder(token, activeDocument.id); navigate(`/sales/orders/${order.id}`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to convert quote to sales order."; setStatus(message); setIsConverting(false); } } async function handleOpenPdf() { if (!token) { return; } setIsOpeningPdf(true); setStatus(`Rendering ${config.singularLabel.toLowerCase()} PDF...`); try { const blob = entity === "quote" ? await api.getQuotePdf(token, activeDocument.id) : await api.getSalesOrderPdf(token, activeDocument.id); const objectUrl = URL.createObjectURL(blob); window.open(objectUrl, "_blank", "noopener,noreferrer"); window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000); setStatus(`${config.singularLabel} PDF ready.`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : `Unable to render ${config.singularLabel.toLowerCase()} PDF.`; setStatus(message); } finally { setIsOpeningPdf(false); } } async function handleApprove() { if (!token) { return; } setIsApproving(true); setStatus(`Approving ${config.singularLabel.toLowerCase()}...`); try { const nextDocument = entity === "quote" ? await api.approveQuote(token, activeDocument.id) : await api.approveSalesOrder(token, activeDocument.id); setDocument(nextDocument); setStatus(`${config.singularLabel} approved.`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : `Unable to approve ${config.singularLabel.toLowerCase()}.`; setStatus(message); } finally { setIsApproving(false); } } return (

{config.detailEyebrow}

{activeDocument.documentNumber}

{activeDocument.customerName}

Rev {activeDocument.currentRevisionNumber}
Back to {config.collectionLabel.toLowerCase()} {canManage ? ( <> Edit {config.singularLabel.toLowerCase()} {activeDocument.status !== "APPROVED" ? ( ) : null} {entity === "quote" ? ( ) : null} {entity === "order" && canManageShipping ? ( New shipment ) : null} ) : null}
{canManage ? (

Quick Actions

Update document status without opening the full editor.

{salesStatusOptions.map((option) => ( ))}
) : null}

Issue Date

{new Date(activeDocument.issueDate).toLocaleDateString()}

Expires

{activeDocument.expiresAt ? new Date(activeDocument.expiresAt).toLocaleDateString() : "N/A"}

Lines

{activeDocument.lineCount}

Approval

{activeDocument.approvedAt ? new Date(activeDocument.approvedAt).toLocaleDateString() : "Pending"}
{activeDocument.approvedByName ?? "No approver recorded"}

Discount

-${activeDocument.discountAmount.toFixed(2)}
{activeDocument.discountPercent.toFixed(2)}%

Tax

${activeDocument.taxAmount.toFixed(2)}
{activeDocument.taxPercent.toFixed(2)}%

Freight

${activeDocument.freightAmount.toFixed(2)}

Total

${activeDocument.total.toFixed(2)}

Revision History

Automatic snapshots are recorded when the document changes status, content, or approval state.

{activeDocument.revisions.length === 0 ? (
No revisions have been recorded yet.
) : (
{activeDocument.revisions.map((revision) => (
Rev {revision.revisionNumber}
{revision.reason}
{new Date(revision.createdAt).toLocaleString()}
{revision.createdByName ?? "System"}
))}
)}

Customer

Account
{activeDocument.customerName}
Email
{activeDocument.customerEmail}

Notes

{activeDocument.notes || "No notes recorded for this document."}

Line Items

{activeDocument.lines.length === 0 ? (
No line items have been added yet.
) : (
{activeDocument.lines.map((line: SalesDocumentDetailDto["lines"][number]) => ( ))}
Item Description Qty UOM Unit Price Total
{line.itemSku}
{line.itemName}
{line.description} {line.quantity} {line.unitOfMeasure} ${line.unitPrice.toFixed(2)} ${line.lineTotal.toFixed(2)}
)}
{entity === "order" && planning ? (

Demand Planning

Net build and buy requirements

Sales-order demand is netted against available stock, active reservations, open work orders, and open purchase orders before new build or buy quantities are recommended.

Generated {new Date(planning.generatedAt).toLocaleString()}
Status {planning.status}

Build Recommendations

{planning.summary.totalBuildQuantity}
{planning.summary.buildRecommendationCount} items

Purchase Recommendations

{planning.summary.totalPurchaseQuantity}
{planning.summary.purchaseRecommendationCount} items

Uncovered

{planning.summary.totalUncoveredQuantity}
{planning.summary.uncoveredItemCount} items

Planned Items

{planning.summary.itemCount}
{planning.summary.lineCount} sales lines
{planning.items.map((item) => ( ))}
Item Gross Linked WO Linked PO Available Open WO Open PO Build Buy Uncovered Actions
{item.itemSku}
{item.itemName}
{item.grossDemand} {item.linkedWorkOrderSupply} {item.linkedPurchaseSupply} {item.availableQuantity} {item.openWorkOrderSupply} {item.openPurchaseSupply} {item.recommendedBuildQuantity} {item.recommendedPurchaseQuantity} {item.uncoveredQuantity}
{canManageManufacturing && item.recommendedBuildQuantity > 0 ? ( Draft WO ) : null} {canManagePurchasing && item.recommendedPurchaseQuantity > 0 ? ( Draft PO ) : null}
{canManagePurchasing && planning.summary.purchaseRecommendationCount > 0 ? (
Draft purchase order from recommendations
) : null}
{planning.lines.map((line) => (
{line.itemSku} {line.itemName}
Sales-order line demand: {line.quantity} {line.unitOfMeasure}
))}
) : null} {entity === "order" && canReadShipping ? (

Shipping

Shipment records currently tied to this sales order.

{canManageShipping ? ( Create shipment ) : null}
{shipments.length === 0 ? (
No shipments have been created for this sales order yet.
) : (
{shipments.map((shipment) => (
{shipment.shipmentNumber}
{shipment.carrier || "Carrier not set"} · {shipment.trackingNumber || "No tracking"}
))}
)}
) : null}
); }