diff --git a/components/InvoiceCheck.tsx b/components/InvoiceCheck.tsx
new file mode 100644
index 0000000..c2b7911
--- /dev/null
+++ b/components/InvoiceCheck.tsx
@@ -0,0 +1,155 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { ScanSearch, Loader2, CheckCircle2, AlertTriangle, XCircle, HelpCircle } from 'lucide-react';
+import BottomSheet from '@/components/BottomSheet';
+import { cn } from '@/lib/utils';
+import {
+ checkInvoice,
+ invoiceCheckBase,
+ readSeen,
+ rememberSeen,
+ type InvoiceCheckResult,
+ type Verdict,
+} from '@/lib/invoiceCheck';
+
+interface InvoiceCheckProps {
+ poNumber: string;
+ /** Invoice pages already uploaded for this PO; the helper needs at least one. */
+ pages: number;
+ size?: 'sm' | 'lg';
+}
+
+const STYLE: Record
= {
+ green: { icon: CheckCircle2, row: 'text-[#125a3e]', label: 'Matches' },
+ amber: { icon: AlertTriangle, row: 'text-[#8a5a02]', label: 'Check' },
+ red: { icon: XCircle, row: 'text-[#cf2d56]', label: 'Mismatch' },
+ unchecked: { icon: HelpCircle, row: 'text-[rgba(38,37,30,0.55)]', label: 'Not checked' },
+};
+
+// Beta helper. Renders nothing unless the driver switched it on in
+// Settings → Beta, so the normal PO buttons are untouched. It only reads:
+// tapping "Check invoice" asks WFDASH to compare the photo with the lines
+// collected on this PO and shows the answer. It never blocks anything.
+export default function InvoiceCheck({ poNumber, pages, size = 'sm' }: InvoiceCheckProps) {
+ const [enabled, setEnabled] = useState(false);
+ const [open, setOpen] = useState(false);
+ const [busy, setBusy] = useState(false);
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState('');
+ const [seen, setSeen] = useState<'ok' | 'override' | undefined>();
+
+ useEffect(() => {
+ setEnabled(Boolean(invoiceCheckBase()));
+ setSeen(readSeen()[poNumber]);
+ }, [poNumber]);
+
+ if (!enabled || !poNumber || pages === 0) return null;
+
+ const run = async () => {
+ if (busy) return;
+ setOpen(true);
+ setBusy(true);
+ setError('');
+ setResult(null);
+ try {
+ setResult(await checkInvoice(poNumber));
+ } catch (e) {
+ setError(e instanceof Error ? e.message : 'Check failed');
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const finish = () => {
+ if (result) {
+ const how = result.all_green ? 'ok' : 'override';
+ rememberSeen(poNumber, how);
+ setSeen(how);
+ }
+ setOpen(false);
+ };
+
+ const lg = size === 'lg';
+ const btn = lg
+ ? 'h-12 w-full rounded-xl text-[15px] font-semibold flex items-center justify-center gap-2 touch-manipulation'
+ : 'h-9 px-3 rounded-lg text-[13px] font-semibold flex items-center gap-1.5 touch-manipulation';
+
+ return (
+ <>
+
+
+ {open && (
+ setOpen(false)}>
+ {busy && (
+
+
+ Reading the invoice… this can take up to a minute.
+
+ )}
+ {error && (
+
+ Couldn’t check: {error}. You can carry on as normal.
+
+ )}
+ {result && (
+ <>
+ {result.lines.length === 0 ? (
+ No collected lines on this PO to check yet.
+ ) : (
+
+ {result.lines.map((l) => {
+ const st = STYLE[l.verdict];
+ const Icon = st.icon;
+ return (
+ -
+
+
+
+ {l.description || 'Item'} × {l.qty_collected}
+
+
{st.label}
+ {l.reason &&
{l.reason}
}
+
+
+ );
+ })}
+
+ )}
+ {result.error && (
+ Note: {result.error}
+ )}
+
+ >
+ )}
+ {!busy && (
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/components/PoActions.tsx b/components/PoActions.tsx
index 4d99643..0f1284a 100644
--- a/components/PoActions.tsx
+++ b/components/PoActions.tsx
@@ -4,6 +4,7 @@ import { useRef, useState } from 'react';
import { Camera, FileText, Loader2, Check } from 'lucide-react';
import { OrderItem, PurchaseOrder } from '@/lib/types';
import { buildPoFileUrl, countInvoicePages, invoicePhotoTarget } from '@/lib/poDocs';
+import InvoiceCheck from '@/components/InvoiceCheck';
import { cn } from '@/lib/utils';
interface PoActionsProps {
@@ -130,6 +131,10 @@ export default function PoActions({
/>
>
)}
+ {/* Beta helper: renders nothing unless switched on in Settings. */}
+ {purchaseOrder?.po_number && (
+
+ )}
);
}
diff --git a/lib/invoiceCheck.ts b/lib/invoiceCheck.ts
new file mode 100644
index 0000000..3346a8e
--- /dev/null
+++ b/lib/invoiceCheck.ts
@@ -0,0 +1,66 @@
+// Beta helper: ask WFDASH whether the invoice photo matches what the driver
+// collected on a PO. Advisory only — nothing here writes to PocketBase or
+// changes how collecting works. Off unless the driver switches it on in
+// Settings.
+
+import { pb, getSettings } from './pocketbase';
+
+export type Verdict = 'green' | 'amber' | 'red' | 'unchecked';
+
+export interface CheckedLine {
+ id: string;
+ kind: 'customer' | 'stock';
+ description: string;
+ qty_collected: number;
+ verdict: Verdict;
+ reason: string;
+}
+
+export interface InvoiceCheckResult {
+ po_number: string;
+ lines: CheckedLine[];
+ all_green: boolean;
+ error: string;
+}
+
+/** The WFDASH address to call, or '' when the helper is off or not set up. */
+export function invoiceCheckBase(): string {
+ const s = getSettings();
+ if (!s.aiCheckEnabled) return '';
+ const url = (s.aiCheckUrl || process.env.NEXT_PUBLIC_WFDASH_URL || '').trim();
+ return url.replace(/\/+$/, '');
+}
+
+export async function checkInvoice(poNumber: string): Promise