Workflow: Catering Order — From Enquiry to Invoice
Modules involved: Catering (Events · Menus · Items · Packages · Staff · Ingredients) · Estimates · Invoices · Projects
Used by: Catering Manager · Chef · Event Coordinator · Finance
Typical duration: Enquiry → Confirmation = 1–7 days · Event = 1 day · Invoicing = immediately after event
Overview
The Catering Management module covers the complete cycle of a catering event: from the initial enquiry, through menu and package configuration, confirmation, event execution with staff assignment, through to estimate generation and invoicing.
Internal cost and margin calculation (ingredient cost vs. selling price) is available, but visible exclusively to users with the special catering_view_costs permission.
Warehouse: The module has its own internal ingredient system (
catering_ingredients) with reference stock. It does not integrate with the core Warehouse module — stock decreases do not happen automatically when an event is executed.
There is no automatic approval workflow. Any staff member with the
catering_events → editpermission can change the event status to any value, without gate logic.
Flow diagram
[SETUP — admin, one time]
│
├── Event Types (event categories: Wedding, Corporate, Conference, etc.)
├── Dietary Types + Allergens (nutritional references per product)
├── Menu Categories (hierarchical) + Menu Sections (groups within a menu)
├── Ingredients (with avg_cost_per_unit, stock_on_hand, reorder_level)
├── Menu Items (with unit_cost, unit_price, dietary types, allergens, ingredients)
├── Template Menus (collections of items grouped by sections)
├── Packages (fixed bundle with qty_per_guest per item, min/max guests)
└── Staff Roles (Head Chef, Chef, Server, Bartender, Manager, Driver — 6 seeded by default)
│
↓
[1. ENQUIRY]
│
├── Catering → Events → New Event
├── Required fields: event_name, event_type_id, event_start, event_end
├── Client: client_id (existing client) OR lead_id (prospect)
│ ⚠ Estimate/Invoice can only be generated if client_id exists, not lead_id
├── guest_count_expected: estimated number of guests
├── Venue: venue_name, venue_address
├── Optional: create_project=1 → automatically creates a project linked to the event
└── Initial status: enquiry
│
↓
[2. MENU CONFIGURATION]
│
├── Events → {event} → Menu tab → Assign Menu / Package
├── Select template menu OR package
├── Pricing mode:
│ ├── per_person: price × guest_count_expected per item
│ ├── fixed: fixed total price
│ └── package: price from package (price_per_person × guests)
├── Additional fields: multiplier (markup), weekend_surcharge, notes
├── Items from template are copied into catering_event_menu_items
│ (snapshot with current version of the item + dietary/allergen snapshot JSON)
└── Any item modifications → recorded in catering_event_menu_history
│
↓
[3. GENERATE ESTIMATE]
│
├── Events → {event} → Generate Estimate button
├── Automatically creates a standard Estimate in CRM with lines:
│ ├── Section headers (qty=0, rate=0)
│ ├── Item lines: qty = guest_count_expected, rate = unit_price
│ └── Staff labor lines: qty = hours, rate = hourly_rate
├── estimate_id saved on event
├── Event status: enquiry → quoted
└── Estimate can be edited, sent to client, signed
│
↓
[4. CONFIRMATION]
│
├── Client accepts the estimate
├── Event status: quoted → confirmed (changed manually by staff)
└── Optional: staff assignment for event (shift, role, estimated hours)
│
↓
[5. EVENT EXECUTION]
│
├── Event status: confirmed → in_progress
├── Assigned staff see their role and shift
├── Staff assignment status: pending → confirmed/declined → completed
└── Internal notes: catering_event_notes (visible_to_client optional)
│
↓
[6. COMPLETION + INVOICING]
│
├── Event status: in_progress → completed
├── guest_count_final filled in (actual number of guests)
├── Events → {event} → Convert to Invoice button
│ → calls Estimates_model::convert_to_invoice(estimate_id)
│ → invoice_id saved on event
└── Invoice tracked and paid as standard in Invoices
│
↓
[INTERNAL FINANCIALS — optional, visible with catering_view_costs]
│
├── calculate_event_financials(): calculates internal P&L:
│ ├── menu.total_cost = qty × unit_cost per item
│ ├── menu.total_revenue = qty × unit_price per item
│ ├── labor.total_cost = hours × hourly_rate per staff
│ └── summary: net_profit, profit_margin
└── Visible ONLY with the catering_view_costs permission
[EVENT INVOICED ✓ · P&L CALCULATED ✓]
Step by step
1. Initial configuration (admin)
Configuration order:
1a. Event Types
Where: Catering → Settings → Event Types → Add
Each type has a background and text color (for the calendar). At least one type is required when creating an event.
1b. Dietary Types and Allergens
Where: Catering → Settings → Dietary Types / Allergens
Nutritional references (gluten-free, vegan, halal, etc.) and allergens (nuts, dairy, etc. — with severity: mild/moderate/severe). Attached to Menu Items.
1c. Menu Categories (hierarchical) and Sections
Where: Catering → Settings → Categories / Sections
Categories are hierarchical (parent_id). Sections group items within a menu (e.g.: "Starters", "Main course", "Dessert").
1d. Ingredients
Where: Catering → Ingredients → Add
| Field | Description |
|---|---|
name |
Ingredient name |
unit |
Unit of measure |
avg_cost_per_unit |
Average cost per unit |
stock_on_hand |
Available stock (reference — does not decrease automatically) |
reorder_level |
Reorder level (visual alert) |
supplier_id |
Supplier (from catering_suppliers) |
1e. Menu Items
Where: Catering → Menu Items → Add
| Field | Description |
|---|---|
item_name |
Product name |
category_id |
Category |
unit_cost |
Cost per portion (ingredient cost) |
unit_price |
Selling price per portion |
default_portion_size |
Portion size |
prep_time_minutes |
Preparation time |
Version control: If
unit_costorunit_pricechanges,versionis automatically incremented. The snapshot at the time of assignment to an event is saved initem_versiononcatering_event_menu_items.
Add dietary types, allergens, and ingredients (with qty_per_portion) to each item.
1f. Template Menus and Packages
Menus (Catering → Menus → Add): collection of items organized by sections. When assigned to an event, items are copied with a snapshot.
Packages (Catering → Packages → Add):
| Field | Description |
|---|---|
package_name |
Package name |
price_per_person |
Price per person |
min_guests / max_guests |
Minimum/maximum number of guests |
| Items | Added with qty_per_guest per item |
1g. Staff Roles
Where: Catering → Settings → Staff Roles
6 roles seeded by default: Head Chef ($45/h), Chef ($35/h), Server ($25/h), Bartender ($30/h), Manager ($40/h), Driver ($20/h). Add custom roles with default_hourly_rate.
2. Creating an event
Where: /admin/catering_management_module/events → New Event
Key fields:
| Field | Required | Description |
|---|---|---|
event_name |
Yes | Event name |
event_type_id |
Yes | Event type |
event_start / event_end |
Yes | Start/end date and time |
client_id |
Recommended | Existing client (required for estimate/invoice generation) |
lead_id |
Alternative | Lead (without the ability to generate financial documents) |
guest_count_expected |
Yes | Estimated number of guests (used in per_person pricing calculation) |
venue_name / venue_address |
Yes | Event location |
dietary_notes / allergen_summary |
No | General dietary notes for the event |
create_project |
No | 1 = automatically creates a linked CRM project |
3. Event statuses
| Status | Meaning | Transition |
|---|---|---|
enquiry |
Initial enquiry | → quoted on estimate generation |
quoted |
Estimate generated and sent | → confirmed on acceptance |
confirmed |
Booking confirmed | → in_progress on event date |
in_progress |
Event in progress | → completed on finish |
completed |
Event finished | → generate invoice |
cancelled |
Cancelled (terminal) | — |
lost |
Lost (terminal) | — |
All transitions are manual — any staff with
catering_events → editcan set any status.
4. Staff assignment for event
Where: Events → {event} → Staff tab → Add Staff
| Field | Description |
|---|---|
staff_id |
Assigned employee |
role |
Role at the event |
shift_start / shift_end |
Work shift |
hours |
Total hours (automatically calculated from shift_start/end) |
hourly_rate |
Hourly rate (pre-populated from Staff Role) |
Assignment statuses: pending → confirmed / declined → completed
Conflict detection: The system automatically checks whether the staff member has an overlapping assignment (check_scheduling_conflicts()) before saving.
Staff notification:
notify_all_staff()exists in the code but does not send email — it only logs to the activity log. Notify staff manually.
5. Generate estimate and invoice
Generate estimate: Events → {event} → Generate Estimate button
The created estimate includes:
- Section headers (for visual organization)
- Product lines: qty =
guest_count_expected, rate =unit_priceper item - Labor lines: qty =
hours, rate =hourly_rateper assigned staff
The estimate appears in /admin/estimates and can be edited, sent, and signed like any standard estimate.
Convert to invoice: Events → {event} → Convert to Invoice button (available after Generate Estimate)
Calls Estimates_model::convert_to_invoice($estimate_id) → standard invoice in /admin/invoices.
6. Internal P&L calculation
Available exclusively with the catering_view_costs permission:
| Metric | Calculation |
|---|---|
menu.total_cost |
unit_cost × portion_per_guest × guest_count_expected per item |
menu.total_revenue |
unit_price × portion_per_guest × guest_count_expected per item |
labor.total_cost |
hours × hourly_rate per assigned staff |
net_profit |
total_revenue - total_cost - labor_cost |
profit_margin |
net_profit / total_revenue × 100 |
External expenses (
expenses.total_cost) are a placeholder in the code — not implemented. External costs cannot be added directly to the P&L calculation.
Required permissions
| Permission | Access |
|---|---|
catering_events → view/create/edit/delete |
Event management |
catering_menus → view/create/edit/delete |
Template menus |
catering_menu_items → view/create/edit/delete |
Catering products |
catering_packages → view/create/edit/delete |
Packages |
catering_categories → view/create/edit/delete |
Categories |
catering_allergens / catering_dietary_types |
Nutritional data |
catering_view_costs |
View costs and P&L margins |
estimates → create |
Generate estimate from event |
invoices → create |
Convert to invoice |
Gotchas
| Problem | Cause | Solution |
|---|---|---|
| Generate Estimate button does not appear | client_id missing (event has only lead_id) |
Convert the lead to a client and update the event with client_id |
| Price on estimate is wrong | unit_price on item changed after assignment |
Module takes a snapshot on assignment — use Regenerate Estimate to fetch current prices |
| Staff not appearing in dropdown | No Staff Roles configured | Add at least one Staff Role |
| Ingredient stock does not decrease | Correct behavior — stock is reference, not transactional | Manually deduct from stock_on_hand after the event |
| P&L not visible | Missing catering_view_costs permission |
Grant the specific permission to the role |
| Staff notification not working | notify_all_staff() does not actually send email |
Notify staff manually or use standard CRM notifications |
Module references
- Estimates — estimates generated from events
- Invoices — invoices generated from events
- Projects — project optionally linked to event
- Catering — module documentation
Build reusable menu packages with fixed per-head pricing before any events are booked — attaching a package to an event takes seconds and ensures consistent pricing across similar events without re-entering menus from scratch.
Allergen flags on menu items must be maintained before building menus. Incomplete allergen data on served items is a compliance risk — build and verify the allergen master list before any catering operation goes live with customer-facing menus.