# Cross-Cutting Reference

> **KB version:** 2.0 · **Code baseline:** `21e26e1` · **Last reviewed:** 2026-11-03
> **Epic:** EP-TBD · **Status:** Pending PO review
> **Pending PO review:**
> - kitchen status `pending`→`ready` rename, cross-referenced from Module 4
> - recipe/replacement/add-on/order-line-type glossary + constants, cross-referenced from M03/M04
> - item source (`items.created_from`) glossary: `dashboard` or the pushing platform's `slug`; any authenticated platform may push items; CostControl item sync via `/pos/items/cc/item-creation` (+ dry-run simulate endpoint, runs as the calling platform); risk/open questions on CostControl's missing platform row
> - CostControl (CC) order send: glossary entry; source constants `standard` / `branch-plan` / `delivery-app`; `services.cost_control` config (UAT host in production, QC host elsewhere; token TTL); `cost_control` daily log channel with masked secrets; `integration_logs` one row per request with nullable `attempt`; risk/open questions on `order_number` uniqueness and the assumed login token field shape
> **Related flags:** TBD (requires As-Built Spec cross-reference, out of scope this pass) · **Open decisions:** TBD (requires As-Built Spec OD register, out of scope this pass)

---

## Business Glossary
| Term | Meaning in Dinex |
|---|---|
| **Order** | A single customer transaction at a branch; the financial document with VAT-inclusive pricing. |
| **Business day** | The window from the first open order until the cashier closes the day; keyed by `related_date` (the open date). |
| **Day close** | The end-of-day operation freezing orders, producing the DSR pair, and resetting order numbers. |
| **DSR** | Daily Sales Report — the printable end-of-day reconciliation (sales report + items report). |
| **Open order** | `day_closed = 0` and `closed_at IS NULL` — belongs to the current business day. |
| **Operation status** | The kitchen/preparation status, independent of payment status. |
| **External order** | An order imported from a platform (mobile app), identified by `external_order_id` and paid via an `is_external` payment type. |
| **Platform** | An authenticated external system (e.g. Asparagus Mobile App) allowed to submit orders and receive status callbacks. |
| **Mapper slug** | The stable external identifier of an item or branch used by platforms (`items.mapper_slug`, `branches.mapper_slug`). |
| **Custom item / Make your own meal** | `is_custom=1` items are components assembled through the multi-category resolver; grouped on the order via `group_id`. |
| **Additional fee** | A percentage surcharge tied to a payment method (e.g. card fee), folded into the taxable base and line prices. |
| **Net (price_before_discount)** | Order value excluding VAT, including surcharges, before discount. |
| **Sub total (price_after_discount)** | Net minus discount. |
| **Final price** | Sub total + mobile delivery fee + VAT — what the customer pays. |
| **Tracker** | The local mirror of a Noon delivery task (`order_trackers`). |
| **Outlet** | Noon's registered pickup location, linked 1:1 to a branch. |
| **Guest** | Reporting proxy: 1 guest = 1 order. |
| **TRN** | UAE Tax Registration Number, printed on invoices per branch. |
| 🚩 **SKU** | `items.sku` — the identifier the mobile app now orders by (unique, nullable); `mapper_slug` survives only as the SKU-sync matching key. |
| 🚩 **MYOM** | "Make Your Own Meal" ordered from the mobile app — a composed meal bound to a seeded `myom-N` placeholder item, with the platform's own price/name captured in `order_items.custom_name`. Temporary mapping, distinct from the older custom-category component flow in Module 3. |
| 🚩 **Order time** | The kitchen prep-time *estimate* shown on a ticket (`orders.preparation_time` → `order_time`), derived from the slowest item's `preparation_time`, scaled by meal count. Not the same as `actual_order_time` (measured). |
| 🚩 **Order History** | The order-by-order ledger report (`/dashboard/reports/orders-history`) behind the aggregate reports, with a companion invoice-ZIP export. |
| **Ingredient category** | A node of the CostControl-style ingredient tree in `ingredient_categories`: type (level 0) > category (level 1) > sub category (level 2). Only leaf categories hold ingredients. See Module 3. |
| **Recipe line** | One `item_ingredients` row — an ingredient and its size in an item's recipe, with `is_removable` (cashier may remove it) and `sort_order`. See Module 3. |
| **Replacement** | An allowed swap for a recipe line (`item_ingredient_replacements`); the substitute must belong to the same leaf ingredient category as the original and may carry an `extra_charge`. See Modules 3 and 4. |
| **Add-on** | An item placed in the menu category with code `ASP_CAT_1000` or any category below it; a meal offers specific add-ons through `item_addons`. See Modules 3 and 4. |
| **Order line type** | `order_items.type` — `item` (meal/regular line) or `addon`; an add-on line sold with a meal points to it through `parent_order_item_id`. See Module 4. |
| **Recipe snapshot** | `order_item_ingredients` — the per-line copy of the recipe at sale time, each row with status `default`, `replaced` or `removed`. See Module 4. |
| **Item source (created_from)** | `items.created_from` (nullable string) — where an item was created: `dashboard` (dashboard item form) or the `slug` of the platform that pushed it (any platform authenticated by `PlatformAuthMiddleware` may push items; none is refused). Set on creation only; an update keeps the original source and `created_by`. Null for items created before the column existed. For platform pushes `created_by` is the platform's related user; for the dashboard it is the dashboard user. See Module 3. |
| **CostControl (CC)** | External system that consumes stock for served orders. Dinex sends it each completed order that is out of the kitchen (operation status ready or completed), once per order, with item/ingredient SKUs, replacements, removals and add-ons (`CostControlService`). |

## Domain Dictionary (entity → business identity)
- `users` — branch/back-office staff; single role, single branch.
- `branches` — locations; own tax policy, invoice identity, day cycle, Noon outlet link.
- `branch_settings` — printer/scanner/receipt-language per branch.
- `categories`/`parent_categories` — menu navigation DAG with levels 0–2.
- `items`/`item_categories`/`item_ingredients`/`item_attributes` — sellable products, their menu placement, recipe lines, variants.
- `ingredients` — bilingual recipe components (pcs/gram/ml/liter), optionally assigned to a leaf `ingredient_categories` node.
- `ingredient_categories` — three-level ingredient tree (type > category > sub category).
- `item_ingredient_replacements` — allowed same-leaf-category substitutes per recipe line, with `extra_charge`.
- `item_addons` — meal → add-on item offers.
- `orders` — sales ledger rows (UUID); frozen money + dual status + day-close stamps.
- `order_items` — line snapshots (qty, unit price incl. fee share, meal group, line `type`, `parent_order_item_id`).
- `order_item_ingredients` — per-order-line recipe snapshot (default/replaced/removed).
- `order_payments` — tender records enabling split payment; source of truth for "how was it paid". 
- `discounts` — applied discount per order (type, value, computed value, promo link).
- `promo_codes` — named discount templates (percentage/amount), no expiry.
- `payment_types` — tenders; surcharge config; `is_external` channel marker; platform mapping.
- `order_types` — service modes with **fixed standard IDs**: Dine-in (1), Pickup (2), Delivery (3); the IDs are part of the business contract (Noon dispatch keys on 3).
- `order_type_payment_type` — allowed tender per service mode (client-enforced).
- `platforms`/`platform_tokens`/`platform_payment_type` — external systems, their auth, and their tender mapping (`type` pivot: `branch-order`, `branch-plan`). A platform's `slug` is recorded as the item source (`items.created_from`) of items it pushes; it does not decide whether the platform may push items.
- `external_orders_data` — platform order context (customer, address, geo, car number) + `is_delivered`.
- `order_trackers` — Noon task mirror per order.
- `noon_outlets` — Noon outlet registry mirror.
- `integration_logs` — webhook/integration audit. For CostControl (`provider = cost_control`) one order has many rows, one per request sent (login and order creation, each try); the nullable `attempt` column records which job try wrote the row.
- `daily_sales_reports` — DSR PDF registry (parent sales + child items rows).
- `dashboard_settings` — global key/value business settings.
- `oauth_user_tokens` — staff bearer tokens (1-day, single-session).

## Acronyms
- **POS** — Point of Sale. **DSR** — Daily Sales Report. **TRN** — Tax Registration Number (UAE VAT). **VAT** — Value Added Tax (5% UAE). **COD** — Cash on Delivery (always 0 here). **EAV** — Entity-Attribute-Value (item attributes). **CRUD** — Create/Read/Update/Delete. **mp_task_nr** — Noon's marketplace task number (delivery reference).

## Important Constants (hardcoded in code)
| Constant | Value | Where |
|---|---|---|
| VAT rate | 5% (`×100/105` net factor, `×5/100` VAT) | Order pricing, DSR, reports |
| Token lifetime (users & platforms) | 1 day | Auth services |
| Token length | 80 chars (SHA-256 stored) | Auth services |
| Order statuses | active 1, completed 2, cancelled 3, returned 4, inReview 5 | `OrderStatusEnum` |
| Operation statuses | inProgress 1, completed 2, ready 3 (🚩 renamed from `pending`, draft · 2026-09; API still accepts `pending` as alias), cancelled 4, inReview 5 | `OperationStatusEnum` |
| Noon statuses | pending_assignment 1 … delivered 6, cancelled 7 | `NoonOrderStatusEnum` |
| Order-type ID standard | Dine-in 1, Pickup 2, Delivery 3 — **fixed company standard**; Noon dispatch checks `OrderType::DELIVERY` | `OrderType` model constants + `order_types` seeder (`OrderTypesEnum` deprecated) |
| Invoice paper | 70 × 140 mm, DPI 300 | OrderService |
| DSR paper | 65 × 250 mm, DPI 300 | DailySalesRepository |
| Timezone for documents | Asia/Dubai | invoices, DSR, exports |
| Split-payment tolerance | 0.01 | orderUpdate |
| Payment amount minimum | 0.01 (client-side rule) | Order requests |
| API rate limit | 60 req/min per user/IP | RouteServiceProvider |
| Noon webhook throttle | 60/min | webhook route |
| Webhook job retries | 3 tries; backoff 30/60/120 s; queue `noon` | ChangeOrderStatusNoonWebhookJob |
| Noon prepaid value | `ceil(final_price)`; COD 0 | NoonIntegrationService |
| Coordinate format | signed digits, truncated to 9 | NoonIntegrationService |
| Default item image | `items/default-item.png` | ItemController |
| Guest key | hardcoded string in `config/app.php` | GuestApiMiddleware |
| Seeded superadmin | `admin@dinex.com` / `12345678` | UserSeeder ⚠ |
| Noon client code | `noon_food`; country `ae` | dispatch payload |
| 🚩 Item default prep time | 10 minutes | `Item::DEFAULT_PREPARATION_TIME` |
| 🚩 Order prep-time scaling | 1–3 meals ×1, 4–8 meals ×2, 9+ meals ×2.5 (rounded up) applied to the slowest item's `preparation_time` | `OrderService::calculateOrderPreparationTime` |
| 🚩 Order History invoice ZIP caps | 10,000 invoices/download; 100 regenerated missing PDFs/request; flush every 1,000 | `ReportsRepository` |
| Add-on menu category code | `ASP_CAT_1000` (this category and all below it hold add-on items) | `Item::ADDON_CATEGORY_CODE` (`app/Models/Item.php`) |
| Ingredient category max level | 2 (three levels: 0 type, 1 category, 2 sub category) | `IngredientCategory::MAX_LEVEL` |
| Order line types | `item`, `addon` | `OrderItem::TYPE_ITEM`, `OrderItem::TYPE_ADDON` |
| Recipe snapshot statuses | `default`, `replaced`, `removed` | `OrderItemIngredient::STATUS_DEFAULT` / `STATUS_REPLACED` / `STATUS_REMOVED` |
| Item push dry run | `POST /pos/items/cc/item-creation-simulate` validates and saves exactly as an item push by the calling platform (any authenticated platform; its `slug` is the would-be item source), rolls back, and returns the validated payload plus an item preview; nothing is stored | `Pos\ItemController::ItemCreationSimulateCC` |
| CostControl order sources | `standard`, `branch-plan`, `delivery-app` (external order: `branch-plan` when the platform sent it as one, else `standard`; POS order: `delivery-app` when a line used the delivery-app price, else `standard`) | `CostControlService::SOURCE_STANDARD` / `SOURCE_BRANCH_PLAN` / `SOURCE_DELIVERY_APP` |
| CostControl integration provider | `cost_control` | `CostControlService::PROVIDER` |
| CostControl request timeout | 30 s | `CostControlService::request` |
| Composition lookup limit | 50 results per lookup | `ItemCompositionService::LOOKUP_LIMIT` (`app/Services/Dashboard/ItemCompositionService.php`) |

## Configuration Affecting Business (`dashboard_settings` keys)
| Key | Business effect |
|---|---|
| `logo`, `company_name`, `invoice_header_slogan`, `invoice_footer_slogan` | Invoice/DSR branding |
| `address`, `trn`, `display_trn` | Invoice legal identity (branch TRN also exists and is what invoices print) |
| `display_order_number` | Show/hide order number on receipts |
| `order_number_start` | Base of the daily order number sequence |
| `close_day_automatic`, `automatic_day_end_time`, `close_day_manual` | **Defined but not implemented** — no scheduler exists |

## Feature Flags (implicit)
- `branches.is_taxable` — per-branch VAT on/off (default on).
- `payment_types.has_additional_fee` — per-tender surcharge on/off.
- `payment_types.is_external` — routes orders to the external tab & platform callbacks.
- `payment_types.is_subscription` — **dormant** (no logic).
- `items.is_custom` — custom-meal component behavior.
- `categories.is_not_main` — hide from POS home.
- `noon_outlets.is_serviceable`, `is_active` — stored but **never checked** before dispatch.

## Environment-Dependent Behavior
| Concern | production | non-production |
|---|---|---|
| Noon API | `https://food-api-team.noon.team/public/v1` + `NOON_LIVE_API_KEY` | `…noonstg.team…` + `NOON_TEST_API_KEY` |
| Noon webhook auth | 503 if `NOON_WEBHOOK_API_KEY` unset | bypassed in `local`/`testing` |
| Platform callback (Asparagus) | `live_url` = `https://test.asparagus.ae` (⚠ Asparagus's *production* host despite the name) | `test_url` = `https://temptest.asparagus.ae` |
| CostControl API (`services.cost_control.base_url`) | `COST_CONTROL_UAT_URL` (CostControl's UAT host) | `COST_CONTROL_QC_URL` (CostControl's QC host) |
| Logging | `integration`, `noon_integration` and `cost_control` daily log channels | same |

### CostControl configuration (`config/services.php` → `services.cost_control`)
| Key | Value |
|---|---|
| `base_url` | `COST_CONTROL_UAT_URL` when `APP_ENV=production`, otherwise `COST_CONTROL_QC_URL`; when empty, orders are skipped (logged) |
| `login_url` | `/api/v1/platform/login` |
| `order_creation_url` | `/api/v1/platform/orders/order-creation` |
| `username` / `password` | `COST_CONTROL_USERNAME` / `COST_CONTROL_PASSWORD` |
| `token_ttl` | `COST_CONTROL_TOKEN_TTL` minutes (default 50) — how long the cached login token is reused; a 401 forces one fresh login and resend |

### CostControl log channel (`config/logging.php` → `cost_control`)
- Daily file `storage/logs/cost_control-YYYY-MM-DD.log`.
- One entry per request sent (login and order creation), plus one warning per skipped order (order missing, not completed-and-ready, branch without `mapper_slug`, no base URL) and permanent job failures.
- Passwords and tokens (request `Authorization` header, login `password`, `token`/`access_token`/`refresh_token` in responses) are written as `***`, both in the log file and in `integration_logs`.

## Consolidated TODO / Risk Register (highest business impact first)
1. ~~**Order update always responds as an error after committing** (missing second URL)~~ — **fixed 2026-07-07**: controller now returns only the PDF URL with an "updated" message.
2. ~~**Per-line fee bug in order creation** (used stale `$itemPrice`)~~ — **fixed 2026-07-07**.
3. ~~**Platform callbacks hit the platform's TEST URL in production**~~ — **fixed 2026-07-07**: callbacks now use `live_url` when `APP_ENV=production`, `test_url` otherwise.
4. ~~**Day-close window uses the globally-earliest open order**~~ — re-analysis showed the request-bound `BranchScope` global scope was implicitly branch-filtering this query in normal cashier requests; the risk was limited to non-request contexts (console/queue) and branchless users. **Hardened 2026-07-07** with an explicit `branch_id` filter in `DailySalesRepository::dsr()`.
5. **`close_orders` guard bypass** — presence of the parameter (even `0`) skips the active-orders check.
6. ~~**Unauthenticated endpoints:** dashboard branch list; Noon test dispatch/fetch endpoints.~~ — **fixed 2026-07-07**: branch list moved inside the admin auth group; Noon test endpoints now require a staff token (`auth_api`). ⚠ Verify the dashboard frontend doesn't call `branches/list` before login.
7. **No status-transition rules, no discount cap** — fraud/error surface at the POS. *Addressed 2026-07-07:* day-closed orders are now frozen (status/kitchen-status/approve-reject all blocked); qty is now validated (BL-4); free live-day transitions and uncapped discounts remain **accepted business policy** per the product owner's decision.
8. ~~**`inReview` platform orders silently swept into day close.**~~ **Fixed 🚩 SPRINT 1 · 09-08-2026** — day close is now hard-blocked while any `inReview` order exists (Module 6).
9. ~~Approved-then-dispatched delivery orders **cannot be cancelled at Noon** (cancel function unwired).~~ **Fixed 🚩 SPRINT 1 · 09-08-2026** — `cancelNoonOrder` is wired into the reject/cancel flow (Module 8).
10. ~~Duplicate discount rows on update; mobile delivery fee lost on update.~~ **Both fixed 2026-07-07** (BL-1, BL-2).
11. Automatic day-close settings exist without any scheduler.
12. Dead/legacy code: old order repositories, `MobileOrderController`, `ReportsRepository2`, `oldstore`, `dsrOld`, `CategoryTestFucntionController`, misspelled `ResponseMessageEnum` constant.
13. Public, guessable CSV export and PDF invoice URLs (revenue data exposure).
14. Seeded default superadmin credentials in the repo.
15. `orders.related_date` migration file contains nested `Schema::table` and references `closed_date` — migration hygiene risk on fresh installs.
16. Ingredient categories are maintained manually; no CostControl sync exists for ingredient categories. Items (with recipe, replacements and add-ons) sync from CostControl via `POST /pos/items/cc/item-creation` (Module 3).
17. No CostControl platform row exists in `platforms` / `PlatformSeeder`, so CostControl cannot authenticate to push items until it has its own platform row and token; its items will then be labelled (`items.created_from`) with that row's `slug`.
18. Ingredients without a category cannot have replacements until they are categorised (648 uncategorised in the local dataset) (Module 3).
19. Reports and the DSR do not distinguish meal lines from add-on lines by `order_items.type` (Modules 4, 6, 7).
20. CostControl receives the daily `order_number` as its `order_id`; order numbers reset at day close, so the same number can repeat across days for CostControl.
21. The CostControl login token is read from an assumed response field (`data.token`, `token`, `data.access_token` or `access_token`); any other shape fails every send with "returned no token".

## Consolidated Open Questions (cannot be inferred from code)
1. Refund/return money flow — `returned` status exists with no financial counterpart.
2. Canonical accounting source: frozen DSR PDFs vs live dashboard numbers.
3. Commercial meaning of "Branch plan" tender and the dormant subscription flag.
4. Whether cross-midnight shifts should report on open date (current) or close date.
5. Intended enforcement of order-type ↔ payment-type matrix (server or client?).
6. ~~Whether Noon out-of-order webhook statuses should be rejected (currently accepted).~~ **Resolved 🚩 SPRINT 1 · 09-08-2026** — status regressions are now rejected (BL-9, Module 8). Noon's own retry/ordering contract is still unconfirmed.
7. Ownership of UAE VAT-invoice compliance for the 100/105 method and rounding.
8. Why chief holds order CRUD permissions that no route exposes.
9. Whether one guest = one order is acceptable to the business for per-guest KPIs.
10. Expected behavior when a branch has no Noon outlet but sells delivery (today: silent non-delivery).
11. Replacement `extra_charge` lets a replacement raise the line price, which conflicts with the recorded decision that replacements never change the price — needs PO confirmation (Modules 3, 4).
12. Catalog-level add-ons (`item_addons`) conflict with the recorded decision that add-ons have no catalog relation and are linked in the cart only — needs PO confirmation (Modules 3, 4).
13. Whether reports/DSR should split meal and add-on sales by `order_items.type` (Modules 4, 6, 7).
14. Who creates CostControl's platform row (and chooses its slug) with its related user and token, and when — CostControl cannot push items until it exists, and that slug becomes the item source of its items.
15. Whether the CostControl dry-run endpoint (`/pos/items/cc/item-creation-simulate`) should be restricted to the CostControl platform; today any authenticated platform can call it.
16. Whether CostControl treats `order_id` (the daily `order_number`) as unique, or needs a globally unique order reference (e.g. the order UUID, or number + branch + date).
17. The exact field CostControl's login response returns the token in — the code accepts `data.token`, `token`, `data.access_token` or `access_token`.
