After an invoice photo, exact matches are marked collected (with undo); uncertain ones are listed. Still off unless enabled in Settings > Beta. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
// Beta helper: photograph the invoice and let WFDASH match it against
|
|
// everything assigned to this driver at that supplier. Exact matches (same
|
|
// product, invoice quantity = ordered quantity) are marked collected through
|
|
// the same writes the bulk-collect button uses, and can be undone.
|
|
// Off unless the driver switches it on in Settings → Beta.
|
|
|
|
import { pb, getSettings, updateStockCollectionStatus } from './pocketbase';
|
|
|
|
export interface MatchedLine {
|
|
id: string;
|
|
kind: 'customer' | 'stock';
|
|
po_number: string;
|
|
description: string;
|
|
quantity: number;
|
|
previous: Record<string, unknown>;
|
|
}
|
|
|
|
export interface ReviewLine {
|
|
id: string;
|
|
kind: 'customer' | 'stock';
|
|
po_number: string;
|
|
description: string;
|
|
reason: string;
|
|
}
|
|
|
|
export interface InvoiceMatchResult {
|
|
po_number: string;
|
|
matched: MatchedLine[];
|
|
review: ReviewLine[];
|
|
error: string;
|
|
}
|
|
|
|
/** Fired by the PO buttons; InvoiceMatchHost (once per page) does the work. */
|
|
export const RUN_EVENT = 'invoice-match:run';
|
|
|
|
/** 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 matchInvoice(poNumber: string): Promise<InvoiceMatchResult> {
|
|
const base = invoiceCheckBase();
|
|
if (!base) throw new Error('Invoice check is not set up (Settings → Beta).');
|
|
const res = await fetch(`${base}/api/v1/driver/invoice-match`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${pb.authStore.token}` },
|
|
body: JSON.stringify({ po_number: poNumber }),
|
|
signal: AbortSignal.timeout(120_000),
|
|
});
|
|
const body = await res.json().catch(() => ({}));
|
|
if (!res.ok) throw new Error(body.error || `Check failed (${res.status})`);
|
|
return body as InvoiceMatchResult;
|
|
}
|
|
|
|
/** Mark one matched line collected, exactly as bulkCollect / the stock modal do. */
|
|
export async function collectMatched(line: MatchedLine): Promise<void> {
|
|
if (line.kind === 'stock') {
|
|
await updateStockCollectionStatus(line.id, { qty_collected: line.quantity, expectedQty: line.quantity });
|
|
return;
|
|
}
|
|
const now = new Date().toISOString();
|
|
await pb.collection('order_items').update(line.id, {
|
|
item_status: 'collected',
|
|
drv_status: 'collected',
|
|
drv_collected_at: now,
|
|
qty_collected: line.quantity,
|
|
collected_date: now,
|
|
});
|
|
}
|
|
|
|
/** Put a matched line back to how it was before the auto-collect. */
|
|
export async function undoMatched(line: MatchedLine): Promise<void> {
|
|
await pb
|
|
.collection(line.kind === 'stock' ? 'stock_purchase_items' : 'order_items')
|
|
.update(line.id, line.previous);
|
|
}
|