Routelink
INTERNAL · LL OPS TRAINING + TECH REFERENCE

Routelink Ops Workbench — Visual Manual (v0.4)

How Leisure Link operators run a venue's channels end-to-end: one inbox, one manifest, one money truth. Every screenshot is the live prototype at routelink.ai (demo data). Tech: each section's last note tells you what changes in the real build — the console + REQUIREMENTS_ANSWERED.docx are the spec.
ALL

The demo cast — three research merchants

Every booking, statement and invoice in the demo flows from three seeded venues, modeled on the research merchants:

  • V1 · Beach Club — Pattaya. Day Pass ฿1,500 adult / ฿750 child (+ ฿400 add-on) · Cabana Package ฿7,000 flat · Sunset Cruise Add-on ฿2,200 / ฿1,100, non-refundable.
  • V2 · Dinner Cruise — Bangkok. Dinner Cruise — Standard Seat ฿1,200 / ฿600 (+ ฿350 add-on), non-refundable · Window Table Upgrade ฿5,500 flat.
  • V3 · Elephant Park — Bangkok. Half-Day Park Visit ฿1,900 / ฿950 · Full-Day Keeper Experience ฿3,500 / ฿1,750 · contracts: Klook 10% / GetYourGuide 9% / agent 15%.

Venue roles (owner, manager, gate staff) are venue-locked — merchants never see each other's bookings. Only the Routelink operator sees the portfolio across all three venues.

This manual describes the demo: local, single-browser, state saved in your own browser only. There is also the operations hub build (staging) — multi-user, shared live state, real logins — running the same rules and the same fee math. Same workbench, different substrate.

ALL

Orientation — roles, venues, saving

The workbench is one file, four jobs. The top bar switches VENUE (left) and ROLE (right). Everything you do saves in the browser instantly and survives reload; Admin → Export moves your session to another machine.

Steps

  1. Open the workbench — you land on the role's home screen (Operator → Portfolio; Owner → Overview; Manager → Bookings; Gate → Check-in).
  2. Pick a venue in the top bar. Operator can see ALL venues at once; venue roles are venue-locked.
  3. Everything is demo data (MOCK) — reset any time in Admin → Reset demo data.

Data touched

S.role, S.venue, localStorage rl_v04

Edge cases

Corrupt or half-saved browser state auto-recovers to fresh seed — the app never bricks.

For the tech team

Real build: roles come from login (SSO/email), not a switcher. State moves server-side; localStorage remains as offline cache.

01_portfolio
Orientation · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

The Actions queue — start every shift here

Zero-inbox for operations: everything that needs a human is one list, newest risks first. Clearing the queue = the venue is healthy.

Steps

  1. Portfolio → 'Actions required' card counts everything open across your venues.
  2. Each row deep-links to the exact surface: cancel requests → the booking drawer; discrepancies → the statement; approvals → the sign-off screen.
  3. Resolve top-to-bottom. The count updates live as you clear items.

Data touched

Derived live from bookings, ingest, approvals, holds (<48h), statements, staffing

Edge cases

Queue is venue-scoped — switching venue re-filters every item. Holds far in the future do NOT nag.

For the tech team

Queue sources are pluggable — each item type is a rule over state. Real build adds webhooks (payment failed, API disconnect) as new rules.

01_portfolio
The Actions queue · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

One bookings inbox for every channel (PP1)

Stop living in five extranets. Marketplace, direct web, walk-in and agent bookings land in ONE searchable list with one status vocabulary.

Steps

  1. Bookings tab → filter by channel kind (Marketplace / Direct / Agent) or status.
  2. Search by guest name, booking ID or platform ref — the ⌘K box in the top bar jumps here from anywhere.
  3. Click any row → the booking drawer: full detail, money trace, timeline, actions.

Data touched

S.bookings — one array, every channel

Edge cases

PENDING bookings (manual-confirm channels) are flagged in the queue until confirmed.

For the tech team

Statuses are ONE vocabulary everywhere: PENDING · CONFIRMED · CHECKED-IN · NO-SHOW · CANCEL-REQ · CANCELLED · REFUNDED. Map every platform's states into these on ingest.

02_bookings
One bookings inbox for every channel (PP1) · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Mail-ingest — bookings arrive by themselves (PP1)

Each venue gets a generated ops email registered as a sub-account in every marketplace extranet. Booking emails land there and become cards here — no platform API needed on day one.

Steps

  1. Ingest inbox → each card shows the raw email and what the parser extracted.
  2. HIGH CONFIDENCE cards: click Accept → a real booking is created, capacity decremented, ref preserved.
  3. Low-confidence cards are flagged REVIEW — check the raw text before accepting. Reject logs the item for platform follow-up.

Data touched

S.ingest → S.bookings on accept; audit on both paths

Edge cases

Accept is capacity-guarded — if holds/bookings already fill the date, the accept is blocked, not silently overbooked.

For the tech team

Real build: IMAP ingest + parser per platform template; the generated address pattern (rl-ingest-<venue>-<key>@ingest.routelink.ai) is already the account-provisioning scheme.

08_ingest_inbox
Mail-ingest · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
ALL

Today Manifest — who's coming (PP2)

The gate view: every arrival today, grouped by time slot, with party size, channel and assigned staff. Print it or work it live.

Steps

  1. Today tab → per-venue manifest grouped by slot.
  2. Check guests in straight from the row, or open the drawer for detail.
  3. Print manifest for a paper backup at the gate.

Data touched

Live filter of bookings where date = today

Edge cases

Cancelled/refunded rows are excluded automatically; PENDING shows so the gate knows who still needs confirmation.

For the tech team

Real build: auto-refresh + kitchen/beach display mode. Session-level notes ride on the booking record.

04_today_manifest
Today Manifest · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
GATE

Gate check-in — 3 taps, duplicates rejected (PP2)

Phone-first screen for gate staff: type a name or scan, tap CHECK IN, see a huge green or red answer. Gate staff see nothing else — no money, no management.

Steps

  1. Type any part of the guest name / booking ID (a QR scan types the ref for you).
  2. Tap CHECK IN on the match.
  3. Green = welcome in (party size shown). Red = the exact reason: already checked in, wrong day, needs confirmation.

Data touched

booking.status → CHECKED-IN + checkedInAt; audit

Edge cases

Duplicate scans are rejected loudly — the #1 fraud/confusion case at gates. Booking drawer in gate role shows guest info only.

For the tech team

Real build: camera QR scan; the validation chain (status, date, duplicate) is already the exact rule order to implement.

22_gate_screen
Gate check-in · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
MGR

Walk-in / phone booking — 4 fields (PP10)

Front desk sells a day pass in under 20 seconds without touching the rate card: product, guests, name, payment. Price is computed, capacity enforced, campaign discounts auto-applied only if owner-approved.

Steps

  1. Walk-in tab → pick product and party size — the total updates live.
  2. Type the guest name, pick card or PromptPay.
  3. Confirm → booking is CONFIRMED, QR issued, capacity decremented, it appears on Today instantly.

Data touched

newBooking() → capacity + ledger + manifest all recompute

Edge cases

Overbooking is blocked at the exact seat boundary. No manual price field exists — discounts only via approved campaigns.

For the tech team

Real build: PromptPay QR + card terminal hooks; receipt print. The ≤4-field constraint is a product requirement, not a demo simplification.

05_walkin
Walk-in / phone booking · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
MGR

Staff assignment — arrivals into hands (PP3)

Every booking today should have an owner: host, beach crew, driver. The staff cards count loads; unassigned bookings nag in the Actions queue.

Steps

  1. Staff tab → see per-staff load for today.
  2. Assign each booking from the dropdown — saved instantly, shows on the manifest and in the drawer.
  3. Gate + manifest both display the assigned name, so anyone can route a guest.

Data touched

booking.assignedTo; audit per change

Edge cases

Unassigned TODAY bookings surface in the operator queue — assignment is part of daily hygiene, not optional.

For the tech team

Real build: shift roster + notification to staff phone (LINE) on assignment; task states (assigned → in-progress → done).

07_staff_assign
Staff assignment · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
MGR

Message center — the email burden (PP4)

Five templates cover 90% of guest/platform mail: confirmation, pre-arrival info, reschedule offer, cancellation+refund, platform ops reply. Variables fill themselves from the booking.

Steps

  1. Open a booking → 'Message guest' (or Messages tab for the outbox log).
  2. Pick a template — {name}, {date}, {refund}% etc. are already filled from the booking and its policy.
  3. Copy to your mail/LINE client, then 'Mark sent' — the outbox logs who got what, when.

Data touched

TEMPLATES × booking → S.outbox; audit

Edge cases

Refund template pulls the POLICY number, not a typed guess — the #1 source of refund disputes.

For the tech team

Real build: direct send via venue SMTP/LINE OA + threading. This is also the ✨Assist P3 slot: AI drafts channel-specific replies from the same context.

13_message_center
Message center · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Capacity, holds & the week grid (PP6)

One grid answers 'can we sell?': bookable = capacity − booked − held. Click any cell to override a day's capacity; release channel holds the moment they stop earning.

Steps

  1. Capacity tab → week grid per product. Red = full, orange = low.
  2. Click a cell → set a capacity override (never below already-booked).
  3. Holds table below: seats parked for a channel with an expiry — release manually or let them expire.

Data touched

capOverride, holds; every surface reads capBookable()

Edge cases

Overrides can't undercut existing bookings. Released seats are sellable the same second.

For the tech team

Real build: holds auto-release on schedule (labeled simulated here); channel-level allotments sync out via connectivity partner.

06_capacity_bulk
Capacity, holds & the week grid (PP6) · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Statement reconciliation — the month-end crown (PP8)

Import the platform's settlement, and the workbench matches every row to the ledger, flags what's wrong, computes the operation fee, and locks the month so nothing can rewrite it.

Steps

  1. Reconcile tab → pick venue, channel, month. The bar shows: what the platform paid vs what the ledger expected vs the gap.
  2. Every row is judged: MATCHED · MISMATCH (with the exact ฿ delta and why) · DUPLICATE · ORPHAN (paid but no booking) · plus MISSING bookings the platform never paid.
  3. Resolve each open item: Accept (with note) · Raise claim · Link/Write off. Notes go to the audit log — that's your claim file.
  4. When open items = 0, LOCK THE MONTH: the commission % is snapshotted, the invoice basis freezes, and later renegotiations cannot rewrite history.

Data touched

STATEMENTS × bookings → resolved{}, locks{} (with commission snapshot)

Edge cases

The six discrepancy types seeded in the demo are the six that happen in real life — including double-paid and cancelled-but-paid. ฿1 rounding diffs are flagged as acceptable.

For the tech team

Real build: CSV/XLS import with remembered column-mapping per platform; claims export. The month-lock snapshot IS the invoice contract — this rule survives into production as written.

09_reconcile_gyg
Statement reconciliation · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OWNER

Two-pocket invoices — one number from one report

The month's money in two clean lines: the operation fee on marketplace/agent sales (remit − 80% × rate card), and the 5% system fee on direct-settled money. VAT and WHT computed. Locked months bill from their snapshot, full stop.

Steps

  1. Invoices tab → pick the month (June = locked statements; July = live MTD).
  2. LL invoice = marketplace + agent performance. RL line = direct flow + forfeit shares from partial refunds (billed via LL as collection agent until the entity novates).
  3. Every subtotal traces to the reconciled statement — the note on each invoice says exactly how.

Data touched

fees() per booking + lock snapshots; VAT 7% / WHT 3%

Edge cases

A refund marked later does not erase the forfeit share the policy kept — engine and invoice agree.

For the tech team

Real build: PDF invoice generation + e-tax; the two-pocket split and novation clause come straight from the ratified commercial model.

20_invoices_july
Two-pocket invoices · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Contracts, renegotiation & parity (PP7)

The commission matrix the whole ledger reads. Renegotiate a channel and every FUTURE booking's fee grows; locked months are untouchable. The parity checker catches channels drifting off the rate card.

Steps

  1. Contracts tab → per-venue contract cards with lifecycle (DRAFT → PROPOSED → ACTIVE → FROZEN). Activating a renewal freezes the old term automatically.
  2. The renegotiation demo (10% → 8%) shows the fee line grow — that yield is the human product.
  3. Parity checker: paste channel prices → instant deviation report against the rate card.

Data touched

S.contracts (one ACTIVE per venue-channel); parity is read-only + audit

Edge cases

Renegotiation applies to the live month forward; history is protected by lock snapshots.

For the tech team

Real build: effective-date ranges per contract term (the snapshot mechanism generalizes to full rate-card version history).

19_parity_checker
Contracts, renegotiation & parity (PP7) · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OWNER

Approvals — no rogue discounts (PP7)

Discounts move only with the owner's signature. Managers request, owners sign, and only then does the campaign price into new bookings — automatically, inside its window.

Steps

  1. Manager: Approvals → '+ Request campaign' (title, %, window).
  2. Owner: Approvals → Approve & sign (note goes to the contract file) or Reject.
  3. Approved campaigns price into walk-in/web bookings automatically during the window — nothing else can discount.

Data touched

S.approvals → activeCampaign() → walk-in/web pricing

Edge cases

The rate card is the law: there is no manual price field anywhere in the workbench.

For the tech team

Real build: e-sign + campaign end reminders; per-channel campaign scoping.

15_owner_approvals
Approvals · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Agents on credit — gated like a bank (PP1)

Concierges and resellers book on credit terms. The gate is hard: past the credit line, the booking is BLOCKED — not flagged, blocked.

Steps

  1. Agents tab → each agent card: model, credit limit, live usage, available room.
  2. Book against credit — the remaining credit updates instantly and persists.
  3. Over the line → BLOCKED with the exact numbers, and an audit entry.

Data touched

S.agents (persisted); newBooking on success

Edge cases

The debit survives reload — refreshing the page is not a credit reset.

For the tech team

Real build: agent portal login + statement runs + payment reconciliation into the same ledger.

11_contracts
Agents on credit · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OWNER

Reports — same ledger, no second truth (PP9)

Channel mix, direct-share trend, no-show rates and campaign usage — computed from the identical ledger the invoices bill from. If reports and invoices ever disagree, that's a bug, not a reconciliation project.

Steps

  1. Reports tab → channel mix by value, direct share by week, no-show by channel.
  2. Month-at-a-glance per venue: bookings, guests, check-ins, no-shows, GMV.
  3. Owner sees the same numbers in their own Overview — no separate export.

Data touched

Pure derivations over S.bookings

Edge cases

Direct share includes web + walk-in + QR. No-show needs the gate to mark them — which the drawer now supports.

For the tech team

Real build: date-range pickers, CSV export, and the GTTD-ready product feed export (P2) hang off this same data.

12_reports
Reports · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Admin — audit, roles, data

Every mutation in the workbench writes an audit line: who, when, what. That log is the professional-service evidence LL runs on. Export/import moves a whole session; reset returns to the pristine demo.

Steps

  1. Admin tab → audit log (newest first).
  2. Export state JSON before experiments; import to restore.
  3. Reset demo data → pristine seed.

Data touched

S.audit (capped 400); export = full state file

Edge cases

Import validates structure — an incomplete file is rejected instead of bricking the app.

For the tech team

Real build: audit becomes append-only server log; roles become auth; retention policy applies.

18_admin_audit
Admin · live screen from v0.4 · screenshots show an earlier demo cast — flows are identical
OP

Known limits of v0.4 (read before demoing)

One statement month is seeded (June 2026); "today" is frozen at 2026-07-15. Simulated and labeled: statement file import, message sending, hold auto-release, QR camera scan. The role switcher is a demo affordance, not authentication. Timelines derive from current status. Rate-card history exists via lock snapshots, not full versioning. The ✨Assist buttons are reserved P3 slots — no AI runs yet, by design: the data substrate comes first.