import type { ConfirmRow } from "@/lib/server/collection-confirm-service"; import type { CollectionDocType } from "@/lib/types"; type AiDoc = NonNullable; 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(); const seen = new Set(); 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(" · "); }