Menu¶
System Overview¶
Two independent data flows feed into the menu system:
flowchart TD
subgraph POS["POS Adapter (X-Provider header)"]
PS["POST /api/v1/products/sync"] --> Items["items, item_modifiers, item_modifier_options"]
OS["POST /api/v1/outlets/sync"] --> Stores["stores, store_credentials"]
end
subgraph Cata["Cata Menu Editor (no provider)"]
ME["Menu V2 Draft Endpoints"] --> Drafts["menu_drafts, menu_draft_*"]
PB["Publish"] --> Live["menus, menu_*, item_store"]
end
Items -.->|"item_id referenced in sections"| Drafts
Stores -.->|"store_id used at publish"| PB
Product sync feeds flat master data (items + modifiers). Menu V2 composes those products into menus. The two flows connect at items.id — section items reference products by item_code.
Menu Draft (Menu Editor)¶
Cata-owned menu composition using a draft-first workflow. Menus are built and edited in draft tables, then published to target outlets.
Concept¶
- Drafts are independent from any POS provider — always owned by Cata
- Items are flat — synced separately via
POST /api/v1/products/syncfrom POS adapters - Drafts compose items into menus — organize items into sections with ordering and visibility
- Publish targets outlets — a draft is a template; you choose which outlets to push it to
- Publish is batch — multiple drafts can be published to multiple outlets in one action
Draft vs Live¶
| Concept | Draft (editing) | Live (published) |
|---|---|---|
| Tables | menu_drafts, menu_draft_sections, menu_draft_section_items, menu_draft_operating_hours, menu_draft_stores, menu_draft_store_operating_hours |
menus, menu_sections, menu_section_items, menu_items, menu_operating_hours, menu_stores |
| Who edits | Cata Menu Editor | Read-only (populated by publish) |
| Provider | Not applicable | cata (hardcoded) |
| Items | Referenced by item_id in sections |
Derived from section items at publish time |
Workflow¶
flowchart LR
A["Create Draft"] --> B["Add Sections"]
B --> C["Assign Items to Sections"]
C --> D["Set Default Operating Hours"]
D --> E["Assign Outlets"]
E --> F["Override Hours per Outlet (optional)"]
F --> G["Review"]
G --> H["Publish to Outlets"]
Publish Flow¶
When publishing draft(s) to outlet(s):
flowchart TD
P["Publish(draft_ids, store_ids)"] --> L["Create publish log per store"]
L --> D["For each draft × store:"]
D --> M1["1. Copy menu_drafts → menus"]
M1 --> M2["2. Copy menu_draft_sections → menu_sections"]
M2 --> M3["3. Copy menu_draft_section_items → menu_section_items"]
M3 --> M4["4. Derive menu_items from section items"]
M4 --> M5["5. Copy operating hours → menu_operating_hours (per-outlet override or default)"]
M5 --> M6["6. Create menu_stores assignment"]
M6 --> M7["7. Ensure item_store rows exist (INSERT IGNORE, do not overwrite)"]
M7 --> M8["8. Update menu_draft_stores with published outlets"]
M8 --> M9["9. Log in publish_log_details"]
Database Schema¶
menu_drafts¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
name |
varchar(100) | Menu display name |
internal_name |
varchar(100) | Internal reference name |
priority |
int | Sort priority (default 0) |
channel_type |
enum | pickup, delivery, pickup_delivery, eatin |
start_date |
datetime | Menu availability start |
end_date |
datetime | Menu availability end |
created_at |
timestamp | |
updated_at |
timestamp | |
deleted_at |
timestamp | Soft delete |
menu_draft_sections¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
uuid |
uuid | Unique identifier |
menu_draft_id |
bigint FK | References menu_drafts.id |
name |
varchar(255) | Section name |
description |
text | Optional description |
display_type |
varchar(20) | LIST, GRID, etc. |
sort_num |
int | Display order |
visible |
bool | Visibility flag |
menu_draft_section_items¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
section_id |
bigint FK | References menu_draft_sections.id |
item_id |
bigint FK | References items.id (flat product) |
sort_num |
int | Display order within section |
visible |
bool | Visibility flag |
Unique constraint: (section_id, item_id)
menu_draft_operating_hours¶
Default operating hours for the draft. Used as fallback when no per-outlet override exists.
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
menu_draft_id |
bigint FK | References menu_drafts.id |
weekday |
int | 0=Sunday, 1=Monday, ..., 6=Saturday |
start_time |
varchar(5) | e.g. 09:00 |
end_time |
varchar(5) | e.g. 17:00 |
menu_draft_stores¶
Outlets assigned to this draft (candidates for publish).
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
menu_draft_id |
bigint FK | References menu_drafts.id |
store_id |
bigint FK | References stores.id |
Unique constraint: (menu_draft_id, store_id)
menu_draft_store_operating_hours¶
Per-outlet operating hours override. If set for a store, these are used instead of the default hours at publish time.
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
menu_draft_id |
bigint FK | References menu_drafts.id |
store_id |
bigint FK | References stores.id |
weekday |
int | 0=Sunday, 1=Monday, ..., 6=Saturday |
start_time |
varchar(5) | e.g. 09:00 |
end_time |
varchar(5) | e.g. 17:00 |
menu_draft_publish_logs¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
store_id |
bigint FK | Target outlet |
published_by |
bigint | User ID who triggered publish |
published_at |
datetime | When the publish happened |
menu_draft_publish_log_details¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
publish_log_id |
bigint FK | References menu_draft_publish_logs.id |
menu_draft_id |
bigint FK | Which draft was published |
menu_id |
bigint | Resulting live menu ID in menus table |
Channel Type Mapping¶
The channel_type enum maps to the legacy boolean columns in the live menus table:
channel_type |
is_pickup |
is_delivery |
is_eatin |
|---|---|---|---|
pickup |
true | false | false |
delivery |
false | true | false |
pickup_delivery |
true | true | false |
eatin |
false | false | true |
Menu V2 API Endpoints¶
CRUD operations on draft tables. All endpoints require X-Tenant-ID header.
Draft Menu CRUD¶
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/menus |
Create a new draft menu |
GET |
/api/v1/menus/{menuId} |
Get full draft menu details |
PUT |
/api/v1/menus/{menuId} |
Update draft menu (partial) |
DELETE |
/api/v1/menus/{menuId} |
Soft-delete draft menu + cascade |
GET |
/api/v1/outlets/{outletId}/menus |
List menus by store |
Operating Hours¶
| Method | Endpoint | Description |
|---|---|---|
PUT |
/api/v1/menus/{menuId}/operating-hours |
Replace operating hours |
Sections¶
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/menus/{menuId}/sections |
Create section |
PUT |
/api/v1/menus/{menuId}/sections/{sectionId} |
Update section |
DELETE |
/api/v1/menus/{menuId}/sections/{sectionId} |
Delete section |
Section Items¶
| Method | Endpoint | Description |
|---|---|---|
PUT |
/api/v1/menus/{menuId}/sections/{sectionId}/items |
Set section items (ordered list) |
Publish¶
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/menus/publish |
Batch publish drafts to outlets |
Administration¶
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/menus/prune |
Hard-delete soft-deleted drafts older than 30 days |
Draft Pruning Strategy¶
When a draft is archived (soft-deleted), it and all its children remain in the database. The prune endpoint hard-deletes drafts where deleted_at is older than 30 days, along with all related child rows.
Deletion order (children first, parent last):
| Step | Table | Action |
|---|---|---|
| 1 | menu_draft_section_items |
Hard-delete (via section → draft join) |
| 2 | menu_draft_sections |
Hard-delete (by menu_draft_id) |
| 3 | menu_draft_operating_hours |
Hard-delete (by menu_draft_id) |
| 4 | menu_draft_store_operating_hours |
Hard-delete (by menu_draft_id) |
| 5 | menu_draft_stores |
Hard-delete (by menu_draft_id) |
| 6 | menu_drafts |
Hard-delete (where deleted_at < NOW() - 30 days) |
Not pruned (kept as audit trail):
- menu_draft_publish_logs
- menu_draft_publish_log_details
Response example:
{
"code": 200,
"isSuccess": true,
"message": "pruned soft-deleted drafts older than 30 days",
"pruned": {
"drafts": 3,
"sections": 8,
"sectionItems": 24,
"operatingHours": 12,
"storeOperatingHours": 6,
"stores": 3
}
}
Menu V1 (Legacy)¶
Deprecated
Menu V1 will be deprecated. Use Menu V2 for new integrations.
POS-driven menu sync. The POS pushes a full menu tree to Cata.
Submit Menu¶
POST /api/v1/menu
Async Operation
Menu submission is async. You will receive a jobId immediately. Processing can take time depending on menu size.
Track via GET /api/v1/jobs/{jobId} or listen to the menu.processed / menu.failed webhook events.
Update Item Availability¶
PATCH /api/v1/menu/items/{itemId}/availability
Synchronous — returns immediately.