# Console

Web admin console manuals (CMS, Marketing, Kiosk, Employees).

# Getting started

Signing in, your profile, finding your way around, and why you see what you see.

# Signing in

> **Audience:** anyone using the console for the first time.

## Signing in

You sign in with your **employee identifier** — the same one your company uses for you, not an email address — and your password.

If you don't have a password yet, you cannot create one yourself. Someone with employee management rights sets it for you; point them at [Employees - Password](/books/console-yF7/page/employees-password).

## Two things the console may tell you at sign-in

**Your account has an expiry.** Employee accounts expire, and as the date approaches you are told how many days remain. This is a warning, not an error — you can still work. Ask whoever manages employees to extend it before it runs out, because once it does you cannot sign in to ask.

**Your password may be flagged as weak.** If it doesn't meet the strength rules you are told so, and you should change it — see [Your profile and password](/books/console-yF7/page/your-profile-and-password).

Neither message blocks you. Both are worth acting on the day you see them rather than the day they become a problem.

## If you can't sign in

Work through these in order:

| Check | What to do |
|---|---|
| Is the identifier right? | It is your employee identifier, not an email |
| Do you have a password at all? | A new employee has none until someone sets it |
| Has your account expired? | Ask for it to be extended — you can't do this yourself |
| Right customer/environment? | The console is per customer; confirm you are on the right address |

If all four are fine and it still fails, ask whoever manages employees to confirm your record exists and is active — see [Finding an employee](/books/console-yF7/page/finding-an-employee).

## After you're in

- **Set a password you'll keep** if yours was assigned to you — [Your profile and password](/books/console-yF7/page/your-profile-and-password).
- **Get your bearings** — [Finding your way around](/books/console-yF7/page/finding-your-way-around).
- **Understand why you see what you see** — [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why).

# Your profile and password

> **You need:** nothing beyond being signed in. Your profile is your own record.

## What your profile shows

Your own details as held in your employee record: your identifier, name, and contact details such as email, phone, city, address and date of birth where they are filled in.

It is a view of **your** record — the same record an employee manager sees in the Employees area, from your side.

> If something here is wrong, you generally cannot correct it yourself. Ask whoever manages employees — see [Editing an employee](/books/console-yF7/page/editing-an-employee).

## Changing your password

You can change your own password whenever you like, and you should if it was assigned to you by someone else.

**The rules are narrower than most systems**, so read them before choosing:

| Rule | Value |
|---|---|
| Minimum length | 8 characters |
| **Maximum length** | **10 characters** |
| Must contain | At least one uppercase letter |

The maximum is the one that catches people out. A long passphrase will be rejected — you need something between 8 and 10 characters with at least one capital.

Within that, favour something unpredictable over something memorable-but-guessable, and don't reuse a password from elsewhere.

## If you're told your password is weak

The console flags a password that doesn't meet the rules at sign-in. That flag doesn't lock you out, so it is easy to ignore for months.

Change it when you see it. It takes a minute and you already have the screen open.

## If you've forgotten it

There is no self-service reset. Someone with employee management rights sets a new one for you — see [Employees - Password](/books/console-yF7/page/employees-password). They cannot tell you your old password; they can only replace it.

## Related pages

- [Signing in](/books/console-yF7/page/signing-in) — first access and what the warnings mean
- [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why) — your roles and scope
- [Employees - Password](/books/console-yF7/page/employees-password) — for whoever is setting it

# Finding your way around

> **Audience:** anyone finding their way around for the first time.

## The areas

The console is organised by what you are doing, not by which team owns it.

| Area | What you do there | Chapter |
|---|---|---|
| **Hub** | Work live orders: assign, cancel, log incidents | [The Hub: what it is and who can do what](/books/console-yF7/page/the-hub-what-it-is-and-who-can-do-what) |
| **Delivery** | Configure how orders get assigned: zones, carriers, stores, the dispatcher | [What the dispatcher does](/books/console-yF7/page/what-the-dispatcher-does) |
| **Marketing** | Issue promotions, vouchers, subscriptions, wallet credit | [Marketing: the five areas](/books/console-yF7/page/marketing-the-five-areas) |
| **CMS** | Maintain content customers see, and the complaints tree | [CMS: what it covers](/books/console-yF7/page/cms-what-it-covers) |
| **Reports** | Read performance data | [Reports and Metrics: which to use](/books/console-yF7/page/reports-and-metrics-which-to-use) |
| **Metrics** | Investigate a specific order | [Order logs and timelines](/books/console-yF7/page/order-logs-and-timelines) |
| **Employees** | Maintain staff records and drivers | [Employees: what this area covers](/books/console-yF7/page/employees-what-this-area-covers) |
| **Roles** | Decide who can do and see what | [Roles and scope: the two ideas](/books/console-yF7/page/roles-and-scope-the-two-ideas) |

**You will not see all of these.** The menu reflects what your roles allow — see [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why).

## Brands

If your company runs more than one brand, most screens work **within one brand at a time** and offer a brand selector. With a single brand, the selector usually doesn't appear at all.

This matters more than it looks:

- Data is per brand. A report, an employee list, a promotion — all scoped to the brand you are in.
- **Switching brand changes what you are editing.** Creating a promotion in the wrong brand is easy and not always obvious afterwards.

Check the selector before creating or changing anything.

## Live versus historical

A recurring distinction across the console, and choosing wrongly wastes the most time:

| You want to… | Go to |
|---|---|
| Act on something happening now | **Hub** |
| Understand something that already happened | **Metrics** |
| See how a period went | **Reports** |
| Change how things will behave | **Delivery**, **Marketing**, **CMS** |

## Language

The console is available in more than one language, and the documentation exists in parallel: every page here has a twin in the other language.

Note that **content** language is separate from **interface** language. Banners and documents are stored per language and must each be filled in — see [Resources and sources](/books/console-yF7/page/resources-and-sources).

## Two pages worth bookmarking

- [Glossary](/books/console-yF7/page/glossary) — what brand, source, scope, hub and business date actually mean here.
- [FAQ](/books/console-yF7/page/faq) — the questions that come up across every area.

## When a screen isn't there

An area missing from your menu is almost always a permission, not a fault. [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why) explains how to tell, and who can change it.

# What you can see, and why

> **Audience:** anyone wondering why their console looks different from a colleague's.

Two separate things decide what you get. Almost every "why can't I…" question is one of them.

| | Decides | Set by |
|---|---|---|
| **Your roles** | What you can **do** | Whoever manages employees |
| **Your scope** | What data you can **see** | The same person, separately |

They are independent. You can have full rights over a narrow slice of data, or read-only rights over everything.

## Why a menu item is missing

Because your roles don't include that area. The console shows what you can act on; it doesn't list things and then refuse them.

If you need an area, ask for the role that covers it — the catalogue is in [The role catalogue](/books/console-yF7/page/the-role-catalogue), and most real jobs are two roles combined rather than one perfect fit.

## Why you see fewer rows than a colleague

Because your scope is narrower. Scope filters every screen to the brands, stores and sources you are allowed to see.

This is expected behaviour, and both of you are seeing the correct answer for yourselves. A store manager's report *should* differ from a regional manager's.

> This is the single most reported "the report is wrong", and it usually isn't. See [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## Why you can do something in one place but not another

Because permissions are granular. Being able to view orders in the Hub does not mean being able to cancel one; those are separate permissions, deliberately, so that critical actions can be given to fewer people.

[Access by section](/books/console-yF7/page/access-by-section) is the full grid of who can reach what.

## Getting something changed

| You need | Ask for |
|---|---|
| A whole area you don't have | A role — [The role catalogue](/books/console-yF7/page/the-role-catalogue) |
| One specific ability | An individual exception — [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions) |
| To see more data | A wider scope — [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope) |

Whoever manages employees can do all three. A brand-new role definition is rarer and goes through a request — see [Requesting a new or changed role](/books/console-yF7/page/requesting-a-new-or-changed-role).

## Before you ask

Check whether it is scope rather than permission: can you see the screen but not the data, or not the screen at all? The first is scope, the second is a role. Saying which saves a round trip.

# Delivery & Dispatcher

How automatic order assignment decides which driver or carrier delivers which order, and how to configure it.

# What the dispatcher does

> **Audience:** Operations and dispatch managers who configure, supervise, or explain automatic order assignment to their team.
> **Purpose:** Understand *what* the dispatcher does and *why* it makes a given decision.

The Dispatcher is the system that automatically decides **which driver (or external carrier) delivers which order**, and **when**. It runs on its own, on a schedule you control, and re-evaluates every store's pending orders each time it runs.

These pages explain the logic behind those decisions in business terms: what the algorithm optimises for, what determines priority, which settings are available to you, and what to expect — or not expect — from it.

## How to read this chapter

You don't need to read it front to back. Most questions land on one page:

| If you want to know… | Go to |
|---|---|
| What a term means | [Dispatcher glossary](/books/console-yF7/page/dispatcher-glossary) |
| What happens on each run | [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step) |
| Why an order stayed with an external carrier | [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier) |
| Why one driver was chosen over another | [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority) |
| Why an order changed driver | [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver) |
| Why an order was left unassigned | [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net) and the [Dispatcher FAQ](/books/console-yF7/page/dispatcher-faq) |
| How often it runs, and how to start or stop it | [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works) |
| Where to change a particular setting | [Where each setting lives](/books/console-yF7/page/where-each-setting-lives) |
| The whole flow on one page | [End-to-end overview](/books/console-yF7/page/end-to-end-overview) |
| A rule, without the explanation | [Quick reference rules](/books/console-yF7/page/quick-reference-rules) |
| How to report something that looks wrong | [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue) |

Once the dispatcher has made its decisions, the live board where a person can intervene is the Hub — see [The Hub: what it is and who can do what](/books/console-yF7/page/the-hub-what-it-is-and-who-can-do-what).

## What it does not do

The dispatcher is deliberately bounded. It does not try to find a mathematically perfect plan across every driver and every order at once, it never touches an order that has already left the store, and it does not reorder work just because one order is more urgent than another. Those limits are listed in full under [Known system boundaries](/books/console-yF7/page/known-system-boundaries) — they are design decisions, not faults, and knowing them saves a lot of guesswork.

# Dispatcher glossary

The terms below are used consistently throughout the Delivery & Dispatcher chapter.

| Term | Meaning |
|---|---|
| **Own Fleet / Own Drivers** | Your restaurant's own delivery staff — delivery riders and, in some cases, kitchen staff who can also deliver |
| **Third-Party Carrier** | An external delivery provider (e.g. a courier platform) used when your own fleet isn't the best option |
| [Priority Customers (Platinum)](/books/console-yF7/page/priority-customers-platinum) | An optional feature that gives certain orders — typically loyalty or VIP customers — a better chance of being delivered on time, without unfairly harming regular orders |
| **PG Mode** | A first-in-first-out (FIFO) assignment method: orders are assigned to the best available driver in the order they came in |
| **Google OR-Tools** | An advanced route-optimisation engine, normally used when there are more pending orders than available drivers, so routes can be planned more efficiently |
| **Last-Option** | A final, same-cycle safety net for orders that couldn't be placed by the normal process — see [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net) |
| **Idle** | A driver with zero orders currently assigned |
| **Waiting** | A driver who has orders assigned but hasn't left the store yet |
| **On The Way** | A driver who has already left the store to deliver at least one order |
| **At-Store Zone** | Being within roughly 350 metres of the store — close enough to be treated as "at the store" for assignment purposes |
| **Time To Branch** | Estimated time for a driver to arrive back at the store |
| **Time To Delivery** | Estimated time for a driver to complete a delivery |
| **Assignment Cycle / Run** | One full execution of the algorithm across all your stores, triggered automatically on the schedule you define |
| **Clubbing** | Grouping more than one order onto the same driver or carrier for a single trip |
| **Critical** | An order that is still unassigned and already past its promised delivery time |
| **Pending** | An order that is still unassigned but not yet late |

# The assignment cycle, step by step

Every time the dispatcher runs, it processes each store independently, following five steps in order.

## The five steps

| # | Step | What happens |
|---|---|---|
| 1 | **Fetch data** | Pulls current orders, available drivers, and store and zone configuration. This is also when any order still sitting with a Third-Party carrier from a *previous* cycle gets re-examined — see [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier). |
| 2 | **Cache check** | If nothing has changed since the last run, the store is skipped rather than re-processed, to save time and resources. |
| 3 | **Assign orders** | The core decision-making step, covered in [The three assignment mechanisms](/books/console-yF7/page/the-three-assignment-mechanisms). This is where Third-Party, your own fleet and — optionally — Priority Customers logic all come together. |
| 4 | **Post-process** | Results are categorised (new assignments, still-pending orders, orders sent to Third-Party, and so on), orders that can be grouped onto the same driver are identified, and anything still unassigned gets one final relaxed attempt — see [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net) — before being marked Pending or Critical. |
| 5 | **Save and notify** | Final assignments are saved, drivers are notified by push notification, and any relevant alarms are processed. |

> Step 2 is the reason a run can complete almost instantly for a quiet store. A skipped store is not an error, and nothing is lost — the next cycle picks it up the moment anything changes.

## What "independently, per store" means in practice

Each store is solved on its own. A driver shortage at one store never pulls drivers from another, and a store whose data hasn't changed is skipped without affecting the rest. This is also why a report of a dispatcher problem should always name the store — see [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue).

## Where the deeper detail lives

Steps 1 and 3 carry almost all of the decision-making, and each has its own page:

- Step 1's re-examination of carrier-held orders → [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier)
- The test for whether a replacement is genuinely available → [When does an alternative exist?](/books/console-yF7/page/when-does-an-alternative-exist)
- Step 3's assignment logic → [The three assignment mechanisms](/books/console-yF7/page/the-three-assignment-mechanisms)
- Step 4's final attempt → [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net)

# Re-checking orders already with a carrier

Before the dispatcher evaluates any *new* assignments, it first re-examines every order that is **already sitting with a Third-Party carrier from a previous cycle** — meaning a carrier has been given the order but hasn't yet left the store with it.

This happens during step 1 of [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step), for every store, every cycle. It never touches brand-new orders or orders already assigned to your own fleet.

## The three questions, in order

For each carrier-held order, up to three questions are asked. The first one that resolves the order stops the sequence.

### 1. Has the carrier been sitting on this order too long?

Governed by **Max time to accept** (set per store, per carrier — see [Where each setting lives](/books/console-yF7/page/where-each-setting-lives)).

- Limit exceeded **and** a real alternative exists right now → the order is cancelled with the carrier immediately and freed for reassignment in this same cycle.
- Limit exceeded but **no alternative exists** → the order is left exactly as it is. Cancelling would only leave it worse off, with nowhere better to send it.
- Limit not reached → move to question 2.

### 2. Is the carrier's own estimate for reaching the store slipping too far?

Governed by **Max time to reach store** (per store, per carrier).

If the carrier's estimated arrival at the store has drifted past this limit, the order is cancelled immediately — regardless of whether an alternative is available. An unreliable estimate from the carrier itself is reason enough to give up on it.

### 3. Are we still inside the free-cancellation window, and does our own fleet have room?

Governed by **Free-cancellation window** (per carrier, company-wide).

If yes to both, the order is reset to look **unassigned** — only in the dispatcher's working memory. Nothing is saved, and nothing is cancelled with the carrier yet. The order simply **rejoins the normal assignment process for that store**, exactly like any other unassigned order in that cycle.

> This is not a special "try own fleet" routine. From this point on, the dispatcher has no notion that the order was ever with a carrier — it goes through PG Mode, Google OR-Tools and Third-Party like everything else, under all the same rules.

**If the normal process assigns an own driver to it,** two things then happen in strict order:

1. A real cancellation is sent to the original Third-Party carrier.
2. **Only if that cancellation succeeds**, the new internal-driver assignment is saved.

If the cancellation fails, step 2 never happens: nothing changes and the order stays with the carrier. This avoids a confusing state where one order looks assigned in two places at once.

**If the normal process doesn't place it with an own driver,** the in-memory reset is discarded. The order goes back to exactly how it was — still validly assigned to the carrier — as if the reset had never happened. No cancellation is ever sent in this case.

## Questions 1 and 2 look similar but compare different things

This is the single most common source of confusion on this page.

| Question | What it actually compares | Consequence |
|---|---|---|
| **Max time to accept** | **Real elapsed time** — now, versus when the order was given to the carrier. Only fails once enough wall-clock time has genuinely passed. | Cancels, but only if an alternative exists |
| **Max time to reach store** | **The carrier's own promise** — their reported arrival estimate, versus when the order was given to them. It never looks at the current time at all. Can fail seconds after assignment, if the carrier's own estimate is already too far out. | Cancels regardless of alternatives |
| **Free-cancellation window** | Same style of comparison as *Max time to accept* (now versus assignment time), but with a different consequence: passing it cancels nothing by itself. It only opens the door to the no-commitment own-fleet attempt described above. | Enables the risk-free retry |

## What happens if the own-fleet attempt doesn't work out

The order is deliberately **skipped from that same cycle's Third-Party fallback pass** — it is not re-evaluated against carriers again in this run.

This is intentional. The order is already validly assigned to a carrier, so re-running the fallback would be redundant at best, and risks producing a second, conflicting carrier assignment for an order that already has one. It carries on with its existing assignment until the next relevant check — a future cycle, or an actual delivery-time failure.

## Why this matters

Without this check, an order could sit with a slow or unresponsive carrier indefinitely, or stay there even after your own fleet became free to take it sooner.

The own-fleet attempt is deliberately risk-free: if it doesn't pan out the order is left exactly as it was, the carrier is none the wiser, and nothing is ever cancelled without a confirmed replacement already lined up.

# When does an alternative exist?

The first question on [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier) only cancels a slow carrier if there is a genuine alternative ready to take over.

That check has two independent halves — whether *your own fleet* could take it, and whether *another Third-Party carrier* could take it. They differ a lot in depth. **Either one being true is enough.**

## Own fleet — one simple check

| Check | What it verifies | Where it's configured |
|---|---|---|
| Free capacity slot | At least one own-fleet driver (in-house or own-fleet contractor) currently has room for one more order | Maximum orders per driver — see [Where each setting lives](/books/console-yF7/page/where-each-setting-lives) |

> This is intentionally a quick, optimistic check. It only asks "does *any* driver have a free slot" — not whether that driver is close enough, idle, or actually able to take *this* order. It exists purely to answer "is it even worth cancelling the carrier's order?" The real evaluation happens afterwards, once the order rejoins the normal assignment process.

## Third-Party — several checks, and only carriers further down the list

An important scoping rule first: this only looks at carriers that come **after** the currently-assigned one in that zone's configured carrier order. Carriers earlier in the list are never considered here, even if they would otherwise qualify. The search stops at the **first** carrier that passes every check below — it does not look for the "best" one.

For each candidate carrier, in order, all of the following must pass:

| Check | What it verifies |
|---|---|
| Enabled & order-type match | The carrier's master switch is on, and it applies to this order's type and sales channel |
| Payment compatibility | Cash orders don't exceed the carrier's cash-on-delivery limit; card orders are only offered if the carrier accepts cards |
| Product restrictions | None of the order's products fall into a category this carrier is excluded from |
| Capacity at this store | The carrier still has a free slot in its shared per-store capacity pool |
| Distance | The store-to-customer distance is within the carrier's allowed range |
| Operating schedule | The current time falls within the carrier's configured working hours for this store |

The first candidate to pass all six counts as "an alternative exists", and the search stops there.

> **Deliberately not checked here:** the normal delivery-time-window check is skipped. At this point the system is only confirming that a carrier could *structurally* take the order — enabled, payment, products, capacity, distance, hours. Whether it could deliver it *on time* is evaluated later, if and when the order is actually offered to that carrier.

All of these settings live on the carrier's general or per-store configuration — see [Where each setting lives](/books/console-yF7/page/where-each-setting-lives).

# The three assignment mechanisms

Every order is ultimately resolved using one — or a combination — of three mechanisms.

| Mechanism | When it's used |
|---|---|
| **PG Mode (FIFO)** | You have enough own drivers to cover pending orders |
| **Google OR-Tools** | There are more pending orders than own drivers, so routes need planning more efficiently |
| **Third-Party Carriers** | Your external delivery network |

> Which of PG Mode or Google OR-Tools is used for a given store is decided automatically, from the ratio of pending orders to available drivers — unless you force one specific mode in the scheduling plan. See [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works).

## How they combine

Zone configuration decides **whether Third-Party gets the first attempt** or only acts as a safety net. Everything else works the same either way.

**Standard zone** — own fleet first:

1. **Own solver** (PG Mode or Google OR-Tools).
2. **Idle driver retry** — free store drivers the solver didn't use.
3. **Third-Party fallback** — last resort.

**Third-Party-first zone** — carrier first:

1. **Third-Party carrier**, one attempt. If assigned, done.
2. **Own solver** (PG Mode or Google OR-Tools).
3. **Idle driver retry**.
4. **No further fallback** — the order already had its Third-Party turn.

## The rule that keeps it fair

Each order gets **exactly one attempt with each resource type** — own fleet and Third-Party — per assignment cycle.

The zone configuration only decides *which one goes first*, never *how many* attempts an order gets:

| Zone type | Attempt order | Total attempts |
|---|---|---|
| Standard | Own fleet → Third-Party | 2 |
| Third-Party-first | Third-Party → Own fleet | 2 |

Always two. Never three, never one.

## Before the order is left for the next cycle

There is one more relaxed attempt within the same cycle — see [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net).

If the order is still unassigned after that, the next scheduled run starts completely fresh, including a brand-new Third-Party attempt. How soon that happens is set by the store's scheduling plan.

# Last-Option: the final safety net

After the own solver and the Third-Party attempt have each had their one shot at an order, some orders may still be unassigned. Rather than leaving them for the next cycle straight away, the dispatcher makes one more attempt — per store, at the end of the same cycle — called **Last-Option**.

## What problem it solves

Last-Option only looks at orders still unassigned after the normal process. Its goal is simple: avoid leaving an order completely unassigned if there is *any* reasonable way to place it, even if that means being more flexible than the normal rules.

## How candidates are found

Candidates are sourced differently depending on where they come from, and Last-Option **relaxes some checks** the normal process enforces.

| Source | How the candidate is chosen | What's relaxed |
|---|---|---|
| **Own fleet** (in-house and own-fleet contractors) | Best available driver, by capacity and how soon they could leave | The usual delivery-time window is **not** re-checked |
| **Third-Party carriers** | Any carrier already tried and rejected earlier in this same cycle | The original rejection reason is **not** re-checked — only whether capacity has since freed up |

Whichever candidate can start the delivery soonest is the one chosen.

## Order of processing

Orders are processed one at a time, **most urgent first**, by promised delivery time.

> Priority Customers (Platinum) status does **not** affect which order gets a shot at Last-Option first. Platinum only affects the sequence of multiple orders already on the *same* driver's route.

## Step by step

1. Collect the orders still unassigned after the own solver and the Third-Party attempt.
2. Sort them by promised time, most urgent first.
3. For each order in turn, gather candidates:
   - the best available own driver — capacity checked, delivery-time window not re-checked;
   - carriers rejected earlier this cycle, reconsidered only if capacity has since freed up.
4. Pick whichever candidate can start the delivery soonest.
5. If none is available, the order stays unassigned and is marked **Critical** or **Pending**.

## If even Last-Option can't place it

The order is categorised as **Critical** (already past its promised time) or **Pending** (not yet late). The very next scheduled run tries again from scratch, including a brand-new Third-Party attempt.

> **Trade-off to be aware of:** because Last-Option relaxes the normal delivery-time check for own drivers, an order placed through it may end up with a slightly later estimated arrival than the normal tolerance allows. This is intentional — better to get it moving, slightly delayed, than leave it unassigned entirely.

# Driver classification and priority

Drivers are ranked by three things: their current status, their type, and how close they are to the store.

## By status

| Status | Description |
|---|---|
| **Idle** | Has zero orders currently assigned — fully available |
| **Waiting** | Has orders assigned, but none have left the store yet |
| **On The Way** | Has at least one order already out for delivery |

## By type

| Type | Description | Priority |
|---|---|---|
| Internal delivery driver | Your dedicated delivery staff | Highest |
| Internal kitchen staff (can deliver) | Kitchen staff who can also deliver when needed | Medium |
| External / own-fleet contractor | Externally contracted own-fleet driver | Lower |
| Third-Party carrier | External delivery platform | Used per zone and configuration rules |

## Status and location combined

The "At-Store Zone" is roughly 350 metres from the store.

| Category | Status | Location | Priority |
|:---:|---|---|:---:|
| 1 | Idle | At store | Highest |
| 2 | Idle | Away from store | High |
| 3 | Waiting | At store | Medium |
| 4 | Waiting | Away from store | Lower |
| 5 | On The Way | Out delivering | Lowest |

The reasoning is straightforward: an idle driver already at the store is the fastest possible option, so they are always preferred. A driver already out delivering is the last resort, since assigning them a new order means adding it to an existing route.

## Where "at store versus away" actually matters

This distinction is not only used for the ranking above. It changes real decisions in several places:

- **Assignment speed.** An idle driver at the store is assigned instantly, with no further checks. An idle driver away from the store must first pass a delivery-time check.
- **Reassignment priority.** It is one of the main factors when deciding whether to move an order to a different driver — see [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver).
- **Arrival tracking.** When a driver is confirmed at the store as an order is assigned to them, an arrival timestamp is recorded on that order.
- **Pickup Delay alarms.** The same determination triggers the alarm warning that a driver has been at the store too long without leaving with a ready order.
- **Frozen-driver detection.** A driver considered "at the store" is never flagged as stuck — being at the store is a valid, expected state.
- **Driver status in operational screens.** Labels such as "At Branch", "On The Way" or "Away" come from this same check.
- **Whether the dispatcher re-runs at all.** If no driver's at-store status has changed since the last run — along with everything else being unchanged — the store may be skipped. See [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).

> **Upcoming change, currently in internal testing.** Today, whether a driver counts as "at the store" uses an estimated **driving** distance compared against the ~350 m threshold, not a straight-line distance. This is being replaced by a straight-line ("as the crow flies") calculation, because a driving-route estimate can be inaccurate right around a store. The change is not yet active for all accounts; this page will be updated once it ships.

# Priority Customers (Platinum)

> This page only applies if your account has **Priority Customers (Platinum) evaluation enabled**. It is not part of the core flow — it runs as an extra layer on top of it, only for stores where it's switched on, and only when there are Platinum orders to consider.

## What it does

When enabled, the dispatcher runs a **double simulation** for each store before committing to a decision:

1. **Normal run** — orders are processed in their regular order.
2. **Platinum-first run** — Platinum orders move to the front of the queue and get a little more delivery-time flexibility.

The two results are compared, and the dispatcher picks whichever one better protects Platinum orders **without excessively harming regular orders**.

## How the choice is made

1. Are there at-risk Platinum orders in the normal run?
   - **No** → use the normal run as-is. The second simulation never runs.
   - **Yes** → continue.
2. Run the Platinum-first simulation.
3. Count how many regular orders would be *newly* harmed by it.
   - **Below the impact threshold** → use the Platinum-first result.
   - **At or above the threshold** → discard it and use the normal result.

A Platinum order counts as "at risk" if it wouldn't be assigned at all, or if its estimated delivery would fall outside an acceptable refund-risk window.

## What you can configure

| Setting | What it controls |
|---|---|
| Enable / disable | Turns the entire layer on or off |
| Buffer minutes | Extra delivery-time flexibility given specifically to the Platinum-first simulation |
| Impact threshold | Maximum number of regular orders allowed to be newly harmed before the Platinum-first result is rejected |

## What it deliberately does not do

Platinum protection works **only** through the double simulation above. There is no rebalancing of existing routes to make room for a Platinum order — that limit and the others are listed in [Known system boundaries](/books/console-yF7/page/known-system-boundaries).

Platinum status also plays no part in ordinary reassignment ([When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver)) or in the Last-Option queue order ([Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net)).

If the layer is not enabled, Platinum orders are treated exactly like any other order.

# When an order changes driver

Before every assignment attempt, the dispatcher checks whether an order already assigned to one driver should instead go to a different one — but **only** on driver priority, never on how urgent or important the order itself is.

## When a reassignment is allowed

| Situation | Result |
|---|---|
| Order has no driver yet | Assign freely |
| Previously assigned driver is no longer available | Assign freely |
| Same driver being considered again | No change needed |
| New driver has better priority | Reassign |
| Current driver has equal or better priority | Keep as-is |

Priority is defined in [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority).

## Common scenarios

| Current driver | Candidate driver | Outcome |
|---|---|---|
| External driver | Internal driver | Reassigned — better type |
| Idle, at store | Idle, at store | No change — both equally good |
| Idle, away from store | Idle, at store | Reassigned — better location |
| Waiting, at store | Idle, away from store | No change — current is still better |
| External, away from store | External, away from store | No change — no real improvement |

## Passing the priority check is not a delivery guarantee

Passing it only means the dispatcher will **attempt** the assignment. It still has to verify the new driver can actually deliver on time:

| Driver situation | Delivery time checked? |
|---|:---:|
| Idle, at store | No — always the fastest possible option |
| Idle, away from store | Yes |
| Waiting (already has orders) | Yes — must be able to deliver everything |
| On The Way | Yes — the full route is recalculated |

If the delivery-time check fails, the assignment is reverted and the order stays available for the next driver in line. **It isn't lost.**

## Rules that never change

- Orders already out for delivery — the driver has left the store — are **never** reassigned.
- Manually assigned orders, assigned by an employee rather than the dispatcher, are **completely locked** from automatic reassignment.
- Internal drivers can take an order away from an external driver. The reverse is never allowed.

# Scheduling plans: how the automatic cycle works

The dispatcher runs on a schedule you define per store, or per group of stores. Scheduling and assignment behaviour are configured together, in the same plan.

## How the automatic cycle works

- Each plan defines how often the dispatcher should run for a group of stores.
- You can optionally restrict execution to a daily time window — for example, only between 08:00 and 23:00.
- If a run fails, the cycle is **not** broken. The next run is still scheduled automatically, as long as the plan is active.
- If a run falls outside the configured time window it is skipped for that cycle, but the schedule keeps going and resumes normally once back inside the window.

## Scheduling settings

| Setting | Default | What it does |
|---|---|---|
| Plan name | — | A label to help you identify this plan. Optional, free text |
| Stores | — | The stores this plan applies to. One plan can cover several |
| Active | Off | Whether the automatic cycle is currently running for this plan |
| Run interval | 60 seconds | How often the dispatcher re-runs. Allowed range: 60–300 seconds |
| Daily time window | None | Optional start and end time. If not set, the plan runs 24/7 |
| Last run | — | Read-only. When this plan last actually ran |

## Assignment behaviour settings

| Setting | Default | What it does |
|---|---|---|
| Assignment mode | Automatic | Forces PG Mode or Google OR-Tools, or leaves the choice automatic (recommended) |
| Routing strategy | Automatic | Only relevant when Google OR-Tools is used |
| Stop time at customer | 5 min | Extra time accounted for at each customer stop |
| Stop time at store | 3 min | Extra time accounted for at each store stop |
| Maximum orders per driver | 3 | Most orders bundled onto a single own driver. Allowed range: 1–5 |
| Offline driver grace period | 5 min | How long a briefly-offline driver is tolerated before being treated as unavailable |
| Apply results | On | When off, the run is a simulation only and nothing is changed |
| Extra time margin | 10 min | Additional slack allowed. Only relevant to Google OR-Tools |
| Minimum orders to accept a plan | 0 | How many orders Google OR-Tools must successfully assign for its plan to be accepted |
| Priority Customers (Platinum) | Disabled | Enables and configures the Platinum layer — see [Priority Customers (Platinum)](/books/console-yF7/page/priority-customers-platinum) |
| Traffic-aware estimates | Off | More accurate time estimates, at a higher cost |
| Traffic estimate type | — | Optimistic, pessimistic or best guess. Only used when traffic-aware estimates are on |
| Return-time reuse | — | Advanced. Controls how aggressively previously calculated driver-to-store times are reused instead of recalculated |

## Store-level delivery settings

Two further settings live at the **store level**, not inside the scheduling plan. They are configured from the store's dispatcher settings and control how much delay is tolerated before an order is treated as at risk.

| Setting | Applies to | What it does |
|---|---|---|
| Own-fleet delay tolerance | Your own fleet — in-house **and** own-fleet contractors, treated equally | The delay tolerance used when checking whether your own fleet can deliver on time. If a store has **no** in-house drivers at all, this value is automatically replaced by the Third-Party tolerance below, so an all-contractor store isn't held to a stricter standard than it can meet |
| Third-Party delay tolerance | Third-Party carriers | The delay tolerance used to decide whether a carrier can be trusted to deliver on time. It always applies to carriers, whether or not the store also has in-house drivers |

> **Capacity works differently from delay tolerance.** Your own fleet has a **per-driver** limit — each vehicle carries up to the configured maximum at once. Third-Party carriers have a **single shared pool per store, per carrier company** — for example "this carrier can handle 10 concurrent orders at this store", however many individual couriers they have on the road. This is why a carrier can appear full for a store even when it looks like it has plenty of drivers: the limit is set at company level, not per courier.

## Activating and pausing a plan

This is the part most likely to cause confusion.

| Action | Starts the cycle? | Stops the cycle? |
|---|:---:|:---:|
| Create a plan with Active switched on | Yes | — |
| Create a plan with Active switched off | No — it stays dormant | — |
| **Edit** an existing plan and switch Active on | **No effect** — it only updates the stored value | — |
| **Edit** an existing plan and switch Active off | — | Yes, from the next run onwards |
| Use the dedicated **Activate** action | Yes | — |
| Use the dedicated **Pause** action | — | Yes |
| Delete a plan | — | Yes |

> **Recommendation:** don't rely on editing the Active field through a general edit form. Always use the dedicated **Activate** and **Pause** actions — editing the field directly can leave a plan marked active but not actually running.

# Where each setting lives

Beyond the scheduling plan, several other parts of the product hold settings that directly affect dispatcher decisions. This page lists them **by where you'd actually go to change them**, and only includes settings you can edit.

For the scheduling plan itself, see [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works).

## Zones

Edited from the zone's driver-availability settings.

| Setting | What it controls |
|---|---|
| Own vs. Third-Party priority | Whether orders in this zone go straight to carriers first, or start with your own fleet as usual |
| Third-Party ranking rule | When more than one carrier is available for the zone, the order they're tried in: your configured order, lowest cost first, or fastest time first |
| Allowed Third-Party carriers | Which carriers can be offered orders from this zone |
| Allowed own-driver types | Which of your own driver types can deliver in this zone. **Leaving this empty blocks all own drivers from the zone entirely** |
| Extra time buffer | Additional minutes added to the expected time at the customer's location for orders in this zone — for example, to account for difficult access |

> Zones also expose a **delivery-time deviation** field, along with pricing fields such as delivery fees and minimum order amounts. These are editable on the same screen but are **not used by the dispatcher** — the delivery-time deviation is consumed by the ordering website, not by the assignment logic.

## Stores

Edited from the store's dispatcher settings.

| Setting | What it controls |
|---|---|
| Own-fleet delay tolerance / Third-Party delay tolerance | See [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works) for how these two work together |
| Kitchen-driver max distance | How far from the store a kitchen-staff driver can be sent. Set to zero to remove the restriction entirely |

> Stores also have opening and closing hours, per day and per shift, configured elsewhere in the product. Those are **not** used by the dispatcher.

## Third-Party carriers — general settings

Edited from the carrier's general configuration. These apply company-wide, across every store.

| Setting | What it controls |
|---|---|
| Enabled | Master on/off switch. While disabled, this carrier is never considered, at any store |
| Order type / sales channel | Restricts which order types and sales channels this carrier can be offered |
| Integration endpoint | The connection used to reach this carrier's platform. Normally set once, during onboarding |
| Default delivery cost | Used to estimate and rank this carrier when no live pricing is available |
| Minimum readiness buffer | How long an order must still need before it can be offered to this carrier at all |
| Max cash-on-delivery amount | Cash-paid orders above this amount won't be offered to this carrier |
| Accepts credit card | Whether this carrier can be offered card-paid orders |
| Excluded product families | Product categories — for example age-restricted items — this carrier will never be offered |
| Max distance | Farthest store-to-customer distance this carrier will be offered |
| Free-cancellation window | Minutes after assignment during which the order can still be pulled back without penalty. See [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier) |
| Description | Internal label for this carrier |

> A "default delivery time" setting also exists at this level, but it is no longer used. It moved to the per-store settings below so it can be tuned more precisely.

## Third-Party carriers — per-store settings

Edited from the carrier's settings for a specific store. These add store-specific detail on top of the general settings above, for the same carrier.

| Setting | What it controls |
|---|---|
| Max time to reach store | If this carrier's driver doesn't arrive within this time after accepting, the order is cancelled and reassigned |
| Estimated time to reach store (fallback) | Used when no live estimate is available for this carrier at this store |
| Max time to accept | If this carrier doesn't accept the order within this time, it's released for reassignment |
| Default delivery time | The delivery-time estimate used when no historical average is available for this carrier at this store |
| Clubbing allowed | Whether this carrier's driver can carry more than one order per trip at this store |
| Capacity at this store | A shared pool for this carrier at this store, **not** a per-courier limit |
| Operating schedule | Days and shifts during which this carrier is available at this store. Outside these hours it is skipped entirely |

# Quick reference rules

A one-page summary of the rules that come up most often. Each links to the page that explains it in full.

| Rule | Behaviour |
|---|---|
| Orders already out for delivery | Never revalidated, never reassigned — only used to estimate when the driver returns |
| Orders still waiting to leave | Can be reassigned if a better driver becomes available |
| Reassignment checks | Run before every single assignment attempt |
| Reassignment criteria | Based only on driver priority — never on how urgent the order is |
| Manually assigned orders | Fully locked from automatic reassignment |
| Internal vs. external drivers | Internal can take orders from external. External can never take from internal |
| Attempts per cycle | Exactly one with your own fleet and one with Third-Party. The zone decides the order, not the count |
| Priority Customers (Platinum) | Not considered during regular reassignment — only inside its own optional evaluation layer |
| Order already with a carrier | Can still be pulled back to your own fleet before the carrier collects it, if inside the free-cancellation window and your fleet has room |
| A carrier order is never cancelled blindly | Nothing is cancelled without a confirmed replacement already lined up |
| Unassigned at the end of a cycle | Marked Critical or Pending, and retried from scratch on the next scheduled run |

## Where to read more

| Topic | Page |
|---|---|
| Driver ranking | [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority) |
| Reassignment rules | [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver) |
| Carrier pull-back | [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier) |
| The final attempt | [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net) |
| Design limits | [Known system boundaries](/books/console-yF7/page/known-system-boundaries) |

# Known system boundaries

These are intentional design limits, not faults. Knowing them keeps expectations realistic and saves time when something looks wrong but isn't.

## No proactive rebalancing

The dispatcher never moves a regular order out of a driver's route just to make room for a Platinum order. Platinum protection works only through the double simulation described in [Priority Customers (Platinum)](/books/console-yF7/page/priority-customers-platinum).

## Orders out for delivery are frozen

Once a driver has left the store with an order, that order is never touched again by the dispatcher. It is still used to estimate when that driver will be free, but it cannot be reassigned, revalidated or cancelled by the algorithm.

## Decisions are local, not global

Each assignment decision compares the current driver against one candidate driver. The dispatcher does not attempt to find a perfect overall solution across all drivers and all orders at once.

This is why two decisions that each look right individually can, together, look less than ideal in hindsight.

## Order urgency isn't a reassignment factor

Only driver priority matters when deciding whether to reassign. A very urgent regular order will not jump the queue over a less urgent one on its own. See [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver).

## Last-Option can exceed the normal delay tolerance

As a last resort, an order may be placed with a slightly later estimated arrival than the normal delay tolerance would otherwise allow, rather than being left unassigned. See [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net).

## A store with no change is skipped

If nothing has changed for a store since the last run, it is skipped rather than re-processed. A run that appears to have "done nothing" for a quiet store is behaving correctly — see [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).

# End-to-end overview

The full path a store's orders take in a single cycle, pulling together everything in this chapter.

## One cycle, one store

1. **Fetch orders and drivers** for this store. Orders already sitting with a Third-Party carrier from an earlier cycle are re-examined here — see [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier).
2. **Is this a Third-Party-first zone?**
   - **Yes** → try a Third-Party carrier first. If it takes the order, that order is done.
   - **No** → go straight to step 3.
3. **Own solver** — PG Mode or Google OR-Tools, chosen automatically unless you force one.
   - If Priority Customers (Platinum) is enabled and applicable, this step is replaced by the double simulation described in [Priority Customers (Platinum)](/books/console-yF7/page/priority-customers-platinum). Everything before and after stays the same.
4. **Idle driver retry** — free store drivers the solver didn't use.
5. **Third-Party fallback**, but only for orders from a standard zone. An order from a Third-Party-first zone has already had its carrier turn and gets no second one.
6. **Post-process** — categorise results and identify orders that can be grouped onto one driver.
7. **Still unassigned?**
   - **No** → save and notify.
   - **Yes** → the final relaxed attempt, described in [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net). If it places the order, save and notify. If not, the order is marked Critical or Pending and the next cycle retries from scratch.
8. **Save and notify** — assignments are written, drivers are pushed a notification, alarms are processed.

## The two things worth remembering

**Every order gets exactly two attempts per cycle** — one with your own fleet, one with Third-Party — plus the Last-Option safety net at the end. The zone configuration changes the order of those two attempts, never the number.

**Nothing is ever cancelled without a replacement lined up.** An order already with a carrier is only pulled back once a real own-fleet assignment has been found and the cancellation with the carrier has succeeded.

## Related pages

- [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step) — the five steps in detail
- [The three assignment mechanisms](/books/console-yF7/page/the-three-assignment-mechanisms) — how PG Mode, Google OR-Tools and carriers combine
- [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net) — what happens at step 7
- [Known system boundaries](/books/console-yF7/page/known-system-boundaries) — what this flow deliberately does not do

# Reporting a dispatcher issue

If something looks wrong — an order assigned to the "wrong" driver, delayed, or stuck unassigned — the fastest way to get it resolved is to report it with the right information the first time.

Screenshots alone, especially of unrelated screens, rarely help. Specific, matched details do.

## What to include

| Detail | Why it matters |
|---|---|
| **Request ID** | Identifies the exact dispatcher run involved. Shown in the execution summary on the plan's detail view in the Scheduler, and looks like `req_1785152470621_6v3uobxk86e`. This is the single most useful thing you can provide — more so than the store, because it points to one exact run |
| **Order ID / ticket number** | Without it, the affected order can't be traced |
| **Store** | Which store's run is affected |
| **Date and approximate time** | Narrows down which cycle to examine |
| **What you expected** | For example: "should have gone to an idle driver at the store", "should have stayed with the original driver" |
| **What actually happened** | For example: "stayed unassigned for 12 minutes", "was reassigned to a different driver", "went to a carrier instead of our own fleet" |
| **One-off or recurring?** | A single order and a pattern across many orders point to very different causes |
| **Was it a Priority Customer (Platinum) order?** | Helps rule the optional Platinum layer in or out |
| **Any recent configuration changes** | A zone, store or carrier setting changed shortly beforehand helps separate cause from coincidence |

## What evidence actually helps

- A screenshot of the **specific order's detail screen**, showing timestamps and current assignment — not an unrelated dashboard or list view.
- If several orders are affected, list **all** the order IDs rather than describing them generally ("several orders around lunchtime").
- If you think a particular driver or carrier was skipped, name them, rather than saying "it didn't pick the right one".

## What to avoid

- Reporting without the Request ID or the order ID. These are the two things nothing else can substitute for.
- General statements such as "the algorithm is broken" or "it's not working today", without a specific order, store and time.
- Attaching many unrelated screenshots instead of the one screen relevant to the order.

## Before you report

Two behaviours are frequently mistaken for faults. Checking them first can save a round trip:

- A store where nothing has changed is **skipped**, not processed — see [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).
- Editing a plan's Active field does **not** start or stop the cycle. Use the **Activate** and **Pause** actions — see [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works).

> The clearer and more specific the report, the faster it can be matched against the actual run that produced the behaviour you're describing.

# Dispatcher FAQ

**Why did an order suddenly get reassigned to a different driver?**

A driver with better priority became available — for example an idle driver at the store, or an internal driver where before only an external one was free. See [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver) for the exact rules.

**Why is an order still unassigned after a run?**

Under normal conditions every order should end up assigned: to your own fleet, to a carrier, through a fallback pass, or via the Last-Option safety net. An order still unassigned at the end of a cycle usually points to one of these:

- **Genuine capacity shortage** — no available driver, own or Third-Party, can reach the customer within the accepted delivery window.
- **Zone / driver-type mismatch** — the order's zone doesn't allow the type of drivers currently available. Note that leaving a zone's allowed own-driver types empty blocks all own drivers from it entirely.
- **Incomplete order data** — for example missing or invalid delivery coordinates.
- **A technical problem during that run** — worth flagging if it repeats for the same store.

It will be retried automatically on the very next cycle, but if it happens often it's worth investigating rather than treating as routine.

**What is Last-Option, and does it mean my order gets worse service?**

Last-Option is a final, same-cycle safety net for orders the normal process couldn't place. It relaxes the usual delivery-time check to avoid leaving an order completely unassigned, so it may accept a slightly later estimated arrival — but only when no better alternative exists, and only after the normal process has had its full attempt. See [Last-Option: the final safety net](/books/console-yF7/page/last-option-the-final-safety-net).

**Why can't I turn a plan on or off just by editing it?**

Editing a plan only updates its stored configuration. It does not start or stop the automatic cycle. Always use the dedicated **Activate** and **Pause** actions. See [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works).

**What does enabling Priority Customers (Platinum) actually change?**

It adds an evaluation step that tries to protect Platinum orders from being delivered late — but only if doing so doesn't harm too many regular orders at the same time. If it isn't enabled, Platinum orders are treated exactly like any other order. See [Priority Customers (Platinum)](/books/console-yF7/page/priority-customers-platinum).

**How often does the dispatcher actually run?**

Whatever run interval you set on the plan — minimum 60 seconds, maximum 300. If a run fails, the schedule doesn't break; the next run is still scheduled normally.

**Can an order already with a Third-Party carrier move back to my own fleet?**

Yes, but only before the carrier's driver has left the store with it, and only if two things are true: the order is still inside the carrier's free-cancellation window, and an own driver with capacity is available right then. Even so, the switch only happens if the cancellation with the carrier succeeds first. If any of that isn't met, the order simply stays with the carrier and nothing changes. See [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier).

**A carrier looks like it has plenty of drivers, so why is it "full" for my store?**

Carrier capacity is a single shared pool per store, per carrier company — not a per-courier limit. See the capacity note in [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works).

**Why did a run appear to do nothing for one of my stores?**

If nothing has changed for that store since the last run, it is deliberately skipped rather than re-processed. See [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).

# Pausing and resuming delivery

> **You need:** *Pause / resume delivery* — held by `admin`, `hub_manager`, `dispatcher_manager` and `branch_manager`. Viewing the status only needs store read access.

Pausing delivery stops a store taking new delivery orders for a set time, without closing the store or touching orders already in flight.

## Pausing a store

1. Open the store's delivery controls.
2. Choose **Pause** and set the **duration in minutes**.
3. Confirm.

The pause is time-boxed: when the duration elapses, delivery resumes on its own. You don't have to remember to turn it back on.

> If the store is **already paused**, pausing again doesn't stack. You'll be told it's already paused rather than silently extending it — check the current status first if you intend to extend a pause.

## Resuming early

**Resume** ends the pause immediately, before the duration is up. Use it when the reason for the pause clears sooner than expected — a kitchen backlog that resolves, drivers coming back on shift.

## Checking the current status

The delivery status view answers two questions: whether the store is paused right now, and if so what is scheduled to bring it back. Check it before pausing, so you don't mistake an existing pause for a new one.

## Active pause jobs

Each pause creates a job that will resume the store when its time is up.

- **Per store** — see the active job for one store, and when it will resume.
- **Across the chain** — see every active pause job at once. Useful during an incident, when several stores were paused at different times by different people.

## Emergency: cancelling all pauses

There is a chain-wide action that **cancels every active delivery pause job at once**.

> This is an emergency control, not a routine one. It affects every store that is currently paused, including stores paused by other people for reasons you may not know about. Check the chain-wide job list first so you know exactly what you are about to resume, and tell the affected stores.

Use it when pauses were applied in bulk by mistake, or when an incident is over and you need everything back online without visiting each store.

## What pausing does not do

- It does **not** stop the dispatcher for that store. Orders already accepted continue to be assigned normally — see [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).
- It does **not** change the store's opening hours, which are configured elsewhere and are not used by the dispatcher.
- It does **not** cancel or reassign existing orders.

## What you can and can't see

The per-store actions are filtered by your scope: you can only pause, resume or inspect stores within it. The chain-wide job list and the emergency cancel are not store-specific, so they are limited to roles that hold delivery control without a store restriction.

# Delivery zones

> **You need:** *Delivery zones — view* to browse, *Delivery zones — edit* to change anything. Held by `admin`, `dispatcher_manager` and `polygon_manager`.

A zone is a delivery area with a map polygon and a set of rules deciding which drivers and carriers serve it. Zones are the single biggest lever on dispatcher behaviour — see [Where each setting lives](/books/console-yF7/page/where-each-setting-lives) for what each rule does.

## Browsing zones

The zones list shows every zone for the brand, with its store and whether it has a polygon drawn. A separate view shows zones alongside their **driver availability** configuration, which is where you see at a glance whether a zone is set to try carriers first.

> The map view needs a configured Maps key for the brand. If the map is blank while the list shows zones, that's a configuration issue rather than missing zones.

## Editing a zone's driver availability

This controls the rules the dispatcher actually uses: own-fleet versus carrier priority, the carrier ranking rule, which carriers are allowed, which own-driver types are allowed, and the extra time buffer.

> **The trap worth repeating:** leaving *allowed own-driver types* empty does not mean "all types". It blocks every own driver from the zone, and orders there will only ever go to carriers. This is the most common cause of a zone whose orders all end up external.

## Drawing and removing polygons

A zone's polygon defines its geographic boundary.

- **Create or update** a polygon by drawing it on the map and saving.
- **Delete** a polygon to leave the zone without a boundary.

A zone with no polygon cannot match an address, so orders won't be routed to it. Deleting a polygon is therefore effectively switching the zone off, even though the zone itself still exists.

## Moving a zone to another store

A zone — together with its polygon — can be migrated to a different store. Use this when a store relocates or when an area is reassigned between stores, rather than deleting the zone and redrawing it, which loses the boundary.

## Bulk changes by file

For anything beyond a handful of zones, use the file-based tools.

| Action | What it does |
|---|---|
| **Export zones** | Downloads the current zone configuration as a CSV. Always export before a bulk change — it is your rollback |
| **Import zones** | Applies zone configuration from a CSV |
| **Import polygons from KML** | Bulk-loads zone boundaries from a KML file, the format mapping tools export |

**KML import is partial by design:** valid rows are imported even when others fail. You get what worked plus a report of what didn't, rather than an all-or-nothing rejection. Read the report — a partially successful import leaves the rest of your zones untouched and still needing attention.

## Time-based zone switching

Zones can switch on a schedule, so an area is served differently at different times of day. These rules are loaded from a CSV.

Unlike the KML import, **this import is all-or-nothing**: if any row is invalid, nothing is applied. Fix the file and re-import.

## Before and after a bulk change

1. **Export first.** The export is the only rollback you have.
2. Apply the import.
3. Read the result carefully — especially for KML, where partial success is normal.
4. Check a sample zone on the map.
5. Confirm with a real order in that area, if you can.

Zone backups are kept and can be restored — see [Backups and restoring configuration](/books/console-yF7/page/backups-and-restoring-configuration).

# External carriers

> **You need:** *Third-party — view* to browse, *Third-party — edit* to change. Held by `admin`, `dispatcher_manager` and `third_party_manager`. Per-store carrier settings are also reachable by `hub_manager`.

Carrier configuration exists at two levels, and knowing which one you're editing is most of the battle.

| Level | Applies to | Typical settings |
|---|---|---|
| **Brand** | Every store in the brand | Whether a carrier is enabled at all, payment rules, product exclusions, maximum distance, free-cancellation window |
| **Store** | One carrier, at one store | Timing limits, capacity at that store, operating schedule, clubbing, default delivery time |

Both levels are listed field by field in [Where each setting lives](/books/console-yF7/page/where-each-setting-lives).

## Brand-level configuration

The brand view lists every carrier type available to you and its current configuration. Editing here changes that carrier **everywhere**.

> The master **Enabled** switch is brand-wide. Turning a carrier off here removes it from every store immediately, whatever the per-store settings say. If you only want to stop using a carrier at one location, change that store's settings instead.

## Store-level configuration

The per-store view shows the same carriers with their settings for that one store. This is where you tune the things that genuinely differ by location: how long a carrier gets to accept, how long they get to reach the store, how many concurrent orders they can hold there, and the hours they operate.

Two of these drive the behaviour described in [Re-checking orders already with a carrier](/books/console-yF7/page/re-checking-orders-already-with-a-carrier) — *Max time to accept* and *Max time to reach store*. They look similar and compare completely different things; that page has the comparison.

## Capacity is per company, not per courier

A carrier's capacity at a store is a **single shared pool for that carrier company**. "Ten concurrent orders at this store" means ten, however many couriers that company has nearby.

This is the usual explanation when a carrier looks busy on their own app but appears full to the dispatcher.

## Bulk changes by file

For chain-wide carrier work, use the file tools rather than editing store by store.

| Action | What it does |
|---|---|
| **Export per-store carrier configuration** | Downloads current per-store settings as CSV. Export before every bulk change |
| **Import per-store carrier configuration** | Applies per-store settings from CSV |

Backups of carrier configuration are kept and restorable — see [Backups and restoring configuration](/books/console-yF7/page/backups-and-restoring-configuration).

## When a carrier is never offered orders

Work through this before reporting a problem. A carrier is skipped entirely if any of these is true:

- It is disabled at brand level.
- The current time is outside its operating schedule for that store.
- Its capacity pool at that store is full.
- The order's zone doesn't list it among the allowed carriers — see [Delivery zones](/books/console-yF7/page/delivery-zones).
- The order fails one of its structural checks: payment type, an excluded product family, or distance.

The full ordered list of those checks is in [When does an alternative exist?](/books/console-yF7/page/when-does-an-alternative-exist)

# Store delivery settings

> **You need:** *Store config — view* to browse, *Store config — edit* to change. Held by `admin`, `hub_manager`, `dispatcher_manager` and `branch_manager`.

Each store carries its own dispatcher settings, separate from the scheduling plan that governs when the dispatcher runs. Those two are configured in different places and are easy to confuse — the plan decides *when and how* the dispatcher runs, the store decides *how tolerant it is* for that location.

## Store list

The list shows every store within your scope. A `branch_manager` sees their own store; a chain-wide role sees all of them. If a store you expect is missing, that is a scope question rather than a configuration one — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## Dispatcher settings for a store

Two settings matter most, and both are explained in full in [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works):

- **Own-fleet delay tolerance** — how much delay is accepted when checking whether your own drivers can deliver on time.
- **Third-party delay tolerance** — the same, for carriers.

> There is a rule here that surprises people: if a store has **no in-house drivers at all**, the own-fleet tolerance is automatically replaced by the third-party value. An all-contractor store is not held to a stricter standard than it can meet. You don't configure this — it just happens.

A third setting, **kitchen-driver max distance**, caps how far a kitchen-staff driver can be sent from the store. Setting it to zero removes the restriction entirely rather than blocking all kitchen drivers.

## Promised delivery times

Each store exposes the delivery times it can promise a customer. These feed the time a customer is quoted at ordering, and the promised time the dispatcher then works against when deciding what is late — see the definitions of Critical and Pending in the [Dispatcher glossary](/books/console-yF7/page/dispatcher-glossary).

## Products and promotions per store

A store's view also lists the products and promotions currently active there. This is a read-only cross-check: useful when an order behaves unexpectedly and you want to confirm what that store actually offers, rather than what the brand catalogue says.

## What belongs elsewhere

| Looking for | Actually lives in |
|---|---|
| How often the dispatcher runs for this store | The scheduling plan — [Scheduling plans: how the automatic cycle works](/books/console-yF7/page/scheduling-plans-how-the-automatic-cycle-works) |
| Which drivers and carriers serve an area | The zone — [Delivery zones](/books/console-yF7/page/delivery-zones) |
| Carrier timing and capacity at this store | Carrier per-store settings — [External carriers](/books/console-yF7/page/external-carriers) |
| Pausing deliveries temporarily | [Pausing and resuming delivery](/books/console-yF7/page/pausing-and-resuming-delivery) |
| Store opening hours | The general store configuration. **Not used by the dispatcher** |

# Backups and restoring configuration

> **You need:** the view or edit permission for whatever the backup contains — not a separate backup permission. A zones backup needs delivery-zone access; a carrier backup needs third-party access; an employee backup needs employee access.

Every time you export configuration, a backup is kept. You can list them, download them, restore one, or delete it.

## What gets backed up

| Backup type | Covers | Permission it follows |
|---|---|---|
| **Zones** | Delivery zone configuration | Delivery zones |
| **Schedule zones** | Time-based zone switching rules | Delivery zones |
| [External carriers](/books/console-yF7/page/external-carriers) | Per-store carrier configuration | Third-party |
| **Employees** | Employee and own-driver configuration | Employees |

Because each backup follows the permission of its own content, someone who can restore a zones backup cannot necessarily restore an employees one. Access is decided by what the backup *contains*, checked when you open it.

## Browsing backups

The list can be filtered by type. Each entry shows what it covers and when it was taken. Filenames follow the pattern `backup-<type>-<date>.csv`, so a downloaded file stays identifiable after it leaves the console.

## Downloading

Download gives you the CSV snapshot exactly as it was captured. Two good reasons to download rather than restore:

- **To inspect before restoring.** Open the CSV and confirm it holds what you expect, especially if the backup is old.
- **To compare.** Diffing yesterday's export against today's answers "what actually changed?" faster than reading screens.

## Restoring

Restoring re-applies a captured snapshot.

> **Read this before restoring.** A restore reinstates the configuration as it was at capture time. Anything changed since — by you or anyone else — is overwritten for the entities in that snapshot. If two people have been editing zones this week, restoring last week's backup discards both sets of changes, not just the one you meant to undo.

The safe sequence:

1. **Export now**, creating a fresh backup of the current state. This is your way back.
2. **Download the backup you intend to restore** and read it.
3. Restore.
4. Verify on a real zone, store or carrier that the result is what you wanted.

## Deleting

Deleting removes a backup permanently. Do it to clear out noise, not to tidy up after a change — the older backups are exactly what you need when a problem is noticed late.

Before deleting, check it isn't the last known-good snapshot from before a significant change.

## Related pages

- [Delivery zones](/books/console-yF7/page/delivery-zones) — where zone exports and imports are done
- [External carriers](/books/console-yF7/page/external-carriers) — carrier configuration exports
- [Where each setting lives](/books/console-yF7/page/where-each-setting-lives) — what each restored field actually controls

# Hub

The live order board: assigning, incidents, critical actions and alarms.

# The Hub: what it is and who can do what

> **Audience:** hub operators, shift managers and operations managers working the live order board.
> **You need:** *Hub — orders and status* at minimum. Individual actions each need their own permission, listed below.

The Hub is the live operational view of orders in flight. Where the dispatcher decides automatically, the Hub is where a person intervenes: assigning a driver by hand, changing a promised time, logging an incident, or — rarely — forcing an order closed.

## What the Hub is for

The dispatcher already assigns most orders on its own, every cycle. You open the Hub when something needs a human: an order the algorithm couldn't place, a customer calling about an ETA, a driver who never picked up, a store whose alarms are firing.

If you want to understand *why* an order was assigned the way it was, that is the dispatcher's logic rather than the Hub's — see [What the dispatcher does](/books/console-yF7/page/what-the-dispatcher-does).

## What each action needs

Access is granular on purpose. Holding the Hub view does not imply holding the critical actions.

| Action | Permission | Who typically has it |
|---|---|---|
| See orders and status | *Hub — orders and status* | `hub_operator` and above |
| See an order's pickup code | *Hub — pickup code* | Kept separate from assigning, deliberately |
| Assign a driver | *Hub — assign* | `hub_operator` and above |
| Unassign a driver | *Hub — unassign* | `hub_manager` |
| Change the promised time | *Hub — modify ETA* | `hub_manager`, `branch_manager` |
| Log an incident | *Hub — incidents* | `hub_manager`, `branch_manager` |
| Cancel an order | *Hub — cancel* | `hub_manager` |
| Force delivery | *Hub — force delivery* | `hub_manager` |
| View driver alerts | *Hub — driver alerts* | `hub_operator` and above |
| View branch alarms | *Hub — branch alarms* | `hub_operator` and above |
| Archive alarms | *Hub — archive alarms* | `hub_manager` |
| Change alarm thresholds | *Alarm settings — edit* | `alarms_config_manager` |

A `hub_operator` deliberately has the everyday actions but **not** force delivery, cancel or ETA changes. See [Access by section](/books/console-yF7/page/access-by-section) for the full grid.

> The pickup code is a separate permission from assigning, even though in practice the same people usually hold both. Keeping them apart means reading a pickup code never has to be bundled with the ability to move orders between drivers.

## The pages in this chapter

| Task | Page |
|---|---|
| Find an order, understand the board | [The order board](/books/console-yF7/page/the-order-board) |
| Look at one order in detail | [Order details and pickup code](/books/console-yF7/page/order-details-and-pickup-code) |
| Put an order on a driver, or take it off | [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver) |
| Change what the customer was promised | [Changing the promised time](/books/console-yF7/page/changing-the-promised-time) |
| Record what went wrong | [Logging an incident](/books/console-yF7/page/logging-an-incident) |
| Stop an order entirely | [Cancelling an order](/books/console-yF7/page/cancelling-an-order) |
| Close an order the system couldn't close | [Forcing a delivery](/books/console-yF7/page/forcing-a-delivery) |
| See stores and driver alerts | [Stores and driver alerts](/books/console-yF7/page/stores-and-driver-alerts) |
| See and act on store alarms | [Branch alarms](/books/console-yF7/page/branch-alarms) |
| Tune when alarms fire | [Alarm settings](/books/console-yF7/page/alarm-settings) |
| Review alarms already dealt with | [Archived alarms](/books/console-yF7/page/archived-alarms) |

## What you can and can't see

Everything in the Hub is filtered by your scope. A `branch_manager` sees their own store's orders; a chain-wide role sees all of them. Two colleagues looking at the same board can legitimately see different orders — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

# The order board

> **You need:** *Hub — orders and status*.

The order board is the working list of orders currently in play across the stores in your scope.

## Filtering by store

The main filter is **stores**: pick one, several, or leave it open for everything within your scope.

This is the filter to reach for first. A chain-wide role looking at every store at lunchtime is reading hundreds of rows; narrowing to the two stores actually having trouble makes the board usable.

> The store filter narrows *within* your scope — it never widens it. If a store isn't in the list at all, that's scope, not filtering. See [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## Identifying an order

Every order is identified by four things together, and you need all four when referring to one:

| Part | What it is |
|---|---|
| **Store** | Which store the order belongs to |
| **Ticket number** | The order number at that store |
| **Source** | Where the order came from — web, mobile app, an aggregator, and so on |
| **Brand** | Which brand, when the chain runs more than one |

A ticket number on its own is **not** unique across stores. This is why [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue) and any support conversation asks for store plus ticket number plus source rather than just a number.

## Reading the board

For each order the board shows who it is currently with, what state it is in, and the promised time it is being measured against.

Two terms matter most, and they come from the dispatcher rather than the Hub:

- **Pending** — not yet assigned, but not yet late either.
- **Critical** — not yet assigned and already past its promised time.

Both are defined in the [Dispatcher glossary](/books/console-yF7/page/dispatcher-glossary). An order sitting in Pending is normal between cycles; an order sitting in Critical is the board telling you to intervene.

## When an order looks stuck

Before acting by hand, it is worth knowing whether the dispatcher has genuinely given up or simply hasn't run yet:

1. The dispatcher re-evaluates each store every cycle — by default no more than 60 seconds apart, and up to 300.
2. An order that failed to place in one cycle is retried from scratch in the next, including a fresh attempt with external carriers.
3. Only after that does manual intervention add anything.

The common reasons an order legitimately stays unassigned are listed in the [Dispatcher FAQ](/books/console-yF7/page/dispatcher-faq) — genuine capacity shortage, a zone that allows no available driver type, or incomplete address data.

> **Manual assignment has a lasting consequence.** An order assigned by a person is permanently locked out of automatic reassignment: the dispatcher will never move it again, even if a much better driver frees up seconds later. Assign by hand when you mean to override the algorithm, not to hurry it along.

## From here

- One order in depth → [Order details and pickup code](/books/console-yF7/page/order-details-and-pickup-code)
- Put it on a driver → [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver)
- It's not going to make the promised time → [Changing the promised time](/books/console-yF7/page/changing-the-promised-time)

# Order details and pickup code

> **You need:** *Hub — orders and status* for the detail view; *Hub — pickup code* to see the pickup code, which is a separate permission.

The detail view is the single screen worth screenshotting when you report a problem — it carries the timestamps and the current assignment together.

## Opening an order

An order is identified by store, ticket number and source together — see [The order board](/books/console-yF7/page/the-order-board). The detail view needs all of them, which is why you open it from the board rather than by typing a number.

## What it tells you

| You're asking | The detail view answers |
|---|---|
| Who has it right now? | The current driver or carrier, and since when |
| Is it going to be late? | The promised time against the current estimate |
| What has already happened? | Timestamps for each step, including arrival at the store where recorded |
| Has anything been logged against it? | Any incidents recorded on the order |

## The pickup code

The pickup code is the code a driver or carrier uses to collect the order.

It sits behind its own permission, separate from assigning. In practice the same people usually hold both, but keeping them apart means someone can be given the ability to read a code — to a driver on the phone, say — without also being able to move orders between drivers.

> Treat the pickup code like any other collection credential. Read it to the person collecting; don't paste it into a group chat or a ticket, and blank it out of screenshots before attaching them to a support request.

## Using it in a support request

If you're reporting something, this screen is the evidence that helps. Include:

- The **store, ticket number and source** — a ticket number alone isn't unique.
- The visible **timestamps** and **current assignment**.
- If the dispatcher is involved, the **Request ID** from the Scheduler, which points to one exact run.

What doesn't help is a screenshot of the board, or of an unrelated dashboard. Full checklist in [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue).

## From here

- Change the driver → [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver)
- Change the promised time → [Changing the promised time](/books/console-yF7/page/changing-the-promised-time)
- Record what went wrong → [Logging an incident](/books/console-yF7/page/logging-an-incident)

# Assigning and unassigning a driver

> **You need:** *Hub — assign* to assign and to see available drivers. Unassigning is a **separate** permission, *Hub — unassign*, typically held only by `hub_manager`.

## Seeing who is available

Open the order and ask for available drivers. The list is built for that specific order — it accounts for the order's store and its zone rules, so it is not simply "everyone on shift".

If the list is empty or shorter than you expect, the reasons are the same ones the dispatcher works from: capacity, zone restrictions on driver types, distance, carrier schedules. [When does an alternative exist?](/books/console-yF7/page/when-does-an-alternative-exist) lists the carrier checks in order.

## Assigning your own driver versus a carrier

These are genuinely different operations, and the difference trips people up:

| | Own driver | Third-party carrier |
|---|---|---|
| What you pick | The specific driver | The **carrier company**, not a courier |
| Who chooses the courier | You | The carrier does |
| What you can predict | Exactly who is going | Only that the carrier accepted it |

With a carrier you are handing the order to a company. Which of their couriers turns up, and when, is theirs to decide — which is why carrier timing is governed by the limits in [External carriers](/books/console-yF7/page/external-carriers) rather than by your choice.

Your own driver types are fixed. The carrier types available depend on what is configured and active for your brand, so the list differs between brands and can change without the Hub changing.

## Assigning

1. Open the order — see [Order details and pickup code](/books/console-yF7/page/order-details-and-pickup-code).
2. Request available drivers.
3. Pick an own driver, or a carrier.
4. Confirm.

> **This is permanent in one specific sense.** An order assigned by a person is locked out of automatic reassignment for good. The dispatcher will not move it later, even if an idle driver appears at the store moments afterwards. That is the intended behaviour — a manual assignment is treated as a deliberate override, not a hint. See [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver).

## Unassigning

Unassigning takes the driver off the order and puts it back in play.

It is a separate, higher permission than assigning because it undoes someone else's action, including the dispatcher's. Use it when an assignment was made in error, or when the assigned driver has become unavailable and you want the order back in the pool.

After unassigning, the order is treated as unassigned again and the next dispatcher cycle will consider it normally.

> Unassigning an order that is already **with a carrier** is not the same as cancelling with that carrier. If your intent is to stop the carrier, see [Cancelling an order](/books/console-yF7/page/cancelling-an-order), which handles the carrier side atomically.

## What you cannot do

- **An order already out for delivery cannot be reassigned.** Once the driver has left the store, the dispatcher freezes it and so does the Hub. See [Known system boundaries](/books/console-yF7/page/known-system-boundaries).
- **You cannot assign outside your scope.** Orders and drivers are both filtered to what you can see.

# Changing the promised time

> **You need:** *Hub — modify ETA*. Held by `hub_manager` and `branch_manager`.

The promised time is what the customer was told, and what the system measures lateness against. Changing it is the right move when the original promise is no longer achievable and the customer has been told — not a way to make a late order look on time.

## Choosing a new time

New times come from the store's **available promised times**, the same set the ordering flow offers. Pick from those rather than inventing a time: they reflect what that store can actually commit to.

The store's promised times are part of its configuration — see [Store delivery settings](/books/console-yF7/page/store-delivery-settings).

## You may be asked to confirm

This is the part worth knowing in advance.

When the change has consequences the system wants you to acknowledge, the first attempt **does not go through**. Instead you are shown the specific warnings and asked to confirm. Confirming re-submits the same change, now with your acknowledgement attached.

| What you'll see | What it means |
|---|---|
| The change applies immediately | No side effects worth flagging |
| Warnings and a confirmation prompt | Real consequences — read them, then confirm if you still want it |
| The confirmation has expired | You took too long. Start again; the warnings are re-evaluated against the current state |

> The confirmation is deliberately not a generic "are you sure?". The warnings name what will actually happen. Read them — that is the whole point of the step, and the state may have moved since you opened the order.

## Orders already with a carrier

When the order is with an external carrier, the change goes to the carrier **first**, and only then is it recorded on your side.

That order matters: the carrier call is the irreversible half and the one that can fail. Doing it first means a failure leaves both sides unchanged, rather than leaving your records showing a new time while the carrier's courier is still working to the old one.

If the carrier rejects the change, the promised time is not updated. That is not a bug — it is the system refusing to record a promise the carrier has not accepted.

## Before you change it

1. **Has the customer been told?** The promised time is a commitment, and changing it silently just moves where the disappointment lands.
2. **Is the real problem the assignment?** If the order is simply unassigned, assigning it may be the fix — see [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver).
3. **Is this a pattern?** One order is an incident; a store missing its promised times all lunchtime is a configuration or capacity question. See [Store delivery settings](/books/console-yF7/page/store-delivery-settings).

## Related pages

- [Logging an incident](/books/console-yF7/page/logging-an-incident) — to record *why* the time changed
- [The order board](/books/console-yF7/page/the-order-board) — how Pending and Critical are judged against the promised time
- [Store delivery settings](/books/console-yF7/page/store-delivery-settings) — where promised times come from

# Logging an incident

> **You need:** *Hub — incidents* to record one. Reading the incident catalogue needs only *Hub — orders and status*.

An incident records what went wrong on a specific order, so it can be explained later and counted across orders.

## The incident catalogue

Incidents are chosen from a catalogue rather than typed free-hand. The catalogue is what makes incidents countable: "how often did this happen last month" only has an answer if everyone picked the same entry for the same situation.

Alongside the catalogue entry you add a **comment**. The entry says *what kind* of problem it was; the comment says what actually happened on this order.

## Recording one

1. Open the order — see [Order details and pickup code](/books/console-yF7/page/order-details-and-pickup-code).
2. Choose the incident from the catalogue.
3. Write a comment with the specifics: what happened, what was done, who was involved.
4. Save.

## Writing a comment that's worth reading

The comment is read weeks later by someone who wasn't there. What helps:

- **Times.** "Driver arrived 19:40, order not ready until 19:55" beats "order was late".
- **Names or identifiers**, not roles. "Carrier X's courier never arrived" beats "the courier didn't show".
- **What was done about it.** Whether the customer was called, whether a refund was offered.
- **The customer outcome.** Whether they were satisfied.

What doesn't help: "late", "problem", "see phone call".

## Incidents are a record, not an action

Recording an incident does not change the order. It does not reassign, cancel, change the promised time or notify anyone automatically. If the order also needs one of those, do it separately:

| Also need to… | Page |
|---|---|
| Move it to another driver | [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver) |
| Change what the customer was promised | [Changing the promised time](/books/console-yF7/page/changing-the-promised-time) |
| Stop it entirely | [Cancelling an order](/books/console-yF7/page/cancelling-an-order) |
| Close it as delivered | [Forcing a delivery](/books/console-yF7/page/forcing-a-delivery) |

> Record the incident **as well as** taking the action, not instead of it. An order that was force-delivered without an incident explaining why is the hardest kind to reconstruct later.

## When to record one

Whenever something happened that a person would need explained later: a late delivery, a missing or wrong item, a driver or carrier failure, a customer complaint tied to this order, or any use of cancel or force delivery.

Consistency matters more than volume. An incident type nobody records is indistinguishable from a problem that never happens.

# Cancelling an order

> **You need:** *Hub — cancel*. Typically `hub_manager` only — deliberately not given to `hub_operator`.

Cancelling stops an order entirely. It is one of the two critical Hub actions, alongside [Forcing a delivery](/books/console-yF7/page/forcing-a-delivery).

## Before you cancel

| Check | Why |
|---|---|
| Is cancelling actually what's wanted? | If the order is just going to be late, changing the promised time may serve the customer better — see [Changing the promised time](/books/console-yF7/page/changing-the-promised-time) |
| Has the customer been told? | A cancellation they learn about from a missing dinner is a different conversation from one they agreed to |
| Is the order already out for delivery? | If the driver has left with it, cancelling here does not recall them |
| Do you know the reason? | It is required, and it is what anyone reviewing this later will read |

## Cancelling

1. Open the order.
2. Choose cancel.
3. Enter the **cancellation reason** — required, and worth writing properly.
4. Confirm.

## If the order is with a carrier

When an external carrier holds the order, the cancellation covers **both sides together**: the order is cancelled with the carrier and released on your side as one operation, not two.

This matters because the failure mode it prevents is a nasty one. Cancelling only your side would leave the carrier still holding a live order, with a courier on the way to collect something nobody expects them to collect.

> If the carrier side fails, the cancellation does not silently half-apply. Check the order's state afterwards rather than assuming it went through, and retry rather than cancelling again from a different screen.

## Write a real reason

The reason is the only explanation that survives. It is read by whoever handles the customer's follow-up, and by anyone reviewing cancellation rates for that store.

**Useful:** "Customer called to cancel — ordered twice by mistake, ticket 4471 kept."
**Not useful:** "cancel", "error", "customer".

## Also log an incident

Cancelling records *that* the order stopped, with your reason. It does not categorise the cancellation into anything countable.

If the cancellation came from something that went wrong — rather than a customer simply changing their mind — log an incident as well, so it shows up in the counts. See [Logging an incident](/books/console-yF7/page/logging-an-incident).

## What cancelling does not do

- It does not recall a driver already out with the order.
- It does not issue a refund. That is handled wherever your refunds are handled.
- It does not notify the customer automatically. Assume they need to be told.

# Forcing a delivery

> **You need:** *Hub — force delivery*. Typically `hub_manager` only. This is the most consequential action in the Hub — read the whole page before using it.

Force delivery closes an order as delivered when the normal flow could not close it — most often because the driver's device failed to register the delivery, or an external carrier never reported completion.

## What it actually does

It records the order as delivered, with a driver and a distance that **you** supply rather than ones the system observed.

That is the whole point, and the whole risk: you are asserting something the system could not confirm. Everything downstream — delivery counts, driver figures, distance-based reporting — takes your word for it.

## Use it only when the delivery really happened

Force delivery is for correcting a **recording** failure, not an operational one.

| Situation | Force delivery? |
|---|---|
| Customer received the order; the app never registered it | ✅ Yes — that is exactly this |
| Carrier delivered but never reported back | ✅ Yes |
| Order is very late and you want it off the board | ❌ No — it is still in progress |
| Order was never delivered | ❌ No — cancel it instead |
| You are not sure whether it was delivered | ❌ No — find out first |

> Forcing an order that was never delivered puts a false delivery into your records permanently. There is no clean undo. When in doubt, confirm with the driver or the customer before acting.

## Doing it

1. Open the order — see [Order details and pickup code](/books/console-yF7/page/order-details-and-pickup-code).
2. Choose force delivery.
3. Enter **who delivered it**: your own driver, or the carrier. For an own driver you name the specific person; for a carrier the company is enough, since the courier is theirs.
4. Enter the **estimated distance** for the delivery.
5. Confirm.

## The distance matters

The distance you enter is used as the delivery's distance. It feeds driver figures and any distance-based reporting or settlement.

Estimate it honestly from the store to the delivery address. A placeholder — zero, or a round number entered to get past the field — quietly corrupts whatever is calculated from it, and nobody will notice until the numbers are questioned months later.

## Always log an incident too

A forced delivery with no incident is the hardest thing to reconstruct later: the record shows a normal delivery, with nothing explaining why a person had to close it by hand.

Log an incident describing why the force was needed — device failure, carrier silence, whatever it was. See [Logging an incident](/books/console-yF7/page/logging-an-incident).

## If you are unsure

Do something reversible instead. Change the promised time, log an incident, or leave the order alone and escalate. Every other Hub action can be worked around afterwards; this one cannot.

# Stores and driver alerts

> **You need:** *Hub — orders and status* for the store view, *Hub — driver alerts* for driver alerts.

Alongside the order board, the Hub shows the state of your stores and raises alerts about drivers.

## The store view

A per-store operational view of the stores within your scope: what each one currently has in play, so you can tell at a glance which store needs attention rather than reading the whole order board.

Use it as the entry point during a busy service. Find the store that looks wrong, then drill into its orders.

> This is the Hub's *operational* view of a store. Changing how a store behaves — delay tolerances, pausing delivery — happens elsewhere; see [Store delivery settings](/books/console-yF7/page/store-delivery-settings) and [Pausing and resuming delivery](/books/console-yF7/page/pausing-and-resuming-delivery).

## Driver alerts

Driver alerts flag drivers whose behaviour needs looking at rather than orders that are late.

The most common is the **pickup delay** alert: a driver has been at the store for too long without leaving with an order that is ready. It fires from the same at-store determination the dispatcher uses for priority — roughly 350 metres from the store. See [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority) for what "at store" means and everywhere else it is used.

Others cover drivers who appear stuck or unavailable in ways that will affect assignment if left alone.

## Acting on an alert

An alert is a prompt, not an instruction. Work out which of these it is:

| What you find | What to do |
|---|---|
| Driver is genuinely delayed at the store | An operational conversation, not a console action |
| Driver's device or connection has dropped | Their orders may need reassigning — [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver) |
| Driver has finished but the system doesn't know | The order may need [Forcing a delivery](/books/console-yF7/page/forcing-a-delivery) — read that page first |
| The alert is firing too readily across many drivers | The threshold is wrong — [Alarm settings](/books/console-yF7/page/alarm-settings) |

> A single alert is about one driver. The same alert firing across many drivers at once is about the threshold, or about something systemic at that store. Don't work through fifty alerts one at a time — check the threshold first.

## Alerts, alarms, incidents

Three similar-sounding things that are genuinely different:

| | What it is | Where |
|---|---|---|
| **Driver alert** | The system flagging a driver's state | This page |
| **Branch alarm** | The system flagging a store's state | [Branch alarms](/books/console-yF7/page/branch-alarms) |
| **Incident** | A person recording what went wrong on an order | [Logging an incident](/books/console-yF7/page/logging-an-incident) |

The first two are raised automatically and you respond to them. The third is created by you and is a permanent record on one order.

# Branch alarms

> **You need:** *Hub — branch alarms* to view. Archiving needs *Hub — archive alarms*, typically `hub_manager`.

Branch alarms flag that a **store** needs attention — as opposed to driver alerts, which flag a person, and incidents, which you record yourself.

## What they tell you

An alarm fires when a store crosses a configured threshold: too much load, too many cancellations, too many orders missing their promised time. What counts as "too much" is configurable per alarm type — see [Alarm settings](/books/console-yF7/page/alarm-settings).

An alarm is a signal, not a diagnosis. It tells you where to look, not what is wrong.

## Working through them

1. **Look at the store, not the alarm.** Open its orders and see what is actually happening.
2. **Decide whether it is an operational problem or a threshold problem.** One store alarming during a genuine rush is the system working. Every store alarming every lunchtime means the threshold is wrong.
3. **Act on the underlying issue**, if there is one — reassign, pause delivery, change a promised time.
4. **Archive the alarm** once it has been handled.

## Archiving

Archiving marks an alarm as dealt with and takes it off the active view. It doesn't delete it: archived alarms remain available for review — see [Archived alarms](/books/console-yF7/page/archived-alarms).

Archive when the alarm has been **handled or understood**, not merely seen. An alarm archived without being looked at is worse than one left active, because it now looks resolved to everyone else.

> Archiving is a higher permission than viewing, on purpose. Anyone on shift can see what is firing; deciding something has been dealt with is a shift-manager judgement.

## When the board is full of alarms

Alarms that fire constantly stop being read. If a store or an alarm type is permanently lit:

- Check the threshold against what that store can realistically do — [Alarm settings](/books/console-yF7/page/alarm-settings).
- Check whether it is the same root cause producing many alarms, rather than many problems.
- Consider whether the store's delay tolerances match its reality — [Store delivery settings](/books/console-yF7/page/store-delivery-settings).

Tuning a threshold so it fires when someone should act, and stays quiet otherwise, is worth more than working through a long list.

## Related pages

- [Stores and driver alerts](/books/console-yF7/page/stores-and-driver-alerts) — the driver-side equivalent
- [Alarm settings](/books/console-yF7/page/alarm-settings) — where thresholds are configured
- [Archived alarms](/books/console-yF7/page/archived-alarms) — reviewing what has been handled
- [Logging an incident](/books/console-yF7/page/logging-an-incident) — recording what went wrong on a specific order

# Alarm settings

> **You need:** *Alarm settings — view* to read, *Alarm settings — edit* to change. The dedicated role is `alarms_config_manager`; `admin` also has it.

Alarm settings decide when an alarm fires. They apply across the whole chain, not per store.

## The three families

| Type | Watches | Example of what it catches |
|---|---|---|
| **Order** | Individual orders | Orders sitting too long, or past their promised time |
| **Driver** | Drivers | A driver at the store too long without leaving — the pickup delay alert |
| **Branch** | Stores | A store under too much load, or with too many cancellations |

Each family holds several individual alarms. You can browse a family, open one alarm and change its thresholds or turn it off.

## Chain-wide, not per store

This is the thing to internalise before changing anything: a threshold applies to **every store**.

Tuning an alarm because one busy city-centre store is noisy also retunes it for the quiet suburban ones — where it may now never fire at all. You are always trading a false-positive problem at one store for a false-negative problem at another.

> If the real need is genuinely per store, a chain-wide threshold cannot express it. Set the threshold for the majority, and handle the outlier operationally rather than by moving the number until that one store goes quiet.

## Changing a threshold

1. Open the alarm family — order, driver or branch.
2. Open the specific alarm.
3. Change the threshold, or switch the alarm off.
4. Save.

Changes affect what fires from then on. They do not retroactively clear alarms already raised — clear those by archiving them, see [Branch alarms](/books/console-yF7/page/branch-alarms).

## Choosing a number

A good threshold fires **when someone should act** and stays quiet otherwise. Both failure modes are real:

| Too sensitive | Too lax |
|---|---|
| Fires constantly | Fires rarely or never |
| People stop reading alarms | Real problems pass unnoticed |
| Genuine problems buried in noise | Discovered from customer complaints instead |

The second is more dangerous; the first is more common — and it causes the second, because an alarm board nobody reads is functionally an alarm that never fires.

Before changing a number, look at what actually fired over the last week or two. Tuning from a specific bad shift produces a threshold shaped by an outlier.

## Turning an alarm off

Switching one off is legitimate when it does not apply to how you operate. It is not a fix for an alarm that fires too often — that is a threshold question, and turning it off removes the signal entirely rather than sharpening it.

Note what you turned off and why. An alarm silently disabled a year ago is indistinguishable from one that never fires.

## Related pages

- [Branch alarms](/books/console-yF7/page/branch-alarms) — acting on store alarms
- [Stores and driver alerts](/books/console-yF7/page/stores-and-driver-alerts) — acting on driver alerts
- [Archived alarms](/books/console-yF7/page/archived-alarms) — the history to tune against

# Archived alarms

> **You need:** *Archived alarms — view*. Exporting needs the metrics export permission. `hub_manager` holds the view; chain-wide export is typically `admin`.

Archiving an alarm doesn't delete it. Archived alarms are kept and can be reviewed later — the history is where you find patterns that no single shift reveals.

## Where to find them

Archived alarms live with **Metrics**, not with the live Hub board. That split is deliberate: the Hub is for what needs acting on now, Metrics is for what happened.

## What the history is for

| Question | How the history answers it |
|---|---|
| Is this store always like this? | Compare its alarm volume against comparable stores over the same period |
| Is our threshold sensible? | An alarm archived in seconds every time is firing too readily — see [Alarm settings](/books/console-yF7/page/alarm-settings) |
| Did that change help? | Compare the weeks either side of a configuration change |
| Which alarms does nobody act on? | An alarm type that is always archived immediately, with nothing else happening, is noise |

## Tuning thresholds from history

This is the main reason the history exists. Before changing a threshold in [Alarm settings](/books/console-yF7/page/alarm-settings), look at what that alarm actually did over a representative period — a fortnight, not one bad shift.

Two patterns are worth acting on:

- **Fires constantly, always archived immediately.** Too sensitive. Raise it, or switch it off if it doesn't apply to how you operate.
- **Never fires, yet the problem shows up in customer complaints.** Too lax. Lower it.

Remember thresholds are chain-wide: a change made to quieten one store changes every store.

## Exporting

The history can be exported for analysis outside the console. Export is a separate, higher permission than viewing, because an export leaves the console's access controls behind entirely.

> An exported file is no longer scope-filtered. Whoever opens the spreadsheet sees everything in it, regardless of what they could see in the console. Treat exports accordingly, and prefer sharing a conclusion over the raw file.

## Related pages

- [Branch alarms](/books/console-yF7/page/branch-alarms) — the live view and archiving
- [Alarm settings](/books/console-yF7/page/alarm-settings) — where thresholds are changed
- [Stores and driver alerts](/books/console-yF7/page/stores-and-driver-alerts) — driver-side alerts

# Marketing

Promotional codes, vouchers, personal promotions, subscriptions and wallet credit.

# The Marketing screen

To access the code management section, go to the **MARKETING** menu in the console's central panel.

![Marketing - Code management](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/TY3zfNVVIeNLP6LW-manual-consola-marketing-en-p1-1.png)

In the sidebar, you'll find all the sections available for viewing, editing, creating, or expiring codes: Promotional codes, Vouchers, Personal promotions, Loyalty subscriptions, and Wallets.

![Marketing sidebar](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/XMADaxkaQOZByg5t-manual-consola-marketing-en-p1-2.png)

Each section loads a table containing the data for those codes. At the top of each section, you'll find the available options:

![Options available at the top of each section](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/0LiQ7x0ADxLedwMC-manual-consola-marketing-en-p1-3.png)

- **Brands**: If you have multiple brands, a dropdown will appear with the list of brands, and you must select the appropriate one for your query. If you only have one brand, this dropdown will not appear.
- **Export CSV**: Exports the data from the table below in CSV format.
- **Import CSV**: Loads a section where you can import a CSV file with the new codes you want to use. Each section has different specifications depending on the type of codes being imported.

> Only CSV files are accepted, and they must strictly comply with the specifications.

## CSV specifications and import

Selecting **Show CSV specifications** displays all the necessary information for that section on screen: how the first row of the CSV must be formatted, followed by a table with the required fields, a description of each field, and the allowed values.

![CSV specifications](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/dWj16RwoIk5akEHe-manual-consola-marketing-en-p2-0.png)

The **SELECT CSV** button is only enabled once all required fields (if any) have been filled in.

Once a file is selected, if it passes validation (meets all requirements), a **"File successfully validated"** message appears next to the file, and the **Upload CSV** button becomes available.

![File successfully validated](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/R3b4qZ6wpHfw6Xgg-manual-consola-marketing-en-p2-1.png)

Once the import begins, a message shows the status of the process, and a confirmation message appears when it finishes.

If the selected file fails validation, a **"Validation Error"** message appears next to the file, followed by a list of all errors found. At that point you can only select another file with those errors corrected — the upload button stays disabled until validation succeeds.

![Invalid file, with a list of errors](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/4j6XQg1pM1n5F8B7-manual-consola-marketing-en-p3-0.png)

## Other available actions

- **New code/voucher/…**: Used to create a single record in that section. Each section has its own specifications; if not all required fields are completed, the save button will not be enabled.
- **Search filters**: Each section has different search filters; some require filling in several fields to filter by that criterion. Filters can be reset at any time.

![Search filters](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/PzumYhIO91pp0Mx4-manual-consola-marketing-en-p3-2.png)

# Marketing: the five areas

> **Audience:** marketing and customer-care staff who issue promotions, vouchers, subscriptions and wallet credit.
> **You need:** a permission per area — they are separate, and holding one grants nothing in the others.

Marketing covers five independent areas. They look similar and behave similarly, but each has its own data, its own permission, and its own rules.

| Area | What it holds | Permission |
|---|---|---|
| [Promotional codes](/books/console-yF7/page/promotional-codes) | Campaigns and the coupons generated from them | *Promotional codes — view / edit* |
| [Vouchers](/books/console-yF7/page/vouchers) | Single-use vouchers of a fixed amount | *Voucher manager — view / edit* |
| [Personal promotions](/books/console-yF7/page/personal-promotions) | Promotions attached to specific customers | *Customer promo manager — view / edit* |
| [Loyalty subscriptions](/books/console-yF7/page/loyalty-subscriptions) | Customer subscriptions | *Subscriptions manager — view / edit* |
| **Wallets** | Customer wallet credit and its movements | *Wallet manager — view / edit* |

Each area also has a **CSV import** permission of its own, separate again from view and edit. Someone who can edit wallet records cannot necessarily import wallet credit in bulk.

## Finding the screens

All five live under the Marketing menu, each with its own section in the sidebar and the same row of options at the top — brand selector, export, import. [The Marketing screen](/books/console-yF7/page/the-marketing-screen) walks through that layout with screenshots, including the CSV specification panel each section provides.

## Two things shared by all five

Before using any area, read these two pages. They explain mechanics that repeat everywhere and cause most of the confusion:

- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — the check → apply → track pattern, and the rule that protects already-redeemed records.
- [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) — what the importers accept and how failures behave.

## The rule that makes this safe

Across every area, bulk operations act only on records that have **not been redeemed**. A voucher a customer has already used, a wallet credit already spent, a coupon already applied to an order — these are never touched by a bulk update or a bulk delete.

This is what makes bulk changes recoverable in practice: the worst case is that you change records nobody has used yet.

> The one place this bites is when the count comes back smaller than you expected. That usually isn't a filter problem — it's redemptions being excluded, exactly as intended.

## Picking the right area

| You want to… | Area |
|---|---|
| Run a campaign with codes customers enter | [Promotional codes](/books/console-yF7/page/promotional-codes) |
| Give one customer a fixed-value voucher | [Vouchers](/books/console-yF7/page/vouchers) |
| Attach an offer to specific customers | [Personal promotions](/books/console-yF7/page/personal-promotions) |
| Manage a customer's subscription | [Loyalty subscriptions](/books/console-yF7/page/loyalty-subscriptions) |
| Add or adjust customer credit | **Wallets** |

These overlap less than they appear. A voucher is a fixed amount; a promotional code is a campaign rule; wallet credit is a balance. Choosing by *what the customer receives* gets it right nearly every time.

## The pages in this chapter

| Topic | Page |
|---|---|
| The shared bulk mechanics | [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) |
| The shared import mechanics | [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) |
| Campaigns, coupons, offers | [Promotional codes](/books/console-yF7/page/promotional-codes) |
| Importing promotional codes | [Importing promotional codes](/books/console-yF7/page/importing-promotional-codes) |
| Fixed-value vouchers | [Vouchers](/books/console-yF7/page/vouchers) |
| Customer-specific offers | [Personal promotions](/books/console-yF7/page/personal-promotions) |
| Subscriptions | [Loyalty subscriptions](/books/console-yF7/page/loyalty-subscriptions) |
| Finding a customer's wallet | [Wallets: finding a customer](/books/console-yF7/page/wallets-finding-a-customer) |
| Adding and adjusting credit | [Wallets: managing records](/books/console-yF7/page/wallets-managing-records) |

## What you can and can't see

Everything here is scoped to the brand you are working in, and filtered further by your own scope — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

# How bulk changes work

The five Marketing areas are introduced in [Marketing: the five areas](/books/console-yF7/page/marketing-the-five-areas).

Wallets, vouchers, personal promotions and promotional codes all support changing many records at once. They follow the same three-step shape, and learning it once covers all four.

## The three steps

| Step | What it does | Permission |
|---|---|---|
| **1. Check** | Counts what *would* be affected. Changes nothing | The area's **view** permission |
| **2. Apply** | Starts the change as a background job | The area's **edit** permission |
| **3. Track** | Follows the job until it finishes | The area's **edit** permission |

The check step is deliberately harmless and deliberately separate. It exists so you can see the blast radius before committing to it.

> **Always run the check.** It takes seconds and it is the only preview you get. A count that looks wrong — far larger or far smaller than expected — means your criteria are wrong, and that is much cheaper to discover now.

## Step 2 runs in the background

Apply does not finish while you wait. It returns a job that runs on its own, and you follow its progress.

This matters in three ways:

- **Closing the screen doesn't stop it.** The job carries on.
- **Progress is polled**, so the figure you see may lag slightly behind reality.
- **A job that is still running is not a job that failed.** Give a large change time before assuming something is wrong.

## Already-redeemed records are never touched

Every bulk operation acts only on records that have **not been redeemed**.

| Area | What is protected |
|---|---|
| Wallets | Credit already spent |
| Vouchers | Vouchers already used |
| Personal promotions | Promotions already applied to an order |
| Promotional codes | Coupons already redeemed |

You cannot retroactively change or remove value a customer has already received. This is why bulk operations are recoverable in practice, and it is also the usual explanation for a count being smaller than expected.

## Delete and update are separate operations

Bulk **update** changes fields — typically dates or amounts — on matching records. Bulk **delete** removes them.

Both are protected by the same non-redeemed rule, and both have their own check step. They are not variants of one action: pick the one you mean.

## Promotional codes work differently — read this

Promotional codes use the same three words for a materially different operation.

| In wallets, vouchers, personal promotions | In promotional codes |
|---|---|
| **Check** counts matching records | **Check** asks whether the campaign has *any* redeemed coupons |
| **Apply** updates matching records | **Apply deletes the whole campaign** and its coupons — and only if none has been redeemed |

So in promotional codes, "apply bulk changes" is a **delete**, gated on the campaign being entirely unused. If even one coupon has been redeemed, it will not proceed.

To retire a campaign that *has* been used, expire it instead — see [Promotional codes](/books/console-yF7/page/promotional-codes).

> This is the single most dangerous naming overlap in the chapter. In four areas "apply" means update; in promotional codes it means delete a campaign. Check which screen you are on before clicking it.

## A safe sequence

1. Set your criteria narrowly.
2. **Check**, and read the count.
3. If the count surprises you, stop and work out why.
4. Apply.
5. Track to completion.
6. Spot-check a couple of affected records.

## If a job seems stuck

Give it time proportional to the count — tens of thousands of records take a while. If progress genuinely stops advancing, don't re-run the apply: a second job on the same criteria can compound the first. Report it with the area, the criteria and roughly when you started.

# Importing from a CSV

Four of the areas described in [Marketing: the five areas](/books/console-yF7/page/marketing-the-five-areas) accept a CSV, so you can issue something to many customers at once.

| Import | What it creates | Permission |
|---|---|---|
| [Personal promotions](/books/console-yF7/page/personal-promotions) | A promotion per customer, from a phone list | *Personal promotions — import* |
| [Loyalty subscriptions](/books/console-yF7/page/loyalty-subscriptions) | A subscription per customer, from a phone list | *Loyalty subscriptions — import* |
| **Wallet credit** | Credit per customer, from a phone list | *Wallet codes — import* |
| [Vouchers](/books/console-yF7/page/vouchers) | Voucher codes | *Voucher codes — import* |

Promotional codes also import from CSV, but with progress tracking the others don't have — it gets its own page, [Importing promotional codes](/books/console-yF7/page/importing-promotional-codes).

> Each import permission is **separate** from the area's view and edit permissions. Being able to create wallet records one at a time does not imply being able to load thousands from a file.

## Phone lists

Three of the four identify customers by **phone number**. The file is a list of phones plus whatever the import needs — an amount, a set of dates.

Phone numbers are where these imports go wrong. Before importing:

- **Check the format matches** how phones are stored for this brand — country prefix or not, spaces or not. A mismatch means rows silently match no customer.
- **Watch for spreadsheet damage.** Excel and Google Sheets both like to drop leading zeros and turn long numbers into scientific notation. A column formatted as *text* survives; a column formatted as *number* may not.
- **De-duplicate.** The same phone twice usually means that customer gets the benefit twice.

## Before any import

1. **Start with a handful of rows.** Import five, confirm they landed as intended, then import the rest. This catches format problems for the price of five records instead of five thousand.
2. **Know what happens to an existing customer.** Whether a second import adds to what they have or replaces it decides whether re-running a file is safe.
3. **Keep the file.** It is the only record of exactly what you sent, and the only way to reverse it if you need to.

## Imports are not transactional

An import that fails partway through does not roll back what it already did. Some customers will have received the benefit and others won't.

That is why the small test import matters, and why re-running a whole file after a partial failure is risky: the rows that already succeeded may be applied a second time.

If an import fails partway, work out **where** it stopped before deciding what to do — don't simply re-submit.

## After importing

Spot-check two or three customers from the file and confirm they received what you intended, with the right amount and the right dates. The area's own browse screen is the place to do it: [Personal promotions](/books/console-yF7/page/personal-promotions), [Loyalty subscriptions](/books/console-yF7/page/loyalty-subscriptions), [Wallets: finding a customer](/books/console-yF7/page/wallets-finding-a-customer) or [Vouchers](/books/console-yF7/page/vouchers).

## If you need to undo one

There is no "undo import". What you have instead is a bulk delete, which removes matching **non-redeemed** records — see [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work).

Anything customers already redeemed stays. This is the honest limit of reversing an import: you can take back what hasn't been used, not what has.

# Promotional codes

> **You need:** *Promotional codes — view* to browse, *Promotional codes — edit* to create or change anything.

Promotional codes are organised as **campaigns** that generate **coupons**. Getting that relationship right makes the rest of the screen obvious.

| Term | What it is |
|---|---|
| **Promotional code** | The rule: what the discount is, when it is valid |
| **Campaign** | A promotional code plus the set of coupons generated from it |
| **Coupon** | One individual code a customer actually enters |

A campaign is created with its first coupon. You then add further coupons to it as needed.

## Browsing

The main list supports filters and pagination. From a campaign you can open its **generated coupons** — the individual codes — and see or edit them one at a time.

**Active offers** is a separate view: the offers currently live for a given store, with the detail of any one of them. Use it to answer "what can this store actually offer right now", which is not the same question as "what campaigns exist".

## Creating

| To create | Use |
|---|---|
| A standalone promotional code | **Create promotional code** |
| A campaign, with its first coupon | **Create campaign** |
| Another coupon on an existing campaign | **Add coupon** |

Create the campaign first, then add coupons to it. Creating separate codes that were meant to be one campaign leaves you unable to manage them together afterwards.

## Editing

Two different edits, at two levels:

- **The promotional code** — its dates, renewal day, and the times it is valid.
- **A single generated coupon** — one specific code within a campaign.

Editing the code changes the rule for the campaign. Editing a coupon changes one customer-facing code. If a customer reports a problem with one code, the coupon is usually the right level.

## Expiring versus deleting

This is the decision that matters most, and the two are not interchangeable.

| | Expire | Delete |
|---|---|---|
| What it does | Sets the end date to today — the code stops working from now | Removes the campaign and its coupons entirely |
| Redeemed coupons | Fine — history is preserved | **Blocked.** Will not proceed if any coupon has been redeemed |
| Reversible | The record still exists | No |
| Use when | A live campaign should stop | A campaign was created in error and never used |

**Expiring is almost always the right answer.** It stops the campaign while keeping the record of what happened, which is what you need when a customer asks why their code stopped working.

There is also a bulk expire, which marks **every coupon of a campaign as used** in one go. Use it to close a campaign out completely rather than letting unredeemed coupons linger.

> Deleting a campaign is the operation described under "apply bulk changes" on this screen — a naming overlap that means something different everywhere else in Marketing. Read [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) before using it.

## If a customer's code doesn't work

Check in this order:

1. **Is the campaign still within its dates?** Including the valid times, not just the days.
2. **Has that specific coupon already been redeemed?** Open the generated coupons and look.
3. **Is the offer live for that store?** Check active offers for that store — a campaign can exist without being offered everywhere.
4. **Was it bulk-expired?** A campaign closed out has all its coupons marked used.

## Related pages

- [Importing promotional codes](/books/console-yF7/page/importing-promotional-codes) — loading codes from a file
- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — before any bulk operation here
- [Personal promotions](/books/console-yF7/page/personal-promotions) — for offers attached to specific customers instead

# Importing promotional codes

> **You need:** *Promotional codes — edit*.

Promotional codes are the one import with full job tracking. Unlike the other CSV imports in [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv), you can see a running import's progress, and you get told if one is already going.

## One active import at a time, per person

The import tracks an **active job for you**. Before starting, the screen tells you whether you already have one running.

This is a guard rather than a limitation. It stops the common accident of starting an import, seeing no immediate feedback, and starting it again — which would apply the same file twice.

| What you see | What it means |
|---|---|
| No active import | Clear to start |
| An active import | Yours is still running. Watch it rather than starting another |
| An active import you don't recognise | Something you started earlier and left. Check its progress before clearing it |

## Running one

1. Check no import is already active for you.
2. Upload the CSV.
3. Follow the progress as it runs.
4. Confirm the result before telling anyone the codes are live.

## Progress

The progress figure is polled, so it lags slightly — a number that hasn't moved for a few seconds is normal. Large files take proportionally longer.

Closing the screen does not stop the import. It runs to completion on its own, and you can come back to its progress.

## Clearing a stuck active job

You can clear your active import. What that means is worth being precise about:

> Clearing removes the **record that you have an import in progress**. It does not undo rows already imported, and it isn't a cancel button. Use it when a job is genuinely finished or abandoned and is blocking you from starting a new one — not to stop a running import you've changed your mind about.

If you clear a job and start the same file again, the rows that already imported may be applied twice. Check what landed first.

## Before importing

The general advice in [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) applies here too, particularly:

- **Test with a few rows first.** Confirm the format is accepted before committing a large file.
- **Keep the file.** It is your record of exactly what was issued.
- **Imports are not transactional.** A failure partway leaves earlier rows in place.

## After importing

Open the promotional codes list and confirm the codes exist with the dates and validity you expected — see [Promotional codes](/books/console-yF7/page/promotional-codes). If they need to be part of a campaign, confirm that too, because a campaign relationship is hard to add afterwards.

## If it goes wrong

Removing imported codes uses the campaign delete described in [Promotional codes](/books/console-yF7/page/promotional-codes), which only proceeds when **nothing has been redeemed**. Once customers start using codes, they stay — expire the campaign instead.

# Vouchers

> **You need:** *Voucher manager — view* to browse, *Voucher manager — edit* to create or change. Importing needs *Voucher codes — import*, which is separate again.

A voucher is a fixed amount a customer can redeem once. Simpler than a promotional code — there is no campaign, no rule, just a value and a validity period.

## Browsing

The list shows **available vouchers** — those not yet redeemed — for the brand.

> Redeemed vouchers are not in this list. That is deliberate: the screen is for vouchers still in play. If you are looking for one a customer says they used, its absence here is the answer.

## Creating one

Create a voucher with its amount and validity. This is the right path for a single customer — a service recovery, a goodwill gesture, a one-off.

For many vouchers at once, import them instead — see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

## Editing

You can update an existing voucher: its amount, its dates. Only vouchers that haven't been redeemed can be changed, which is the same rule that protects bulk operations.

## Expiring

Expiring ends one or more vouchers immediately. Use it when a voucher was issued in error, or when a promotion is being pulled.

Expiring is the safe option: the record survives, so there is still an answer when the customer asks what happened.

## Bulk changes

Vouchers support the standard bulk pattern — read [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) first.

Voucher bulk operations match on **starting date, ending date, amount and validity days** together. All four form the criteria; they are not filters you can partly fill in and hope for the best.

| Operation | Effect |
|---|---|
| **Check** | Counts non-redeemed vouchers matching all four criteria |
| **Bulk update** | Updates every matching non-redeemed voucher |
| **Bulk delete** | Removes every matching non-redeemed voucher |

Because the criteria are a combination, it is easy to match more than you meant. Run the check and read the count — it is the only preview.

> Only non-redeemed vouchers are ever affected. A customer who already used theirs keeps the benefit, whatever you do here.

## Choosing between a voucher and wallet credit

They feel similar and behave differently:

| | Voucher | Wallet credit |
|---|---|---|
| Shape | A fixed amount, used once | A balance that can be spent down |
| Good for | A specific gesture with a clear value | Ongoing credit, refunds, loyalty balance |
| Expiry | Per voucher | Per credit record |

If the customer should have "€10 off one order", that is a voucher. If they should have "€10 to spend", that is wallet credit — see [Wallets: managing records](/books/console-yF7/page/wallets-managing-records).

## Related pages

- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — before any bulk operation
- [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) — issuing vouchers in bulk
- [Wallets: managing records](/books/console-yF7/page/wallets-managing-records) — the alternative shape of credit

# Personal promotions

> **You need:** *Customer promo manager — view* to browse, *… — edit* to change. Importing needs *Personal promotions — import*, separate again.

A personal promotion attaches an existing offer to a **specific customer**. Where a promotional code is something any customer can enter, this is granted to one person.

## What you can attach

The **available promos** view lists what can be attached for a given store. It is deliberately not the full catalogue — it shows offers that are:

- **hidden** — not publicly advertised, meant to be granted individually;
- **manually added** — designed for exactly this purpose;
- **not expired**.

> If an offer you expect isn't in the list, it is usually one of those three: it is a public offer rather than a grantable one, or it has expired. That is the list doing its job, not a missing record.

## Browsing existing records

Search customer promo records to see what a customer already has, or to review what was granted in a campaign of individual grants.

## Granting one

1. Find the offer under available promos for the relevant store.
2. Create the record against the customer.
3. Set the dates it is valid between.

For many customers at once, import a phone list instead — see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

## Changing and removing

| Action | Applies to | Note |
|---|---|---|
| **Update dates** | Non-redeemed records only | Extend or shorten validity |
| **Delete** | Non-redeemed records only | Removes the grant entirely |

Both are limited to records the customer hasn't used. Once redeemed, a personal promotion is part of that customer's history and stays.

To withdraw an offer a customer has *not* used, shortening the dates is gentler than deleting: the record survives, so there is an answer if they ask.

## Bulk changes

Personal promotions follow the standard pattern — read [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) first.

Here the criteria are a **promo or offer code**: you are acting on every record granting that particular offer.

| Operation | Effect |
|---|---|
| **Check** | Counts records matching the promo/offer code |
| **Bulk update** | Changes the start and end dates on every matching non-redeemed record |
| **Bulk delete** | Removes every matching non-redeemed record |

This is how you correct a grant campaign that went out with the wrong dates, without touching customers who already used theirs.

## Choosing between this and a promotional code

| | Personal promotion | Promotional code |
|---|---|---|
| Who can use it | One named customer | Anyone with the code |
| How they get it | Granted to them | They enter a code |
| Good for | Win-back, VIP treatment, service recovery | Public campaigns |

If you find yourself granting the same offer to hundreds of customers individually, a campaign with coupons is probably the better tool — see [Promotional codes](/books/console-yF7/page/promotional-codes).

## Related pages

- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — before any bulk operation
- [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) — granting from a phone list
- [Promotional codes](/books/console-yF7/page/promotional-codes) — the public-campaign alternative

# Loyalty subscriptions

> **You need:** *Subscriptions manager — view* to browse, *… — edit* to change. Importing needs *Loyalty subscriptions — import*, separate again.

A subscription gives a customer an ongoing benefit for a period, rather than a single-use code or amount.

This is the smallest area in Marketing: browse, create, update, expire. There are no bulk operations here — changes are per subscription, or via an import.

## Finding a subscription

Search either way round:

- **By customer** — everything that customer has.
- **Most recent** — browse the latest subscriptions across the brand, for a general view of what has been issued.

Searching by customer is the usual path when someone gets in touch.

## Creating one

Create the subscription against the customer with its period. For many customers at once, import a phone list instead — see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

## Updating

An existing subscription can be updated — typically its dates. Use this to extend a subscription, or to correct one entered wrongly.

## Expiring

Expiring sets the subscription to end **today**. It stops the benefit from now on without removing the record of it having existed.

There is no delete here, and that is the right design: a subscription a customer has held is part of their history. Expiring is the complete answer for "this should stop".

> Because expiry lands on today rather than a date you choose, it takes effect immediately. If the customer should keep the benefit until the end of a paid period, **update the end date** instead of expiring.

## Common situations

| Situation | What to do |
|---|---|
| Customer says their subscription ended early | Check the end date; update it if it was entered wrongly |
| Customer wants to cancel | Expire it, or set the end date to the end of their paid period |
| Subscription created against the wrong customer | Expire it and create the right one. There is no delete |
| Bulk-issuing subscriptions | Import a phone list — [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) |

## Choosing between a subscription and the alternatives

| | Subscription | Personal promotion | Wallet credit |
|---|---|---|---|
| Shape | Ongoing benefit for a period | A specific offer, granted once | A spendable balance |
| Ends by | Date | Use, or date | Being spent, or date |

If the benefit is "free delivery for three months", that is a subscription. If it is "20% off their next order", that is a personal promotion — see [Personal promotions](/books/console-yF7/page/personal-promotions).

## Related pages

- [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) — issuing subscriptions in bulk
- [Personal promotions](/books/console-yF7/page/personal-promotions) — single-use granted offers
- [Wallets: managing records](/books/console-yF7/page/wallets-managing-records) — balance-shaped benefits

# Wallets: finding a customer

> **You need:** *Wallet manager — view*.

Most wallet work starts with a customer on the phone. This page is how you get from what they can tell you to what you need on screen.

## Start from the phone number

Customers know their phone number. They rarely know their customer ID.

The **lookup by phone** resolves a phone number to the customer, which is the identifier the rest of the wallet screens use. Start here.

> If the lookup finds nobody, the usual cause is a format mismatch — country prefix, spaces — rather than the customer not existing. Try the variant the brand stores before concluding they have no account. The same trap catches CSV imports, see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

## Check the balance

With the customer identified, the balance answers the first question: how much credit do they have right now.

The balance is the net result of their wallet records. When it doesn't match what the customer expects, the records explain why — some may have expired, some may already be spent.

## Read the records

Wallet records are the individual movements making up the balance. Searching them, filtered to that customer, shows how the balance was arrived at.

This is where "I had €20 and now I have €5" gets answered: either the credit was spent, or a record reached its end date.

## Open the order behind a record

Where a record is linked to an order, you can open that order's full detail.

This closes the loop on the most common dispute — "I was charged even though I had credit". The order detail shows what was actually applied.

## A working order for a customer call

1. **Phone → customer.** Confirm you have the right person.
2. **Balance.** What do they have now?
3. **Records.** How did it get that way?
4. **Order detail**, where a record points at one. What was actually applied?

By step 4 you can usually explain the discrepancy without changing anything — which is the better outcome, since adding credit to close a conversation hides the underlying cause.

## When the answer is that something is wrong

If the records genuinely show a mistake, fixing it is on [Wallets: managing records](/books/console-yF7/page/wallets-managing-records) — creating a correcting record, adjusting one, or expiring it.

> Prefer a **new correcting record** over editing history. It leaves the original visible alongside the correction, which is what anyone reviewing this later needs to see.

## Related pages

- [Wallets: managing records](/books/console-yF7/page/wallets-managing-records) — creating and adjusting credit
- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — before touching many records
- [Vouchers](/books/console-yF7/page/vouchers) — for a fixed-amount gesture instead of a balance

# Wallets: managing records

> **You need:** *Wallet manager — edit* to create or change records. Browsing needs only the view permission. Importing credit needs *Wallet codes — import*, separate again.

A wallet record is one movement of credit. The customer's balance is the net of their records — so you never edit a balance directly, you add or adjust the records behind it.

## Adding credit

Create a record against the customer with its amount and the dates it is valid between. This is the manual path: service recovery, a goodwill gesture, a correction.

For many customers at once, import a phone list — see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

> **Set an end date deliberately.** Credit with an unnecessarily long life is a liability that sits on the books; credit that expires sooner than the customer expects becomes a complaint. Pick the date on purpose rather than accepting a default.

## Adjusting a record

An existing record's **end date and amount** can be updated.

Use this to correct a genuine data-entry error. For anything a customer has already seen, prefer adding a correcting record instead: editing the original rewrites what the customer was told, and leaves nobody able to see what happened.

## Expiring records

Expiring ends one or more records immediately. The credit stops being spendable from now on, and the record stays visible.

This is the right tool for credit issued in error, or for closing out a promotion. It is gentler than deletion and it preserves the explanation.

## Bulk changes

Wallets follow the standard pattern — read [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) first.

The criteria are **starting date, ending date, amount and, optionally, a comment**.

| Operation | Effect |
|---|---|
| **Check** | Counts non-redeemed records matching the criteria |
| **Bulk update** | Updates every matching non-redeemed record |
| **Bulk delete** | Removes every matching non-redeemed record |

> **The comment is the most useful criterion you have.** If you tag a batch with a distinctive comment when you create it, you can later act on exactly that batch and nothing else. Without it you are matching on dates and amount, which will eventually catch records from a different campaign that happen to share them.

Credit already spent is never affected, whatever you do here.

## Deciding what to do

| Situation | Action |
|---|---|
| Customer owed compensation | Create a record |
| Credit issued at the wrong amount | Create a correcting record; don't edit the original |
| Credit issued to the wrong customer | Expire it, then create it on the right customer |
| A whole batch issued with the wrong dates | Bulk update, matched on the batch's comment |
| A batch that should never have gone out | Bulk delete — removes only what nobody has spent |

## Before any bulk operation

1. Run the check and read the count.
2. If it isn't what you expected, your criteria are wrong — especially if you are matching without a comment.
3. Apply, then track to completion.
4. Spot-check a customer from the batch on [Wallets: finding a customer](/books/console-yF7/page/wallets-finding-a-customer).

## Related pages

- [Wallets: finding a customer](/books/console-yF7/page/wallets-finding-a-customer) — lookup, balance and records
- [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work) — the shared pattern
- [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv) — issuing credit in bulk
- [Vouchers](/books/console-yF7/page/vouchers) — a fixed single-use amount instead of a balance

# CMS

Web content: complaints tree, resources, banners and documents.

# CMS: what it covers

> **Audience:** content editors maintaining what customers see on the ordering channels.
> **You need:** *CMS — view* to browse, *CMS — edit* to change, *CMS — delete* to remove. Three separate levels.

The CMS covers the content layer of the ordering experience: the images and text customers see, and the complaints structure behind customer service.

## Two areas

| Area | What it holds |
|---|---|
| **Resources** | Banners, logos, documents, and the presentation of promotions, products, toppings and payment methods |
| **Complaints** | The tree of complaint types used when logging a customer complaint |

They share the same permissions but are otherwise independent.

## Three permission levels, not two

Most console areas have view and edit. The CMS has a third:

| Level | Lets you |
|---|---|
| **View** | Browse resources and the complaints tree |
| **Edit** | Create and change resources, and add or edit complaint nodes |
| **Delete** | Remove a node from the complaints tree |

Delete is deliberately separate. Removing a node from the complaints tree affects how complaints are categorised from that point on, which is harder to reverse than editing a label.

`cms_editor` is the role built for this area and holds all three.

## Content is per source

This is the concept that makes the rest of the CMS make sense.

Resources are stored **per source** — web, app, kiosk, each aggregator. The same brand can show a different banner on the web than in the app, because they are different records.

So when you edit a resource you are always editing it *for a source*. Changing the web banner does not change the app banner.

> This also means "the banner didn't change" is usually the wrong source selected rather than a failed save. Check the selector before re-uploading.

Your own scope can further limit which sources you see — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## The pages in this chapter

| Topic | Page |
|---|---|
| Resource types and the source selector | [Resources and sources](/books/console-yF7/page/resources-and-sources) |
| Finding incomplete content | [Content validation](/books/console-yF7/page/content-validation) |
| The complaints tree | [The complaints tree](/books/console-yF7/page/the-complaints-tree) |
| Editing banners, step by step | [CMS - Resources: Banners](/books/console-yF7/page/cms-resources-banners) |
| Documents per language | [CMS - Resources: Documents](/books/console-yF7/page/cms-resources-documents) |
| The resources screen | [CMS - Resources](/books/console-yF7/page/cms-resources) |
| Loading complaints from a file | [CMS - Complaints CSV Upload](/books/console-yF7/page/cms-complaints-csv-upload) |

# Resources and sources

> **You need:** *CMS — view* to browse, *CMS — edit* to change.

Resources are the content records behind what customers see. This page covers the model; [CMS - Resources](/books/console-yF7/page/cms-resources) walks through the screen itself with screenshots.

## The resource types

| Type | What it controls |
|---|---|
| **Banners** | Promotional images and where they link to |
| **Logos** | Brand imagery |
| **Promotions** | How promotions are presented |
| **Products** | How articles are presented |
| **Toppings** | How ingredients and extras are presented |
| **Payment methods** | Which payment options appear, and how |

Editing here changes **presentation**, not behaviour. A promotion's rules live in [Promotional codes](/books/console-yF7/page/promotional-codes); what the customer sees when it is offered lives here. The same split applies to products and payment methods.

## Always pick the source first

Every resource belongs to a source — web, app, kiosk, an aggregator. Selecting the source is the first thing you do, not an afterthought.

If a type has no content for the chosen source, the screen says so. That is a real answer: it means nothing is configured for that combination, not that the screen failed.

| What you see | What it means |
|---|---|
| Content listed | That type is configured for that source |
| "No data available" | Nothing configured for that type *and* source |
| A source missing from the list | Outside your scope, or not enabled for the brand |

## Languages

Several resource types are per language as well as per source — banners and documents in particular. A banner exists once per language, and each language needs its own image and destination.

That means a resource is not complete until every language is filled in. A missing translation shows as a gap for customers in that language only, which is exactly the kind of thing nobody notices internally.

[CMS - Resources: Banners](/books/console-yF7/page/cms-resources-banners) covers the per-language banner flow, and [CMS - Resources: Documents](/books/console-yF7/page/cms-resources-documents) the document one.

## Before you finish

1. **Confirm the source** you edited was the one you meant.
2. **Check every language** for that resource, not just the one you were asked about.
3. **Run the validation list** to catch anything left incomplete — see [Content validation](/books/console-yF7/page/content-validation).

# Content validation

> **You need:** *CMS — view*.

The validation list shows resources that are incomplete — the gaps that would otherwise only surface as something missing on a customer's screen.

## Why it exists

Resources are stored per source and, for several types, per language. That produces a lot of combinations, and a gap in any of them is invisible unless you go looking: the affected customers are the ones on that one source, in that one language.

Validation is the going-looking, done for you.

## When to run it

| Moment | Why |
|---|---|
| After a bulk content change | The fastest way to find what didn't land |
| Before a campaign goes live | A missing banner on one source is a campaign that partly didn't run |
| When adding a new source or language | Everything is a gap until it isn't |
| Routinely | Content decays as products and promotions change around it |

Running it before a launch is worth more than running it after. A gap found the day before is a fix; found the day after, it is an incident.

## Reading it

Each entry points at a specific combination — a type, a source and where relevant a language. That is enough to go straight to the right screen and fill it.

Work by source rather than down the list. Fixing everything wrong with the web in one pass is faster than jumping between sources, because you keep the same selector.

## Not everything listed is a problem

A gap is only a problem if that combination should have content. If your brand doesn't use the kiosk, kiosk resources being empty is correct.

Get to know which combinations matter for your brand; after that the list is short and every entry means something.

## Related pages

- [Resources and sources](/books/console-yF7/page/resources-and-sources) — the model behind the combinations
- [CMS - Resources](/books/console-yF7/page/cms-resources) — the screen, with screenshots

# The complaints tree

> **You need:** *CMS — view* to browse, *CMS — edit* to add or change, *CMS — delete* to remove.

The complaints tree is the structure of complaint types used when a customer complaint is logged. It is what makes complaints countable: a tree everyone uses the same way turns individual annoyances into a pattern you can act on.

## Browsing

The tree is a hierarchy of nodes — broad categories at the top, specific types beneath. Browse it to see the current structure before changing anything.

## Adding and editing a node

Add a node under the right parent, or edit an existing one.

> **Changing a label changes it everywhere, including on complaints already filed under it.** That is usually what you want when correcting wording. It is not what you want when the meaning shifts — if the node should now mean something different, add a new one and stop using the old.

## Deleting a node

Delete has its own permission, and for good reason. Removing a node changes how complaints can be categorised from that moment on, and existing complaints filed under it lose the structure that explained them.

Before deleting, ask:

| Question | If yes |
|---|---|
| Have complaints been filed under it? | Prefer stopping its use over deleting it |
| Is it a duplicate of another node? | Consolidate deliberately, and note which absorbed which |
| Was it created by mistake and never used? | Safe to delete |

## Keep the tree small

A tree that grows a node per incident stops being useful: with sixty options, nobody picks consistently, and the counts mean nothing.

Two tests before adding a node:

1. **Would a different person pick this node for the same complaint?** If it overlaps an existing one, it will be used inconsistently.
2. **Would you act differently on this than on its neighbour?** If not, it doesn't need to be separate.

## Loading many at once

The tree can be loaded from a file rather than built node by node — see [CMS - Complaints CSV Upload](/books/console-yF7/page/cms-complaints-csv-upload), which covers the format and the flow in detail.

As with every import, start small and confirm the structure before loading everything.

## Related pages

- [CMS - Complaints CSV Upload](/books/console-yF7/page/cms-complaints-csv-upload) — bulk loading, in detail
- [CMS: what it covers](/books/console-yF7/page/cms-what-it-covers) — permissions and the two CMS areas
- [Logging an incident](/books/console-yF7/page/logging-an-incident) — the Hub's per-order record, which is a different thing

# CMS - Complaints CSV Upload

## Overview

The Complaints Management CSV Upload system allows you to upload and manage complaint flow data using two CSV files - one in English and one in Arabic. This system provides validation, preview functionality, and error handling to ensure data integrity.

## Code Reference Legend

### Actions

| Code | Definition |
|---|---|
| X | Go to next node |
| Q | Question to a customer |
| QP | Ask for proof to a customer, upload picture |
| S | Show customer order to pick an item |
| CB | Call back to customer |
| R | Refund |
| R30 | 30% refund of the item in default |
| T | Rating |
| TT | Ticket Transfer to agent for manual evaluation |
| QD | Dialog box |
| QPD | Both Dialog Box and Picture upload |
| PHONE | Provide phone number of the delivery partner in the variable field `{%phone%}` |
| R100P150 | 100% refund of the order value, up to 150 SAR |

### Status

| Code | Definition |
|---|---|
| CA | Closed Abandoned |
| C | Closed |
| O | Open |

### Conditions

| Code | Definition |
|---|---|
| NOPHONE | Driver phone number is not available |
| YESPHONE | Driver phone number is available |
| RP | Platinum Guarantee Refund Processed |
| RNP | Refund Guarantee Refund Not Processed |
| GSE | Gold Subscription Expired |
| GSNE | Gold Subscription Not Expired |
| GTP | Customer moved from Gold to Platinum |
| PSE | Platinum Subscription Expired |
| PSNE | Platinum Subscription Not Expired |
| SLABD30 | Delivery SLA Breached - Platinum Guarantee of 30 mins from theoretical start production time to at customer location |
| NOSLAB30 | Platinum delivery guarantee SLA of 30 mins not breached |
| SLABPU15 | Pickup SLA breached - Platinum guarantee of 15 mins from theoretical start production time to actual end production time |
| NOSLAB15 | Platinum pickup guarantee SLA of 15 mins not breached |
| SLABD | Delivery SLA breach (SLA = 40 mins) |
| SLABPU | Pickup SLA breach (SLA = 15 mins) |
| N | No Condition |
| ITEMCHECKS | Item selected from the order should be a side dish only |
| NOSLAB | No SLA Breach in Pickup or Delivery |
| CHECKCUSTY | Customizations are added in the Pizza |
| CHECKCUSTN | Customizations are not added in the Pizza |
| ORSPT | Order is Pickup or Takeaway |
| ORSD | Order is Delivery |
| SLABD20 | Time breach after the SLA time is between 1 min to 20 mins |
| SLABD2035 | Time breach after the SLA time is above 20 mins and below 35 mins |
| SLABD3550 | Time breach after the SLA time is above 35 mins and below 50 mins |
| SLABD50+ | Time breach after the SLA time is above 50 mins |
| WMP | Wallet balance is present in the account |
| WMPF | Wallet balance is 0 |
| EXEMPT | If refund is given with Exempt in Action, that refund should not be counted in future customer profiling |

### Variables

| Code | English | Arabic |
|---|---|---|
| `{%wallet%}` | (%AMOUNT%) in your wallet | (%AMOUNT%) في محفظتك. |
| `{%wallet%}` | (%Loyalty Points%) rewards points | (%Loyalty Points%) نقطة مكافأة. |
| `{%wallet%}` | (%AMOUNT%) in your wallet & (%Loyalty Points%) rewards points | (%AMOUNT%) في محفظتك و (%Loyalty Points%) نقطة مكافأة. |
| `{%currency%}` | SAR | ريال سعودي. |
| `{%currency%}` | AED | درهم إماراتي. |

### Loyalty Tiers

| Code | Definition |
|---|---|
| S | Silver |
| G | Gold |
| P | Platinum |

### Customer Types

| Code | Definition |
|---|---|
| H | Healthy |
| A | Alert |
| B | Abusive |

## Upload Process Workflow

### 1. File Selection

- **English CSV**: Upload the main complaint flow structure with all node data
- **Arabic CSV**: Upload the Arabic translations for node descriptions
- **File Format**: Only `.csv` files are accepted
- **Requirement**: Both files must be selected before proceeding

### 2. File Validation

The system performs comprehensive validation in the following order:

1. **Format Validation**: Checks file structure and required columns
2. **Data Validation**: Validates data types and required fields
3. **Flow Validation**: Verifies the logical flow of complaint processes

### 3. Upload Execution

- Files are uploaded to test database tables
- Existing data for the selected brand complaints is replaced with new data
- Transaction-based upload ensures data consistency

## File Format Requirements

### English CSV Structure

Required columns (case-sensitive):

- `NODE_ID` - Numeric identifier (required, unique)
- `CUST_ID` - Customer ID (numeric, required)
- `PARENT_NODE_ID` - Parent node reference (numeric or NULL)
- `DESCRIPTION` - Node description in English (required, non-empty)
- `SHORT_NAME` - Short display name (required)
- `CUSTOMER_TYPE` - Customer type classification
- `LOYALTY_TIER` - Loyalty level indicator
- `ACTION` - Action type for this node
- `CONDITION` - Conditional logic (if applicable)
- `LAST_NODE` - End node indicator
- `STATUS` - Node status

**Column Separator**: Semicolon (`;`)

### Arabic CSV Structure

Required columns (case-sensitive):

- `NODE_ID` - Must match English CSV NODE_ID (numeric, required)
- `CUST_ID` - Must match English CSV CUST_ID (numeric, required)
- `PARENT_NODE_ID` - Parent node reference (numeric or NULL)
- `Description (Arabic)` - Arabic translation (required, non-empty)
- `SHORT_NAME` - Short name in Arabic

**Column Separator**: Semicolon (`;`)

## Validation Process

### Step 1: File Format Validation

The system checks:

- ✅ File contains headers and at least one data row
- ✅ All required columns are present
- ✅ Column count matches across all rows
- ✅ File encoding is UTF-8 compatible

**Common Format Errors:**

- Missing required columns
- Incorrect column separator (must be `;`)
- Empty files or header-only files
- Mismatched column count between rows

### Step 2: Data Type Validation

The system validates:

- ✅ `NODE_ID` is numeric
- ✅ `CUST_ID` is numeric
- ✅ `PARENT_NODE_ID` is numeric or NULL
- ✅ Required text fields are not empty
- ✅ Arabic descriptions contain valid text

**Common Data Errors:**

- Non-numeric values in ID fields
- Empty description fields
- Invalid character encoding
- Missing required field values

### Step 3: Flow Validation

Advanced validation checks:

- ✅ Node hierarchy integrity
- ✅ Parent-child relationships
- ✅ Circular reference detection
- ✅ End node validation
- ✅ Action type consistency
- ✅ Variable pattern validation

## Upload Status Indicators

### File Status Icons

- 🔄 **Validating**: File validation in progress
- ✅ **Validated**: File passed all validation checks
- ⏳ **Waiting**: File queued for validation
- ⚠️ **Warning**: File selected but not validated
- ❌ **Error**: Validation failed

### Upload Status Messages

- 📤 **"Uploading"**: Files are being processed on server
- ✅ **"Files uploaded successfully"**: Upload completed without errors
- ❌ **"Upload failed"**: Server error during upload process

## Error Messages and Solutions

### File Selection Errors

| Error | Solution |
|---|---|
| "Select both English and Arabic CSV files" | User must select both English and Arabic CSV files |
| "Both files must be validated" | Click "Validate Files" after selecting both files |

### Format Validation Errors

| Error | Cause | Solution |
|---|---|---|
| "File must contain header and at least one data row" | Empty file or missing data | Ensure file has header row and data rows |
| "Missing required column: [COLUMN_NAME]" | Required column not found | Add missing column to CSV header |
| "Row X: Column count mismatch" | Inconsistent number of columns | Ensure all rows have same number of columns |
| "Error reading CSV file" | File corruption or encoding issue | Re-save file as UTF-8 CSV with semicolon separator |

### Data Validation Errors

| Error | Cause | Solution |
|---|---|---|
| "Row X: NODE_ID must be numeric" | Non-numeric NODE_ID | Ensure NODE_ID contains only numbers |
| "Row X: CUST_ID must be numeric" | Non-numeric CUST_ID | Ensure CUST_ID contains only numbers |
| "Row X: PARENT_NODE_ID must be numeric or NULL" | Invalid parent reference | Use numbers or exactly "NULL" |
| "Row X: Arabic description is required" | Missing Arabic text | Add Arabic description for all nodes |

### Server Processing Errors

| Error | Cause | Solution |
|---|---|---|
| "No valid English CSV data found" | Empty or invalid English file | Check English file format and data |
| "No valid Arabic CSV data found" | Empty or invalid Arabic file | Check Arabic file format and data |
| "English CSV row X: Invalid NODE_ID" | Server-side validation failure | Review NODE_ID format and uniqueness |
| "Arabic CSV row X: Missing Description (Arabic)" | Server-side validation failure | Ensure all Arabic descriptions are present |
| "Maximum 2 files allowed" | Too many files uploaded | Upload exactly 2 files (English + Arabic) |

### Flow Validation Errors

| Error | Cause | Solution |
|---|---|---|
| "The flow of both files must be validated" | Flow validation not performed | Click "Validate Flow" button |
| "The files have flow validation errors that must be corrected" | Flow validation found issues | Review flow validation dialog for specific errors |

## Troubleshooting Guide

### Problem: Files won't upload

**Check:**
- Both files selected
- Both files validated successfully
- Flow validation completed without errors
- Correct brand selected

### Problem: Validation fails repeatedly

**Solutions:**
1. Download current data to see expected format
2. Check file encoding (must be UTF-8)
3. Verify column separator is semicolon (`;`)
4. Ensure no hidden characters or formatting

### Problem: Flow validation shows errors

**Actions:**
1. Open flow validation dialog
2. Review specific error messages
3. Check parent-child relationships in data
4. Verify node hierarchy integrity
5. Correct data and re-validate

### Problem: Upload fails after validation

**Investigation:**
1. Check server connection
2. Verify file size limits
3. Review browser console for errors
4. Contact system administrator if issues persist

## Technical Notes

### System Behavior

- **Environment**: CSV uploads always to test environment
- **Data replacement**: Existing complaints data is completely replaced when uploading a CSV in the test environment
- **Transaction safety**: Failed uploads don't corrupt existing data
- **File processing**: Files processed sequentially during validation
- **Memory limits**: Large files may cause browser performance issues

## Best Practices

### File Preparation

1. **Use consistent encoding**: Save CSV files as UTF-8
2. **Check separators**: Use semicolon (`;`) as column separator
3. **Validate NODE_IDs**: Ensure matching NODE_IDs between English and Arabic files
4. **Review descriptions**: Verify all descriptions are meaningful and complete

### Error Prevention

1. **Template usage**: Use existing data export as template for new uploads
2. **Data validation**: Check data in spreadsheet application before export
3. **Backup strategy**: Keep original files as backup before processing
4. **Test with small dataset**: Validate process with subset of data first

### Upload Strategy

1. **Sequential validation**: Complete file validation before flow validation
2. **Review before upload**: Check all validation messages before final upload
3. **Brand selection**: Verify correct brand is selected before upload
4. **Timing**: Perform uploads during low-traffic periods

# CMS - Resources

To access the resource management section, go to the **WEB CMS** menu in the central panel.

![WEB CMS - Resource management](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/ErrVquUS0CLuw5DJ-manual-consola-cms-recursos-p1-0.png)

Then go to the **RESOURCES** menu.

![Resources](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/RTLr32akQXdDXoEa-manual-consola-cms-recursos-p1-1.png)

Once in RESOURCES, the sidebar shows all the sections available for viewing and editing resources: Banners, Payment methods, Ingredients, Logos, Products, Promotions, and Miscellaneous.

![Resources sidebar](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/pGUKmOc9BiwtQS66-manual-consola-cms-recursos-p1-2.png)

## Selecting a source

Once a resource type is selected, if that resource has no data, the screen will show **"No data available"**.

*Example: Resource: Banners - Source: AI*

![No data available](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/m2NoVgJe038BhfiW-manual-consola-cms-recursos-p2-0.png)

If resources are available instead, the following options appear on screen:

- **Source dropdown**: to select which source to display (Web, App, Kiosk, Store, JustEat, UberEats, Stuart, Tablet, etc.).

*Example: Resource: Banners - Source: Web*

![Source dropdown](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/jPd5RmKpD6rg9Lf7-manual-consola-cms-recursos-p2-1.png)

## Search filter and parameters

- **Search filter**: to search within the displayed table.

*Example: Resource: Banners - Source: Web*

![Search filter](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/6Vdcac9ewvJRs2Td-manual-consola-cms-recursos-p3-0.png)

- **Supported parameters** for the selected resource, depending on the chosen source.

*Example: Resource: Banners - Source: Web*

![Supported parameters - Banners](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/d1aMqkCRAqe0ewG9-manual-consola-cms-recursos-p3-1.png)

*Example: Resource: Payment methods - Source: Web*

![Supported parameters - Payment methods](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/o9jQB00JeYcHVPWy-manual-consola-cms-recursos-p3-2.png)

> If no parameters are defined, the following message appears and those resources cannot be edited unless you contact the administrator: **"To edit these resources, please contact your administrator"**.

## Editing resources

- **Edit button**: to edit the resources selected in the table.

*Example: Resource: Banners - Source: Web*

![Edit button](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/wHkgpUqSVJejRLmT-manual-consola-cms-recursos-p4-1.png)

If the file is valid, the **Save** button becomes enabled. If it saves without any errors during upload, a confirmation message appears.

![Selected resources dialog](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/z57CYN2v2Wfxa6K1-manual-consola-cms-recursos-p4-3.png)

![File saved successfully](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/Jmz8t76q3JMphMP4-manual-consola-cms-recursos-p4-4.png)

If the file is not valid, a **"Invalid format"** message appears showing the incorrect parameters of the file.

![File error](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/ks54pSeonBIjkMJM-manual-consola-cms-recursos-p4-5.png)

## Preview and formats

- **Preview button**: to view a preview of the images selected in the table.

*Example: Resource: Banners - Source: Web*

![Preview button](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/NViWB4TPG7ldbFA8-manual-consola-cms-recursos-p5-1.png)

![Selected images - preview](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/KI5t9RL2GuAJItXV-manual-consola-cms-recursos-p5-2.png)

- **Size buttons (000px x 000px)** (only visible when multiple sizes are defined): selecting one shows the table for that format.

*Example: Resource: Promotions - Source: Web*

![Size format selector](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/tmWNADXo0esCagza-manual-consola-cms-recursos-p5-4.png)

# CMS - Resources: Banners

To edit a banner, select the redirect type and choose a valid file.

## Languages

Each banner has a corresponding banner in **Catalan** and **English**.

![Image language selector](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/NQUh5rmoFf66Y5SL-manual-consola-cms-recursos-banners-p1-0.png)

Each banner, in its respective language, must have an image and a redirect assigned to it.

![Banner table by language](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/7RcNGLLJOZWZ2M3M-manual-consola-cms-recursos-banners-p1-2.png)

## Destination URL

There are 3 available options for adding a banner's redirect (**only one can be chosen**):

![Selected resources - choose redirect type](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/9iWHU4lgMK7Jfyhv-manual-consola-cms-recursos-banners-p2-0.png)

- **PRODUCT** → select from the list the product the banner should redirect to.

![Product selection](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/bzcDcKNT8W7uwzSq-manual-consola-cms-recursos-banners-p2-1.png)

- **PROMOTION** → select from the list the promotion the banner should redirect to.

![Promotion selection](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/HCsz9CL8fhtYGSQ7-manual-consola-cms-recursos-banners-p3-0.png)

- **URL** → enter the complete, correct URL of the page you want to redirect the customer to, as long as it isn't a specific offer or product.

*Example:*
- Redirect to the "promocion-sala" landing page
- URL: `https://www.pizzeriascarlos.es/promocion-sala/`

![Destination URL field](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/MCfV3zg63Bg73DqC-manual-consola-cms-recursos-banners-p3-1.png)

# CMS - Resources: Documents

To update documents, go to the **WEB CMS** menu and then **RESOURCES**.

![WEB CMS - Resource management](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/TKPxHV9cerBG4O2U-manual-consola-cms-recursos-documentos-p1-0.png)
![Resources](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/CJ6pFzD41idOuukY-manual-consola-cms-recursos-documentos-p1-1.png)

## Languages

Each language has its own corresponding documents.

![Document language selector](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/yJJEQ8s4a1kvj3UF-manual-consola-cms-recursos-documentos-p1-3.png)

![Documents table](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/Kx9K8yg6TGNtBbv6-manual-consola-cms-recursos-documentos-p1-4.png)

> **Only one document can be uploaded at a time.**

## Updating a document

To update a document, select a file with a valid extension and a size under the maximum allowed. In this case, a **PDF** file smaller than **2 MB**.

If the selected file is valid, the **Save** button becomes enabled.

![Selected resources - upload document](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/FZUkT9pwMmnPRFio-manual-consola-cms-recursos-documentos-p2-1.png)

Once it has saved successfully, a confirmation message appears:

![File saved successfully](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/fovrqdfZOsKpdhmc-manual-consola-cms-recursos-documentos-p2-2.png)

# Reports & Metrics

Sales, products, promotions, trends, operations, alerts and per-order logs.

# Reports and Metrics: which to use

> **Audience:** managers and analysts reading performance data, and operations staff investigating a specific order.
> **You need:** *Sales reports* for Reports, *Metrics* for Metrics. They are separate permissions.

The console has two data areas, and picking the right one saves a lot of time.

| | Reports | Metrics |
|---|---|---|
| Answers | "How did we do?" | "What happened to this order?" |
| Shape | Aggregated — totals, breakdowns, trends | Per-order — logs, timelines |
| Period | A business date or a date range | Recent activity |
| Typical reader | Management, analysts, area managers | Support, operations, anyone investigating |

If you are looking at numbers, you want Reports. If you are looking for one order's history, you want Metrics.

## What's in Reports

| Report | Answers |
|---|---|
| **Sales** | Revenue over the period |
| **Product sales** | Which articles sold, grouped by family |
| **Promotions** | How much each promotion was used |
| [Sales by trend](/books/console-yF7/page/sales-by-trend) | Monday-to-Sunday pattern, by category |
| **Operations** | Live-ish operational picture: order states, staff on shift, load over the day, individual orders |
| [Operational alerts](/books/console-yF7/page/operational-alerts) | Stores currently outside a threshold you set |

## What's in Metrics

| View | Answers |
|---|---|
| **Order logs** | Which orders had activity, and what was done to them |
| **Order timeline** | The full sequence of actions on one order |
| [Archived alarms](/books/console-yF7/page/archived-alarms) | Alarms already dealt with |

## Permissions

| To do this | You need |
|---|---|
| Open any report | *Sales reports* |
| Open order logs and timelines | *Metrics* |
| Export from Metrics | *Metrics — export*, a separate and higher permission |
| See archived alarms | *Archived alarms* |

`report_viewer` is the role built for this: reports, metrics and store visibility, nothing else. It combines well with an operational role — see [Common role combinations](/books/console-yF7/page/common-role-combinations).

## Everything is scoped

Every figure is filtered to your scope before you see it. Two colleagues running the same report on the same day can legitimately get different totals, because they can see different stores or brands.

This is the single most common "the report is wrong" report, and it usually isn't. See [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice) before investigating further.

## The pages in this chapter

| Topic | Page |
|---|---|
| Filters, dates and the dashboard | [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites) |
| Revenue | [Sales report](/books/console-yF7/page/sales-report) |
| Articles sold | [Product sales report](/books/console-yF7/page/product-sales-report) |
| Promotion usage | [Promotions report](/books/console-yF7/page/promotions-report) |
| Weekly patterns | [Sales by trend](/books/console-yF7/page/sales-by-trend) |
| The operations screen | [Operations report](/books/console-yF7/page/operations-report) |
| Threshold-based store alerts | [Operational alerts](/books/console-yF7/page/operational-alerts) |
| One order's history | [Order logs and timelines](/books/console-yF7/page/order-logs-and-timelines) |
| Getting data out | [Exporting data](/books/console-yF7/page/exporting-data) |

# Filters, dates and favourites

> **You need:** *Sales reports*.

Every report is driven by the same filter set. Understanding it once covers the whole chapter.

## The filters

| Filter | Required? | Notes |
|---|---|---|
| **Brand** | Yes, on most reports | Reports are built per brand. With one brand it may be preselected |
| **Stores** | No | Empty means every store within your scope |
| **Sources** | No | Web, app, aggregators. Only applies where source filtering is active |
| **Business date** | Yes | The day the report covers |
| **End date** | Only in range mode | Turns one day into a period |
| **Time window** | No | Narrows to part of the day |

The available values are loaded from your own scope, so the store list is what you can see, not everything that exists.

## Day mode versus range mode

- **Day** — one business date. The default for operational questions.
- **Range** — a start and an end date, for a week, a month, a comparison.

Switching modes changes what some reports mean. A 15-day chart in range mode anchors on the **end** date, not the start.

## Business date is not calendar date

This is the one that catches people out.

A business date covers a trading day, which for late-night service runs past midnight. An order placed at 01:30 on Saturday usually belongs to **Friday's** business date.

So "yesterday's sales" means yesterday's *business date*, not the calendar day. When a total looks like it's missing the late evening, check which date the late-night orders landed on.

> Two reports disagreeing by roughly one evening's trade is almost always this, not a fault.

## The time window

Start and end times narrow a report to part of the day — the lunch service, the evening peak.

The window applies **within** each business date in your range. Setting 19:00–23:00 over a week gives you seven evenings, not one continuous stretch.

## Favourites and the dashboard

The dashboard pulls its widgets in one batch, so it loads as a single view rather than piece by piece.

Use it for the numbers you check daily. For anything you need to slice, go to the report itself — the dashboard is a summary, not a filtered view.

## Getting a report you can trust

1. **Set the brand first.** Most reports need it, and changing it later resets other choices.
2. **Confirm the mode**, day or range.
3. **Set the business date**, remembering it is not the calendar date.
4. **Add stores and sources only if you mean to narrow.** Empty means "everything I can see", which is usually what you want.
5. **Check the store count** against what you expect. Fewer stores than expected is a scope question — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## Related pages

- [Reports and Metrics: which to use](/books/console-yF7/page/reports-and-metrics-which-to-use) — picking the right area
- [Operations report](/books/console-yF7/page/operations-report) — the most filter-sensitive screen
- [Exporting data](/books/console-yF7/page/exporting-data) — taking figures out of the console

# Sales report

> **You need:** *Sales reports*.

The sales report gives revenue over the period you select, filtered to your scope.

## Reading it

Set the brand, the mode and the business date — see [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites) — and the report covers exactly that period for the stores you can see.

In day mode it answers "how did we do yesterday". In range mode it answers "how did we do last month", and is the right shape for comparing periods.

## Before comparing two periods

Comparisons go wrong for mundane reasons more often than interesting ones:

| Check | Why |
|---|---|
| Same store set? | Adding or closing a store changes the total without changing performance |
| Same sources? | An aggregator switched on mid-period moves volume without creating it |
| Comparable days? | Four weekends against five is a 20% swing on its own |
| Same time window? | A window applied to one period and not the other isn't a comparison |

## When the total looks wrong

1. **Business date, not calendar date.** Late-night trade lands on the previous business date. This explains most "missing evening" reports — see [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites).
2. **Your scope.** You see your stores, not all stores. A colleague's different total may be correct for both of you — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).
3. **Cancellations.** A high cancellation rate suppresses revenue while order counts stay high — check [Operational alerts](/books/console-yF7/page/operational-alerts).

## Related pages

- [Product sales report](/books/console-yF7/page/product-sales-report) — the same period broken down by article
- [Promotions report](/books/console-yF7/page/promotions-report) — how much was given away over that period
- [Sales by trend](/books/console-yF7/page/sales-by-trend) — the weekly pattern behind the total

# Product sales report

> **You need:** *Sales reports*.

Product sales shows what actually sold, by article, grouped into families.

## What it's for

The sales total tells you the revenue. This tells you what produced it — which articles moved, and which families carry the period.

Typical questions it answers:

- Which articles are worth keeping on the menu?
- Did a new product land, or is it sitting?
- Has the family mix shifted since last month?

## Reading the grouping

Articles roll up into families, so you can read it at either level: the family view for the shape of the menu, the article view for individual decisions.

A family that looks flat can hide one article growing while another falls. Open the family before concluding nothing changed.

## Pairing it with other reports

| Question | Also look at |
|---|---|
| Did discounting drive these sales? | [Promotions report](/books/console-yF7/page/promotions-report) |
| Is this a weekday or weekend product? | [Sales by trend](/books/console-yF7/page/sales-by-trend) |
| Did the revenue actually land? | [Sales report](/books/console-yF7/page/sales-report) |

An article that sells well only alongside heavy promotion is a different business case from one that sells at full price. The two reports together say which you have.

## Scope applies here too

Figures cover the stores within your scope for the period selected. A product that looks absent may simply not be sold by the stores you can see — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

# Promotions report

> **You need:** *Sales reports*.

The promotions report shows usage grouped by promotion code — how often each was applied over the period.

## What it's for

It answers the question marketing actually needs: **which promotions are being used**, and therefore which are worth repeating.

Read alongside [Sales report](/books/console-yF7/page/sales-report) and [Product sales report](/books/console-yF7/page/product-sales-report), it also answers the harder question — whether the promotion brought in business or discounted business that was coming anyway.

## Reading it

Usage is grouped by code, so a campaign appears as the code customers entered. If you ran several codes for one campaign, you will see them separately; group them yourself, or run future campaigns as one campaign with multiple coupons so they stay together — see [Promotional codes](/books/console-yF7/page/promotional-codes).

## Questions worth asking of it

| Pattern | What it usually means |
|---|---|
| High usage, flat sales | Discounting business you already had |
| Low usage, wide distribution | The code isn't reaching people, or isn't attractive |
| Usage concentrated in a few stores | Local awareness, or the offer only applies there |
| Usage stops abruptly mid-period | The campaign hit its end date, or was expired — check [Promotional codes](/books/console-yF7/page/promotional-codes) |

## Zero usage is a finding

A promotion with no usage is worth investigating before repeating it. The usual causes are all fixable: the campaign was not live for those stores, the dates or valid times excluded the trading period, or the codes were never distributed.

The [Promotional codes](/books/console-yF7/page/promotional-codes) page lists the checks in order.

## Related pages

- [Promotional codes](/books/console-yF7/page/promotional-codes) — creating, expiring and troubleshooting campaigns
- [Personal promotions](/books/console-yF7/page/personal-promotions) — offers granted to specific customers
- [Sales report](/books/console-yF7/page/sales-report) — whether the revenue moved

# Sales by trend

> **You need:** *Sales reports*.

Sales by trend lays the period out as a Monday-to-Sunday matrix, broken down by category.

## What it's for

Totals hide the shape of a week. This report shows it: which days carry the business, which are quiet, and whether that differs by category.

It is the report to use for decisions that are **about the week** rather than about the total — staffing, opening hours, when to run a promotion.

## Reading the matrix

Rows are categories, columns are days of the week. The useful reading is across a row: a category that is flat all week behaves differently from one that spikes at the weekend.

Over a multi-week range each weekday aggregates all its occurrences, so a four-week range gives you four Mondays combined. That is what makes the pattern readable rather than noisy.

## Watch the range length

An uneven range distorts the matrix. Thirty days contains five of some weekdays and four of others, so those columns are not comparable.

> Use whole weeks when you want to compare days. Use any range you like when you only want each day's total.

## What to do with it

| Pattern | Worth considering |
|---|---|
| One weekday consistently quiet | A promotion aimed at that day, or reduced staffing |
| Weekend-heavy in one category only | Menu or pricing specific to those days |
| A day that changed shape recently | Compare against an earlier range before assuming a trend |

## Related pages

- [Sales report](/books/console-yF7/page/sales-report) — the total behind the pattern
- [Product sales report](/books/console-yF7/page/product-sales-report) — which articles drive each category
- [Operations report](/books/console-yF7/page/operations-report) — the shape of a single day, hour by hour

# Operations report

> **You need:** *Sales reports*.

The operations report is the widest screen in the chapter. It answers "what is happening operationally", where the other reports answer "what did we earn".

## Order status and breakdown

Counters for where orders currently stand, plus a full breakdown by order type — delivery, pickup, takeaway and dine-in — each split into time buckets.

The time buckets are the useful part. A high total tells you it is busy; the buckets tell you whether orders are moving or piling up in the slow end.

## Staff on shift

Who is currently active, grouped by job position, alongside the status of every store that is open.

An employee counts as **active** when they have clocked in for the shift and not yet clocked out. So this is a live view of who is actually on, not who was rostered.

Read it next to the order counters: heavy volume against thin staffing is the situation worth acting on, and neither number says it alone.

> Requires a brand. Stores are optional, and empty means every store in your scope.

## The 15-day chart

A time series of total orders in fixed 15-minute slots, for the reference date and the previous 14 business days.

The reference date is the business date in day mode, and the **end** date in range mode — worth knowing before you conclude the chart is showing the wrong period.

Fourteen days of history is what makes today readable. A slow hour only means something against how that hour normally looks.

## Orders list, count and detail

Three views of the same underlying set:

| View | Use it for |
|---|---|
| **Count** | How many orders match the filters, without loading them |
| **List** | The orders themselves |
| **Detail** | Line-level detail of one order |

Run the count first when you are unsure how wide your filters are — it is much faster than loading a list you are about to discard.

Order detail is scoped per order, so you can only open orders for stores, brands and sources within your scope.

## Store configuration changelog

The most recent configuration change per store.

This is the report that answers "did something change?" when a store's behaviour shifts. Before investigating a store that has started missing its promised times, check whether its settings moved — see [Store delivery settings](/books/console-yF7/page/store-delivery-settings).

## Related pages

- [Operational alerts](/books/console-yF7/page/operational-alerts) — the threshold-based version of this screen
- [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites) — business date, modes and time windows
- [The order board](/books/console-yF7/page/the-order-board) — the live Hub view, for acting rather than reading

# Operational alerts

> **You need:** *Sales reports*.

Three reports that return only the stores currently outside a threshold you set. Where the operations report shows everything, these show you only what needs attention.

## The three

| Alert | Flags a store when | Default threshold |
|---|---|---|
| **Delivery load** | Pending deliveries per active driver exceed the threshold | 2 |
| **Cancellations** | The share of valid orders falls below the threshold | 90% |
| **On-time** | On-time percentage is below the threshold **and** average delivery time exceeds the maximum | 80% and 60 min |

Each threshold is a parameter you pass when running the report, not a saved setting. Change it and re-run to widen or narrow what comes back.

## The on-time alert needs both conditions

This is the one people misread. A store is only flagged when it is **both** below the on-time percentage **and** above the average-time limit.

That is deliberate. A store missing its target by a little while still delivering quickly is a different problem from one that is slow across the board — and only the second is worth interrupting someone for.

If you want the looser view, raise the maximum-minutes value rather than lowering the percentage.

## These are not the Hub alarms

Easy to confuse, genuinely different:

| | Operational alerts | Branch alarms |
|---|---|---|
| Where | Reports | Hub |
| Threshold | Passed per run, by you | Configured chain-wide |
| Lifecycle | Recomputed each time you run it | Raised, then archived |
| Use | Reviewing a period | Acting during service |

Use these when reviewing; use [Branch alarms](/books/console-yF7/page/branch-alarms) when running the shift.

## A workflow that works

1. Run all three at their defaults for yesterday.
2. For any store that appears, open the [Operations report](/books/console-yF7/page/operations-report) filtered to it.
3. Check the **store configuration changelog** — did something change?
4. If several stores appear together, suspect a shared cause before working through them one at a time.
5. If the same store appears every day, the threshold is describing normal for that store. Either fix the store or accept it — a permanently-flagged store is noise.

## Related pages

- [Operations report](/books/console-yF7/page/operations-report) — the full picture behind an alert
- [Branch alarms](/books/console-yF7/page/branch-alarms) — the live, chain-wide equivalent
- [Alarm settings](/books/console-yF7/page/alarm-settings) — where the Hub's thresholds are configured
- [Store delivery settings](/books/console-yF7/page/store-delivery-settings) — the tolerances a store is measured against

# Order logs and timelines

> **You need:** *Metrics*.

Order logs answer "what happened to this order" — the question Reports cannot answer, because Reports aggregates.

## The paginated list

Orders that have recorded activity, filtered by store, brand and source within your scope. Use it to find the order you care about, then open its timeline.

## The timeline

The full sequence of actions recorded against one order, in order.

An order is identified by store, ticket number and source together — a ticket number alone is not unique across stores. See [The order board](/books/console-yF7/page/the-order-board) for why.

The timeline is what answers:

| Question | What to look for |
|---|---|
| Who handled this, and when? | The sequence of actions and their timestamps |
| Was it reassigned? | Assignment changes, and whether a person or the dispatcher made them |
| Why was it late? | Where the gap actually sits — kitchen, pickup, or on the road |
| Did someone force or cancel it? | Those actions appear here with who did them |

## Using it in an investigation

1. **Get the identifiers.** Store, ticket number, source.
2. **Open the timeline.** Read it end to end before forming a theory — the interesting event is often earlier than expected.
3. **Find the gap.** Look for the long pause rather than the last event.
4. **Cross-check the dispatcher** if the gap is in assignment — a Request ID from that period ties it to a specific run. See [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue).

## Metrics versus the Hub

Both show orders, for different purposes:

| | Metrics | Hub |
|---|---|---|
| Time frame | What already happened | What is happening now |
| Purpose | Explain | Act |
| Can you change anything? | No | Yes |

If the order is still live and needs intervention, work in the Hub — see [The order board](/books/console-yF7/page/the-order-board). If it is finished and needs explaining, work here.

## Related pages

- [Exporting data](/books/console-yF7/page/exporting-data) — taking this out of the console
- [Archived alarms](/books/console-yF7/page/archived-alarms) — the alarm-side history
- [The order board](/books/console-yF7/page/the-order-board) — the live equivalent

# Exporting data

> **You need:** *Metrics — export*. This is a **separate, higher** permission than *Metrics*, and is normally held by `admin` rather than by everyone who can read metrics.

Two things can be exported: **order action logs** and **archived alarms**. Both come out as data files for analysis outside the console.

## Why export is a separate permission

Reading inside the console is scoped. Every screen filters to the stores, brands and sources you are allowed to see, every time you open it.

**An exported file is a snapshot with that protection removed.** Once it is a file on someone's laptop, it can be opened, forwarded and combined by anyone, regardless of what they could see in the console. The scope applied at export time; it does not travel with the file.

That is why export sits behind its own permission, and why it is worth thinking about before using it.

## Before exporting

| Ask | Because |
|---|---|
| Do I need the raw rows, or an answer? | A conclusion shared in a message is safer and usually more useful than a spreadsheet |
| Can the report answer it instead? | [Reports and Metrics: which to use](/books/console-yF7/page/reports-and-metrics-which-to-use) — aggregated reports often answer the question without raw data |
| Who will end up with this file? | Assume everyone the recipient works with |
| Does it need customer data? | Narrow the filters before exporting, not after |

## Exporting

1. Filter down to what you actually need — the export honours your filters and your scope.
2. Export.
3. Store it somewhere appropriate, and delete it when the analysis is done.

Narrow filters are the main control you have. An export of one store for one day is a very different object from an export of the whole chain for a month.

## What you get

The export reflects exactly what you could see when you ran it. It is not live — reopening the file a week later shows the state at export time, not now.

For anything recurring, re-export rather than reusing an old file, and take the answer from the console where you can.

## Related pages

- [Order logs and timelines](/books/console-yF7/page/order-logs-and-timelines) — what is being exported
- [Archived alarms](/books/console-yF7/page/archived-alarms) — the other exportable history
- [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice) — what scope does, and what it stops doing at export

# Employees

Staff records, hubs, contracted drivers, and bulk import/export.

# Employees: what this area covers

> **Audience:** HR and anyone who maintains staff records and their console access.
> **You need:** *Employees — view* to browse, *Employees — edit* to change.

The Employees area holds your staff records: who works where, which of them drive, and who can sign in to the console.

## What it covers

| Task | Page |
|---|---|
| Find someone and read their record | [Finding an employee](/books/console-yF7/page/finding-an-employee) |
| Change their details or where they work | [Editing an employee](/books/console-yF7/page/editing-an-employee) |
| Add a contracted driver | [External employees](/books/console-yF7/page/external-employees) |
| Move many records at once | [Importing and exporting employees](/books/console-yF7/page/importing-and-exporting-employees) |
| Give someone a console password | [Employees - Password](/books/console-yF7/page/employees-password) |

## Employees, roles and drivers

Three things that attach to one person and are managed in different places:

| | What it is | Where |
|---|---|---|
| **The employee record** | Who they are, where they work | Here |
| **Their console access** | What they can do and see | [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console) |
| **Their driver configuration** | How the dispatcher treats them | Here, on the record, for drivers |

Creating an employee does **not** give them console access, and giving them a role does **not** give them a password. Those are three separate steps, deliberately — most employees never sign in to the console at all.

## Everything is per brand

The Employees area works within the brand you are in, and is filtered further by your scope. `employee_manager` is the role built for it and is scoped at brand level.

A colleague seeing a different list is normal — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

## Hubs

Employees are associated with hubs — the operational groupings your stores belong to. The hub list is available when editing someone, and determines which operation they are counted as part of.

Get this right before worrying about roles: a correct hub with no role is a normal employee; a role with the wrong hub is someone appearing in the wrong operation's numbers.

## What this area does not do

- It does not set passwords. That is the [Employees - Password](/books/console-yF7/page/employees-password) flow.
- It does not grant permissions. That is [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console).
- It does not configure carriers' couriers. External carriers' staff are theirs, not yours — see [External carriers](/books/console-yF7/page/external-carriers).

# Finding an employee

> **You need:** *Employees — view*.

## The employee list

The list shows staff within the brand you are in and within your scope. Search by name or identifier; if the person exists and is visible to you, they appear.

> Someone missing from the list is usually scope rather than a missing record. A brand-scoped user cannot see another brand's staff even though the record exists. Check [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice) before creating a duplicate.

Creating a duplicate employee because the original wasn't visible is the most expensive mistake in this area — it splits their history across two records.

## The employee record

Opening someone shows their details and, **if they are a driver, their driver configuration alongside**.

That combination is the useful part. For a driver you can see in one place who they are, which hub they belong to, and how the dispatcher treats them — rather than reconciling two screens.

For non-drivers the driver section simply isn't there.

## Hubs

The available hubs for the brand can be listed independently of any one employee. Use it to check the hub structure before assigning someone, especially if hub names are similar.

## What to check on a record

| Looking for | Where |
|---|---|
| Are they in the right operation? | Their hub |
| Can they deliver? | The driver configuration on the record |
| Can they sign in? | Their roles — [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console) — and their password |
| Why doesn't the dispatcher use them? | Driver type and availability, plus [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority) |

## Related pages

- [Editing an employee](/books/console-yF7/page/editing-an-employee) — changing what you found
- [External employees](/books/console-yF7/page/external-employees) — adding a contracted driver
- [Importing and exporting employees](/books/console-yF7/page/importing-and-exporting-employees) — working in bulk

# Editing an employee

> **You need:** *Employees — edit*.

Editing updates an existing employee's details, their hub, and — for drivers — how the dispatcher treats them.

## Changing details

Open the employee and change what needs changing. The record is the single source for who this person is, so keeping it accurate matters beyond this screen: reports group by hub, and the dispatcher reads driver configuration from here.

## Moving someone between hubs

Changing an employee's hub moves them between operations.

> This changes which operation they count towards from now on. Historical data is not retrospectively re-filed, so a mid-period move shows the person's work split across both hubs for that period. That is correct, and worth knowing before someone asks why a hub's numbers look odd.

Move people at a period boundary where you can.

## Driver configuration

For employees who drive, the record carries the configuration the dispatcher uses — including their driver type, which decides how they rank against others.

Two things follow from that, both explained in [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority):

- **Internal drivers outrank contracted ones.** Changing someone's type changes who wins when both are candidates for an order.
- **Type also decides what they can be given.** A zone that allows only certain own-driver types will skip anyone else.

So a driver who is "never getting orders" is often a type question rather than an availability one — check the zone's allowed types in [Delivery zones](/books/console-yF7/page/delivery-zones) as well.

## What editing does not change

| Not changed here | Change it in |
|---|---|
| What they can do in the console | [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console) |
| What data they can see | [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope) |
| Their password | [Employees - Password](/books/console-yF7/page/employees-password) |

Updating someone's job does not update their console access. If a person changes role in the business, all three need attention, and it is easy to do one and forget the others.

## After a change that matters

For a driver, confirm the change had the effect you intended by watching whether the dispatcher starts using them as expected on the next cycle — no more than 60 to 300 seconds away. See [The assignment cycle, step by step](/books/console-yF7/page/the-assignment-cycle-step-by-step).

# External employees

> **You need:** *Employees — edit*.

An external employee is a **contracted driver who is part of your own fleet** — someone who delivers for you but is not on your payroll.

## The distinction that matters

Three kinds of driver, easy to confuse:

| | What they are | Managed |
|---|---|---|
| **Internal driver** | Your own staff | Here, as a normal employee |
| **External / contracted driver** | Contracted, but part of your fleet | **Here** — this page |
| **Third-party carrier** | A delivery company you hand orders to | Not here — [External carriers](/books/console-yF7/page/external-carriers) |

The middle one is the one this page is about. A contracted driver is still **your fleet**: the dispatcher assigns them individually, by name, exactly as it does your own staff. A third-party carrier is a company — you hand it the order and it picks the courier.

If you are setting up a courier platform, you are in the wrong place; go to [External carriers](/books/console-yF7/page/external-carriers).

## Creating one

Create the external employee against the brand, then set their hub and driver configuration as you would for any driver — see [Editing an employee](/books/console-yF7/page/editing-an-employee).

## How the dispatcher treats them

Contracted drivers rank **below** internal drivers. When both could take an order, the internal one wins, and an internal driver can take an order away from a contracted one — never the reverse. The full ranking is in [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority).

Two consequences worth knowing:

- **A zone that doesn't list their type will never give them work.** Empty allowed-driver-types on a zone blocks all own drivers, contracted included — see [Delivery zones](/books/console-yF7/page/delivery-zones).
- **A store with no internal drivers is treated more leniently.** Its own-fleet delay tolerance is automatically replaced by the third-party value, so an all-contracted store isn't held to a standard it can't meet — see [Store delivery settings](/books/console-yF7/page/store-delivery-settings).

## Adding several at once

Use the CSV import rather than creating them one at a time — see [Importing and exporting employees](/books/console-yF7/page/importing-and-exporting-employees).

## Related pages

- [External carriers](/books/console-yF7/page/external-carriers) — third-party delivery companies, which are not this
- [Driver classification and priority](/books/console-yF7/page/driver-classification-and-priority) — how types rank
- [Delivery zones](/books/console-yF7/page/delivery-zones) — which driver types a zone permits

# Importing and exporting employees

> **You need:** *Employees — view* to export, *Employees — edit* to import.

## Exporting

Export produces a CSV of the employees within your brand and scope.

Two good reasons to export, beyond bulk editing:

- **As a snapshot before a bulk change.** It is your rollback, and takes seconds.
- **To review.** Reading a hundred records in a spreadsheet is faster than clicking through a hundred screens.

> An exported file is no longer scope-filtered. It contains staff data, so treat it accordingly — see the same caution in [Exporting data](/books/console-yF7/page/exporting-data).

## Importing

Import applies employee configuration from a CSV.

The sequence that avoids trouble:

1. **Export first.** This gives you both the current state and the exact column format the import expects.
2. **Edit the exported file** rather than building one from scratch.
3. **Test with a few rows.** Confirm they landed as intended before committing the rest.
4. **Import the remainder.**
5. **Spot-check** two or three people on the employee list.

## Imports are not transactional

A failure partway through leaves earlier rows applied and later rows not. There is no rollback, which is why the export in step 1 matters and why you find out *where* it stopped before re-submitting.

Re-running a whole file after a partial failure may re-apply rows that already succeeded.

## Backups

Employee configuration is covered by the backup system: exports are retained, and can be downloaded or restored — see [Backups and restoring configuration](/books/console-yF7/page/backups-and-restoring-configuration).

Restoring an employee backup follows the employee permission, not a separate backup one. And as with every restore, it reinstates the snapshot wholesale: anything changed since, by anyone, is overwritten for the records it covers.

## What import does not touch

Roles, scope and passwords are not part of employee configuration. Importing a hundred employees gives none of them console access — see [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console).

# Employees - Password

To assign or change an employee's password, go to the **EMPLOYEES** menu.

![Employees - Employee management](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/7EjQuGqcGonzl24y-manual-empleados-contrasena-p1-0.png)

Enter your search term in the search box; if the user exists, they will appear in the table below:

![Filters and search](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/EHXYkCc90KCArTvR-manual-empleados-contrasena-p1-2.png)

![Results table](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/881FozxCarh0xRsb-manual-empleados-contrasena-p1-4.png)

Once you've located the user, click that row; a new window will open with all of that user's information:

![Employee record](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/kxL1FeGbZirzuHPT-manual-empleados-contrasena-p1-5.png)

## Setting or changing the password

In the password section, you can generate a password for that user (if they don't have one) or replace an existing one with a new one.

![Set password - employee not yet registered](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/1OI1MV7OUbMd35fh-manual-empleados-contrasena-p2-2.png)

Once the password has been reset or created, a message appears at the top of the screen and that user will be able to log in to the console.

![Changes updated successfully](https://pav-bookstack-docs-uploads.s3.eu-west-par.io.cloud.ovh.net/uploads/images/gallery/2026-09/2IrGVszxb4G40vCh-manual-empleados-contrasena-p2-4.png)

> Only users with the **"Employee Manager"** or **"Administrator"** roles can create or reset passwords.

# Roles & permissions

Who can do what in the console, and what data each employee sees.

# Roles and scope: the two ideas

> **Audience:** Platform administrators who decide who can do what in the console.
> **You need:** the Assignments and Scope sections, available to `admin` and `employee_manager`.

Access in the console is built on two independent ideas. Keeping them apart is the key to the whole model.

| Concept | What it defines | Where it's configured |
|---|---|---|
| **Roles** | What an employee **can do** — actions and modules | Assigned to the employee; one or more |
| **Scope** | What data an employee **can see** — brands, stores, sources | Configured once, per employee |

An employee can hold several roles at once. The permissions from all of them are combined. The scope, by contrast, is always **shared** — it applies equally to everything that employee can do.

## Why this separation matters

It means you never have to choose between "can operate the hub" and "can only see two stores". Those are answered separately:

- Give them `hub_operator` → they can work the hub.
- Set their scope to stores 1 and 2 → they only ever see those two stores' data, in the hub and everywhere else.

Adding a second role later — reports, for example — does not widen what data they see. The scope still holds.

## The three things you'll configure

| You want to… | Go to |
|---|---|
| Let someone do something new | [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console) |
| Change what data they can see | [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope) |
| Grant or block one specific ability, just for them | [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions) |

Roles decide what someone can do; their staff record is separate — see [Employees: what this area covers](/books/console-yF7/page/employees-what-this-area-covers).

## Where to read next

- [Scope: what data an employee can see](/books/console-yF7/page/scope-what-data-an-employee-can-see) — the two modes and the three dimensions
- [The role catalogue](/books/console-yF7/page/the-role-catalogue) — every role and who it's for
- [Access by section](/books/console-yF7/page/access-by-section) — the full grid of role against console section

# Scope: what data an employee can see

Every employee has a single scope, which controls what data is visible to them everywhere in the console.

## The two modes

| Mode | What it means |
|---|---|
| **Customer** | Full access to all data across the entire chain |
| **Explicit** | Access restricted to specific brands, stores and/or sources |

## The three dimensions of an explicit scope

When scope is explicit, you can combine these filters:

| Dimension | What it filters | Example |
|---|---|---|
| **Brands** | Which brands are visible | Brand "X" only |
| **Stores** | Which specific stores are visible | Stores 1, 2 and 5 |
| **Sources** | Which order sources are visible | Only Glovo and Uber Eats |

**How they combine:**

- Each dimension narrows the view further — they stack, they don't widen each other.
- Leaving a dimension empty means "no restriction" for that dimension.
- Store groupings — predefined sets of stores — are supported, and combine with individually chosen stores.

## Worked examples

| Configuration | What the employee sees |
|---|---|
| Brand = "Example Brand" | All stores and sources for that brand |
| Stores = 1, 2, 3 | All brands operating in those stores |
| Brand = "Example Brand" + Stores = 1, 2 | Only that brand's data, in those two stores |
| Stores = 1 + Sources = Glovo, Uber | Only Glovo and Uber data in store 1, wherever source filtering applies |

## A note on Sources

The source filter is a complementary dimension with **limited activation**. Not every module uses it — it only takes effect in sections where filtering by order origin is meaningful. Those include:

- **Reports** — sales data for specific aggregators only
- **Metrics** — order timelines and logs filtered to certain channels
- **CMS** — content configuration scoped to specific sources
- **Hub** — special cases where an operator only handles orders from certain origins

In modules where source filtering is not active, the employee sees all orders within their brand and store scope, regardless of this setting.

> This makes Sources best suited to specialised roles that need a narrower view inside one particular section — not as a general-purpose restriction.

## What scope does not do

Scope never grants anything. It only narrows. An employee with a store-limited scope and no reporting role still cannot open reports — scope decides *what data*, roles decide *what actions*. See [Roles and scope: the two ideas](/books/console-yF7/page/roles-and-scope-the-two-ideas).

# The role catalogue

Each role covers one area of responsibility. If someone needs two areas, give them two roles — see [Common role combinations](/books/console-yF7/page/common-role-combinations).

| Role | For whom | What it covers |
|---|---|---|
| `admin` | Customer administrator | Everything except managing role definitions. The highest role assigned to customer employees |
| `hub_manager` | Operations manager | Full hub — assign, force delivery, cancel, modify ETA, incidents, alarms — plus store configuration, pause delivery, metrics and archived alarms |
| `hub_operator` | Hub shift operator | Basic hub operations, without the critical actions. View stores and metrics |
| `branch_manager` | Store manager | Their own store: view orders, modify ETA, incidents, alarms, driver alerts, pause delivery, store configuration |
| `branch_viewer` | Read-only store access | Viewing store configuration only. A support role, designed to be combined with others |
| `dispatcher_manager` | Logistics technician | Dispatcher execution, scheduler, store configuration, pause delivery, delivery zones, third-party carriers, metrics |
| `report_viewer` | Management and analysts | Reports, metrics, view stores |
| `polygon_manager` | Delivery zone configuration | Delivery zones, view stores |
| `cms_editor` | Content editor | Complaints tree, resources, images |
| `employee_manager` | HR and staff configuration | Employees and own drivers, plus role assignments, scope and exceptions |
| `third_party_manager` | External fleet configuration | External carrier configuration, at brand level |
| `alarms_config_manager` | Alarm threshold configuration | Alarm thresholds for orders, drivers and stores, across the whole chain |

## Two roles you won't assign

**`super_admin`** is a PizzaGest internal technical role. It has full access and bypasses every check, and is never assigned to customer employees.

**`admin`** is the customer-side equivalent for day-to-day purposes: the same operational reach, except that individual denies still apply to it and it cannot change role definitions.

## How permissions add up

Roles are built from a catalogue of atomic permissions — currently more than fifty, each covering one specific action such as "assign a driver" or "export metrics".

A role is a named subset of that catalogue. When an employee holds several roles, their permissions are combined, without duplicates. Nothing is subtracted by adding a role.

> Permission counts per role change as new modules ship. Rather than memorising a number, check what a role actually grants in [Access by section](/books/console-yF7/page/access-by-section), or open the role in the Role catalogue screen.

## Recently added modules

Chatbot configuration, External API management, the Voice agent, Promotional codes and Voucher codes are currently reachable by `admin` only. Whether they get a dedicated role, or are folded into existing ones, will be decided based on operational need.

If one of these needs to reach a non-admin employee before then, an individual exception is the intended route — see [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions).

# Access by section

The full grid of what each role reaches. **Legend:** 👁 read-only · ✏️ read and write · — no access.

All access shown here is then filtered by the employee's scope — see [Scope: what data an employee can see](/books/console-yF7/page/scope-what-data-an-employee-can-see).

| Section | admin | hub_manager | hub_operator | dispatcher_manager | branch_manager | report_viewer | polygon_manager | cms_editor | employee_manager | third_party_manager | alarms_config_manager |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Hub — orders and status | ✏️ | ✏️ | ✏️ basic | — | 👁 | — | — | — | — | — | — |
| Hub — force delivery / cancel | ✏️ | ✏️ | — | — | — | — | — | — | — | — | — |
| Hub — modify ETA | ✏️ | ✏️ | — | — | ✏️ | — | — | — | — | — | — |
| Branch alarms | ✏️ | ✏️ | 👁 | — | 👁 | — | — | — | — | — | — |
| Driver alerts | ✏️ | 👁 | 👁 | — | 👁 | — | — | — | — | — | — |
| Alarm threshold config | ✏️ | — | — | — | — | — | — | — | — | — | ✏️ |
| Archived alarms | ✏️ | 👁 | — | — | — | — | — | — | — | — | — |
| Dispatcher | ✏️ | — | — | ✏️ | — | — | — | — | — | — | — |
| Scheduler | ✏️ | — | — | ✏️ | — | — | — | — | — | — | — |
| Store config | ✏️ | ✏️ | 👁 | ✏️ | ✏️ | 👁 | 👁 | — | — | — | — |
| Pause / resume delivery | ✏️ | ✏️ | — | ✏️ | ✏️ | — | — | — | — | — | — |
| Third-party — brand config | ✏️ | — | — | ✏️ | — | — | — | — | — | ✏️ | — |
| Third-party — store config | ✏️ | — | — | ✏️ | — | — | — | — | — | — | — |
| Delivery zones | ✏️ | — | — | ✏️ | — | — | ✏️ | — | — | — | — |
| Metrics | ✏️ | 👁 | 👁 | 👁 | — | 👁 | — | — | — | — | — |
| Sales reports | ✏️ | — | — | — | — | 👁 | — | — | — | — | — |
| Employees and own drivers | ✏️ | — | — | — | — | — | — | — | ✏️ | — | — |
| CMS — complaints and resources | ✏️ | — | — | — | — | — | — | ✏️ | — | — | — |
| Chatbot config ¹ | ✏️ | — | — | — | — | — | — | — | — | — | — |
| External API management ¹ | ✏️ | — | — | — | — | — | — | — | — | — | — |
| Voice agent ¹ | ✏️ | — | — | — | — | — | — | — | — | — | — |
| Promotional codes ¹ | ✏️ | — | — | — | — | — | — | — | — | — | — |
| Voucher codes ¹ | ✏️ | — | — | — | — | — | — | — | — | — | — |
| Role and assignment management | ✏️ | — | — | — | — | — | — | — | ✏️ | — | — |

¹ **Recently added modules**, currently reachable by `admin` only. See [The role catalogue](/books/console-yF7/page/the-role-catalogue).

## Notes on reading this table

- `super_admin` has ✏️ on every row and is omitted to keep the table readable.
- `branch_viewer` is a read-only support role covering store configuration only; it is omitted for the same reason. Combine it with another role rather than using it alone.
- "✏️ basic" for `hub_operator` means the everyday hub actions, but **not** the critical ones — force delivery, cancel and modify ETA are excluded.
- **Role and assignment management** splits in two: `employee_manager` can assign existing roles, set scope and manage exceptions, but cannot create or edit role definitions. See [Managing roles from the console](/books/console-yF7/page/managing-roles-from-the-console).

## If a role is nearly right

Don't look for a role that matches a person exactly. Either combine two roles ([Common role combinations](/books/console-yF7/page/common-role-combinations)), or keep the closest role and add a single exception ([Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions)).

# Common role combinations

Because an employee can hold several roles and all of them share one scope, most real profiles are built by combining two or three roles rather than looking for a perfect single match.

| Real profile | Roles | Scope |
|---|---|---|
| Operations manager who also views reports | `hub_manager` + `report_viewer` | Customer, or explicit by store |
| Operations manager who manages alarm thresholds | `hub_manager` + `alarms_config_manager` | Customer, or explicit by store |
| Operations manager who also runs the dispatcher | `hub_manager` + `dispatcher_manager` | Customer, or explicit by store |
| Shift operator who also views reports | `hub_operator` + `report_viewer` | Explicit — their stores |
| Store manager with access to reports | `branch_manager` + `report_viewer` | Explicit — their stores |
| Brand manager who doesn't operate orders | `employee_manager` + `polygon_manager` + `third_party_manager` | Explicit — brand |
| Delivery zone technician | `polygon_manager` | Explicit — brand |
| Management, data only | `report_viewer` | Customer |
| Full administrator | `admin` | Customer |

## Why `hub_manager` doesn't include the dispatcher

`hub_manager` deliberately focuses on hub operations. If your operations manager also needs the dispatcher, the scheduler or third-party carriers, assign `hub_manager` + `dispatcher_manager`.

Each role keeps its own area of responsibility, and the permissions combine automatically. This is preferred over widening `hub_manager`, because it keeps the two responsibilities visible and separately removable.

## Rules of thumb

**Combine roles, don't request new ones.** Most needs are a combination that already exists. A new role definition requires a request to the development team — see [Requesting a new or changed role](/books/console-yF7/page/requesting-a-new-or-changed-role).

**Roles only ever add.** Assigning a second role never reduces what the first one granted, and never widens what data is visible. Only scope controls that.

**Use exceptions for the one-off case.** If a profile matches an existing role except for a single ability, keep the role and add an individual grant or deny — see [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions).

# How scope works in practice

Four behaviours explain almost every "why can this person see that?" question.

## Roles are additive

An employee with `hub_manager` + `report_viewer` can operate the hub **and** view reports. Neither role restricts the other, and the order you assign them in makes no difference.

## Scope filters data automatically

Having access to a module does not mean seeing all of its data. The system filters everything to the employee's scope.

A `report_viewer` whose scope is limited to the brand "Pizza Express" opens the same reports screen as everyone else, but sees only that brand's figures. Nothing is hidden or greyed out — the data simply isn't in their results.

> This is worth explaining to people directly. Two colleagues comparing the same report and seeing different totals is expected behaviour, not a fault.

## Scope applies equally to every role

Unlike older systems, scope is **not** configured per role. If an employee can see stores 1, 2 and 3, that applies to everything they do — hub, reports, metrics, store configuration, all of it.

This is a deliberate simplification. It means you can reason about one employee's visibility by looking at one setting, rather than reconciling several.

## Individual exceptions override roles

You can grant or deny one specific permission to one specific employee, optionally with an expiry date.

**A deny always wins**, whatever their roles grant. See [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions).

## Putting it together

When someone reports they can't do something, check in this order:

1. **Do they have a role that grants the action?** → [Access by section](/books/console-yF7/page/access-by-section)
2. **Is there a deny exception on them?** → [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions)
3. **Is the data simply outside their scope?** → [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope)

Most reports resolve at step 3, and look like a missing permission until you check.

# Managing roles from the console

The console manages roles, assignments, scopes and individual exceptions through four sections.

| Section | What it lets you do | Required permissions |
|---|---|---|
| **Role catalogue** | View, create, edit and delete role definitions — their name and the permissions they include | `roles:read` / `roles:write` |
| **Assignments** | Assign and unassign roles to employees, activate and deactivate them | `employees:read` / `employees:write` |
| **Scope** | Configure what data each employee can access — brands, stores, sources | `employees:read` / `employees:write` |
| **Exceptions** | Create individual grants or denies on a specific permission for an employee, with an optional expiry date | `employees:read` / `employees:write` |

## Who can reach each section

**Role catalogue** is restricted to `super_admin` — PizzaGest's own technical role. Creating or changing a role definition goes through the development team, who review the change before applying it. See [Requesting a new or changed role](/books/console-yF7/page/requesting-a-new-or-changed-role).

**Assignments, Scope and Exceptions** are available to `admin` and `employee_manager`. These are the three you will use day to day: assigning existing roles, setting scope, and managing exceptions.

> This split is deliberate. Changing what a role *means* affects every employee holding it, across the whole chain — so it is reviewed. Changing who *holds* a role affects one person, and is yours to do directly.

## Assigning a role to an employee

1. Open **Assignments**.
2. Find the employee.
3. Add the role or roles they need — see [The role catalogue](/books/console-yF7/page/the-role-catalogue) and [Common role combinations](/books/console-yF7/page/common-role-combinations).
4. Confirm their scope separately, under **Scope**. A new role does not change existing scope.

An assignment can be deactivated rather than removed, which keeps the record while suspending the access.

> Assigning a role does not give someone a way to sign in. Setting or resetting their password is a separate task, done from the Employees screen — see [Employees - Password](/books/console-yF7/page/employees-password).

## Safeguards

These protect you from the changes that are hardest to undo:

- A role with active assignments **cannot be deleted**.
- Permissions attached to a role are validated against the system permission catalogue, so a typo can't create a permission that does nothing.
- Every assignment and every exception records **who created it**, for auditing.
- Individual denies always override what a role grants.

## Related pages

- [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope) — the three dimensions in practice
- [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions) — grants and denies
- [Requesting a new or changed role](/books/console-yF7/page/requesting-a-new-or-changed-role) — when no combination fits

# Setting an employee's scope

> **You need:** `employees:read` and `employees:write` — held by `admin` and `employee_manager`.

Scope is set once per employee and applies to everything they do. See [Scope: what data an employee can see](/books/console-yF7/page/scope-what-data-an-employee-can-see) for the model; this page is the task.

## Before you start

Decide which of the two modes the person needs:

- **Customer** — they legitimately need the whole chain. Management, analysts, chain-wide operations.
- **Explicit** — everyone else. Anyone tied to particular stores or a particular brand.

When in doubt, choose explicit and add to it. Widening a scope later is a one-field change; discovering someone has been seeing another brand's figures for months is not.

## Setting an explicit scope

1. Open **Scope** and find the employee.
2. Switch the mode to **Explicit**.
3. Fill only the dimensions you actually want to restrict:
   - **Brands** — leave empty for "all brands".
   - **Stores** — individual stores, store groupings, or both.
   - **Sources** — only if the restriction genuinely matters; see the caution below.
4. Save, and confirm the result on a screen the employee uses, such as Reports.

> Leaving a dimension empty means **no restriction on that dimension** — not "no access". An explicit scope with every dimension empty is equivalent to full access.

## Using store groupings

Groupings are predefined sets of stores. They combine with individually selected stores rather than replacing them, so you can give someone a region plus one extra store without listing the region store by store.

Prefer a grouping over a long list of store IDs: when a store is added to the region later, the employee's scope follows automatically.

## A caution on Sources

The source filter only takes effect in modules that implement it — Reports, Metrics, CMS and some Hub cases. Elsewhere the employee sees everything within their brand and store scope regardless.

This means a source restriction is **not** a reliable way to hide data in general. Use it to focus a specialised role, not to enforce a boundary. If the boundary matters, express it with brands or stores.

## Checking your work

The fastest verification is to look at a screen that shows data volume, such as Reports or the hub order list, and confirm the employee sees what you intended — no more, no less.

Remember that scope does not grant anything. If they see nothing at all, check their roles before adjusting the scope — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

# Individual permission exceptions

> **You need:** `employees:read` and `employees:write` — held by `admin` and `employee_manager`.

An exception grants or denies one specific permission to one specific employee, on top of whatever their roles say. Use it when a person matches an existing role in every respect but one.

## The two kinds

| Kind | Effect | Typical use |
|---|---|---|
| **Grant** | Adds one permission the employee's roles don't include | A shift operator who also needs to cancel orders, without giving them the whole manager role |
| **Deny** | Removes one permission the employee's roles do include | A store manager who should not be able to pause delivery |

## The one rule that decides conflicts

**A deny always wins.** It beats every grant, from every role the employee holds, and from an explicit grant exception too. There is no ordering to reason about and no priority to configure.

This is deliberate: it means a deny is a reliable way to remove an ability, without auditing which roles might be putting it back.

> `super_admin` is the single exception — it bypasses all checks, including denies. `admin` does **not**; individual denies apply to it normally.

## Creating one

1. Open **Exceptions** and find the employee.
2. Choose the permission.
3. Choose grant or deny.
4. Optionally set an expiry date.
5. Save. The record keeps who created it, for auditing.

## Use the expiry date

Most exceptions are temporary in intent — cover for an absence, a seasonal push, a trial of wider access. Very few are temporary in practice, because nobody remembers to remove them.

Setting an expiry date when you create the exception turns that problem into a non-problem. If the access turns out to be permanent, the right fix is a role change, not an exception that never ends.

## When not to use an exception

**When two roles already cover it.** If the person needs a whole area, assign the role for that area — see [Common role combinations](/books/console-yF7/page/common-role-combinations). Exceptions are for single permissions, not for assembling an area out of parts.

**When several people need the same thing.** Three identical exceptions are a sign the role is wrong. Request a role change instead — see [Requesting a new or changed role](/books/console-yF7/page/requesting-a-new-or-changed-role).

**To restrict data.** Exceptions control actions, not visibility. To limit what someone can see, set their scope — see [Setting an employee's scope](/books/console-yF7/page/setting-an-employees-scope).

## Reviewing them

Exceptions are the part of the access model most likely to drift, because each one is invisible from the role catalogue. Review the exceptions list periodically and remove the ones whose reason has passed.

# Requesting a new or changed role

Creating or editing a role definition is restricted to PizzaGest's technical team. As a customer administrator you assign existing roles, set scope and manage exceptions — but the role catalogue itself goes through a request.

This page explains why, and how to make the request land well.

## Why it's reviewed

A role definition is shared. Changing what `hub_manager` means changes it for every employee holding it, in every store, immediately. Unlike an assignment — which affects one person and is easy to reverse — a role change has chain-wide reach, so it is reviewed before it is applied.

## Try these first

Most requests turn out to be unnecessary. In order of preference:

1. **Combine existing roles.** Two or three roles together cover most real profiles — see [Common role combinations](/books/console-yF7/page/common-role-combinations).
2. **Add an individual exception.** If an existing role fits except for one ability, grant or deny that one permission — see [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions).
3. **Check it's not a scope problem.** "They can't see the data" is usually scope, not permissions — see [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

A new role is the right answer when the same combination-plus-exception pattern keeps recurring across several people.

## What to include in the request

| Detail | Why it matters |
|---|---|
| The job, in plain words | "Regional supervisor for 12 stores" says more than a list of permissions |
| What they must be able to do | The actions, named as they appear in the console |
| What they must **not** be able to do | Often the more important half, and the part that's usually left out |
| The closest existing role | And precisely how it falls short |
| How many people will hold it | One person is usually an exception; several is usually a role |
| Whether it's permanent | A seasonal need is better served by exceptions with expiry dates |

## What happens next

The request is analysed against the existing catalogue — partly to build the right role, and partly to check it doesn't overlap an existing one in a way that would make the model harder to reason about.

If a new role is created, it appears in the Role catalogue and you assign it like any other.

## While you wait

You don't have to leave someone blocked. Assign the closest role and cover the gap with exceptions, ideally with an expiry date matching your expected timeline. When the new role arrives, assign it and remove the exceptions.

# Reference

Glossary, how to report a problem, and cross-area FAQ.

# Glossary

Terms used across the whole console. Dispatcher-specific vocabulary — driver states, Last-Option, TTB — is in the [Dispatcher glossary](/books/console-yF7/page/dispatcher-glossary).

| Term | Meaning |
|---|---|
| **Brand** | A commercial identity within your company. Most screens work within one brand at a time, and data is separated by brand |
| **Store** | A physical location. Called a branch in some screens |
| **Hub** | An operational grouping of stores. Employees belong to a hub, and it determines which operation their work counts towards |
| **Source** | Where an order came from: web, app, kiosk, or an aggregator. Content and some reports are separated by source |
| **Scope** | What data an employee may see — brands, stores, sources. Set once per person and applied everywhere |
| **Role** | What an employee may do. A person can hold several; permissions combine |
| **Permission** | One specific ability, such as assigning a driver or exporting metrics. Roles are built from these |
| **Exception** | A grant or deny of one permission for one person, overriding their roles. A deny always wins |
| **Business date** | The trading day an order belongs to. Late-night orders belong to the previous business date, not the calendar date |
| **Promised time** | The delivery time the customer was given, and what lateness is measured against |
| **Own fleet** | Your own drivers, whether employed or contracted |
| **Third-party carrier** | An external delivery company you hand orders to. They choose the courier, not you |
| **Redeemed** | A promotion, voucher or credit the customer has already used. Bulk operations never touch redeemed records |
| **Bulk change** | Changing many records at once, via check → apply → track |
| **Import** | Loading records from a CSV. Never transactional: a partial failure leaves earlier rows applied |
| **Backup** | A retained snapshot of an export, which can be downloaded or restored |
| **Alarm** | Raised automatically when a store crosses a threshold. Lives in the Hub, and is archived once handled |
| **Alert** | Raised automatically about a driver, or returned by a threshold report in Reports |
| **Incident** | A record *a person* writes against one order, explaining what went wrong |
| **Request ID** | Identifies one dispatcher run. The single most useful thing to include when reporting a dispatcher problem |

## Three that are easy to confuse

**Alarm, alert, incident.** The first two are raised by the system and you respond to them; the third you write yourself and it stays on the order. See [Branch alarms](/books/console-yF7/page/branch-alarms), [Stores and driver alerts](/books/console-yF7/page/stores-and-driver-alerts) and [Logging an incident](/books/console-yF7/page/logging-an-incident).

**Contracted driver versus third-party carrier.** A contracted driver is part of your fleet and is assigned by name. A carrier is a company — you hand it the order and it picks the courier. See [External employees](/books/console-yF7/page/external-employees).

**Role versus scope.** Roles decide actions, scope decides data. See [Roles and scope: the two ideas](/books/console-yF7/page/roles-and-scope-the-two-ideas).

# Reporting an issue

How to report a console problem so it can be resolved without three rounds of questions.

For dispatcher-specific problems there is a fuller checklist in [Reporting a dispatcher issue](/books/console-yF7/page/reporting-a-dispatcher-issue) — the Request ID it asks for is what makes those traceable.

## Check these three first

A large share of reports resolve here, without anyone else being involved:

| Symptom | Likely cause |
|---|---|
| "I can't see the screen" | Your roles — [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why) |
| "I see fewer rows than my colleague" | Your scope — [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice) |
| "The total is missing an evening" | Business date, not calendar date — [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites) |

None of these are faults, and all three are answered in a minute.

## What to include

| Detail | Why |
|---|---|
| **Where you were** | The area and screen, not just "the console" |
| **What you did** | The steps, in order |
| **What you expected** | Often the most useful line, and the one most often missing |
| **What happened instead** | Including the exact wording of any message |
| **When** | Date and approximate time |
| **Who** | Your identifier — it decides your roles and scope, so it determines what you should have seen |
| **Brand** | If your company has more than one |
| **One-off or recurring?** | A single occurrence and a pattern point to different causes |

## Identifying a specific order

An order needs **store, ticket number and source together**. A ticket number alone is not unique across stores, so a report with only a number cannot be traced.

If the dispatcher is involved, add the **Request ID** from the Scheduler — it points to one exact run.

## Evidence that helps

- A screenshot of **the specific screen showing the problem**, with any message visible.
- For an order, its **detail screen** with timestamps — not a list view.
- If several things are affected, **all** the identifiers rather than "a few orders this morning".

## What doesn't help

- "It's broken" or "it's not working today", without a screen, a time and an example.
- Screenshots of unrelated dashboards.
- A description of the fix you want instead of the behaviour you saw. Say what happened; the cause is often not where it appears.

## Before reporting a permissions problem

Say which of the two it is — you can see the screen but not the data, or you can't see the screen at all. The first is scope, the second is a role. That one sentence often resolves it immediately.

# FAQ

Cross-area questions. Each chapter has its own detail; this is the index for the ones that come up everywhere.

## Access

**Why can't I see an area other people can?**
Your roles decide which areas appear. See [What you can see, and why](/books/console-yF7/page/what-you-can-see-and-why).

**Why do I see fewer rows than a colleague running the same report?**
Your scope is narrower, and you are both seeing the correct answer for yourselves. See [How scope works in practice](/books/console-yF7/page/how-scope-works-in-practice).

**I have the role but still can't do one specific thing.**
Permissions are granular, and a deny exception overrides a role. Check [Access by section](/books/console-yF7/page/access-by-section), then [Individual permission exceptions](/books/console-yF7/page/individual-permission-exceptions).

**How do I get a password?**
Someone with employee management rights sets it. There is no self-service reset — see [Signing in](/books/console-yF7/page/signing-in).

## Data and numbers

**Two reports disagree by about one evening's trade.**
Business date is not calendar date; late-night orders belong to the previous business date. See [Filters, dates and favourites](/books/console-yF7/page/filters-dates-and-favourites).

**Reports and Metrics disagree about an order.**
They read different stores of data. An order present in Reports but absent from Metrics generated no log events — it is not lost. See [Reports and Metrics: which to use](/books/console-yF7/page/reports-and-metrics-which-to-use).

**A bulk change affected fewer records than the check said.**
Records redeemed between the check and the apply are excluded, as are any already redeemed. See [How bulk changes work](/books/console-yF7/page/how-bulk-changes-work).

## Orders

**Why did an order change driver by itself?**
A driver with better priority became available. See [When an order changes driver](/books/console-yF7/page/when-an-order-changes-driver).

**Why is an order still unassigned?**
Usually capacity, a zone that allows no available driver type, or incomplete address data. See the [Dispatcher FAQ](/books/console-yF7/page/dispatcher-faq).

**I unassigned an order but the carrier still came.**
Unassigning does not cancel with the carrier. Cancelling does, atomically. See [Assigning and unassigning a driver](/books/console-yF7/page/assigning-and-unassigning-a-driver).

**An order I assigned by hand never got reassigned.**
Manual assignments are permanently locked out of automatic reassignment. That is intended. See [The order board](/books/console-yF7/page/the-order-board).

## Content and promotions

**I changed a banner and nothing changed.**
Almost always the wrong source selected — content is stored per source, and per language for banners and documents. See [Resources and sources](/books/console-yF7/page/resources-and-sources).

**A customer's promo code doesn't work.**
Check dates and valid times, whether that coupon is already redeemed, and whether the offer is live for that store. See [Promotional codes](/books/console-yF7/page/promotional-codes).

**A carrier looks available but shows as full.**
Carrier capacity is one shared pool per store per company, not per courier. See [External carriers](/books/console-yF7/page/external-carriers).

## Imports and exports

**An import failed halfway. Can I just re-run it?**
Not safely. Imports are not transactional, so earlier rows are already applied and re-running may apply them twice. Find where it stopped first — see [Importing from a CSV](/books/console-yF7/page/importing-from-a-csv).

**Can I undo an import?**
Only for records nobody has redeemed, via a bulk delete. Anything already used stays.

**Why is exporting a separate permission?**
Because an exported file is no longer scope-filtered. See [Exporting data](/books/console-yF7/page/exporting-data).

## Still stuck

[Reporting an issue](/books/console-yF7/page/reporting-an-issue) lists what to include so it can be resolved first time.