JEI_Import_Documents-App/app/order/page.tsx
Mavis 7cf8d4b2e2 feat(order): new /order tab — stock-code picker + qty, cost preview, PDF export
Quick order-builder for the user. Pick a stock code, set a qty, hit Add.
Default unit price is the HIGHEST price ever paid for each code (worst-
case budget), with the reference invoice [no + date] shown in the table
so the price source is auditable. Ties broken by most-recent date so
the source reflects current market, not an old historical peak.

UI:
- Stock code dropdown shows code, description, default price, ref inv
- Inline-editable qty + unit price per row
- Running grand total at bottom
- Clear button (with confirm) wipes the list
- Export PDF button calls window.print() — browser "Save as PDF" gives
  a clean table with no chrome (header/nav/controls hidden via @media
  print stylesheet)

Persistence:
- List kept in localStorage key 'jei:order-list' across reloads

Files:
- app/order/page.tsx       [server: fetches stock codes + lines, builds highest-price map]
- app/order/OrderClient.tsx [client: UI, state, print stylesheet]
- app/layout.tsx           [nav: added Order link]

PB query note: when using `fields=...` to limit the response, you must
also include 'expand.invoice.invoice_no' and 'expand.invoice.invoice_date'
in the field selector, otherwise PB drops the expand data entirely.
2026-09-19 01:13:22 +02:00

87 lines
No EOL
3.1 KiB
TypeScript

import { getPb } from "../lib/pb";
import OrderClient from "./OrderClient";
export const dynamic = "force-dynamic";
type StockWithHighestPrice = {
id: string;
code: string;
description: string;
highestUnitPrice: number | null;
refInvoiceNo: string | null; // invoice_no where the highest price was paid
refInvoiceDate: string | null; // invoice_date of that invoice
};
export default async function OrderPage() {
const pb = getPb();
let stockCodes: StockWithHighestPrice[] = [];
let errorMsg: string | null = null;
try {
// 1) All stock codes (just the fields we need)
const scRes = await pb.collection("JEI_stock_codes").getList(1, 5000, {
sort: "code",
fields: "id,code,description_goods",
});
// 2) All invoice lines + their parent invoice (so we can grab the
// invoice_no + invoice_date of whichever line carries the MAX
// unit_price for each stock code).
// PB only returns the expanded record when the field selector
// includes `expand.invoice.<field>`; `invoice` alone just returns
// the relation id and drops the expansion.
const linesRes = await pb.collection("JEI_invoice_lines").getList(1, 5000, {
expand: "invoice",
fields: "id,stock_code,unit_price,expand.invoice.invoice_no,expand.invoice.invoice_date",
});
// 3) For each stock_code, keep the line with the highest unit_price
// and remember which invoice (no + date) it came from. Ties broken
// by most-recent invoice_date so the price source reflects the
// current market rather than an old historical peak.
type Highest = { price: number; date: string; invNo: string };
const highestByStock = new Map<string, Highest>();
for (const line of linesRes.items) {
const scId = line.stock_code as string;
if (!scId) continue;
const inv = line.expand?.invoice as any;
const invNo: string = inv?.invoice_no || "";
const invDate: string = inv?.invoice_date || "";
const price = Number(line.unit_price) || 0;
const cur = highestByStock.get(scId);
const isHigher = !cur || price > cur.price;
const isTieNewer =
cur && price === cur.price && invDate > cur.date;
if (isHigher || isTieNewer) {
highestByStock.set(scId, { price, date: invDate, invNo });
}
}
stockCodes = scRes.items.map((s: any) => {
const h = highestByStock.get(s.id);
return {
id: s.id,
code: s.code,
description: s.description_goods || "",
highestUnitPrice: h?.price ?? null,
refInvoiceNo: h?.invNo || null,
refInvoiceDate: h?.date ? h.date.slice(0, 10) : null,
};
});
} catch (e: any) {
errorMsg = String(e?.message || e);
}
return (
<div className="space-y-6">
{errorMsg ? (
<div className="bg-red-50 border border-red-200 text-red-800 rounded p-4">
<div className="font-semibold">Cannot reach PocketBase</div>
<div className="text-sm mt-1">{errorMsg}</div>
</div>
) : (
<OrderClient stockCodes={stockCodes} />
)}
</div>
);
}