This commit is contained in:
2026-03-15 16:40:25 -05:00
parent 15116807ce
commit 59754c7657
33 changed files with 1620 additions and 49 deletions

View File

@@ -24,6 +24,8 @@ function PlanningNodeCard({ node }: { node: SalesOrderPlanningNodeDto }) {
</div>
</div>
<div className="text-right text-xs text-muted">
<div>Linked WO {node.linkedWorkOrderSupply}</div>
<div>Linked PO {node.linkedPurchaseSupply}</div>
<div>Stock {node.supplyFromStock}</div>
<div>Open WO {node.supplyFromOpenWorkOrders}</div>
<div>Open PO {node.supplyFromOpenPurchaseOrders}</div>
@@ -61,6 +63,8 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
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) {
@@ -90,6 +94,31 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
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;
@@ -431,12 +460,15 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
<tr>
<th className="px-2 py-2">Item</th>
<th className="px-2 py-2">Gross</th>
<th className="px-2 py-2">Linked WO</th>
<th className="px-2 py-2">Linked PO</th>
<th className="px-2 py-2">Available</th>
<th className="px-2 py-2">Open WO</th>
<th className="px-2 py-2">Open PO</th>
<th className="px-2 py-2">Build</th>
<th className="px-2 py-2">Buy</th>
<th className="px-2 py-2">Uncovered</th>
<th className="px-2 py-2">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-line/70 bg-surface">
@@ -447,17 +479,46 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
<div className="mt-1 text-xs text-muted">{item.itemName}</div>
</td>
<td className="px-2 py-2 text-muted">{item.grossDemand}</td>
<td className="px-2 py-2 text-muted">{item.linkedWorkOrderSupply}</td>
<td className="px-2 py-2 text-muted">{item.linkedPurchaseSupply}</td>
<td className="px-2 py-2 text-muted">{item.availableQuantity}</td>
<td className="px-2 py-2 text-muted">{item.openWorkOrderSupply}</td>
<td className="px-2 py-2 text-muted">{item.openPurchaseSupply}</td>
<td className="px-2 py-2 text-muted">{item.recommendedBuildQuantity}</td>
<td className="px-2 py-2 text-muted">{item.recommendedPurchaseQuantity}</td>
<td className="px-2 py-2 text-muted">{item.uncoveredQuantity}</td>
<td className="px-2 py-2">
<div className="flex flex-wrap gap-2">
{canManageManufacturing && item.recommendedBuildQuantity > 0 ? (
<Link
to={buildWorkOrderRecommendationLink(item.itemId, item.recommendedBuildQuantity)}
className="rounded-2xl border border-line/70 px-2 py-1 text-xs font-semibold text-text"
>
Draft WO
</Link>
) : null}
{canManagePurchasing && item.recommendedPurchaseQuantity > 0 ? (
<Link
to={buildPurchaseRecommendationLink(item.itemId)}
className="rounded-2xl border border-line/70 px-2 py-1 text-xs font-semibold text-text"
>
Draft PO
</Link>
) : null}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{canManagePurchasing && planning.summary.purchaseRecommendationCount > 0 ? (
<div className="mt-4 flex justify-end">
<Link to={buildPurchaseRecommendationLink()} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
Draft purchase order from recommendations
</Link>
</div>
) : null}
<div className="mt-5 space-y-3">
{planning.lines.map((line) => (
<div key={line.lineId} className="rounded-3xl border border-line/70 bg-page/60 p-3">