PO logic
This commit is contained in:
@@ -2,6 +2,7 @@ import { permissions } from "@mrp/shared";
|
||||
import type { PurchaseOrderDetailDto, PurchaseOrderStatus } from "@mrp/shared";
|
||||
import type { WarehouseLocationOptionDto } from "@mrp/shared/dist/inventory/types.js";
|
||||
import type { PurchaseReceiptInput } from "@mrp/shared/dist/purchasing/types.js";
|
||||
import type { DemandPlanningRollupDto } from "@mrp/shared/dist/sales/types.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
|
||||
@@ -23,6 +24,7 @@ export function PurchaseDetailPage() {
|
||||
const [status, setStatus] = useState("Loading purchase order...");
|
||||
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
||||
const [isOpeningPdf, setIsOpeningPdf] = useState(false);
|
||||
const [planningRollup, setPlanningRollup] = useState<DemandPlanningRollupDto | null>(null);
|
||||
|
||||
const canManage = user?.permissions.includes("purchasing.write") ?? false;
|
||||
const canReceive = canManage && (user?.permissions.includes(permissions.inventoryWrite) ?? false);
|
||||
@@ -41,6 +43,7 @@ export function PurchaseDetailPage() {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to load purchase order.";
|
||||
setStatus(message);
|
||||
});
|
||||
api.getDemandPlanningRollup(token).then(setPlanningRollup).catch(() => setPlanningRollup(null));
|
||||
|
||||
if (!canReceive) {
|
||||
return;
|
||||
@@ -90,6 +93,8 @@ export function PurchaseDetailPage() {
|
||||
|
||||
const activeDocument = document;
|
||||
const openLines = activeDocument.lines.filter((line) => line.remainingQuantity > 0);
|
||||
const demandContextItems =
|
||||
planningRollup?.items.filter((item) => activeDocument.lines.some((line) => line.itemId === item.itemId) && (item.recommendedPurchaseQuantity > 0 || item.uncoveredQuantity > 0)) ?? [];
|
||||
|
||||
function updateReceiptField<Key extends keyof PurchaseReceiptInput>(key: Key, value: PurchaseReceiptInput[Key]) {
|
||||
setReceiptForm((current: PurchaseReceiptInput) => ({ ...current, [key]: value }));
|
||||
@@ -258,6 +263,30 @@ export function PurchaseDetailPage() {
|
||||
<p className="mt-3 whitespace-pre-line text-sm leading-6 text-text">{activeDocument.notes || "No notes recorded for this document."}</p>
|
||||
</article>
|
||||
</div>
|
||||
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Demand Context</p>
|
||||
{demandContextItems.length === 0 ? (
|
||||
<div className="mt-6 rounded-3xl border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted">
|
||||
No active shared shortage or buy-signal records currently point at items on this purchase order.
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-5 space-y-3">
|
||||
{demandContextItems.map((item) => (
|
||||
<div key={item.itemId} className="rounded-3xl border border-line/70 bg-page/60 p-3">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="font-semibold text-text">{item.itemSku}</div>
|
||||
<div className="mt-1 text-xs text-muted">{item.itemName}</div>
|
||||
</div>
|
||||
<div className="text-sm text-muted">
|
||||
Buy {item.recommendedPurchaseQuantity} · Uncovered {item.uncoveredQuantity}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Line Items</p>
|
||||
{activeDocument.lines.length === 0 ? (
|
||||
@@ -266,13 +295,16 @@ export function PurchaseDetailPage() {
|
||||
<div className="mt-6 overflow-hidden rounded-2xl border border-line/70">
|
||||
<table className="min-w-full divide-y divide-line/70 text-sm">
|
||||
<thead className="bg-page/80 text-left text-muted">
|
||||
<tr><th className="px-2 py-2">Item</th><th className="px-2 py-2">Description</th><th className="px-2 py-2">Ordered</th><th className="px-2 py-2">Received</th><th className="px-2 py-2">Remaining</th><th className="px-2 py-2">UOM</th><th className="px-2 py-2">Unit Cost</th><th className="px-2 py-2">Total</th></tr>
|
||||
<tr><th className="px-2 py-2">Item</th><th className="px-2 py-2">Description</th><th className="px-2 py-2">Demand Source</th><th className="px-2 py-2">Ordered</th><th className="px-2 py-2">Received</th><th className="px-2 py-2">Remaining</th><th className="px-2 py-2">UOM</th><th className="px-2 py-2">Unit Cost</th><th className="px-2 py-2">Total</th></tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-line/70 bg-surface">
|
||||
{activeDocument.lines.map((line: PurchaseOrderDetailDto["lines"][number]) => (
|
||||
<tr key={line.id}>
|
||||
<td className="px-2 py-2"><div className="font-semibold text-text">{line.itemSku}</div><div className="mt-1 text-xs text-muted">{line.itemName}</div></td>
|
||||
<td className="px-2 py-2 text-muted">{line.description}</td>
|
||||
<td className="px-2 py-2 text-muted">
|
||||
{line.salesOrderId && line.salesOrderNumber ? <Link to={`/sales/orders/${line.salesOrderId}`} className="hover:text-brand">{line.salesOrderNumber}</Link> : "Unlinked"}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-muted">{line.quantity}</td>
|
||||
<td className="px-2 py-2 text-muted">{line.receivedQuantity}</td>
|
||||
<td className="px-2 py-2 text-muted">{line.remainingQuantity}</td>
|
||||
|
||||
Reference in New Issue
Block a user