Workflow: Catering Order — De la Cerere la Factură
Module implicate: Catering (Events · Menus · Items · Packages · Staff · Ingredients) · Estimates · Invoices · Projects
Cine îl folosește: Catering Manager · Chef · Event Coordinator · Financiar
Durata tipică: Cerere → Confirmare = 1–7 zile · Eveniment = 1 zi · Facturare = imediat după eveniment
Overview
Modulul Catering Management acoperă întregul ciclu al unui eveniment de catering: de la cererea inițială (enquiry), prin configurarea meniului și a pachetului, confirmarea, execuția evenimentului cu asignare staff, până la generarea ofertei și facturării.
Calculul intern de costuri și marje (cost ingrediente vs. preț vânzare) este disponibil, dar vizibil exclusiv utilizatorilor cu permisiunea specială catering_view_costs.
Depozit (Warehouse): Modulul are propriul sistem intern de ingrediente (
catering_ingredients) cu stocuri de referință. Nu se integrează cu modulul Warehouse core — scăderile de stoc nu se fac automat la executarea evenimentului.
Nu există flux de aprobare automat. Orice staff cu permisiunea
catering_events → editpoate schimba statusul evenimentului la orice valoare, fără gate logic.
Diagrama fluxului
[SETUP — admin, o singură dată]
│
├── Event Types (categorii evenimente: Nuntă, Corporate, Conferință etc.)
├── Dietary Types + Allergens (referințe nutriționale per produs)
├── Menu Categories (ierarhice) + Menu Sections (grupuri în meniu)
├── Ingredients (cu avg_cost_per_unit, stock_on_hand, reorder_level)
├── Menu Items (cu unit_cost, unit_price, dietary types, allergens, ingrediente)
├── Template Menus (colecții de items grupate pe secțiuni)
├── Packages (bundle fix cu qty_per_guest per item, min/max guests)
└── Staff Roles (Head Chef, Chef, Server, Bartender, Manager, Driver — 6 sedate implicit)
│
↓
[1. CERERE (Enquiry)]
│
├── Catering → Events → New Event
├── Câmpuri obligatorii: event_name, event_type_id, event_start, event_end
├── Client: client_id (client existent) SAU lead_id (prospect)
│ ⚠ Estimate/Invoice se pot genera DOAR dacă există client_id, nu lead_id
├── guest_count_expected: număr estimat invitați
├── Venue: venue_name, venue_address
├── Opțional: create_project=1 → creează automat un proiect legat de eveniment
└── Status inițial: enquiry
│
↓
[2. CONFIGURARE MENIU]
│
├── Events → {event} → Menu tab → Assign Menu / Package
├── Selectare template menu SAU package
├── Pricing mode:
│ ├── per_person: preț × guest_count_expected per item
│ ├── fixed: preț fix total
│ └── package: preț din package (price_per_person × guests)
├── Câmpuri suplimentare: multiplier (markup), weekend_surcharge, notes
├── Items din template se copiază în catering_event_menu_items
│ (snapshot cu version curentă a item-ului + dietary/allergen snapshot JSON)
└── Orice modificare a items → înregistrată în catering_event_menu_history
│
↓
[3. GENERARE OFERTĂ]
│
├── Events → {event} → butonul Generate Estimate
├── Crează automat un Estimate standard în CRM cu linii:
│ ├── Headers de secțiune (qty=0, rate=0)
│ ├── Linii items: qty = guest_count_expected, rate = unit_price
│ └── Linii manoperă staff: qty = hours, rate = hourly_rate
├── estimate_id salvat pe eveniment
├── Status event: enquiry → quoted
└── Estimatul poate fi editat, trimis clientului, semnat
│
↓
[4. CONFIRMARE]
│
├── Client acceptă oferta
├── Status event: quoted → confirmed (schimbat manual de staff)
└── Opțional: asignare staff pentru eveniment (shift, rol, ore estimate)
│
↓
[5. EXECUȚIE EVENIMENT]
│
├── Status event: confirmed → in_progress
├── Staff asignați văd rolul și tura
├── Status staff assignments: pending → confirmed/declined → completed
└── Note interne: catering_event_notes (visible_to_client opțional)
│
↓
[6. FINALIZARE + FACTURARE]
│
├── Status event: in_progress → completed
├── guest_count_final completat (numărul real de invitați)
├── Events → {event} → butonul Convert to Invoice
│ → calls Estimates_model::convert_to_invoice(estimate_id)
│ → invoice_id salvat pe eveniment
└── Factura urmărită și plătită standard în Invoices
│
↓
[FINANCIAR INTERN — opțional, vizibil cu catering_view_costs]
│
├── calculate_event_financials(): calculează P&L intern:
│ ├── 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
└── Vizibil DOAR cu permisiunea catering_view_costs
[EVENIMENT FACTURAT ✓ · P&L CALCULAT ✓]
Pas cu pas
1. Configurare inițială (admin)
Ordinea de configurare:
1a. Event Types
Unde: Catering → Settings → Event Types → Add
Fiecare tip are culoare de fundal și text (pentru calendar). Cel puțin un tip este necesar la crearea unui eveniment.
1b. Dietary Types și Allergens
Unde: Catering → Settings → Dietary Types / Allergens
Referințe nutriționale (gluten-free, vegan, halal etc.) și alergeni (nuci, lactate etc. — cu severity: mild/moderate/severe). Se atașează la Menu Items.
1c. Menu Categories (ierarhice) și Sections
Unde: Catering → Settings → Categories / Sections
Categories sunt ierarhice (parent_id). Sections grupează items în cadrul unui meniu (ex: „Aperitive", „Fel principal", „Desert").
1d. Ingredients
Unde: Catering → Ingredients → Add
| Câmp | Descriere |
|---|---|
name |
Denumire ingredient |
unit |
Unitate de măsură |
avg_cost_per_unit |
Costul mediu per unitate |
stock_on_hand |
Stoc disponibil (referință — nu scade automat) |
reorder_level |
Nivel de reaprovizionare (alertă vizuală) |
supplier_id |
Furnizorul (din catering_suppliers) |
1e. Menu Items
Unde: Catering → Menu Items → Add
| Câmp | Descriere |
|---|---|
item_name |
Denumire produs |
category_id |
Categoria |
unit_cost |
Cost per porție (ingredient cost) |
unit_price |
Preț de vânzare per porție |
default_portion_size |
Mărimea porției |
prep_time_minutes |
Timp de preparare |
Version control: Dacă
unit_costsauunit_pricese modifică,versionse incrementează automat. Snapshot-ul la momentul asignării pe eveniment este salvat înitem_versionpecatering_event_menu_items.
Adăugați dietary types, allergens și ingredients (cu qty_per_portion) pe fiecare item.
1f. Template Menus și Packages
Menus (Catering → Menus → Add): colecție de items organizați pe secțiuni. La asignare pe eveniment, items se copiază cu snapshot.
Packages (Catering → Packages → Add):
| Câmp | Descriere |
|---|---|
package_name |
Denumire pachet |
price_per_person |
Preț per persoană |
min_guests / max_guests |
Număr invitați minim/maxim |
| Items | Adăugați cu qty_per_guest per item |
1g. Staff Roles
Unde: Catering → Settings → Staff Roles
6 roluri sedate implicit: Head Chef ($45/h), Chef ($35/h), Server ($25/h), Bartender ($30/h), Manager ($40/h), Driver ($20/h). Adăugați roluri personalizate cu default_hourly_rate.
2. Crearea evenimentului
Unde: /admin/catering_management_module/events → New Event
Câmpuri cheie:
| Câmp | Obligatoriu | Descriere |
|---|---|---|
event_name |
Da | Denumire eveniment |
event_type_id |
Da | Tipul evenimentului |
event_start / event_end |
Da | Data și ora start/final |
client_id |
Recomandat | Client existent (necesar pentru generare estimate/invoice) |
lead_id |
Alternativă | Lead (fără posibilitate de generare documente financiare) |
guest_count_expected |
Da | Număr estimat invitați (folosit la calculul pricing per_person) |
venue_name / venue_address |
Da | Locația evenimentului |
dietary_notes / allergen_summary |
Nu | Note dietetice generale ale evenimentului |
create_project |
Nu | 1 = crează automat un proiect CRM legat |
3. Statusurile evenimentului
| Status | Semnificație | Tranziție |
|---|---|---|
enquiry |
Cerere inițială | → quoted la generare estimate |
quoted |
Ofertă generată și trimisă | → confirmed la acceptare |
confirmed |
Booking confirmat | → in_progress la data evenimentului |
in_progress |
Evenimentul se desfășoară | → completed la finalizare |
completed |
Eveniment finalizat | → generare factură |
cancelled |
Anulat (terminal) | — |
lost |
Pierdut (terminal) | — |
Toate tranzițiile sunt manuale — orice staff cu
catering_events → editpoate seta orice status.
4. Asignare staff pentru eveniment
Unde: Events → {event} → Staff tab → Add Staff
| Câmp | Descriere |
|---|---|
staff_id |
Angajatul asignat |
role |
Rolul la eveniment |
shift_start / shift_end |
Tura de lucru |
hours |
Ore totale (calculate automat din shift_start/end) |
hourly_rate |
Tarif orar (pre-populat din Staff Role) |
Statusuri asignare: pending → confirmed / declined → completed
Detecție conflicte: Sistemul verifică automat dacă staff-ul are asignare suprapusă (check_scheduling_conflicts()) înainte de salvare.
Notificarea staff-ului:
notify_all_staff()există în cod dar nu trimite email — doar loghează în activity log. Notificați staff-ul manual.
5. Generare ofertă și factură
Generare ofertă: Events → {event} → buton Generate Estimate
Estimatul creat include:
- Headers de secțiune (pentru organizare vizuală)
- Linii de produse: qty =
guest_count_expected, rate =unit_priceper item - Linii de manoperă: qty =
hours, rate =hourly_rateper staff asignat
Estimatul apare în /admin/estimates și poate fi editat, trimis și semnat ca orice ofertă standard.
Conversie la factură: Events → {event} → buton Convert to Invoice (disponibil după Generate Estimate)
Apelează Estimates_model::convert_to_invoice($estimate_id) → factură standard în /admin/invoices.
6. Calcul P&L intern
Disponibil exclusiv cu permisiunea catering_view_costs:
| Metric | Calcul |
|---|---|
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 staff asignat |
net_profit |
total_revenue - total_cost - labor_cost |
profit_margin |
net_profit / total_revenue × 100 |
Cheltuielile externe (
expenses.total_cost) sunt placeholder în cod — nu sunt implementate. Costurile externe nu se pot adăuga direct în calcul P&L.
Permisiuni necesare
| Permisiune | Acces |
|---|---|
catering_events → view/create/edit/delete |
Gestionare evenimente |
catering_menus → view/create/edit/delete |
Template menus |
catering_menu_items → view/create/edit/delete |
Produse catering |
catering_packages → view/create/edit/delete |
Pachete |
catering_categories → view/create/edit/delete |
Categorii |
catering_allergens / catering_dietary_types |
Date nutriționale |
catering_view_costs |
Vizualizare costuri și marje P&L |
estimates → create |
Generare ofertă din eveniment |
invoices → create |
Conversie la factură |
Gotchas
| Problemă | Cauză | Soluție |
|---|---|---|
| Butonul Generate Estimate nu apare | client_id lipsă (evenimentul are doar lead_id) |
Convertiți lead-ul în client și actualizați evenimentul cu client_id |
| Prețul pe ofertă e greșit | unit_price pe item s-a schimbat după asignare |
Modulul face snapshot la asignare — folosiți Regenerate Estimate pentru a prelua prețurile curente |
| Staff-ul nu apare în dropdown | Nu există Staff Roles configurate | Adăugați cel puțin un Staff Role |
| Stocul de ingrediente nu scade | Comportament corect — stocul e referință, nu transactional | Deduceți manual din stock_on_hand după eveniment |
| P&L nu se vede | Lipsă permisiune catering_view_costs |
Acordați permisiunea specifică rolului |
| Notificarea staff-ului nu funcționează | notify_all_staff() nu trimite email în mod real |
Notificați manual staff-ul sau folosiți notificările standard CRM |
Referințe module
- Estimates — oferte generate din evenimente
- Invoices — facturi generate din evenimente
- Projects — proiect legat opțional la eveniment
- Catering — documentație modul
Construiți pachete de meniu reutilizabile cu prețuri fixe per persoană înainte de rezervarea oricăror evenimente — atașarea unui pachet la un eveniment durează secunde și asigură prețuri consecvente pentru evenimente similare fără a reintra meniuri de la zero.
Marcajele de alergeni pe elementele de meniu trebuie menținute înainte de construirea meniurilor. Datele incomplete despre alergeni pe articolele servite reprezintă un risc de conformitate — construiți și verificați lista principală de alergeni înainte ca orice operație de catering să fie lansată cu meniuri vizibile clienților.