/* ============================================================= CoreIQ Office — Ordering, Wholesaler invoices, Settlement, Corporate vouchers (flu), Payments (Adyen) ============================================================= */ const { useState: ordUseState, useMemo: ordUseMemo } = React; // ----------------------------------------------------------------- // SAMPLE DATA // ----------------------------------------------------------------- const HO_WHOLESALERS = [ { id: "ws-sigma", name: "Sigma Healthcare", channel: "PharmX", account: "SIG-89441", terms: "Net 30", contact: "Ben Lee", cutoff: "16:00 daily", rebate: "2.4% Q", status: "live" }, { id: "ws-symbion", name: "Symbion", channel: "PharmX", account: "SYM-22408", terms: "Net 14", contact: "Olivia Marsh", cutoff: "15:30 daily", rebate: "1.8% Q", status: "live" }, { id: "ws-api", name: "API", channel: "EDI", account: "API-77104", terms: "Net 30", contact: "Daniel Park", cutoff: "Twice weekly", rebate: "Volume", status: "live" }, { id: "ws-gsk", name: "GSK Australia", channel: "Direct (DHL)", account: "GSK-44102", terms: "Net 30", contact: "Maria Chen", cutoff: "Order ahead", rebate: "—", status: "live" }, { id: "ws-webster", name: "Webstercare", channel: "Direct (DHL)", account: "WEB-11220", terms: "Net 30", contact: "Jenna Hughes", cutoff: "Order ahead", rebate: "—", status: "live" }, { id: "ws-ndss", name: "Diabetes Australia · NDSS", channel: "EDI", account: "NDSS-018", terms: "Net 30", contact: "—", cutoff: "Daily", rebate: "—", status: "live" }, { id: "ws-csl", name: "CSL Seqirus · vaccines", channel: "Direct (DHL)", account: "CSL-22018", terms: "Pre-season", contact: "Ana Reyes", cutoff: "Seasonal", rebate: "Corporate voucher", status: "live" }, ]; const HO_ORDERS = [ { id: "PO-4422", supplier: "ws-sigma", channel: "PharmX", store: "S-001", lines: 4, units: 158, value: 412.40, status: "received", sent: "Yesterday 22:00", expected: "Today 09:00", received: "Today 09:14", notes: "1 discrepancy · short 2 Atorvastatin" }, { id: "PO-4423", supplier: "ws-symbion", channel: "PharmX", store: "S-001", lines: 2, units: 70, value: 624.60, status: "received", sent: "Yesterday 22:00", expected: "Today 11:30", received: "Today 11:30", notes: "Cold chain logged" }, { id: "PO-4424", supplier: "ws-api", channel: "EDI", store: "S-001", lines: 6, units: 240, value: 1142.20, status: "in-transit", sent: "Today 06:30", expected: "Today 16:00", received: null, notes: "ETA Camberwell 16:00" }, { id: "PO-4425", supplier: "ws-gsk", channel: "Direct (DHL)", store: "S-002", lines: 1, units: 24, value: 380.20, status: "in-transit", sent: "25/05/2026 14:20", expected: "Today 14:00", received: null, notes: "DHL · tracking 8821 0042 11" }, { id: "PO-4426", supplier: "ws-csl", channel: "Direct (DHL)", store: "S-001", lines: 3, units: 600, value: 9420.00, status: "received", sent: "12/04/2026", expected: "20/04/2026", received: "20/04/2026", notes: "Flu pre-season · 3 SKUs · voucher batch" }, { id: "PO-4427", supplier: "ws-ndss", channel: "EDI", store: "S-003", lines: 5, units: 144, value: 410.20, status: "open", sent: "—", expected: "—", received: null, notes: "Drafting order — reorder triggered" }, { id: "PO-4428", supplier: "ws-sigma", channel: "PharmX", store: "S-002", lines: 8, units: 220, value: 884.50, status: "open", sent: "—", expected: "—", received: null, notes: "Auto-suggest from low-stock alerts" }, { id: "PO-4429", supplier: "ws-webster", channel: "Direct (DHL)", store: "S-003", lines: 1, units: 50, value: 600.00, status: "submitted", sent: "Today 11:48", expected: "Tomorrow", received: null, notes: "Webster pack media · weekly" }, { id: "PO-4430", supplier: "ws-symbion", channel: "PharmX", store: "S-003", lines: 6, units: 188, value: 712.30, status: "submitted", sent: "Today 12:14", expected: "Tomorrow 09:30", received: null, notes: "Symbion overnight" }, ]; const HO_INVOICES = [ { id: "INV-SIG-8841", supplier: "ws-sigma", po: "PO-4422", issued: "Today", due: "26/06/2026", total: 412.40, received: 0, status: "open" }, { id: "INV-SYM-2204", supplier: "ws-symbion", po: "PO-4423", issued: "Today", due: "09/06/2026", total: 624.60, received: 0, status: "open" }, { id: "INV-API-1140", supplier: "ws-api", po: "PO-4418", issued: "Yesterday", due: "25/06/2026", total: 1844.20, received: 1844.20, status: "matched" }, { id: "INV-GSK-7720", supplier: "ws-gsk", po: "PO-4412", issued: "20/05/2026", due: "19/06/2026", total: 980.40, received: 0, status: "open" }, { id: "INV-CSL-FLU", supplier: "ws-csl", po: "PO-4426", issued: "21/04/2026", due: "Voucher", total: 9420.00, received: 9420.00, status: "voucher", note: "Settled via corporate voucher · flu programme" }, { id: "INV-SIG-8810", supplier: "ws-sigma", po: "PO-4401", issued: "12/05/2026", due: "11/06/2026", total: 884.10, received: 884.10, status: "matched" }, { id: "INV-SYM-2188", supplier: "ws-symbion", po: "PO-4395", issued: "10/05/2026", due: "24/05/2026", total: 318.20, received: 318.20, status: "matched" }, { id: "INV-NDSS-022", supplier: "ws-ndss", po: "PO-4408", issued: "08/05/2026", due: "07/06/2026", total: 488.50, received: 0, status: "dispute", note: "Pricing dispute · 4 lines flagged" }, ]; const HO_SETTLEMENTS = [ { id: "SET-2226", provider: "Adyen", date: "Today", gross: 4480.20, fees: 31.36, net: 4448.84, batch: 112, status: "settling" }, { id: "SET-2225", provider: "Adyen", date: "Yesterday", gross: 3942.80, fees: 27.60, net: 3915.20, batch: 98, status: "settled" }, { id: "SET-2224", provider: "Adyen", date: "25/05/2026", gross: 5118.40, fees: 35.83, net: 5082.57, batch: 124, status: "settled" }, { id: "SET-2223", provider: "Adyen", date: "24/05/2026", gross: 2812.80, fees: 19.69, net: 2793.11, batch: 84, status: "settled" }, { id: "SET-2222", provider: "Adyen", date: "23/05/2026", gross: 4220.60, fees: 29.54, net: 4191.06, batch: 102, status: "settled" }, { id: "SET-2221", provider: "Adyen", date: "22/05/2026", gross: 4940.20, fees: 34.58, net: 4905.62, batch: 118, status: "settled" }, { id: "SET-2220", provider: "Adyen", date: "21/05/2026", gross: 3680.40, fees: 25.76, net: 3654.64, batch: 92, status: "settled" }, ]; const HO_VOUCHER_PROGRAMS = [ { id: "VP-FLU-2026", name: "Workplace Flu 2026", sponsor: "Multiple corporates · CSL Seqirus supply", season: "Apr → Aug 2026", status: "active", vouchersIssued: 4200, vouchersRedeemed: 2814, vaccineUnits: 4200, valuePerVoucher: 32.50, totalValue: 136500, corporates: 18, notes: "Each voucher = 1 quadrivalent influenza vaccine + admin. Settled monthly with corporate sponsor on issued voucher count.", }, { id: "VP-COV-2026", name: "Workplace COVID booster", sponsor: "Corporate · 6 employers", season: "Mar → Dec 2026", status: "active", vouchersIssued: 880, vouchersRedeemed: 412, vaccineUnits: 880, valuePerVoucher: 28.00, totalValue: 24640, corporates: 6, notes: "Bivalent booster · co-billed with flu where eligible.", }, { id: "VP-TRAVEL-2026", name: "Travel pack · employer", sponsor: "Bridgewater NDIS Services", season: "Year-round", status: "active", vouchersIssued: 60, vouchersRedeemed: 24, vaccineUnits: 0, valuePerVoucher: 88.00, totalValue: 5280, corporates: 1, notes: "Hep A/B, typhoid, malaria scripts — issued by HR before travel.", }, { id: "VP-FLU-2025", name: "Workplace Flu 2025", sponsor: "Multiple corporates", season: "Apr → Aug 2025", status: "closed", vouchersIssued: 3640, vouchersRedeemed: 3622, vaccineUnits: 3640, valuePerVoucher: 29.50, totalValue: 107380, corporates: 14, notes: "Closed · final reconciliation Sep 2025.", }, ]; const HO_CORPORATES = [ { name: "Whitehorse Family Medical Centre", staff: 32, vouchers: 28, redeemed: 22 }, { name: "Camberwell Aged Care Facility", staff: 188, vouchers: 188, redeemed: 142 }, { name: "Bridgewater NDIS Services", staff: 64, vouchers: 64, redeemed: 44 }, { name: "Burke Road Community House", staff: 18, vouchers: 18, redeemed: 12 }, { name: "Eastland Medical Suites", staff: 44, vouchers: 44, redeemed: 32 }, { name: "Glen Iris Day Centre", staff: 80, vouchers: 80, redeemed: 52 }, ]; // ================================================================= // ORDERING (PharmX / EDI / Direct) // ================================================================= function Ordering() { const { setScreen, setScreenState, pushToast } = window.useOffice(); const [tab, setTab] = ordUseState("orders"); const [filter, setFilter] = ordUseState("all"); const [channel, setChannel] = ordUseState("all"); const FILTERS = [ { key: "all", label: "All", pred: () => true }, { key: "open", label: "Drafts", pred: o => o.status === "open" }, { key: "submitted", label: "Submitted", pred: o => o.status === "submitted" }, { key: "in-transit", label: "In transit", pred: o => o.status === "in-transit" }, { key: "received", label: "Received", pred: o => o.status === "received" }, ]; const CHANNELS = ["all","PharmX","EDI","Direct (DHL)"]; const list = HO_ORDERS.filter(o => { if (!FILTERS.find(f => f.key === filter).pred(o)) return false; if (channel !== "all" && o.channel !== channel) return false; return true; }); return (
Head-office function

Wholesaler ordering

o.status === "submitted" || o.status === "in-transit").length} delta={{text: HO_ORDERS.filter(o => o.status === "in-transit").length + " in transit · " + HO_ORDERS.filter(o => o.status === "submitted").length + " submitted"}}/> s + o.value, 0))} delta={{dir:"up", text: "▲ 6.2% vs prior 30d"}}/>
{tab === "orders" && (
{FILTERS.map(f => { const c = HO_ORDERS.filter(f.pred).length; return ( ); })}
{CHANNELS.map(c => ( ))}
{list.length} of {HO_ORDERS.length}
PO
Supplier
Channel
Store
Lines
Units
Value
Expected
Status
{list.map(o => { const ws = HO_WHOLESALERS.find(w => w.id === o.supplier); const store = findS(o.store); return (
{o.id}
{ws.name}
{o.notes}
{o.channel}
{store?.code} · {store?.name}
{o.lines}
{o.units}
{oMoney(o.value)}
{o.expected}
{o.status === "open" && Draft} {o.status === "submitted" && Submitted} {o.status === "in-transit" && In transit} {o.status === "received" && Received}
); })}
)} {tab === "channels" && (
Wholesaler
Channel
Account
Terms
Contact · cutoff
Rebate
Status
{HO_WHOLESALERS.map(ws => (
{ws.name}
{ws.channel}
{ws.account}
{ws.terms}
{ws.contact}
Cutoff {ws.cutoff}
{ws.rebate}
Live
))}
)} {tab === "suggestions" && (
Below-reorder SKUs grouped by preferred wholesaler. Click Generate PO to draft a purchase order.
{HO_WHOLESALERS.slice(0, 4).map(ws => { const sample = OD.PRODUCTS.filter(p => p.supplier === (ws.id.replace("ws-","sup-")) || ws.id === "ws-sigma").slice(0, 3); const total = sample.reduce((s, p) => s + p.cost * 24, 0); return (
{ws.name}
{ws.channel} {sample.length} SKUs suggested · est {oMoney(total)}
Product
On hand
Min
Suggest
Est cost
{sample.map(p => (
{p.name} · {p.strength}
{window.officeTotalStock(p.id)}
8
24
{oMoney(p.cost * 24)}
))}
); })}
)}
); } function OrderNew() { const { setScreen, pushToast } = window.useOffice(); const [ws, setWs] = ordUseState("ws-sigma"); const wsObj = HO_WHOLESALERS.find(w => w.id === ws); const [store, setStore] = ordUseState("S-001"); const [lines, setLines] = ordUseState([ { productId: "p002", qty: 60 }, { productId: "p005", qty: 24 }, { productId: "p008", qty: 36 }, ]); const total = lines.reduce((s, l) => { const p = findP(l.productId); return s + (p ? p.cost * l.qty : 0); }, 0); return (
Wholesaler order

New purchase order

1

Supplier & destination

Channel · {wsObj.channel} · Terms · {wsObj.terms} · Cutoff · {wsObj.cutoff}
2

Lines

Product
Qty
Unit cost
Line total
{lines.map((l, i) => { const p = findP(l.productId); return (
setLines(arr => arr.map((x, j) => j === i ? { ...x, qty: parseInt(e.target.value) || 0 } : x))}/>
{oMoney(p?.cost || 0)}
{oMoney((p?.cost || 0) * l.qty)}
); })}

Order summary

Lines{lines.length}
Units{lines.reduce((s, l) => s + l.qty, 0)}
Subtotal{oMoney(total)}
GST (where applicable){oMoney(total * 0.07)}
Total est. {oMoney(total * 1.07)}

Submitting via {wsObj.channel}

{wsObj.channel === "PharmX" && "Order will be posted to PharmX overnight (next cutoff " + wsObj.cutoff + "). Confirmation back within 30 min of cutoff."} {wsObj.channel === "EDI" && "Order will be transmitted via EDI directly to " + wsObj.name + ". Acknowledgement (ASN) back within 2 hours."} {wsObj.channel === "Direct (DHL)" && "Direct order placed with " + wsObj.name + ". DHL tracking number will appear when dispatched."}
); } // ================================================================= // WHOLESALER INVOICES // ================================================================= function Invoices() { const { pushToast } = window.useOffice(); const [filter, setFilter] = ordUseState("all"); const FILTERS = [ { key: "all", label: "All", pred: () => true }, { key: "open", label: "Open", pred: i => i.status === "open" }, { key: "matched", label: "Matched", pred: i => i.status === "matched" }, { key: "dispute", label: "Dispute", pred: i => i.status === "dispute" }, { key: "voucher", label: "Voucher-settled", pred: i => i.status === "voucher" }, ]; const list = HO_INVOICES.filter(FILTERS.find(f => f.key === filter).pred); const totals = { open: HO_INVOICES.filter(i => i.status === "open").reduce((s, i) => s + i.total, 0), overdue: HO_INVOICES.filter(i => i.status === "open" && /20\d\d/.test(i.due)).reduce((s, i) => s + i.total, 0), matched30d: HO_INVOICES.filter(i => i.status === "matched").reduce((s, i) => s + i.received, 0), dispute: HO_INVOICES.filter(i => i.status === "dispute").length, }; return (
Head-office function

Wholesaler invoices

i.status === "open").length} invoices outstanding`}}/> i.status === "voucher").length} delta={{text: "Corporate flu programme"}}/>
{FILTERS.map(f => { const c = HO_INVOICES.filter(f.pred).length; return ( ); })}
Auto-match via 3-way (PO ↔ GRN ↔ invoice)
Invoice
Supplier
PO
Issued
Due
Total
Outstanding
Status
{list.map(i => { const ws = HO_WHOLESALERS.find(w => w.id === i.supplier); const outstanding = i.total - i.received; return (
{i.id}
{ws.name} {i.note &&
{i.note}
}
{i.po}
{i.issued}
{i.due}
{oMoney(i.total)}
0 ? 'var(--text)' : 'var(--paid-text)', fontWeight: 500}}> {outstanding > 0 ? oMoney(outstanding) : "Settled"}
{i.status === "open" && Open} {i.status === "matched" && Matched} {i.status === "dispute" && Dispute} {i.status === "voucher" && Voucher}
); })}
); } // ================================================================= // PAYMENTS & SETTLEMENT (Adyen) // ================================================================= function Payments() { const { pushToast } = window.useOffice(); const [tab, setTab] = ordUseState("settlements"); const totals = { pendingGross: HO_SETTLEMENTS.filter(s => s.status === "settling").reduce((s, x) => s + x.gross, 0), settledMTD: HO_SETTLEMENTS.reduce((s, x) => s + x.net, 0), feesMTD: HO_SETTLEMENTS.reduce((s, x) => s + x.fees, 0), txCount: HO_SETTLEMENTS.reduce((s, x) => s + x.batch, 0), }; return (
Payments · provider · Adyen

Settlement & payments

{tab === "settlements" && (
Batch ID
Provider
Date
Tx
Gross
Fees
Net to bank
Status
{HO_SETTLEMENTS.map(s => (
{s.id}
{s.provider}
{s.date}
{s.batch}
{oMoney(s.gross)}
− {oMoney(s.fees)}
{oMoney(s.net)}
{s.status === "settling" ? Settling : Settled}
))}
)} {tab === "stores" && } {tab === "accounts" && } {tab === "transactions" && (
Time
Method
Reference
Store
Terminal
Card / wallet
Amount
Result
{[ { ts:"14:32", m:"Eftpos · tap", ref:"Sale 4821", store:"S-001", term:"T-CMW-02", card:"Visa **4421", amt: 60.22, ok:true }, { ts:"14:18", m:"Mobile wallet", ref:"Sale 4820", store:"S-001", term:"T-CMW-01", card:"Apple Pay", amt: 148.20, ok:true }, { ts:"13:48", m:"Cash", ref:"Sale 4818", store:"S-001", term:"—", card:"—", amt: 28.45, ok:true }, { ts:"13:32", m:"Eftpos · ins.",ref:"Sale 4817", store:"S-002", term:"T-GLI-01", card:"Mastercard **0114", amt:38.95, ok:true }, { ts:"13:14", m:"Eftpos · tap", ref:"Sale 4816", store:"S-003", term:"T-RNG-01", card:"Visa **8814", amt: 92.10, ok:true }, { ts:"12:48", m:"Eftpos · tap", ref:"Sale 4814", store:"S-001", term:"T-CMW-02", card:"Visa **8841", amt: 11.20, ok:false }, { ts:"12:14", m:"Refund", ref:"REF-4814", store:"S-001", term:"T-CMW-02", card:"Visa **8841", amt: -24.95, ok:true }, { ts:"11:48", m:"Mobile wallet",ref:"Sale 4815", store:"S-001", term:"T-CMW-01", card:"Google Pay", amt: 92.20, ok:true }, { ts:"11:14", m:"Eftpos · ins.",ref:"Sale 4813", store:"S-002", term:"T-GLI-01", card:"Visa Debit", amt: 18.50, ok:true }, { ts:"10:48", m:"Eftpos · tap", ref:"Sale 4811", store:"S-003", term:"T-RNG-02", card:"Mastercard **2218", amt:46.40, ok:true }, ].map((t, i) => (
{t.ts}
{t.m}
{t.ref}
{findS(t.store)?.code}
{t.term}
{t.card}
{oMoney(t.amt)}
{t.ok ? Approved : Declined}
))}
)} {tab === "terminals" && (
Terminal
Store · register
Model
Connection
Firmware
Today
Status
{[ { id:"T-CMW-01", store:"S-001", reg:"Reg 01", model:"Adyen S1F2 · S1E", conn:"Wi-Fi", fw:"v2.84.1", today: 2418.50, ok:true }, { id:"T-CMW-02", store:"S-001", reg:"Reg 02", model:"Adyen S1F2 · S1E", conn:"Ethernet", fw:"v2.84.1", today: 1812.30, ok:true }, { id:"T-CMW-03", store:"S-001", reg:"Reg 03", model:"Adyen V400m", conn:"4G", fw:"v2.79.4", today: 589.85, ok:false }, { id:"T-GLI-01", store:"S-002", reg:"Reg 01", model:"Adyen S1F2 · S1E", conn:"Wi-Fi", fw:"v2.84.1", today: 1318.20, ok:true }, { id:"T-GLI-02", store:"S-002", reg:"Reg 02", model:"Adyen S1F2 · S1E", conn:"Wi-Fi", fw:"v2.84.1", today: 1100.20, ok:true }, { id:"T-RNG-01", store:"S-003", reg:"Reg 01", model:"Adyen S1F2 · S1E", conn:"Wi-Fi", fw:"v2.84.1", today: 1820.50, ok:true }, { id:"T-RNG-02", store:"S-003", reg:"Reg 02", model:"Adyen V400m", conn:"4G", fw:"v2.84.1", today: 1300.35, ok:true }, ].map(t => (
{t.id}
{findS(t.store)?.name} · {t.reg}
{t.model}
{t.conn}
{t.fw}
{oMoney(t.today)}
{t.ok ? Online : Firmware update}
))}
)} {tab === "fees" && (

Adyen pricing schedule

Interchange++ pricing, contracted Jan 2026.
Card scheme
Domestic
Intl
Premium
{[ { scheme: "Visa Debit", dom: "0.20% + $0.05", intl: "0.85% + $0.05", prem: "1.20%" }, { scheme: "Visa Credit", dom: "0.55% + $0.05", intl: "1.40%", prem: "1.85%" }, { scheme: "Mastercard Debit", dom: "0.20% + $0.05", intl: "0.85% + $0.05", prem: "1.20%" }, { scheme: "Mastercard Credit",dom: "0.55% + $0.05", intl: "1.40%", prem: "1.85%" }, { scheme: "American Express", dom: "1.60%", intl: "1.90%", prem: "—" }, { scheme: "EFTPOS", dom: "0.10% + $0.04", intl: "—", prem: "—" }, { scheme: "Apple Pay · Google Pay", dom: "Underlying card rate", intl: "Underlying", prem: "Underlying" }, ].map((r, i) => (
{r.scheme}
{r.dom}
{r.intl}
{r.prem}
))}
Adyen platform fee: $0.10 per transaction. Settlement: T+1 to nominated bank (NAB · ****8214).
)}
); } // ================================================================= // CORPORATE VOUCHERS (flu programme etc.) // ================================================================= function Vouchers() { const { setScreen, setScreenState, pushToast } = window.useOffice(); return (
Head-office function

Corporate voucher programmes

p.status === "active").length} delta={{text: "Flu · COVID · Travel"}}/> p.status === "active").reduce((s, p) => s + p.vouchersIssued, 0).toLocaleString()} delta={{text:"Across active programmes"}}/> p.status === "active").reduce((s, p) => s + p.vouchersRedeemed, 0) / HO_VOUCHER_PROGRAMS.filter(p => p.status === "active").reduce((s, p) => s + p.vouchersIssued, 0) * 100)}%`} delta={{dir:"up", text: HO_VOUCHER_PROGRAMS.filter(p => p.status === "active").reduce((s, p) => s + p.vouchersRedeemed, 0).toLocaleString() + " redeemed"}}/> p.status === "active").reduce((s, p) => s + p.totalValue, 0))} delta={{text: "Issued · pre-settlement"}}/>
{HO_VOUCHER_PROGRAMS.map(p => { const redemptionPct = Math.round((p.vouchersRedeemed / p.vouchersIssued) * 100); return (
{p.name}
{p.status === "active" ? Active : Closed}
{p.id} · {p.sponsor} · Season {p.season} · {p.corporates} corporates
Vouchers issued
{p.vouchersIssued.toLocaleString()}
Redeemed
{p.vouchersRedeemed.toLocaleString()} · {redemptionPct}%
Value per voucher
${p.valuePerVoucher.toFixed(2)}
Total value
{oMoney(p.totalValue)}
Vaccine units
{p.vaccineUnits > 0 ? p.vaccineUnits.toLocaleString() : "—"}
{p.notes}
); })}

Workplace Flu 2026 · participating corporates

Corporate
Staff
Vouchers
Redeemed
Status
{HO_CORPORATES.map(c => { const pct = Math.round((c.redeemed / c.vouchers) * 100); return (
{c.name}
{c.staff}
{c.vouchers}
{c.redeemed} · {pct}%
{pct >= 80 ? Strong : pct >= 50 ? On track : Encourage}
); })}
); } window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, { ordering: Ordering, orderNew: OrderNew, invoices: Invoices, payments: Payments, vouchers: Vouchers, }); // ----------------------------------------------------------------- // SETTLEMENT ACCOUNTS — where Adyen pays into // ----------------------------------------------------------------- function SettlementAccounts() { const { pushToast } = window.useOffice(); const [addOpen, setAddOpen] = ordUseState(false); const STORE_COLOURS = { "S-001": "var(--teal-700)", "S-002": "var(--navy-600)", "S-003": "var(--violet-600)" }; const ACCOUNTS = [ { id: "SA-MAIN", label: "Group operating account", bank: "NAB Business", bsb: "083 004", account: "****8214", balance: 184220.10, stores: ["S-001","S-002","S-003"], cadence: "T+1", currency: "AUD", primary: true, status: "active", todayIn: 12303.20, mtdIn: 176751.29, adyenMerchantId: "CoreIQGroup_AU", lastDeposit: "Today 10:00 · $4,448.84", purpose: "All POS card receipts settle here unless overridden by store routing.", }, { id: "SA-CMW-TRUST", label: "Camberwell · PBS trust", bank: "CommBank", bsb: "062 902", account: "****1144", balance: 38240.50, stores: ["S-001"], cadence: "Daily", currency: "AUD", primary: false, status: "active", todayIn: 1812.30, mtdIn: 28104.20, adyenMerchantId: "CoreIQGroup_AU_S001", lastDeposit: "Today 09:48 · $1,812.30", purpose: "Camberwell PBS Online Claiming payments only. Separated by accountant.", }, { id: "SA-GLI-OPS", label: "Glen Iris · operating", bank: "NAB Business", bsb: "083 004", account: "****8842", balance: 22418.40, stores: ["S-002"], cadence: "T+1", currency: "AUD", primary: false, status: "active", todayIn: 2418.00, mtdIn: 42118.40, adyenMerchantId: "CoreIQGroup_AU_S002", lastDeposit: "Today 10:00 · $1,488.20", purpose: "Routed for Glen Iris card receipts. Used for local supplier payments.", }, { id: "SA-RNG-OPS", label: "Ringwood · operating", bank: "Westpac Business", bsb: "033 088", account: "****4402", balance: 28110.65, stores: ["S-003"], cadence: "T+1", currency: "AUD", primary: false, status: "active", todayIn: 3120.85, mtdIn: 58210.20, adyenMerchantId: "CoreIQGroup_AU_S003", lastDeposit: "Today 10:00 · $1,920.40", purpose: "Routed for Ringwood card receipts.", }, { id: "SA-VOUCHER", label: "Corporate voucher clearing", bank: "Macquarie Business", bsb: "182 222", account: "****1108", balance: 18420.80, stores: [], cadence: "Monthly", currency: "AUD", primary: false, status: "active", todayIn: 0, mtdIn: 32140.00, adyenMerchantId: "—", lastDeposit: "01/05/2026 · $32,140.00", purpose: "Receives monthly settlement from corporate sponsors (Workplace Flu, COVID booster).", }, { id: "SA-AMEX", label: "AmEx settlement", bank: "NAB Business", bsb: "083 004", account: "****8214", balance: 0, stores: ["S-001","S-002","S-003"], cadence: "T+3", currency: "AUD", primary: false, status: "active", todayIn: 218.40, mtdIn: 4128.20, adyenMerchantId: "CoreIQGroup_AU", lastDeposit: "Yesterday · $412.20", purpose: "Separate settlement schedule per AmEx contract. Merges into operating on T+3.", virtual: true, }, { id: "SA-RESERVE", label: "Adyen reserve · holdback", bank: "Held by Adyen", bsb: "—", account: "—", balance: 8420.00, stores: [], cadence: "Released quarterly", currency: "AUD", primary: false, status: "reserve", todayIn: 0, mtdIn: 0, adyenMerchantId: "CoreIQGroup_AU", lastDeposit: "Released 01/04/2026 · $8,420.00", purpose: "Adyen holds 1.5% of card volume against chargeback risk. Released quarterly.", }, ]; const totals = { balance: ACCOUNTS.reduce((s, a) => s + a.balance, 0), todayIn: ACCOUNTS.reduce((s, a) => s + a.todayIn, 0), mtdIn: ACCOUNTS.reduce((s, a) => s + a.mtdIn, 0), active: ACCOUNTS.filter(a => a.status === "active").length, }; return ( <>
a.id === "SA-RESERVE").balance)} delta={{text: "Released quarterly"}}/>
Adyen routes card receipts to these bank accounts on the cadence shown. The default operating account catches anything not explicitly routed. To change routing per store, open the account and update the Adyen sub-merchant mapping.
{ACCOUNTS.map(a => (
{a.label} {a.primary && Primary} {a.virtual && Virtual} {a.status === "reserve" && Reserve}
{a.bank}{a.bsb !== "—" && ` · BSB ${a.bsb} · ${a.account}`} · {a.cadence}
{a.stores.length === 3 && All stores} {a.stores.length > 0 && a.stores.length < 3 && a.stores.map(sid => { const store = findS(sid); return {store.code}; })} {a.stores.length === 0 && Group-only}
Balance
{oMoney(a.balance)}
Settling today
0 ? 'var(--paid-text)' : 'var(--text-subtle)'}}>{a.todayIn > 0 ? oMoney(a.todayIn) : "—"}
Inflows · 30d
{a.mtdIn > 0 ? oMoney(a.mtdIn) : "—"}
Adyen merchant
{a.adyenMerchantId}
Last deposit · {a.lastDeposit} · {a.purpose}
))}
{/* Routing rules summary */}

Active routing rules

Priority
When
Card scheme
Settles to
Cadence
{[ { p: 1, when: "All stores · AmEx", scheme: "American Express", to: "AmEx settlement", cad: "T+3" }, { p: 2, when: "Camberwell · PBS receipts", scheme: "Any", to: "Camberwell PBS trust", cad: "Daily" }, { p: 3, when: "Glen Iris · all card", scheme: "Visa / MC / EFTPOS / Wallet", to: "Glen Iris · operating", cad: "T+1" }, { p: 4, when: "Ringwood · all card", scheme: "Visa / MC / EFTPOS / Wallet", to: "Ringwood · operating", cad: "T+1" }, { p: 5, when: "Everything else · default", scheme: "Any", to: "Group operating account", cad: "T+1" }, ].map((r, i) => (
{r.p}
{r.when}
{r.scheme}
{r.to}
{r.cad}
))}
{addOpen && setAddOpen(false)} onSubmit={(label, bank) => { setAddOpen(false); pushToast({kind:'paid', icon:'check-circle', title:'Bank account linked', meta:`${label} · ${bank}`}); }}/>} ); } // ----------------------------------------------------------------- // Link account modal — bank details + Adyen routing // ----------------------------------------------------------------- function LinkAccountModal({ onClose, onSubmit }) { const [step, setStep] = ordUseState(0); const STEPS = ["Identify", "Bank details", "Adyen routing", "Verify"]; const [form, setForm] = ordUseState({ label: "", purpose: "operating", holderName: "CoreIQ Pharmacy Group Pty Ltd", bank: "NAB Business", bsb: "", account: "", accountConfirm: "", currency: "AUD", cadence: "T+1", primary: false, stores: ["S-001","S-002","S-003"], schemes: { visa: true, mastercard: true, eftpos: true, amex: false, wallet: true }, adyenMerchantId: "CoreIQGroup_AU", micro: false, }); function set(k, v) { setForm(f => ({ ...f, [k]: v })); } function toggleStore(id) { setForm(f => ({ ...f, stores: f.stores.includes(id) ? f.stores.filter(x => x !== id) : [...f.stores, id] })); } function toggleScheme(id) { setForm(f => ({ ...f, schemes: { ...f.schemes, [id]: !f.schemes[id] } })); } const bsbValid = /^\d{3}\s?-?\s?\d{3}$/.test(form.bsb); const acctValid = /^\d{6,10}$/.test(form.account.replace(/\s/g, "")); const acctMatches = form.account.replace(/\s/g, "") === form.accountConfirm.replace(/\s/g, ""); const stepValid = (() => { if (step === 0) return form.label && form.purpose && form.holderName; if (step === 1) return bsbValid && acctValid && acctMatches && form.bank; if (step === 2) return form.stores.length > 0 || form.purpose === "voucher"; return true; })(); function submit() { onSubmit(form.label, `${form.bank} · BSB ${form.bsb} · •••• ${form.account.slice(-4)}`); } return (
e.stopPropagation()}>

Link a bank account for settlement

{/* Stepper */}
{STEPS.map((label, i) => (
{i < step ? : i + 1}
{label}
{i < STEPS.length - 1 &&
} ))}
{step === 0 && ( <>

What is this account for?

A label helps your team recognise it on the settlement screen.

set("label", e.target.value)}/>
Internal name only — your bank doesn't see this.
{[ { id: "operating", label: "Operating", desc: "Day-to-day card receipts" }, { id: "pbs-trust", label: "PBS trust", desc: "PBS Online Claiming only" }, { id: "voucher", label: "Voucher clearing", desc: "Corporate sponsor payments" }, { id: "amex", label: "AmEx settlement", desc: "Separate T+3 cadence" }, ].map(p => ( ))}
set("holderName", e.target.value)}/>
Must match the name on the bank account exactly — used by Adyen to verify ownership.
)} {step === 1 && ( <>

Bank account details

Australian BSB + account number. Adyen will run a $0 verification when you finish.

set("bsb", e.target.value)} placeholder="000-000" maxLength={7}/>
6 digits · e.g. 083-004
set("account", e.target.value)} placeholder="6–10 digits"/>
set("accountConfirm", e.target.value)} placeholder="Re-enter account number"/> {form.accountConfirm && !acctMatches &&
Account numbers don't match.
}
Encrypted at rest. Only Adyen and senior office admins can view the full account number after save.
)} {step === 2 && ( <>

Adyen routing

Which Adyen sub-merchant settles to this account, and for which stores.

Group MID receives everything by default. Sub-MIDs split settlement per store.
{OD.STORES.map(s => ( ))}
{[["visa","Visa"],["mastercard","Mastercard"],["eftpos","EFTPOS"],["amex","American Express"],["wallet","Apple / Google Pay"]].map(([k, label]) => ( ))}
)} {step === 3 && ( <>

Review & verify

Adyen will deposit and reverse a small amount ($0.01–$0.99) within 24 hours to verify the account.

Account
{form.label || "(no label)"}
findS(s)?.code).join(", ") || "—"}/>
Schemes · {Object.entries(form.schemes).filter(([_, v]) => v).map(([k]) => k).join(", ") || "none"}
)}
Step {step + 1} of {STEPS.length}
{step > 0 ? ( ) : ( )} {step < STEPS.length - 1 ? ( ) : ( )}
); } function ReviewCell({ label, value, mono }) { return (
{label}
{value || "—"}
); } // ----------------------------------------------------------------- // PAYMENTS — by-store view (multi-store comparison) // ----------------------------------------------------------------- function PaymentsByStore() { const STORE_COLOURS = { "S-001": "var(--teal-700)", "S-002": "var(--navy-600)", "S-003": "var(--violet-600)" }; // Per-store payments data (today + 30d aggregates by method) const BY_STORE = [ { id: "S-001", today: { gross: 2418.50 + 1812.30 + 589.85, fees: 16.93, net: 4803.72, tx: 64, methods: { card: 3520.40, wallet: 685.20, eftpos: 482.65, declined: 2 } }, mtd: { gross: 76420.50, fees: 535.30, net: 75885.20, tx: 1864, settled: "Today 10:00 · $4,448.84 to NAB", terminals: 3, terminalsOk: 2 }, }, { id: "S-002", today: { gross: 1318.20 + 1100.20, fees: 16.93, net: 2401.47, tx: 42, methods: { card: 1640.40, wallet: 482.00, eftpos: 296.00, declined: 0 } }, mtd: { gross: 42118.40, fees: 295.04, net: 41823.36, tx: 1042, settled: "Today 10:00 · settled normally", terminals: 2, terminalsOk: 2 }, }, { id: "S-003", today: { gross: 1820.50 + 1300.35, fees: 21.84, net: 3098.01, tx: 56, methods: { card: 2180.20, wallet: 540.65, eftpos: 400.00, declined: 1 } }, mtd: { gross: 58210.20, fees: 407.47, net: 57802.73, tx: 1418, settled: "Today 10:00 · settled normally", terminals: 2, terminalsOk: 2 }, }, ]; // Aggregate totals const total = { today: { gross: BY_STORE.reduce((s, x) => s + x.today.gross, 0), fees: BY_STORE.reduce((s, x) => s + x.today.fees, 0), tx: BY_STORE.reduce((s, x) => s + x.today.tx, 0) }, mtd: { gross: BY_STORE.reduce((s, x) => s + x.mtd.gross, 0), fees: BY_STORE.reduce((s, x) => s + x.mtd.fees, 0) }, }; // Method-mix series for stacked bars const maxToday = Math.max(...BY_STORE.map(s => s.today.gross)); return ( <> {/* Per-store cards */}
{BY_STORE.map(s => { const store = findS(s.id); const effRate = s.mtd.gross > 0 ? ((s.mtd.fees / s.mtd.gross) * 100).toFixed(2) : "—"; return (
{store.code}
{store.name}
{s.mtd.terminals} terminals · {s.mtd.terminalsOk}/{s.mtd.terminals} online
{s.mtd.terminalsOk < s.mtd.terminals ? Terminal alert : Healthy}
Gross today
{oMoney(s.today.gross)}
Tx today
{s.today.tx}
Net MTD
{oMoney(s.mtd.net)}
Effective rate
{effRate}%
{/* Method-mix bar */}
Today by method
Card{oMoney(s.today.methods.card)}
Mobile wallet{oMoney(s.today.methods.wallet)}
EFTPOS{oMoney(s.today.methods.eftpos)}
{s.today.methods.declined > 0 && (
Declined{s.today.methods.declined}
)}
{s.mtd.settled}
); })}
{/* Side-by-side comparison table */}

Side-by-side comparison · 30 days

Store
Gross
Fees
Net
Transactions
Avg sale
Eff rate
{BY_STORE.map(s => { const store = findS(s.id); const avg = s.mtd.tx > 0 ? s.mtd.gross / s.mtd.tx : 0; const eff = s.mtd.gross > 0 ? ((s.mtd.fees / s.mtd.gross) * 100) : 0; return (
{store.code}
{store.name}
{oMoney(s.mtd.gross)}
− {oMoney(s.mtd.fees)}
{oMoney(s.mtd.net)}
{s.mtd.tx.toLocaleString()}
{oMoney(avg)}
{eff.toFixed(2)}%
); })} {/* Group totals row */}
Group total
{oMoney(total.mtd.gross)}
− {oMoney(total.mtd.fees)}
{oMoney(total.mtd.gross - total.mtd.fees)}
{BY_STORE.reduce((s, x) => s + x.mtd.tx, 0).toLocaleString()}
{oMoney(total.mtd.gross / BY_STORE.reduce((s, x) => s + x.mtd.tx, 0))}
{((total.mtd.fees / total.mtd.gross) * 100).toFixed(2)}%
{/* Daily volume by store */}

Daily card volume · last 30 days · stacked by store

{oMoney(total.mtd.gross)} group MTD
{OD.REVENUE_30D["S-001"].map((_, i) => (
{BY_STORE.map(s => ( ))}
))}
{BY_STORE.map(s => { const store = findS(s.id); return ( {store.name} · {oMoney(s.mtd.gross)} ); })}
); }