# 04 — Sales & POS

Because every order is **takeaway**, the POS is far simpler than a restaurant POS. There
are no tables, no seats, no covers, no course firing, no table transfers, no bill splitting
by seat. That simplification is a feature — spend the saved complexity on **speed** and on
**account charging**.

**Target: any regular drink ordered and settled in ≤ 3 taps.**

---

## 1. Order lifecycle

```
        ┌──────────┐
        │  DRAFT   │  building the order on screen
        └────┬─────┘
             │ confirm
             ▼
        ┌──────────┐   settle now ─────────────┐
        │ PENDING  │                           │
        │SETTLEMENT│   charge to account ──────┤
        └────┬─────┘                           │
             │                                 ▼
             │                          ┌─────────────┐
             └─────────────────────────▶│  CONFIRMED  │ ── sent to prep queue
                                        └──────┬──────┘
                                               │
                      ┌────────────────────────┼────────────────────┐
                      ▼                        ▼                    ▼
                ┌──────────┐            ┌───────────┐        ┌───────────┐
                │PREPARING │───────────▶│   READY   │───────▶│ COMPLETED │
                └──────────┘            └───────────┘        └───────────┘
                                                                   │
                                              ┌────────────────────┴──────┐
                                              ▼                           ▼
                                        ┌──────────┐              ┌────────────┐
                                        │ VOIDED   │              │  REFUNDED  │
                                        │(pre-prep)│              │ (post-pay) │
                                        └──────────┘              └────────────┘
```

| State | Meaning |
|---|---|
| `DRAFT` | On screen, not yet committed. Lost if the terminal restarts (acceptable). |
| `PENDING_SETTLEMENT` | Committed, awaiting payment or account charge. |
| `CONFIRMED` | Settled or charged. Financially real. Sent to the prep queue. |
| `PREPARING` / `READY` | Bar/kitchen progress. |
| `COMPLETED` | Handed to the customer. |
| `VOIDED` | Cancelled before or during prep. Reverses stock and any charge. |
| `REFUNDED` | Money returned after completion. A new opposing document, never an edit. |

**Rule: an order becomes `CONFIRMED` only when it is settled or charged.** There is no
"unpaid open ticket" state — that is what an account charge is for. This single rule
eliminates the largest source of losses in cafe systems.

---

## 2. Order record

| Field | Notes |
|---|---|
| `id` | UUID, generated on the device (see doc 10) |
| `order_number` | `<BRANCH_CODE>-<YYYYMMDD>-<seq>`, sequence resets daily per branch |
| `branch_id`, `device_id`, `shift_id`, `cashier_user_id` | |
| `business_day` | Derived from the branch's `business_day_cutoff`, not from the clock date |
| `created_at_local` / `created_at_utc` | |
| `buyer_type` | `walk_in` / `account` / `staff` |
| `authorized_person_id?` | Who actually collected it |
| `charges[]` | Zero or more on-account legs — `{ account_id, amount, funding: covered \| personal }`. Plural, because a company-funded cap can be exceeded mid-order (doc 03 §6.4). |
| `fulfilment` | `counter_pickup` / `deliver_to_office` |
| `office_ref?` | Office/floor for delivery. Required when `deliver_to_office`. |
| `lines[]` | See below |
| `subtotal`, `discount_total`, `tax_total`, `grand_total` | All snapshotted |
| `settlement_type` | `paid` / `on_account` / `mixed` |
| `payments[]` | See §4 |
| `status` | Per the lifecycle above |
| `note` | Free text for the barista ("no sugar please") |
| `sync_state` | `local` / `synced` / `conflict` |

### Order line

| Field | Notes |
|---|---|
| `item_id`, `item_name_snapshot`, `sku_snapshot` | Name is snapshotted for reprintable receipts |
| `quantity` | |
| `unit_price_snapshot` | Resolved price at the moment of sale (doc 02 §4) |
| `modifiers[]` | Each with `name_snapshot` and `price_delta_snapshot` |
| `line_discount`, `discount_reason_code` | |
| `line_total` | |
| `recipe_version_id` | Which recipe deducted stock — keeps historical COGS honest |
| `prep_station`, `prep_state` | Lines can be ready at different times |
| `void_of_line_id?` | Set when this line reverses another |

**Everything price-related is snapshotted.** Reports of last month's sales must never
depend on today's menu.

---

## 3. The counter flow

```
1. Cashier taps items (with defaults preselected)
2. Cashier picks the buyer:
      [ WALK-IN ]  ← default, zero extra taps
      [ ACCOUNT ]  ← search by account code, company name, office number, or person's phone
3. Settle:
      Pay now  → cash / card / wallet / mixed
      Charge   → to the selected account   (only if terms allow and credit check passes)
4. Print receipt (or skip) → order goes to the prep queue
```

### Speed requirements

| Requirement | Why |
|---|---|
| Walk-in + cash is the **default path with no extra taps** | It is still the most frequent single transaction |
| Account search returns in **< 200 ms on local data** | The account list per branch is small; keep it fully cached on device |
| Search matches **office number** as well as name | Tenants identify themselves as "office 305" more often than by company name |
| **Favourites / top-20 screen** | 80% of a cafe's volume is ~15 items |
| Quick-quantity (`×2`, `×3`) buttons | Office orders are almost never for one coffee |
| **Repeat last order** for an account | An office that orders the same 6 drinks every morning should be one tap |

### Multi-drink office orders

An order for one account may contain many drinks for many people. Optional
`line.for_person` free-text lets the barista label cups and lets the tenant's statement
show who consumed what. Cheap to add, disproportionately valued by tenants.

---

## 4. Settlement

```
settlement_type ∈ { paid, on_account, mixed }
```

### Tender types

| Tender | Notes |
|---|---|
| `cash` | Dominant. Records tendered amount and change. Subject to cash rounding (doc 10). |
| `card` | Terminal is typically standalone; the system records the amount + last 4 / ref |
| `wallet` | Mobile wallets (e.g. Zain Cash, FastPay, Qi). Records provider + reference |
| `bank_transfer` | Mainly for account settlement, rarely at the counter |
| `on_account` | Creates a receivable. Not a payment. |

**Rules**
1. **Multiple tenders per order are allowed** (part cash, part card). Tenders plus charges
   must equal `grand_total` exactly, after rounding.
2. **`on_account` may be mixed with a payment** — e.g. tenant pays 10,000 cash and charges
   the remaining 5,000.
3. **A tender in USD records the FX rate used** and the IQD equivalent (doc 10 §2).
4. **Change is always given in IQD**, regardless of tender currency, unless the branch is
   configured otherwise.
5. **`on_account` requires a passing credit check** (doc 03 §5). No exceptions without a
   supervisor override, which is audited.

### Split settlement — company-funded caps

Because tenant offices fund their employees differently (doc 03 §6), one order can be paid
by two parties. The POS computes the split **before prompting**, so the cashier is told the
answer rather than asked a question:

```
Order total                        12,000 IQD
Account: Al-Rafidain Tech (office 305)
Person:  Ahmed  — cap remaining     5,000 IQD

  → Covered by company               5,000  → charge to account
  → Personal balance                 7,000  → [ CASH ] [ CARD ] [ WALLET ] [ OWN ACCOUNT ]
```

| Rule | Reason |
|---|---|
| The split is **computed, not chosen**. The cashier only settles the personal remainder. | Speed. Asking a cashier to work out an allowance during a rush guarantees errors. |
| The company leg and the personal leg are **separate charges/tenders on one order**. | Keeps one order per customer transaction while letting each party be billed correctly. |
| Only the **covered** leg consumes the employee's cap and appears on the company's invoice. | Doc 03 §6.3. |
| If the person's policy is `NONE_COVERED`, `covered = 0` and the whole order is personal. | Same code path, no special case. |
| If the order is **voided or refunded**, both legs reverse and cap is restored. | Doc 03 §6.3. |

---

## 5. Preparation queue

Simple because it is takeaway-only — and simpler still because **each branch has a single
terminal** ([10 §9](10-cross-cutting-rules.md)). There is no separate kitchen display.

The queue is therefore two things:

| Form | Use |
|---|---|
| **Printed prep ticket** | Printed at confirmation, goes to the bar. The primary mechanism. |
| **On-screen queue panel** | A panel on the same terminal the cashier uses, listing open orders. Used to see what is outstanding and to mark orders ready. |

- States: `New → Preparing → Ready → Handed over`.
- Shows: order number, items with modifiers, note, `deliver_to_office` flag + office ref,
  elapsed time, colour escalation past a threshold.
- **`deliver_to_office` orders are visually distinct and print a marked ticket** — someone
  has to physically walk them upstairs, and a missed one is a complaint.
- One tap to `86` an item from this panel.
- Works fully offline against the local order store.

> If a branch later adds a second screen for the bar, the same queue renders on it with no
> model change — the panel is a view, not a device. Do not build for that until a branch
> asks.

**Tracked:** `confirmed_at → ready_at` = prep time. This is the branch's only real
speed-of-service KPI and it belongs in the branch comparison report.

---

## 6. Voids, refunds and discounts

The three controls that matter, because these are the three ways money leaves without goods
leaving.

### Void — before or during preparation

| Rule |
|---|
| Reverses stock deduction and any account charge |
| Requires a **reason code**: `wrong_item`, `customer_left`, `staff_error`, `duplicate`, `test`, `other` |
| Cashier can request; Manager/Owner approves via supervisor PIN |
| Never deletes the order — the void is recorded against it |
| Void rate per cashier is a standing report (doc 09) |

### Refund — after completion

| Rule |
|---|
| Creates a **new opposing document** referencing the original order. The original is never edited. |
| Full or partial (line-level) |
| Refund tender should match the original tender where possible; a cash refund against a card sale requires approval |
| For an account charge, a refund creates a **credit note** on the account, not cash out |
| Requires reason code and approval |
| Cannot exceed the original order value, net of prior refunds |

### Discount

| Rule |
|---|
| Types: percentage or fixed amount; line-level or order-level |
| **Reason code required always** |
| Cashiers have a **maximum discount limit**; beyond it requires supervisor PIN |
| Standing customer discounts belong on the **account or price list**, never typed manually |
| Discount per cashier is a standing report |

---

## 7. Receipts

| Type | Content |
|---|---|
| **Paid receipt** | Branch name (AR/EN), address, phone, order number, business day, timestamp, cashier, lines with modifiers, discounts, total, tenders, change, FX rate if USD used |
| **Charge slip** | Same lines, plus **"CHARGED TO: \<account\> — \<authorized person\>"**, the account's current balance, and a signature line if the account requires one. Shows **no** cash total. |
| **Split slip** | When a cap is partly used: shows **covered by company** and **paid personally** as separate totals, plus the person's **remaining cap for the cycle**. Prevents the month-end argument. |
| **Duplicate** | Any reprint is marked **DUPLICATE** and is audited |

Receipts are **Arabic-first with English secondary**. Printing is optional per branch —
many workspace customers do not want a slip, and skipping the print saves paper and time.
The record exists regardless of whether it is printed.

---

## 8. Standing / scheduled orders (Phase 3)

A real pattern in this environment: *office 305 wants 6 coffees at 09:00 every working day.*

```
standing_order {
  account_id, branch_id, lines[], fulfilment, office_ref,
  schedule: { days_of_week[], time_of_day },
  valid_from, valid_to, is_paused
}
```

At the scheduled time it appears on the POS as a **suggested order awaiting confirmation** —
it is never auto-confirmed, because the office may be closed and nobody should be charged
for coffee nobody drank. One tap converts it into a real order.

**Standing orders never fire on an owner-declared closure day**
([10 §3.1](10-cross-cutting-rules.md)). Since `days_of_week` cannot express a business that
trades every day with ad-hoc closures, the schedule defaults to **every day**, and the
business calendar suppresses it.

This alone can absorb a meaningful share of morning volume at zero cashier effort.
