Alba's site visit (week of 14 Sep) gives us ground truth on how BLVD 24 and Upper House actually move orders, payments and guest data. This plan maps every point in her handoff, and the second feedback batch from the client meeting on 29 Sep, to what is built today, what is missing, and the work to close it by a mid-October launch.
Alba is right: floor staff run paper-to-POS loops between tables and four kitchens, and an app in their hands slows service. The cashier is stationary, already at the POS, and every payment passes through them (1 at BLVD 24, 3 at Upper House). Building the transaction features for that role gives us real per-member, per-venue spend data with zero POS integration.
● Amendment 1. The door (hostess) and the till (cashier) are two modes of one scanner surface, not two apps. Both scan the same thing: a member's identity QR. One identity token, one resolver, two role-gated screens.
● Amendment 2. Log first, design rules later. The ledger Alba asks for (member, venue, timestamp, amount, method, benefit redeemed) ships in R2 as an append-only table with the loyalty accrual hook present but switched off. Points rules get designed from the data, not guessed.
● v2 addition. Beam Checkout is the client's payment rail and the first adapter we build. Money and perks become data with a venue on every row, so accounting can close each venue against its own bank. Credits stay out of the launch cut until Thai counsel answers.
Each item was checked against the member app, the standalone design app, the admin and the schema by a nine-agent verification pass, then challenged by an accounting/legal lens and a venue-ops lens. Decisions below are the survivors.
| # | Alba's item | Verified today | What we do | When |
|---|---|---|---|---|
| 1 | Swipe images on events | Real app renders no event images and never selects events.media. Design app: one bundled thumb per event. | Carousel from events.media[] (PageView, dots, fixed 4:5 crop) on the home card and the event sheet. Images come from a storage bucket, never from the seeded Instagram CDN links, which expire in days. | W2 · WS-C |
| 2 | Share links to invite to the app and on Instagram | Design app shares only the event's Instagram post URL, so recipients land on Instagram, not the app. No deep links, no install page anywhere. | W2 ships the simple version: a share sheet with two targets, "Invite to the app" (activahq.com/get/24blvd, a static install page with TestFlight and Play links, ?ref accepted) and "Share on Instagram" (the event's post). Same canonical link goes in the client's IG bio and Stories sticker. Universal Links, URL scheme and referral rewards move to R2.1: they need frozen bundle IDs and Apple's CDN, not three weeks of slack. | W2 · WS-C |
| 3 | Beam set up for payments (client side) | No gateway code exists. Docs recommend Opn/Omise and treat Beam as a lever. Three spellings in the repo (Beam, Boomie, Beem). | Beam Checkout becomes the first PaymentProvider adapter; Opn drops to fallback behind the same interface. Launch stays cashier-sold (option B). R2.1 wires the Beam webhook to a confirm_payment RPC that mints the pass or ticket. Client action by end of Week 1: Beam onboarding per legal entity with a payout account per venue. A single group merchant would defeat the per-venue settlement ask for in-app money. | W1 client · R2.1 build |
| 4 | Remove the white gradient, several images, upload in Activa, live in app? | Gradient is the design app's cream hero fade. Admin event form has no image field and no bucket exists. Admin edits hit the DB directly; the real app fetches on every open with no cache, so an edit shows on next open today. | Admin drawer gets media upload to an event-media bucket: client-side resize to JPEG (max 1600 px, ~300 KB), HEIC converted, bucket limited to jpeg/png and 2 MB, reorder and remove. Event UUID generated client-side so a new event can carry images on first save. Gradient dropped in the port. Pull-to-refresh and refetch on app resume added. A one-off script uploads the 26 existing thumbs before TestFlight so the client's first look isn't broken images. | W2 · WS-D, WS-C |
| 5 | Home page: upcoming vs signed-up events unclear | Design app: "This week" is literally the first three entries of a constant list under the personal pass card. Real app: bare feed, no ownership marker, and past events linger (no lower bound on date). | Home becomes: pass or identity QR card → "Your night" (the member's tickets and bookings via a new my_upcoming RPC, hidden when empty) → "Coming up" with a "You're going" badge on owned events. Feed filters on coalesce(ends_at, starts_at + 6h) ≥ now and shows "Happening now" instead of hiding an event at 23:30 while the queue is still outside. In the Day 2 prototype sign-off. | W1 proto · W2 build |
| 6 | QR payment: skip Beam fee? QR pop up? Different banks | No PromptPay payment QR is generated anywhere. The design app's QRs encode door keys; the real app draws a glyph. | At launch the printed venue standee stays the default (already at every till, faster than a phone screen in a dark room) and the cashier logs method=promptpay. An on-screen QR with the amount embedded is a second tap from the method chip, built on venue_settlement_accounts and a tested EMVCo payload helper. That QR is the venue's own bank, so zero fee and it honours different banks per venue. On the fee: Activa never surcharges; card fees cannot be passed to the cardholder; Beam's PromptPay rate is a commercial term the client negotiates, and whatever Beam charges is recorded per transaction from the webhook so the settlement report shows gross, fee and net. Per venue we need the company PromptPay (13-digit juristic ID), not a phone number: a personal PromptPay puts venue revenue outside the entity's books. | W2 · WS-A, WS-B |
| 7 | Roles access for the admin portal | Access is per module via a platform matrix; no UI to change it; role has no constraint (a typo silently loses all access); venue scope is displayed as "Scope" but enforced by nothing. | Fixed matrix with cashier and hostess added, a read-only "Who can do what" table on Team, a CHECK constraint on roles with a guard query, and venue-scoped RLS enforced only for cashier and hostess (every other role stays org-wide, so no demo user loses reads). The 9×9×4 matrix editor moves to R2.1 with an audit log and "reset to default": one mis-click at 23:00 that stops the till is not a launch feature. The settlement report is venue-filtered for managers. | W2 · WS-A, WS-D |
| 8 | Adding new venues, seeing each venue's profile | Venues can be added from a card buried in Settings; the form exposes 6 of 19 columns; no profile page; no bank fields; the org switcher is not a venue switcher. | A venues route: list with "Add venue", edit drawer (the six fields plus cover image and a Settlement account card), linked from Settings. Raw columns nobody consumes (lat/long, hours JSON, sort order) stay hidden. Per-venue stats tiles ship with Member 360 in R2.1. The cashier's QR option hides itself when a venue has no settlement account. | W2 · WS-D |
| 9 | Different bank per venue: weekly/monthly report per venue; credits must trace to the deposit | payments has no venue, fee, provider or VAT split; venues have no bank fields; the POS CSV exports three columns; credits tables have zero write paths. | Per-venue money model in the Week 2 round (details in WS-A). A Settlement report in two sections that are never summed: "Collected through Activa" (payments, reconcilable to bank and Beam payouts) and "Logged at the till, not collected by Activa" (the cashier's shadow ledger). Refund and void documents, which do not exist today, are added so the report can tie to a statement. Credits are cut from the launch round: only two nullable columns land now so the later migration is additive; the deposit-lot ledger (spend → deposit, FIFO, inter-venue settlement) is designed in R2.1 once Thai counsel answers the BoT e-money question (Payment Systems Act B.E. 2560). | W2 schema · R2.1 credits |
| 10 | No real discount logic: no venue, no food vs drinks | Perks are prose strings with venue names and "10% off" embedded. plans.discount_percentage is never read by any RPC. | The WS-A benefits catalogue gains category (food / drinks / entry / merch / other) next to venue, kind and value; plan_benefits and event_benefits join tables; the three seeded perks written by hand as rows, prose kept as display fallback. Admin membership form replaces the "one per line" textarea with a benefit picker and inline create. The ledger records bill, discount and paid amounts, and each redemption snapshots the perk's cost and granting venue so promotions and cross-venue perks are bookable. | W1 · WS-A, WS-D |
| 11 | Happy hour 18:00–20:00: QR must not work after 20:00 | Nothing redeems perks today, so no cut-off can be enforced anywhere. | Validity windows on the benefit row (valid_days, valid_from, valid_until) evaluated server-side on the org timezone with a business-day offset (06:00), so a 22:00–02:00 window and a Friday promo at 01:30 both work. Eligibility is computed when the card is resolved, so an expired perk renders greyed before any tap; tapping anyway returns a structured "outside window" verdict and still logs the sale, no rollback, no re-scan. The member app renders the chip from the server's valid_now, never the phone clock. To the client: the identity QR still scans after 20:00 for entry and spend logging; it is the perk that stops. | W1 · WS-A, WS-B |
| 12 | Perks changeable; does it apply right away? | Yes at the DB. The real app fetches on every open (no cache). The design app cannot change at all (constants). | Yes: an admin save is live at the next scan, since the redeem reads the benefit row at call time. The cashier card carries the perk's version, so an edit between scan and tap returns "perk changed, re-scan" instead of applying something the cashier didn't see. Admin shows "affects N active grants" before saving. Each redemption keeps a snapshot for audit. | W1 · WS-A |
| 13 | Training | The rehearsal shift trained one cashier by accident. | A Week 3 deliverable sequenced after the UI freezes: BLVD 24 rehearsal (W2 Thu/Fri) → freeze cashier and door UI → record the three flows on the sim over the weekend → Upper House dry run on W3 day 2 with all three cashiers hitting the same test grant (that doubles as the real-venue concurrency test) → pilot. Two 45-minute sessions per venue, a laminated cashier card in Thai and English, 3-minute recordings per flow for new hires, a refresher after the first ledger review. Alba delivers on site. | W3 · WS-F |
Verified against the migration set, the three app trees and the live admin on 29 Sep.
| Alba's point | Built today | Gap | Work |
|---|---|---|---|
| Cashier scans the member's QR R1 | Staff scanner reads a ticket code and calls check_in_ticket. The Activa member app has no identity QR; the approved 24 BLVD design (standalone app) has a 30 s rotating QR wallet. | Member identity token + rotating QR, and a resolver RPC for cashier/hostess. | A · C |
| See membership + eligible benefit | entitlement_for and active_membership exist. Plan benefits are free-text perks in JSON ("Free first drink at…"). | Machine-readable benefits catalogue and per-member grants. | A |
| Apply / redeem, single-use and atomic across 3 cashiers | Nothing. No redemptions table. | Redemptions with a DB-enforced one-per-grant constraint, same conditional-update pattern that made check-in concurrency-safe. | A |
| Log amount + method per transaction | payments holds member, amount, method, but no venue, no staff attribution, no benefit link. | New member_transactions ledger keyed exactly as Alba specified, with a loyalty hook. | A |
| Under 15 seconds per transaction | Staff app has a menu→cart POS terminal. Fine for LULU F&B, wrong shape for a cashier logging a bill. | Scanner-first cashier screen: scan → card → keypad → method chip → verdict. | B |
| Hostess entry check-in at Upper House | Door scanner exists for tickets. No hostess role, no booking view on scan. | Door mode: scan → member + tonight's booking → check in; hostess role. | A · B |
| Upper House runs on table bookings R1 | resources (type table) and bookings exist as empty scaffold. No tables product, no floor inventory. | Table inventory per venue (3 big VIP, 12 VIP, 3 semi-VIP, 1 private, 38 standing), table booking flow, Tonight view. | A · C · D |
| Tickets THB 500 + 1 free drink | events.entry_mode='ticket' and price exist; rsvp_event is free-entry only. In-app payment not wired (claim_free_pass refuses priced plans by design). | Paid ticket purchase that grants the free-drink benefit. Beam is the dependency (see risks). | A · C dep |
| Only the booker is identified today R1 | Tickets carry quantity but one code per row. No per-guest identity. | Guest passes: one QR per guest on a booking; check-in converts guest → member. | A · C |
| Membership doesn't override door policy R1 | No copy anywhere. | Copy on pass, ticket, checkout, confirmation, T&C. Client confirms before launch. | C |
| LINE fragmentation R1 | Nothing. | Bookings + check-ins live in Activa; staff app Tonight board replaces door / model check-in groups. | B · D · E |
| Who gets Activa access | Roles: owner, admin, manager, staff, operator_admin, provider, member. Venue-scoped roles stored but not enforced. | Add cashier and hostess roles with a tight module matrix and enforced venue scope. | A · D |
| Free-drink redemption point open | n/a | Modelled as a benefit attribute (redemption_point: bar / any). Default bar-only until the client says otherwise. | A · F |
| Paper-to-POS ordering, photo SOPs, daily inventory later | Nothing, by design. | Parking lot. Framed AI-native (agent does the work, not a checklist UI), per the Setpoint Ops rule. | R3+ |
Six streams, run in parallel where the arrows allow. A is the spine; B, C and D hang off it. Prototype-first for every new screen: Nestor signs off the prototype, then we build. Items marked v2 come from the 29 Sep feedback.
Two ordered migration rounds on the demo backend, TDD on local reset, assertions extended. Round 1 (Week 1) is the spine; round 2 (Week 2) is the money model.
Round 1 · Week 1
resolve_member_code(p_org, p_code) returns the member card (name, tier, active pass + expiry, benefits each with valid_now and a reason, tonight's booking) for cashier and hostess roles only. Tolerates one clock window of drift.benefits (org, venue or all, v2 category food / drinks / entry / merch / other, kind: free_item / percent_off / fixed_off / access, value, redemption_point, per-grant limit, v2 validity window valid_days / valid_from / valid_until evaluated on the org timezone with a 06:00 business-day offset, wrap past midnight supported). plan_benefits and event_benefits join tables; the three seeded perks written as rows by hand; prose stays as fallback; the dead discount_percentage is left alone this month.benefit_grants created when a pass is issued (hook in issue_pass / claim_free_pass) or a ticket is bought. Source and expiry travel with the grant.redemptions with a unique index on grant_id and v2 a benefit_snapshot (name, kind, value, window, cost value, granting venue). Three cashiers hitting the same grant at once yields exactly one success. An out-of-window or version-mismatched redeem returns a structured verdict and never rolls back the sale.member_transactions: org, venue, member, staff user, occurred_at, v2 bill / discount / paid amounts, currency, method from one shared enum (cash / card / promptpay / transfer / credits / comp / external), v2 bank_ref and payer name (mandatory for promptpay and transfer, rows without them are flagged unverified), redemption_id, source. One atomic RPC cashier_log_transaction does resolve → optional redeem → log → (off by default) award_points.guest_passes per booking or ticket: guest name/phone, own code, member_id once claimed, checked_in_at. check_in_guest RPC; claiming runs ensure_member.resources rows (tier + capacity); table bookings on the existing bookings table with party size and booker; Tonight query per venue.cashier (redeem + log + memberships read, venue-scoped) and hostess (events + bookings read, check-in only). v2 CHECK constraint on the nine roles with a guard query; venue predicates on member_transactions, redemptions, pos_orders and payments apply only when role is cashier or hostess. my_upcoming(p_org) for the member's own night.Round 2 · Week 2 · money model v2
payments.venue_id (backfilled from pos_orders, issue_pass gains a venue), provider / provider_ref (beam / opn / bank_static / terminal / none), fee, fee VAT and net, sales_amount ex-VAT, tax_amount, service charge, tip, business_date and tax_point_at. One CHECK-constrained method enum shared with the ledger.venue_settlement_accounts (label, bank, account name, last 4, PromptPay ID with its type: juristic tax ID / biller / phone / national ID, account-holder type, Beam payout reference, default flag). Phone or personal IDs are refused for a VAT-registered venue unless an owner overrides, and the override is logged. Readable by owner/admin/manager and by a cashier for their own venue only; never in any public view.legal_entities (Thai and English name, tax ID, registered address, VAT status) with venues.legal_entity_id, branch_no and per-venue tax config (VAT mode, service charge). pos_checkout and issue_pass read the config instead of the hard-coded 7%.payment_reversals (original payment, venue, amount, reason, method, provider ref, credit-note number from a per-venue sequence) with refund_payment and void_pos_order RPCs for manager and owner. Today status='refunded' and 'voided' exist with no path that sets them.report_settlement(p_org, p_from, p_to, p_basis) with basis business / calendar / payout, rows per venue × source × method with count, gross, VAT, fee, net and settlement account, in two sections that are never totalled together. Venue-filtered for managers. CSV carries venue, method, transaction id and source system on every row.credit_transactions.venue_id, deposit_id). The deposit-lot ledger, credits as a first-class tender in pos_checkout and the cashier flow, Beam payout batches and inter-venue settlement documents are R2.1, after counsel. The columns make that migration additive.storage bucket event-media: public read, jpeg/png only, 2 MB limit, write for roles with events write/manage under their org prefix. Used by events.media and venues.cover_image_url. members.referred_by_member_id for the ?ref on the install link.qr_flutter). Hidden when the venue has no settlement account. The printed standee stays the default at launch.apps/member on the Activa backend in Week 1. The standalone app is retired; it cannot receive admin edits and carries none of the backend.events.media; pull-to-refresh and refetch on resume so an open screen catches up with admin edits.activahq.com/get/24blvd) and "Share on Instagram" (the event's post). Deep links and referral rewards in R2.1.valid_now and next window, never the phone clock.confirm_payment) is R2.1.scripts/stage-admin.sh, not deployed until Nestor says go.Alba's original eight questions, each with the default we build so nothing blocks, then the round-2 questions the new feedback raised.
| Question | Default we build |
|---|---|
| BLVD 24 · cashier owns redemption + logging? | Yes. The flow is designed for under 15 s so it fits the payments-only role. |
| BLVD 24 · which POS, manual % discounts? | Ask. We log the discount; the POS applies it. Future integration depends on the answer. |
| BLVD 24 · benefits on Grab / hotel / Fairway delivery? | Dine-in only. |
| Upper House · free drink at bar only or tables too? | Bar only (redemption_point='bar'). One flag flips it. |
| Upper House · does a ticket guarantee entry? | No. Copy says so. Refund only when the venue refuses entry for capacity, not conduct or dress. |
| Upper House · LINE → app at launch or parallel? | Parallel for two weeks. |
| Upper House · what is the "75" headcount? | Information only, no build impact. |
| Both · who gets Activa access? | Manager = admin · LINE admin = manager · hostesses = hostess · cashiers = cashier, all venue-scoped. |
Round 2 questions v2 (first two needed by end of Week 1)
| Question | Why it matters |
|---|---|
| Beam: one merchant per legal entity with a payout account per venue, or one group merchant? Which venues first, who is the Beam contact? | A single group merchant defeats per-venue settlement for in-app money. Confirm with Beam that multiple payout accounts under one merchant are supported. |
| Per venue: the company PromptPay (juristic 13-digit ID) and bank behind the account the cashier shows today. Is each venue its own legal entity? | Drives the settlement accounts, the on-screen QR, and whether credits are inter-entity money. |
| Credits: who holds the deposit, the venue where it was paid or the group? Do credits expire, are they refundable? | Determines inter-venue settlement direction and the BoT e-money question. Our default: no expiry, refundable on request, off until counsel answers. |
| Perks: confirm the categories, whether happy hour is per venue or group-wide, and what a scan should do at 20:01 (we build: deny the perk only, entry and logging continue). | Encoded on the benefit row; the 20:01 behaviour is client-visible on the first night. |
| Event media: who supplies images (their team via admin upload, or reposts from Instagram), preferred aspect ratio, video at launch? | We fix 4:5 and images only unless told otherwise. Someone must upload real media before the first TestFlight. |
| Share: in-app share to Instagram, or one link for their IG bio and Stories? Does bringing a friend earn anything? | The bio link is the launch cut; rewards and deep links are R2.1. |
| Access: the named list of people with role and venue. | We provision them at launch; no self-serve invites yet. |
| Settlement report: weekly or monthly, who receives it, CSV or PDF, Thai tax-invoice fields needed? | Shapes the export and the basis default. |
| Training: attendees per venue, dates in the week of 13 Oct, Thai or English. | Sessions and the cashier card are prepared in that language. |
Three weeks from today. BLVD 24 pilots first (one cashier, simpler), Upper House second (three cashiers, the concurrency case). The cashier flow is the critical path; Week 2 is dense, and the named slip candidates are the venues route and the Settlement report's payout basis.
apps/member; identity QR wallet.Launch is not blocked: passes and tickets are sold at the cashier. R2.1 in-app payments depend on the client's Beam onboarding and its merchant structure. Beam is a young rail, so the adapter interface keeps Opn as a drop-in fallback. Beam settles in payout batches net of fees, so payout records are needed before bank lines reconcile; that lands with the adapter.
Five of the thirteen new items only have a home once the approved design is ported into the multi-tenant app. If the port slips past Week 1, the carousel, share, home IA and instant perk updates slip with it.
A stored balance deposited at one venue and spent at another with separate bank accounts, possibly separate entities, may fall under the Payment Systems Act B.E. 2560. Credits stay off until Thai counsel clears it; the two nullable columns keep the later ledger additive. Expiry implies breakage income and stays unused.
A statement line shows payer, amount and time only, and fake slips are routine. The slip reference and payer name are mandatory in the cashier log and unverified rows are listed for accounting. Slip verification through a bank API is R2.1.
Upper House at 1 am on a DJ night is the hardest network on the estate. Store-and-forward for logs mitigates; redemption itself stays online-only so single-use holds.
The cashier already handles transfer, cash, card and QR with photo proof in a group chat. Our flow must be faster than a photo. The rehearsal shift is where we find out; if it isn't, we cut steps before the freeze.
Happy-hour windows and the settlement report are gated by the org timezone and a 06:00 business-day start on the server. A wrong timezone silently opens or closes a window; the phone clock is never consulted.
Every seeded event carries expiring Instagram links. The migration script fills the bucket before TestFlight; new events need someone on the client side uploading real images.
Port the approved v2 design into apps/member in Week 1 (recommended, and now the gate for half of round 2), or keep the standalone app and wire it to Activa.
Cashier-sold passes and tickets with "pay at venue" for mid-October, Beam as the first adapter in R2.1 with Opn as fallback (recommended), or hold launch for in-app payments.
Named roles with enforced venue scope, fixed matrix at launch, editor in R2.1 (recommended), versus permissions on the generic staff role.
Thirty minutes on Day 2 to walk the Cashier, Door, Tonight and Home prototypes before any Flutter or schema work lands.
Cut the deposit-lot ledger from the launch rounds and design it in R2.1 after BoT counsel (recommended), or author it dormant now on the critical path.
One canonical install link for the share sheet and the client's Instagram bio at launch (recommended), or the full deep-link and referral stack in Week 2.