Foodops
All docs

Settings · Updated 2026-08-22 · vb0a427d

Outlets

Audio narration for this article is generated with the weekly docs refresh.

Plain-English summary

Outlets are your physical restaurant locations — each with its own registers, kitchens, floor/table layout, service toggles (dine-in, delivery, takeout), online-ordering switch, bank details, opening hours, logo and payment QR. An organisation can have multiple outlets; they share master data (items, customers, suppliers) but run their own sessions and stock. In the admin backoffice the Outlets page shows one tab per outlet, and each tab stacks the outlet's details plus its Registers, Kitchens, Services, Floor layout and Bank details sections. An outlet with active registers cannot be deleted.

When you'd use this

  • Onboarding a new branch — create the outlet, add registers, kitchens and tables, switch on services
  • Managing per-outlet settings: opening hours, default warehouse, logo, payment QR, managers
  • Turning delivery/takeout/online ordering on or off per location
  • Multi-site reporting — outlets are the grouping key

How to access

  • Admin backoffice (canonical): /settings/restaurant/outlets — page "Outlets" ("Manage your physical and virtual outlets."), sidebar Settings → Outlets group → Outlets (settingsNavConfig.ts group outlets). [code: foodops-admin-app/apps/admin/src/pages/settings/restaurant/OutletsPage.tsx; settingsNavConfig.ts]
  • Legacy back-office (RETIRED at July-2026 launch): the old app.foodops.io Settings → Restaurant Outlets (haafai.app.foodops) is gone — the admin backoffice is the only surface.
  • API: api/settings/restaurant/... on the sales service — outlets plus all their child resources. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/RestaurantSettingsController.cs (present on main, ~115 KB)]

Walkthrough

Verified against foodops-admin-app@77ec95f + hubits-api-sales@5d917e5.

Creating / editing an outlet

  1. Settings → Outlets → Add outlet (empty state: "Add your first outlet to start taking orders."). [code: OutletsPage.tsx:98-113]
  2. The form collects: Name, Reference code, Phone, Email, Default warehouse (dropdown), Opens/Closes times, address (Street / City / Postcode), Logo upload and Payment QR upload, plus outlet managers (user assignments). [code: OutletFormDialog.tsx:205-381; outletSettingsService.ts:21-59]
  3. Save — multipart POST/PUT /api/settings/restaurant/outlets[/{id}]; logo and QR can also be removed via RemoveLogo/RemovePaymentQR flags. [code: outletSettingsService.ts:103-114; RestaurantSettingsController.cs:1530,1611]

The outlet detail tab

Each outlet tab stacks seven sections [code: OutletDetailPanel.tsx]:

  1. Outlet details — the profile, with Edit and Delete. Delete is disabled while the outlet has active registers ("Outlet has active registers"); the API enforces the same: "Outlet has active registers. Please close and delete all registers before deleting the outlet." [code: OutletDetailPanel.tsx:62-63; RestaurantSettingsController.cs:1402-1414] NEW 2026-07: the details card now shows an outlet-type badge (Store icon + type label) — deterministically "Restaurant" for FoodOps outlets, since the label resolver returns "Retail" only when outletTypeId === OUTLET_TYPE_RETAIL and "Restaurant" otherwise. [code: foodops-admin-app/apps/admin/src/components/settings/restaurant/OutletDetailsSection.tsx — badge render (Store icon + outletTypeLabel); apps/admin/src/services/outletService.ts — outletTypeLabel(): outletTypeId === OUTLET_TYPE_RETAIL ? 'Retail' : 'Restaurant' (added d4ac7b5..395decb)]
  2. Registers — the outlet's sales registers (create/edit/delete via api/settings/restaurant/registers...; a register with unclosed sessions is the blocker for outlet deletion). See Sales Registers. [code: RestaurantSettingsController.cs:2093-2469]
  3. Kitchens — kitchen stations for KOT routing (kitchens CRUD). [code: RestaurantSettingsController.cs:186-320]
  4. Services — three toggles: Dine-in ("Enable dine-in orders for this outlet"), Delivery, Takeout; Delivery and Takeout each pick the register that owns that service's orders (service-table-register / update-service-table-register, toggle via outlets/toggle-order-type). [code: ServicesSection.tsx:30-50,111-113; RestaurantSettingsController.cs:1817-2090]
  5. Floor layout — floors with a drag-and-drop FloorCanvas of tables (table templates for quick adds), saved via tables/layout; the QR codes button opens the per-table QR modal for table ordering. [code: FloorLayoutSection.tsx; RestaurantSettingsController.cs:507-636; QRCodesModal.tsx]
  6. Bank details — per-outlet bank accounts with set-primary and active toggles (outlet-bank-details...). [code: RestaurantSettingsController.cs:1037-1280]
  7. Reference number configuration — a "Use Unique Reference Numbers" toggle that switches the outlet from org-wide numbering to its own sequences, plus per-outlet prefixes for Invoice, Credit Note, Payment and Quotation documents, a starting number, and a sequence-reset action. [code: ReferenceNumbersSection.tsx]

Note: Outlet types in the shared admin app (NEW 2026-07) The Outlets settings list is now brand-scoped by outlet type: the FoodOps admin fetches only restaurant-type outlets — getBrandOutletTypeId() returns OUTLET_TYPE_RESTAURANT (= 2) for every non-salesmade brand — and stamps that type onto newly created outlets. Retail (Salesmade) outlets additionally hide the Kitchens / Services / Floor-layout sections, so FoodOps outlets keep all the sections documented above — this walkthrough is confirmed by the change, not contradicted. [code: foodops-admin-app/apps/admin/src/services/outletService.ts — getBrandOutletTypeId(): getBrand().id === 'salesmade' ? OUTLET_TYPE_RETAIL : OUTLET_TYPE_RESTAURANT (added d4ac7b5..395decb); OutletsPage.tsx — outletSettingsService.list(organisationId, getBrandOutletTypeId()); OutletFormDialog.tsx — outletTypeId: editForm.outlet.outletTypeId ?? getBrandOutletTypeId(); OutletDetailPanel.tsx — isRetail = outlet.outletTypeId === OUTLET_TYPE_RETAIL gating]

Online ordering switch

The outlet's online orders toggle (POST outlets/{outletId}/toggle-online-orders) flips IsAcceptingOnlineOrders and broadcasts OutletOnlineOrdersToggled over SignalR so counters react live. [code: RestaurantSettingsController.cs:1949-1981; foodops.counter.app signalRService.ts:299-301]

Key concepts

  • Outlet = location scope — sessions, registers, kitchens, floors/tables, delivery sectors, bank details and business-day windows all hang off the outlet; items/customers/suppliers are org-wide.
  • Service toggles per outlet — dine-in/delivery/takeout are enabled per outlet (driving which order-type buttons the POS shows); delivery and takeout are anchored to a chosen register. [code: ServicesSection.tsx; POSPage orderButtonConfig]
  • Delete guard — outlets with active registers can't be deleted; delete registers (after closing their sessions) first. [code: RestaurantSettingsController.cs:1402-1414]
  • Default warehouse — links the outlet to the stock location its sales deduct from / receives default to. [code: OutletFormDialog.tsx:250-260]
  • Business-day window — outlet opening hours feed the business-day range used by the Dashboard and reports (late-night trade lands on the right day). [code: hubits-api-sales ClientAppDashboardController.cs:48-54]
  • Logo & payment QR — the outlet logo brands receipts/ordering surfaces; the payment QR renders for QR-payment flows. Both upload AJAX-style with drag-and-drop on both surfaces (legacy refactor ee5fa8c).

Common questions

Q: How many outlets can an organisation have? A: No limit found in current code; the page renders one tab per outlet.

Q: Why can't I delete an outlet? A: It still has active registers — close their sessions and delete the registers first; both the UI and API enforce this. [code: RestaurantSettingsController.cs:1413]

Q: How do I enable delivery for one branch only? A: Open that outlet's tab → Services → toggle Delivery on, and pick the register that owns delivery orders. Other outlets are unaffected.

Q: What does the register picker under Delivery/Takeout do? A: Those services' orders are anchored to a specific register (the service register), so their sessions and cash land somewhere deterministic. [code: ServicesSection.tsx:111-113; RestaurantSettingsController.cs:2000-2090]

Q: How do per-table QR codes work? A: Floor layout → QR codes — the modal lists QR codes per table for the table-ordering flow on the online ordering site. [code: QRCodesModal.tsx]

Q: Can an outlet have its own menu? A: Menu groups are organisation-scoped in the current API — per-outlet menu assignment was not found in current code as of 2026-06-11. See Menu Groups.

Q: What is the Reference code for? A: A short outlet identifier used in references/outlet codes (e.g. the ordering site's outlet-code entry); keep it short and unique.

Q: Who can manage outlets? A: Admin-app users behind the standard protected routes; outlet managers can be assigned on the form (the OutletManagers[] payload), giving per-outlet responsibility. [code: outletSettingsService.ts:21-27]

Q: Do opening hours close the POS automatically? A: No evidence of enforcement — Opens/Closes define the business-day window for reporting; they don't lock registers (not found in current code as of 2026-06-11).

Q: What happens when I toggle online orders off? A: IsAcceptingOnlineOrders flips immediately and a SignalR broadcast tells connected counters; the ordering site stops accepting orders for that outlet. [code: RestaurantSettingsController.cs:1949-1981]

Q: Can I run a virtual outlet (no physical floor)? A: Yes — the page subtitle explicitly covers "physical and virtual outlets"; skip floors/tables and enable only the services you need.

Q: Where do delivery sectors fit? A: Delivery sectors (zones with charges) are restaurant-settings resources alongside outlets (delivery-sectors CRUD) and are picked on delivery orders — see Deliveries. [code: RestaurantSettingsController.cs:749-863]

Edge cases and known issues

  • Delete order matters — sessions → registers → outlet; attempting out of order returns the active-registers error.
  • Logo/QR upload quirk (legacy) — the old form's drop zones ("Click or drop an image" / "Click or drop a QR image") were introduced in ee5fa8c; older screenshots show the pre-AJAX upload.
  • One surface now — the legacy Settings → Restaurant Outlets pages are retired at the July-2026 launch; the admin app is the only outlet-config surface (both wrote the same outlet records via the sales-service API).
  • Floor canvas saves the whole layout — table positions persist via a bulk tables/layout POST; concurrent edits from two admins are last-write-wins. [code: RestaurantSettingsController.cs:636]

What changed since 2026-04-15 (NEW 2026-05)

  • Outlet image upload refactored to AJAX (commit ee5fa8c, 2026-04-22) — logo and payment-QR uploads on the outlet add/edit form now use dedicated AJAX endpoints (UploadLogo / RemoveLogo / UploadPaymentQR / RemovePaymentQR) with drag-and-drop support and immediate feedback. The old IFormFile-based form-post upload path was removed from RestaurantOutletsController and AddEditOutlet view model. Verified 2026-05-04: the edit form shows a Logo drop zone ("Click or drop an image") and a Payment QR drop zone ("Click or drop a QR image") in separate sections.

    Outlet Edit form — AJAX Logo upload drop zone and Payment QR upload drop zone (commit ee5fa8c)

Related

  • Settings Overview — the wider settings map

  • Sales Registers — the registers each outlet hosts

  • Tables (Dine-In) — the floor/table layer in depth

  • Deliveries — sectors + the delivery service this page enables

  • Dashboard — outlet filter + business-day window

  • 2026-08-21 — [email protected] (commit subject: "feat: void bin approval mode + acknowledge review path") extends the void-bin feature this entry's verification history already flagged as frontend-observed/backend-confirmed (EnableVoidBin, 2026-08-03 note). BuildOutletForEditResponse, CreateOutlet and UpdateOutlet now also read/write a new VoidBinApprovalMode field on Outlet alongside EnableVoidBin — the backend now persists an approval-mode setting for the void bin, not just an on/off toggle, consistent with the commit's "acknowledge review path" language (a void likely needs review/acknowledgement rather than auto-applying once void bin is enabled). No foodops-admin-app diff was supplied, so it's unconfirmed whether OutletFormDialog's 'Order corrections' section yet exposes an approval-mode control alongside the existing 'Enable void bin' toggle — flag for a UI check. Doesn't contradict anything currently in the entry's body (void bin isn't documented there yet, only in prior verification notes); this note just keeps that backend-tracking thread current pending a real documentation pass covering the void bin feature end-to-end. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-03 — [email protected] completes the backend side of two behaviors already flagged as frontend-only NEW_INFO in this entry's prior verification note (from [email protected]). (1) BuildOutletForEditResponse, CreateOutlet and UpdateOutlet now read/write a new EnableVoidBin field on Outlet — this is the backend persistence for the admin app's 'Enable void bin' toggle in the OutletFormDialog's 'Order corrections' section, previously observed frontend-only. (2) The outlet reference-number-config endpoint no longer peeks a single scope (outlet.UseUniqueReferenceNumbers ? id : null); it now peeks BOTH the org-wide sequence and this outlet's own sequence for all four document types (Invoice/CreditNote/Payment/Quotation) in one call, with the org-wide preview reading the org sequence's own stamped prefix (falling back to the configured prefix only if the org row has none). This is exactly the backend support needed for ReferenceNumbersSection's live preview that switches between org-wide and outlet-specific previews as the user toggles 'Use Unique Reference Numbers' or edits a prefix, also previously frontend-only. Neither addition contradicts any documented claim (still absent from the entry's walkthrough/body); this note upgrades both from 'frontend-observed, backend unconfirmed' to 'confirmed end-to-end on the backend', still pending a real documentation pass and UI re-check. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-03 — [email protected]. Most of this range is a perf refactor (OutletsPage's outlet list moved onto a cached useOutlets/react-query pattern) — internal only, same endpoint, same load/error/retry UX, not doc-affecting. Two additions to surfaces this entry documents are not yet captured: (1) OutletFormDialog gained a new 'Order corrections' section with an 'Enable void bin' toggle (enableVoidBin); turning it ON requires an explicit confirmation dialog ('Staff cancel/remove permissions will be bypassed while this is on. Continue?'), turning it OFF is immediate with no prompt. Neither the walkthrough's form-field list (step 2) nor Common questions mention this. (2) ReferenceNumbersSection gained a live, unsaved preview of the next reference number per document type (Invoice/Credit Note/Payment/Quotation) that switches between the org-wide preview and an outlet-specific preview (prefix+starting number, or the running counter once sequencesInitialized) as the user toggles 'Use Unique Reference Numbers' or edits a prefix — plus new per-entity 'Set Next Number' override fields that only submit a value when filled in (empty = leave that counter alone). This isn't in the entry's Reference-number-configuration walkthrough (step 7) or Common questions either. Neither addition contradicts existing claims — both are net-new fields/sections layered onto sections already documented. Not yet confirmed live in the UI. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-18 — [email protected] adds per-outlet, plan-based resource caps to three of the seven documented outlet-tab sections: Kitchens, Registers, and Floor layout. Each section now reads planLimits off the subscription store (selectSubscriptionEnabled / selectSubscriptionSummary) and, when the outlet's current count of kitchens/registers/floors hits the plan's maxKitchensAllowed/maxCountersAllowed/maxFloorsAllowed, disables the section's 'Add kitchen' / 'Add register' / 'New floor' button and surfaces an inline amber notice: "Your plan allows a maximum of N <kitchen|register|floor>(s) per outlet. Upgrade your plan to add more." The gate is feature-flagged — when selectSubscriptionEnabled is false (or the plan has no such limit configured), the buttons stay enabled with no cap enforced, per the new KitchensSection/RegistersSection unit tests. This is additive, user-facing behavior not yet in the entry's Kitchens/Registers/Floor-layout descriptions or Common-questions; worth a documented note (e.g. "kitchen/register/floor counts may be capped by your subscription plan") on the next real pass. Separately, OutletDetailsSection's online-orders toggle and ServicesSection's service toggles changed their inactive-state background token from bg-muted to bg-input — a cosmetic Tailwind class swap with no behavioral effect, not doc-affecting. Not yet confirmed live in the UI. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-18 — [email protected] (security patch) touches the Floor layout and Kitchens surfaces this entry documents, but introduces nothing that contradicts current claims. Two additions worth a real pass: (1) Creating a floor or kitchen (CreateFloor, CreateKitchen) now runs through a new PlanLimitHelper — an org that is hard-disabled (billing/plan gate) or has hit a plan-tier floor/kitchen count cap gets a {success:false, message:...} response instead of the row being created; neither the Kitchens nor Floor-layout sections of this entry currently mention any such cap, and it's unconfirmed whether the admin UI surfaces the returned message or fails silently — flag for a UI check. (2) UpdateFloor/DeleteFloor/UpdateKitchen/DeleteKitchen/UpdateTable were fixed for an IDOR (lookup by bare id via FindAsync replaced with an org-scoped FirstOrDefaultAsync), and the controller gained a class-level [ApiKeyOrSessionAuth] attribute requiring auth on endpoints that were previously reachable anonymously — both are backend hardening with no visible change to the documented authenticated-admin workflow, informational only. No foodops-admin-app diff was supplied, so whether the limit/hard-disabled messages are wired into OutletDetailPanel's Kitchens/Floor-layout sections is unconfirmed. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-10 — [email protected] (still main, no launch-affecting subject in the commit list but the diff touches the Outlets surface materially). Three additions not yet in this entry: (1) OutletFormDialog gained an Address Line 2 field and a Country selector (new addressLine2/countryId state, OutletCountryLookup type) — the walkthrough's address field list ("Street / City / Postcode") is now incomplete, and OutletDetailPanel now fetches the full edit-form payload (getEditForm instead of getForEdit) specifically to resolve and display country + outlet-manager names. Logo/QR upload in the form dialog was also redesigned from inline pickers to a shared ImageField preview-box component ('Choose image'/'Replace'/'Remove'). (2) QRCodesModal was overhauled from a flat single-list QR generator into a full 'QR Design Studio' (explicitly a port of the legacy haafai.app.foodops RestaurantOutlets Index page): a floor selector scopes which tables' QR rows are listed, card design is now customizable (colors, header/footer text, logo image or text), QR rendering moved from the qrcode lib to qr-code-styling with a live styled preview, and export is a bulk 2x3in PDF built via html2canvas — the per-table URL pattern also changed from /t/{id} to /table/{id}/login. (3) Two smaller UX changes: DeliverySectorFormDialog now prefixes the delivery-charge input with the outlet's currency code, and ReferenceNumbersSection now hides the prefix/starting-number editor entirely unless 'Use Unique Reference Numbers' is toggled on (previously always visible). None of these contradict documented claims — the sections/fields described still exist — but the Outlet details, form-field list, and QR-codes description should be refreshed on the next real pass to capture the new Country field and the Design Studio's floor/card-customization/PDF-export capabilities. Not yet confirmed live in the UI. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-02 — hubits-api-sales@7b2a1a9 adds a new outlet child-resource not yet in this entry: per-outlet reference-number configuration (GET outlets/{id}/reference-config, backed by new OutletSequenceNumberSettings rows keyed on SequenceNumberTypeId). It exposes/derives custom prefixes and a starting number for Invoice (INV), Credit Note (CN), Payment (PAY) and Quotation (QT) sequences, gated by an Outlet.UseUniqueReferenceNumbers flag — when off, the org-wide sequence applies; when on, counters are scoped per outlet. Only the backend controller diff was supplied (no foodops-admin-app diff), so it's unconfirmed whether the admin backoffice's outlet detail tabs (currently documented as six sections: details/registers/kitchens/services/floor layout/bank details) yet expose a seventh 'Reference numbers' section — flag for a UI check before adding a documented section. Separately, GET outlets gained an optional outletTypeId query param (Retail=1/Restaurant=2) so the same endpoint can be scoped for sibling brand apps (e.g. Salesmade/retail); default remains Restaurant when omitted, so FoodOps admin behavior is unchanged — informational only, not a doc-affecting change. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-04: foodops-admin-app added a 7th outlet-tab section, Reference Number Configuration (new file sections/ReferenceNumbersSection.tsx, wired into OutletDetailPanel.tsx after BankDetailsSection). It lets an outlet opt into its own document-numbering sequences (toggle 'Use Unique Reference Numbers') with editable prefixes for Invoice/Credit Note/Payment/Quotation, a starting number, and a 'reset sequences' action — backed by outletSettingsService.getReferenceConfig/saveReferenceConfig/resetSequences. The same commit range also introduces an outlet-type distinction (OUTLET_TYPE_RETAIL) that hides Kitchens/Services/Floor layout for retail-type outlets, but this does not affect FoodOps-branded admin since OutletsPage.tsx already filters the outlet list to the brand's own outlet type — no FoodOps outlet is ever 'retail'. Body/walkthrough/Common-questions still need a real pass to describe the new section's fields in full; this note only fixes the section-count claim. (auto-applied by the truth pipeline; adversarially gated)

Note: Code-verified 2026-05-04 · codebase Verified_by: codebase · Last verified: 2026-05-04 · haafai.app.foodops:ee5fa8c Curated source_repos[] to Haafai.Restaurant.App/Areas/Settings/{Controllers/RestaurantOutletsController.cs,Views/RestaurantOutlets/**} (the real Razor monolith path). One behavior-affecting commit since 2026-04-15: ee5fa8c 2026-04-22 — outlet image-upload refactor to AJAX endpoints (see "What changed" above). Body remains stub-level pending a Playwright walkthrough.

Note: Re-pointed after backend microservice split 2026-06-03 · codebase Verified_by: codebase · Last verified: 2026-06-03 · haafai.app.foodops:bf7c9d2 + foodops-admin-app:f3ae3e9 Added haafai/foodops-admin-app (the FoodOps backoffice settings UI, prod branch main) with glob apps/admin/src/{pages/settings/restaurant/OutletsPage.tsx,components/settings/restaurant/**} — the React backoffice now hosts the outlet add/edit form, outlet detail panel, register/kitchen/floor/QR dialogs, and floor canvas. All paths verified via gh api ...?ref=main. Re-pinned the monolith glob to prod HEAD bf7c9d2 (path still resolves). Did NOT add foodops.api or hubits.microservices.

Important: Re-verified 2026-06-29 · codebase — NEW-STACK re-point (legacy app.foodops retired) Verified_by: codebase · foodops-admin-app:f6ffd0b (main) + hubits-api-sales:19190b9 (main) Legacy haafai.app.foodops removed from source_repos (Restaurant Outlets monolith pages are retired at launch). Confirmed hubits-api-sales/.../RestaurantSettingsController.cs still present on main (~115 KB) — the single backing controller for outlets + child resources. Confirmed the admin route /settings/restaurant/outlets lives under the Outlets settings group (settingsNavConfig.ts, renamed from the old "Restaurant" group). Re-pinned both repos to prod HEADs. Body code-citations carried forward from the 2026-06-11 walkthrough (line refs not individually re-read this pass — controller line numbers may have drifted; flag for re-check).

  • 2026-06-11 — codebase: promoted stub → draft against foodops-admin-app@77ec95f + hubits-api-sales@5d917e5 (+ monolith f06cdd9). Documented the per-outlet tabbed page and its six sections (details / registers / kitchens / services / floor layout + QR codes / bank details), the full form field set (name, reference code, phone, email, default warehouse, opens/closes, address, logo, payment QR, managers), service toggles with service-register anchoring, the online-orders toggle with SignalR broadcast, and the active-registers delete guard (UI + API). ADDED hubits-api-sales: RestaurantSettingsController.cs to source_repos — it is the single backing controller for outlets and all child resources (path verified on main). Per-outlet menus and hour-enforced register locking verified ABSENT. Confidence medium; UI pending Playwright walk.

Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.

Go beyond POS.
Choose the total solution.

Focus on creating the best experience for your guests while we handle the rest.