// VMAX POS — payment, the attribution-at-print flow (the system's core mechanic), // and the separate audited refund + remake flows. (function () { const DS = window.VMAX365DesignSystem_0f78b2; const { Button, Badge, SegmentedControl } = DS; const { Icon, Sheet, Overline } = window.VMAXUI; const { Avatar, PinPad, Dots, pushToast, EmptyState, Select, TempTag } = window.VMAXW; const { K, tenders, remakeReasons } = window.VMAXP; const { useVMAX } = window.VMAXStore; const TempTagDS = DS.TempTag; const DENOMS = [10, 20, 50, 100, 200]; // ---------- Payment + attribution at print ---------- function PaymentSheet({ open, total, session, onClose, onDone }) { const [s, a] = useVMAX(); const [tender, setTender] = React.useState('Cash'); const [received, setReceived] = React.useState(0); const [step, setStep] = React.useState('pay'); // pay | names | pin | done const [pinFor, setPinFor] = React.useState(null); const [pin, setPin] = React.useState(''); const [err, setErr] = React.useState(false); const [attrib, setAttrib] = React.useState(''); React.useEffect(() => { if (open) { setTender('Cash'); setReceived(0); setStep('pay'); setPin(''); setErr(false); } }, [open]); const change = Math.max(0, received - total); const noPin = !!(s.settings && s.settings.pinForPrint === false); const finish = (name) => { a.takePayment(tender, name); session.markFresh(); setAttrib(name); setStep('done'); }; const tapName = (p) => { if (noPin) { session.setActive(p); finish(p.name); } else if (session.active && p.id === session.active.id && !session.stale) finish(p.name); else { setPinFor(p); setPin(''); setErr(false); setStep('pin'); } }; React.useEffect(() => { if (step === 'pin' && pin.length === 4) { if (session.verify(pinFor.id, pin)) { session.setActive(pinFor); finish(pinFor.name); } else { setErr(true); setTimeout(() => { setErr(false); setPin(''); }, 480); } } }, [pin]); if (!open) return null; if (step === 'done') return ( {}} width={420}> Paid {tender}{tender === 'Cash' && change > 0 ? ` · change ${K(change)}` : ''} Receipt attributed to {attrib} { onDone && onDone(); onClose(); }}>Next customer ); if (step === 'names' || step === 'pin') { const roster = session.people.filter((p) => p.roles.some((r) => r === 'cashier' || r === 'maker' || r === 'owner') && p.pin); const signedIds = session.signedIn.map((p) => p.id); const primary = session.signedIn; const others = roster.filter((p) => !signedIds.includes(p.id)); return ( {step === 'names' ? ( Who's printing this? Tap your name — this is what the order is attributed to. {session.stale && Till was idle — confirm your PIN to verify it's you.} Signed in now {primary.map((p) => { const isActive = session.active && p.id === session.active.id; return ( tapName(p)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', borderRadius: 'var(--radius-lg)', border: '2px solid ' + (isActive && !session.stale ? 'var(--vmax-red)' : 'var(--vmax-line)'), background: isActive && !session.stale ? 'var(--vmax-mint)' : 'var(--surface-card)', cursor: 'pointer', textAlign: 'left' }}> {p.name}{isActive && · you}{p.roles.join(' + ')} {(isActive && !session.stale) || noPin ? : } ); })} {others.length > 0 && Someone else stepping in {others.map((p) => tapName(p)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px 8px 8px', borderRadius: 999, border: '1.5px solid var(--vmax-line)', background: 'var(--surface-card)', cursor: 'pointer', fontWeight: 600, fontSize: 13 }}>{p.name}{!noPin && })} } setStep('pay')} style={{ marginTop: 18, border: 'none', background: 'transparent', color: 'var(--vmax-ink-soft)', fontWeight: 600, fontSize: 13.5, cursor: 'pointer' }}>← Back ) : ( {pinFor.name}'s PIN Confirm the switch — this order will be attributed to {pinFor.name.split(' ')[0]}. setStep('names')} style={{ marginTop: 16, border: 'none', background: 'transparent', color: 'var(--vmax-ink-soft)', fontWeight: 600, fontSize: 13.5, cursor: 'pointer' }}>← Other names )} ); } // step 'pay' return ( Amount due {K(total)} Tender {tenders.map((t) => ( setTender(t)} style={{ padding: '11px 16px', borderRadius: 'var(--radius-pill)', cursor: 'pointer', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 14, border: '2px solid ' + (tender === t ? 'var(--vmax-ink)' : 'var(--vmax-line)'), background: tender === t ? 'var(--vmax-ink)' : 'var(--surface-card)', color: tender === t ? '#fff' : 'var(--vmax-ink)', flex: t === 'Cash' ? '1 0 100%' : '0 0 auto' }}>{t === 'Cash' ? 'Cash' : t} ))} {tender === 'Cash' && ( Cash received {DENOMS.map((d) => setReceived((r) => r + d)} style={{ flex: 1, minWidth: 60, padding: '12px 0', borderRadius: 'var(--radius-md)', border: '1.5px solid var(--vmax-line)', background: 'var(--surface-card)', cursor: 'pointer', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 15 }}>K{d})} setReceived(0)} style={{ padding: '12px 14px', borderRadius: 'var(--radius-md)', border: '1.5px solid var(--vmax-line)', background: 'var(--surface-ground)', cursor: 'pointer', fontWeight: 700, color: 'var(--vmax-ink-soft)' }}>Clear Received {K(received)}Change to give 0 ? 'var(--vmax-red)' : 'var(--vmax-ink)' }}>{K(change)} )} setStep('names')}>Print receipt ); } // ---------- Recent orders → refund / remake ---------- function OrdersSheet({ open, onClose, session }) { const [s] = useVMAX(); const [refund, setRefund] = React.useState(null); const [remake, setRemake] = React.useState(null); const orders = [...s.sales].slice(0, 12); return ( Recent orders {orders.length === 0 ? : orders.map((o) => ( #{o.id} · {o.at} · {o.by} {K(o.amount)} {o.lines.map((l) => `${l.qty}× ${l.name}`).join(' · ')} · {o.tender} setRefund(o)}>Refund setRemake(o)}>Remake ))} {refund && setRefund(null)} />} {remake && setRemake(null)} />} ); } function RefundFlow({ order, session, onClose }) { const [, a] = useVMAX(); const [picked, setPicked] = React.useState(order.lines.map((_, i) => i)); const [made, setMade] = React.useState(null); // true=made&discarded, false=not made const amount = order.lines.filter((_, i) => picked.includes(i)).reduce((x, l) => x + l.price * l.qty, 0); const toggle = (i) => setPicked((p) => p.includes(i) ? p.filter((x) => x !== i) : [...p, i]); const confirm = () => { a.refund(order.id, picked, made, session.active.name); pushToast('Refund recorded · ' + K(amount), 'ok'); onClose(); }; return ( Refund #{order.id} Money returns. This writes its own attributed record. What's being refunded {order.lines.map((l, i) => ( toggle(i)} style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '11px 13px', borderRadius: 'var(--radius-md)', border: '1.5px solid ' + (picked.includes(i) ? 'var(--vmax-red)' : 'var(--vmax-line)'), background: picked.includes(i) ? 'var(--vmax-danger-tint)' : 'var(--surface-card)', cursor: 'pointer', textAlign: 'left' }}> {picked.includes(i) && } {l.qty}× {l.name} {K(l.price * l.qty)} ))} Was it made? setMade(false)} title="Not made" sub="Ingredients return to stock" /> setMade(true)} title="Made & discarded" sub="Recorded as consumed" /> {K(amount)} Record refund ); } function RemakeFlow({ order, session, onClose }) { const [, a] = useVMAX(); const [lineIdx, setLineIdx] = React.useState(0); const [reason, setReason] = React.useState(''); const confirm = () => { a.remake(order.id, lineIdx, reason, session.active.name); pushToast('Remake recorded · ' + order.lines[lineIdx].name, 'ok'); onClose(); }; return ( Remake from #{order.id} No money moves — but stock is consumed again. That's why it's logged separately. Which drink {order.lines.map((l, i) => ( setLineIdx(i)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 13px', borderRadius: 'var(--radius-md)', border: '2px solid ' + (lineIdx === i ? 'var(--vmax-ink)' : 'var(--vmax-line)'), background: lineIdx === i ? 'var(--vmax-mint)' : 'var(--surface-card)', cursor: 'pointer', textAlign: 'left' }}> {l.name} {l.temp && } ))} Reason {remakeReasons.map((r) => setReason(r)} style={{ padding: '9px 14px', borderRadius: 999, cursor: 'pointer', fontWeight: 600, fontSize: 13, border: '1.5px solid ' + (reason === r ? 'var(--vmax-red)' : 'var(--vmax-line)'), background: reason === r ? 'var(--vmax-danger-tint)' : 'var(--surface-card)', color: reason === r ? 'var(--vmax-red-deep)' : 'var(--vmax-ink)' }}>{r})} Record remake ); } function ChoiceBtn({ on, onClick, title, sub }) { return ( {title} {sub} ); } window.VMAXPAY = { PaymentSheet, OrdersSheet }; })();