End-to-End Workflow

ENTRY POINTS Square Invoice / Order DRAFT · UNPAID · PAID · Online Iris Conversational Intake Tina dictates phone/email request Manual Entry BEO admin UI — no Square order create-beo skill Event + BEO + line items Airtable sync enqueue dn_catering PostgreSQL · port 5432 events · beos beo_line_items · packages SYNC WORKER (5 min poll) square_sync worker dn-warehouse.db → dn_catering status sync · lock on PAID LINE ITEM MODEL Package → Components package_line_items table 🏷 FOH sees package name 🍽 Kitchen sees components indented · invoice-matched flag 🔒 Invoice PAID line_items_locked = TRUE Square DRAFT Invoice created by Iris intake Tina reviews + sends LEGEND Sync / data flow Polling worker trigger Iris → Square write-back

Package Line Item Model

Square sells packages as a single line item (e.g. "Corporate Package — 25 Count"). The BEO needs to show both views:

  • FOH / invoice reconciliation — sees the package name to match against the Square invoice
  • Kitchen / production — sees the individual component items, each on its own line with quantity

Print / UI Rendering

📦 Corporate Package (25 Count)   × 2  ·  Sq Invoice #000891 ✓ Bánh Mì Sliders  × 50 Ham & Cheese Sliders  × 50 Caesar Wrap  × 25 Sparkling Water  × 50
Breakfast Burrito   × 10  ·  ⚠ not on invoice

Line items added after invoice payment are editable but flagged "not on invoice." Kitchen sees them; FOH knows they're additions outside the original charge.

Do We Need a Package Reference Table?

Yes — a package_line_items table is required.
Without it, package expansion is manual every time and there's no data integrity. The table maps a Square catalog item (package) to its component items with quantities. Ops maintains it; the system reads it at BEO creation time to auto-expand packages.
-- Package reference: maps Square package catalog IDs to BEO component items
CREATE TABLE IF NOT EXISTS package_components (
  id              TEXT PRIMARY KEY,
  package_catalog_id  TEXT NOT NULL,  -- Square catalog item ID for the package
  package_name        TEXT NOT NULL,  -- e.g. "Corporate Package (25 Count)"
  component_name      TEXT NOT NULL,  -- e.g. "Bánh Mì Sliders"
  component_variation TEXT,                     -- e.g. "Regular (2 Count)"
  quantity_per_package INTEGER NOT NULL DEFAULT 1,
  menu_item_id        TEXT REFERENCES menu_items(id),
  sort_order          INTEGER DEFAULT 0,
  active              BOOLEAN DEFAULT TRUE,
  created_at          TIMESTAMPTZ DEFAULT NOW(),
  updated_at          TIMESTAMPTZ DEFAULT NOW()
);

-- beo_line_items additions
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS parent_line_item_id TEXT REFERENCES beo_line_items(id);
-- NULL = top-level item; non-NULL = component child of a package
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS is_package_parent  BOOLEAN DEFAULT FALSE;
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS invoice_matched    BOOLEAN DEFAULT NULL;
-- TRUE = matches Square invoice line item; FALSE = added post-invoice; NULL = unknown
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS square_line_item_id TEXT;

Auto-Expansion Logic

  1. Square order line item arrives with catalog_object_id
  2. Look up package_components by package_catalog_id
  3. If found → create parent BEO line item (package) + child items (components), linked via parent_line_item_id, qty = package qty × component qty_per_package
  4. If NOT found → create single line item, set needs_expansion = TRUE, flag in health check for manual expansion by ops

Status Mapping — Square → BEO

Square: DRAFT
→ Estimate Out  /  In Progress Order
Kitchen print suppressed. Not sent to customer yet.
Square: UNPAID
→ Estimate Out  /  In Progress Order
Sent to customer, awaiting payment.
Square: CANCELED
→ Cancelled  /  Cancelled
Event and BEO both cancelled.

Post-Payment Line Item Editing

Model (confirmed by Robert 2026-05-13): When an invoice is paid, line items remain editable — but any item added or changed after payment is flagged invoice_matched = FALSE. The flag persists on the BEO print so FOH knows these items are outside the original charge and kitchen knows what to actually produce.
Why not a second BEO? A second BEO would confuse the kitchen — they'd have two documents to reconcile for one event. Instead, all items for an event stay on one BEO. The invoice_matched flag provides the financial audit trail FOH needs without splitting the kitchen's pick list.

UI Behaviour

  • Items with invoice_matched = TRUE → show invoice # badge (e.g. #000891 ✓)
  • Items with invoice_matched = FALSE → show ⚠ not on invoice badge
  • No hard lock after payment — editing stays open, flag is automatic
  • BEO print template renders invoice-matched and non-matched items with distinct visual treatment
  • Package children inherit the parent's invoice_matched status

Full Schema Migration (additive only)

-- New table
CREATE TABLE IF NOT EXISTS package_components ( ... );

-- events
ALTER TABLE events ADD COLUMN IF NOT EXISTS square_invoice_id TEXT;

-- beos
ALTER TABLE beos ADD COLUMN IF NOT EXISTS square_invoice_id     TEXT;
ALTER TABLE beos ADD COLUMN IF NOT EXISTS square_invoice_status TEXT;

-- beo_line_items
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS parent_line_item_id TEXT REFERENCES beo_line_items(id);
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS is_package_parent  BOOLEAN DEFAULT FALSE;
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS invoice_matched    BOOLEAN DEFAULT NULL;
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS square_line_item_id TEXT;
ALTER TABLE beo_line_items ADD COLUMN IF NOT EXISTS needs_expansion    BOOLEAN DEFAULT FALSE;

All changes are additive. No existing columns dropped, renamed, or type-changed. No FK changes to existing tables.

Pre-Build Checklist

ItemStatusOwner
Robert answers 5 open questionsDone — 2026-05-13Robert
Axiom confirms schema is additive-onlyIn progressAxiom
Dedup audit script written + run read-onlyIn progressAxiom
create-beo skill writtenIn progressAxiom
Quasar test plan writtenIn progressAxiom
Robert approves to proceed to buildPending checklistRobert

Phased Delivery

Phase 2
Iris conversational intake · Square DRAFT invoice creation · Square webhooks (replace polling)
Phase 3
Ops-maintained package_components UI · BEO → Square partial reverse sync (pre-payment only)