feat(orders): label lines split off a short collection
Shows "Short delivered · on <PO>" next to the description on /orders and the order page so the buyer doesn't re-order units already on back order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7fceb47005
commit
e4fbcf3748
4 changed files with 67 additions and 1 deletions
|
|
@ -70,6 +70,7 @@ import type { MatchedAttachment } from "@/components/orders/attachment-list";
|
|||
|
||||
import { AiItemHistoryTrigger } from "@/components/ai-item-history-trigger";
|
||||
import type { Order, OrderItem, PurchaseOrder, Quote, ItemIntel, ItemStatus } from "@/lib/types";
|
||||
import { ShortDeliveredPill } from "@/components/orders/short-delivered-pill";
|
||||
import type { ScoreBreakdown, LineMatchBreakdown } from "@/lib/server/order-quote-match";
|
||||
import type { OrderDetail } from "@/lib/server/orders-service";
|
||||
|
||||
|
|
@ -687,6 +688,7 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
|
|||
className="mt-0.5"
|
||||
/>
|
||||
<span>{item.description}</span>
|
||||
<ShortDeliveredPill item={item} />
|
||||
</div>
|
||||
{item.po_description && item.po_description !== item.description && (
|
||||
<div className="text-xs text-muted-foreground mt-1 whitespace-pre-wrap break-words">
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import type {
|
|||
} from "@/lib/server/orders-service";
|
||||
import { AiItemHistoryTrigger } from "@/components/ai-item-history-trigger";
|
||||
import { ItemStatusPill } from "@/components/item-status-pill";
|
||||
import { ShortDeliveredPill } from "@/components/orders/short-delivered-pill";
|
||||
import {
|
||||
TAB_GROUP_VISUAL,
|
||||
TAB_GROUP_ITEM_STATUSES,
|
||||
|
|
@ -1004,7 +1005,10 @@ function OrderCard({
|
|||
intel={null}
|
||||
className="-my-1"
|
||||
/>
|
||||
<span className="flex-1 truncate">{item.description}</span>
|
||||
<span className="flex-1 min-w-0 flex items-center gap-1.5">
|
||||
<span className="truncate">{item.description}</span>
|
||||
<ShortDeliveredPill item={item} />
|
||||
</span>
|
||||
<span className="text-muted-foreground shrink-0">×{item.quantity ?? "—"}</span>
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{item.selling_price !== null && item.quantity !== null
|
||||
|
|
|
|||
19
src/components/orders/short-delivered-pill.test.ts
Normal file
19
src/components/orders/short-delivered-pill.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { shortDeliveredLabel } from "./short-delivered-pill";
|
||||
|
||||
describe("shortDeliveredLabel", () => {
|
||||
it("names the PO the remainder is already on", () => {
|
||||
const label = shortDeliveredLabel({
|
||||
order_item_details: JSON.stringify({
|
||||
shortCollection: { sourceItemId: "o", ordered: 770, collected: 549, remainderQty: 221, poNumber: "PO202610005BID", reason: "[Other]" },
|
||||
}),
|
||||
});
|
||||
expect(label?.text).toBe("Short delivered · on PO202610005BID");
|
||||
expect(label?.title).toContain("Only 549 of 770");
|
||||
});
|
||||
|
||||
it("is null for the collected part and for ordinary lines", () => {
|
||||
expect(shortDeliveredLabel({ order_item_details: { shortCollection: { remainderItemId: "r" } } })).toBeNull();
|
||||
expect(shortDeliveredLabel({ order_item_details: null })).toBeNull();
|
||||
});
|
||||
});
|
||||
41
src/components/orders/short-delivered-pill.tsx
Normal file
41
src/components/orders/short-delivered-pill.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"use client";
|
||||
|
||||
import { readShortCollectionMarker } from "@/lib/short-collection";
|
||||
|
||||
/**
|
||||
* Label for a line split off a short collection (see
|
||||
* `src/lib/short-collection.ts`), shown next to its description so the
|
||||
* buyer doesn't order it a second time: the supplier already has it on
|
||||
* the original PO, it just came short.
|
||||
*/
|
||||
export function shortDeliveredLabel(item: {
|
||||
order_item_details?: string | Record<string, unknown> | null;
|
||||
}): { text: string; title: string } | null {
|
||||
const marker = readShortCollectionMarker(item.order_item_details);
|
||||
if (!marker || marker.role !== "remainder") return null;
|
||||
const po = marker.poNumber || "the original PO";
|
||||
return {
|
||||
text: `Short delivered · on ${po}`,
|
||||
title:
|
||||
`Only ${marker.collected} of ${marker.ordered} came in on ${po}. ` +
|
||||
`These ${marker.remainderQty} were already ordered — check with the supplier before ordering again.` +
|
||||
(marker.reason ? ` Driver: ${marker.reason}` : ""),
|
||||
};
|
||||
}
|
||||
|
||||
export function ShortDeliveredPill({
|
||||
item,
|
||||
}: {
|
||||
item: { order_item_details?: string | Record<string, unknown> | null };
|
||||
}) {
|
||||
const label = shortDeliveredLabel(item);
|
||||
if (!label) return null;
|
||||
return (
|
||||
<span
|
||||
title={label.title}
|
||||
className="shrink-0 rounded-full border border-warn/40 bg-warn/10 px-2 py-0.5 text-[11px] font-medium text-warn-foreground dark:text-warn"
|
||||
>
|
||||
{label.text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue