M2: the confirm dialog pre-fill uses the card's newest document (as the chip) and only when it is a tax invoice. M3: the AI ticks column (and its min-width) shows only when a row has ai_check; the POS column stays whenever in_pos is known. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
import type { ConfirmRow } from "@/lib/server/collection-confirm-service";
|
|
import type { CollectionDocType } from "@/lib/types";
|
|
|
|
type AiDoc = NonNullable<ConfirmRow["ai_doc"]>;
|
|
|
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
|
|
/**
|
|
* Union of the document findings across a card's distinct reads, then each
|
|
* row's own line findings (`ai_check.findings`), deduped, in order.
|
|
*/
|
|
export function cardFindings(rows: ConfirmRow[]): string[] {
|
|
const seenReads = new Set<string>();
|
|
const seen = new Set<string>();
|
|
const out: string[] = [];
|
|
const add = (finding: string) => {
|
|
if (!finding || seen.has(finding)) return;
|
|
seen.add(finding);
|
|
out.push(finding);
|
|
};
|
|
for (const row of rows) {
|
|
const doc = row.ai_doc;
|
|
if (!doc || seenReads.has(doc.read_id)) continue;
|
|
seenReads.add(doc.read_id);
|
|
for (const finding of doc.findings ?? []) add(finding);
|
|
}
|
|
for (const row of rows) {
|
|
for (const finding of row.ai_check?.findings ?? []) add(finding);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** The card's newest document (first non-null wins on equal dates). */
|
|
export function cardDocument(rows: ConfirmRow[]): AiDoc | null {
|
|
let best: AiDoc | null = null;
|
|
for (const row of rows) {
|
|
const doc = row.ai_doc;
|
|
if (!doc) continue;
|
|
if (!best || (doc.doc_date ?? "") > (best.doc_date ?? "")) best = doc;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/**
|
|
* Pre-fill for the confirm dialog: the number and date of the card's
|
|
* document (the same newest document the chip shows), only when it is a
|
|
* tax invoice — a delivery note's number is not the invoice number.
|
|
*/
|
|
export function aiInvoiceDefaults(
|
|
rows: ConfirmRow[],
|
|
): { number: string; date?: string } | undefined {
|
|
const doc = cardDocument(rows);
|
|
if (!doc || doc.doc_type !== "tax_invoice") return undefined;
|
|
const number = (doc.document_no ?? "").trim();
|
|
if (!number) return undefined;
|
|
const date = doc.doc_date ?? "";
|
|
return /^\d{4}-\d{2}-\d{2}$/.test(date) ? { number, date } : { number };
|
|
}
|
|
|
|
/** Toast for an Add to POS response: plain success, or the counts when any row didn't go in. */
|
|
export function posResultToast(
|
|
results: Array<{ status: string }>,
|
|
): { level: "success" | "warning"; message: string } {
|
|
const count = (status: string) => results.filter((r) => r.status === status).length;
|
|
const created = count("created");
|
|
const failed = count("failed");
|
|
const needsSku = count("needs_sku");
|
|
if (failed === 0 && needsSku === 0) {
|
|
return { level: "success", message: `Added ${created} item${created === 1 ? "" : "s"} to the POS` };
|
|
}
|
|
const parts = [`${created} added`];
|
|
if (failed > 0) parts.push(`${failed} failed`);
|
|
if (needsSku > 0) parts.push(`${needsSku} need${needsSku === 1 ? "s" : ""} a SKU`);
|
|
return { level: "warning", message: parts.join(", ") };
|
|
}
|
|
|
|
export function docTypeLabel(type: CollectionDocType): string {
|
|
if (type === "tax_invoice") return "Tax invoice";
|
|
if (type === "delivery_note") return "Delivery note";
|
|
return "Document";
|
|
}
|
|
|
|
/** "2026-10-16" -> "16 Oct 2026"; anything else is returned as is. */
|
|
export function formatDocDate(iso: string): string {
|
|
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec((iso ?? "").trim());
|
|
if (!m) return iso ?? "";
|
|
const month = Number(m[2]) - 1;
|
|
if (month < 0 || month > 11) return iso;
|
|
return `${Number(m[3])} ${MONTHS[month]} ${m[1]}`;
|
|
}
|
|
|
|
export function documentChipLabel(doc: AiDoc): string {
|
|
const head = [docTypeLabel(doc.doc_type), doc.document_no].filter(Boolean).join(" ");
|
|
return [head, formatDocDate(doc.doc_date), "read from driver photo"]
|
|
.filter(Boolean)
|
|
.join(" · ");
|
|
}
|