feat: invoice match on by default with the WFDASH address built in

The match never ran on drivers' phones: it was off by default and the
build had no WFDASH address, so photos were stored but nothing collected.
Now it is on for every driver (switched on once for phones saved while
it was off; a later "off" sticks) and falls back to wfdash.apphub.co.za.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-10-26 10:46:27 +02:00
parent 89cfa5c442
commit 30c30ed821
5 changed files with 36 additions and 10 deletions

View file

@ -15,6 +15,10 @@ COPY . .
ARG NEXT_PUBLIC_POCKETBASE_URL
ENV NEXT_PUBLIC_POCKETBASE_URL=${NEXT_PUBLIC_POCKETBASE_URL}
# WFDASH address the invoice match calls (overridable per phone in Settings)
ARG NEXT_PUBLIC_WFDASH_URL=https://wfdash.apphub.co.za
ENV NEXT_PUBLIC_WFDASH_URL=${NEXT_PUBLIC_WFDASH_URL}
# Generate PWA icons
RUN node scripts/generate-icons.js

View file

@ -6,6 +6,7 @@ import { useAuth } from '@/components/AuthProvider';
import BottomNav from '@/components/BottomNav';
import { getSettings, saveSettings, logout } from '@/lib/pocketbase';
import { AppSettings, DEFAULT_SETTINGS } from '@/lib/types';
import { DEFAULT_WFDASH_URL } from '@/lib/invoiceCheck';
import { cn } from '@/lib/utils';
import { setDarkMode, applyDarkMode } from '@/lib/theme';
import {
@ -305,7 +306,7 @@ export default function SettingsPage() {
<div className="pr-3">
<span className="text-sm font-semibold text-[#26251e] block">Invoice check</span>
<span className="text-[11px] text-[rgba(38,37,30,0.55)]">
Adds a Check invoice button after you photograph an invoice. Only advises; nothing else changes.
After you photograph an invoice, items that match it exactly are marked collected (you can undo). Anything unsure is left for you.
</span>
</div>
<button
@ -326,13 +327,13 @@ export default function SettingsPage() {
</div>
{settings.aiCheckEnabled && (
<label className="block">
<span className="text-[11px] text-[rgba(38,37,30,0.55)]">WFDASH address</span>
<span className="text-[11px] text-[rgba(38,37,30,0.55)]">WFDASH address (leave empty to use the default)</span>
<input
type="url"
inputMode="url"
value={settings.aiCheckUrl}
onChange={(e) => updateSetting('aiCheckUrl', e.target.value.trim())}
placeholder="https://your-wfdash-address"
placeholder={process.env.NEXT_PUBLIC_WFDASH_URL || DEFAULT_WFDASH_URL}
className="mt-1 w-full h-11 rounded-xl border border-[rgba(38,37,30,0.15)] bg-white px-3 text-[14px]"
/>
</label>

View file

@ -2,10 +2,13 @@
// 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.
// On by default; a driver can switch it off in Settings → Beta.
import { pb, getSettings, updateStockCollectionStatus } from './pocketbase';
/** Used when neither the phone's setting nor the build sets an address. */
export const DEFAULT_WFDASH_URL = 'https://wfdash.apphub.co.za';
export interface MatchedLine {
id: string;
kind: 'customer' | 'stock';
@ -33,11 +36,11 @@ export interface InvoiceMatchResult {
/** 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. */
/** The WFDASH address to call, or '' when the driver switched the helper off. */
export function invoiceCheckBase(): string {
const s = getSettings();
if (!s.aiCheckEnabled) return '';
const url = (s.aiCheckUrl || process.env.NEXT_PUBLIC_WFDASH_URL || '').trim();
const url = (s.aiCheckUrl || process.env.NEXT_PUBLIC_WFDASH_URL || DEFAULT_WFDASH_URL).trim();
return url.replace(/\/+$/, '');
}

View file

@ -1,5 +1,6 @@
import PocketBase from 'pocketbase';
import {
AI_CHECK_DEFAULT_VERSION,
AppSettings,
DEFAULT_SETTINGS,
OrderItem,
@ -914,7 +915,16 @@ export const getSettings = (): AppSettings => {
const saved = localStorage.getItem(SETTINGS_KEY);
if (saved) {
try {
return { ...DEFAULT_SETTINGS, ...JSON.parse(saved) };
const stored = JSON.parse(saved) as Partial<AppSettings>;
const settings: AppSettings = { ...DEFAULT_SETTINGS, ...stored };
// Phones saved while the invoice match was off by default: switch it
// on once. Saved back so a driver's later "off" is kept.
if ((stored.aiCheckDefaultVersion ?? 0) < AI_CHECK_DEFAULT_VERSION) {
settings.aiCheckEnabled = true;
settings.aiCheckDefaultVersion = AI_CHECK_DEFAULT_VERSION;
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}
return settings;
} catch {
return DEFAULT_SETTINGS;
}

View file

@ -264,12 +264,19 @@ export interface AppSettings {
darkMode: boolean;
fontSize: 'small' | 'normal' | 'large';
offlineMode: boolean;
/** Beta: show a "Check invoice" helper on POs. Off by default; changes nothing else. */
/** Beta: match an invoice photo and collect exact matches. On by default. */
aiCheckEnabled: boolean;
/** WFDASH base URL the helper calls, e.g. https://wfdash.example.com */
/** WFDASH base URL the helper calls; empty uses the built-in address. */
aiCheckUrl: string;
/**
* Bumped when the invoice match default changes. A phone saved before the
* match became default-on gets it switched on once; a later "off" sticks.
*/
aiCheckDefaultVersion: number;
}
export const AI_CHECK_DEFAULT_VERSION = 1;
export const DEFAULT_SETTINGS: AppSettings = {
locationInterval: 60,
minimumAccuracy: 50,
@ -277,8 +284,9 @@ export const DEFAULT_SETTINGS: AppSettings = {
darkMode: false,
fontSize: 'normal',
offlineMode: false,
aiCheckEnabled: false,
aiCheckEnabled: true,
aiCheckUrl: '',
aiCheckDefaultVersion: AI_CHECK_DEFAULT_VERSION,
};
export const PROBLEM_TYPES = [