# 03 — Customers & Accounts

This is the module that distinguishes this system from an ordinary cafe POS. Most of the
revenue comes from **known, resident customers who do not pay at the counter**.

---

## 1. The core distinction

Every sale has a **buyer** and a **payer**. Usually the same. Often not.

```
Buyer  = who ordered and took the coffee   (person standing at the counter)
Payer  = who owes the money                (their company's account)
```

The system therefore separates:

| Concept | Meaning |
|---|---|
| **Customer** | An identifiable person or organization the cafe knows |
| **Account** | A financial container that accumulates charges and gets billed |

A customer without an account pays immediately. An account can be shared by many customers.

---

## 2. Customer types

| Type | Identified by | Default payer | Notes |
|---|---|---|---|
| **Walk-in** | Nothing | Themselves, immediately | No record is created. This must remain the fastest path in the POS. |
| **Tenant company** | Company name / office number | Its own account | The office-renting company. |
| **Tenant employee** | Name / phone / office | Their company's account, themselves, or **split** between the two | Funding depends on their company's policy — see §6. |
| **Workspace employee** | Name / phone | The **building operator's** company account, or themselves | Staff of the company that runs the building. Same machinery as a tenant employee. |
| **Own staff** | Their user record | Staff-meal account | Cafe employees. Tracked so staff consumption is visible and can be deducted at payroll. |

> **Confirmed:** "workspace employee" means **staff of the building operator**. The building
> operator is itself an ordinary **company account** in this system, with its own billing
> terms, credit limit and funding policy. Its staff are **authorized persons** on that
> account. No special-case entity is needed.

---

## 3. Account

The billable entity.

| Field | Notes |
|---|---|
| `code` | Short, unique, typed at the POS to find the account fast |
| `type` | `company` / `individual` |
| `name_ar` / `name_en` | |
| `home_branch_id` | The branch/building where this account lives |
| `allow_cross_branch` | If true, may charge at other branches (multi-site tenants) |
| `office_ref` | Office/floor identifier — used for delivery and for finding them |
| `parent_account_id` | For an individual whose charges roll up to a company |
| `contacts[]` | Name, phone, role (who approves, who pays, who receives the invoice) |
| `billing_terms` | See §4 — the heart of this module |
| `employee_funding_policy` | See §6 — how much of its employees' consumption this company pays for |
| `credit_limit` | Maximum outstanding balance. `0` = must pay immediately. `null` = unlimited. |
| `price_list_id` | Optional negotiated pricing (doc 02 §4) |
| `discount_pct` | Optional standing discount |
| `status` | `active` / `on_hold` / `closed` |
| `on_hold_reason` | `over_limit` / `overdue` / `manual` |
| `opened_at`, `closed_at` | |
| `notes` | |

### Derived balances (never stored as a single mutable number)

| Value | Definition |
|---|---|
| `unbilled_balance` | Charges created but not yet included in an invoice |
| `invoiced_balance` | Issued invoices not yet fully paid |
| `current_balance` | `unbilled + invoiced − unapplied credits` |
| `available_credit` | `credit_limit − current_balance` |

Balances are computed from the immutable charge/invoice/payment records. A stored running
total that can drift out of sync with its own history is the classic bug in this kind of
system — do not create one. Cache it if needed, but always recomputable.

---

## 4. Billing terms — the requirement that drives everything

The business stated: *some pay monthly, some daily, some at the moment, some every period
of days.* All four are expressed by one structure.

```
billing_terms {
  mode:              IMMEDIATE | DAILY | EVERY_N_DAYS | MONTHLY
  interval_days?:    n              // EVERY_N_DAYS only, e.g. 7, 10, 15
  anchor_day?:       1..28 | EOM    // MONTHLY only — the day the cycle closes
  cycle_start_date?: date           // EVERY_N_DAYS only — anchors the period grid
  due_days:          n              // days after invoice issue that payment is due (0 = on issue)
  grace_days:        n              // days after due before overdue actions trigger
  currency:          IQD | USD
  auto_hold_when_overdue: bool
  invoice_delivery:  print | whatsapp | email | collected_in_person
}
```

### The four modes

| Mode | Cycle closes | Typical customer | Behaviour |
|---|---|---|---|
| **IMMEDIATE** | No cycle | Walk-in; account holders who prefer to pay each time | Sale must be settled before the order is completed. An account may still be attached (for history, pricing, loyalty) while being paid immediately. |
| **DAILY** | End of each business day | High-volume tenants; individuals running a daily tab | Billing run at day-end produces one invoice per account per day. |
| **EVERY_N_DAYS** | Every `n` days from `cycle_start_date` | Tenants who settle weekly / fortnightly | Grid is fixed by the anchor date so periods never drift. |
| **MONTHLY** | On `anchor_day` each month | Most office tenants | Anchor `EOM` = calendar month. Anchor `25` = 25th to 24th. |

### Rules

1. **`billing_terms` lives on the account, not the order.** The cashier never chooses a
   billing cycle — they only choose *pay now* or *charge to account*.
2. **A term change takes effect from the next cycle.** The open cycle keeps its old terms so
   an in-flight period is never re-cut. Changes are audited and dated.
3. **An account can always pay early**, regardless of mode. Early payment applies to the
   oldest outstanding invoice first, then to unbilled charges as a credit on account.
4. **IMMEDIATE accounts never produce a receivable.** Attempting to charge one is blocked
   at the POS with a clear message.
5. **`due_days` and `grace_days` are separate.** Due date drives aging and reminders;
   grace drives automatic hold. Merging them removes the business's room to be polite.

### Cycle close reference

| Terms | Example |
|---|---|
| `DAILY, due_days 0` | Charges 5 Aug → invoice at 5 Aug close, due same day |
| `EVERY_N_DAYS n=7, start 1 Aug, due_days 3` | 1–7 Aug → invoice 7 Aug, due 10 Aug |
| `MONTHLY, anchor EOM, due_days 10` | 1–31 Aug → invoice 31 Aug, due 10 Sep |
| `MONTHLY, anchor 25, due_days 5` | 25 Jul–24 Aug → invoice 24 Aug, due 29 Aug |

---

## 5. Credit control

Checked **at the moment of charging**, on the terminal, before the order completes.

```
can_charge(account, person, amount) :=
      account.status == active
  AND (account.allow_cross_branch OR order.branch == account.home_branch)
  AND (credit_limit is null OR current_balance + amount <= credit_limit)
  AND NOT has_invoice_overdue_beyond_grace(account)
  AND covered_amount(account, person, amount) > 0        // funding policy, §6
```

The two checks are independent and both apply:

| Check | Question | Scope |
|---|---|---|
| **Credit limit** | Can the *company* owe this much? | Whole account |
| **Funding policy / cap** | Is the *company* paying for this *person's* coffee? | Per authorized person |

A person may be fully within their cap while the company is over its credit limit, and
vice versa. Report both distinctly at the POS — "cap exhausted" and "account over limit"
require different responses from the cashier.

| Outcome | POS behaviour |
|---|---|
| **Pass** | Charge posted, order completes normally |
| **Over limit** | Blocked. Manager/Owner may override with PIN; override is audited with a reason code. |
| **Overdue past grace** | Blocked, with the overdue amount and age shown on screen so the cashier can say something accurate. |
| **Account on hold** | Blocked. No override at cashier level. |

**Offline behaviour.** A branch offline cannot see charges made elsewhere. Mitigations:
- The last known balance is cached locally and used for the check.
- Cross-branch charging is off by default, so the local cache is authoritative for the
  common case.
- Charges made offline are marked `credit_check: provisional` and reconciled at sync;
  breaches surface on the **Credit Exceptions** report rather than being silently accepted.

---

## 6. Authorized persons & employee funding

The business confirmed: **each office handles its employees differently.** Some companies
fund all of their employees' consumption, some fund none, and some fund each employee up to
a monthly cap. This section is the model for that.

### 6.1 Funding policy — set on the company account

```
employee_funding_policy ∈ {
  ALL_COVERED,          // any authorized person charges freely, up to the account credit limit
  NONE_COVERED,         // the account exists for the company's own orders; employees pay personally
  CAPPED_PER_EMPLOYEE   // each authorized person has a spend cap per period
}
```

| Policy | Real-world case | Behaviour at the counter |
|---|---|---|
| `ALL_COVERED` | "Put anything my staff take on our account" | Person charges; only the account credit limit applies |
| `NONE_COVERED` | Company account is for meetings/guests only; staff buy their own | Employee charges are refused; they pay personally or use their own individual account |
| `CAPPED_PER_EMPLOYEE` | "Each employee gets 50,000 IQD a month on us" | Charge covered up to the person's remaining cap; the excess is handled per §6.4 |

The policy is the **account default**. Any individual person can override it (§6.2) —
which covers the realistic case where within one office the manager is uncapped and the
juniors are capped.

### 6.2 Authorized person

```
authorized_person {
  account_id, name, phone, office_ref,
  identification: name_lookup | phone | card | qr_badge,

  funding_override: INHERIT | COVERED | NOT_COVERED | CAP(amount),   // default INHERIT
  cap_amount?, cap_period: BILLING_CYCLE | CALENDAR_MONTH,
  cap_carryover: bool,                  // default false — unused allowance does not roll over
  on_cap_exceeded: BLOCK | SPLIT_TO_PERSONAL | ALLOW_WITH_APPROVAL,

  per_order_limit?, daily_limit?,       // optional extra guards, independent of the cap
  requires_signature: bool,
  valid_from, valid_to, status
}
```

**Resolution order for one person:** `funding_override` → account `employee_funding_policy`.

### 6.3 Cap accounting

```
cap_consumed(person, period)  = Σ covered charges in the period
                              − Σ voids, refunds and credit notes against those charges
cap_remaining(person, period) = cap_amount − cap_consumed
```

| Rule | Reason |
|---|---|
| **`cap_consumed` is derived from charges, never a stored counter.** | Same rule as account balances (§3). A counter that can drift from its own history is the defining bug of this kind of system. |
| **Only the covered portion consumes cap.** Personally-paid excess does not. | Otherwise a split order double-counts. |
| **Voids and refunds restore cap** within the same period. | A cancelled coffee should not cost an employee their allowance. |
| **`cap_period` defaults to `BILLING_CYCLE`**, so the cap resets when the company is invoiced. | Aligns what the employee is told with what appears on the invoice. `CALENDAR_MONTH` is available for companies that think in calendar months regardless of billing. |
| **No carryover by default.** | Rolling allowances create balances tenants argue about. |
| Cap changes take effect **immediately**, not at the next period. | A manager raising someone's cap expects it to work now. |

### 6.4 When a person exceeds their cap

This is the flow that has to be right, because it happens at the counter with a queue
behind. Compute the split before asking anything:

```
covered  = min(cap_remaining, order_total)
personal = order_total − covered
```

| `on_cap_exceeded` | POS behaviour |
|---|---|
| **`SPLIT_TO_PERSONAL`** (recommended default) | `covered` is charged to the company; `personal` is settled immediately by the customer (cash / card / wallet) or charged to their own individual account if they have one. One order, two settlement legs. |
| **`BLOCK`** | The charge is refused. The customer pays the whole order personally. |
| **`ALLOW_WITH_APPROVAL`** | Manager PIN charges the full amount to the company anyway; the overage is audited and appears on the **Cap Exceptions** report so the tenant is not surprised at invoicing. |

**Consequence for the order model:** an order may produce **more than one charge** — a
company charge plus a personal charge or payment. See
[04 — Sales & POS §4](04-sales-and-pos.md).

**Warn before blocking.** When `cap_remaining` falls below a configurable threshold, the POS
shows it on the account line at selection time — *"Ahmed — 4,500 of 50,000 remaining this
cycle"* — so the cashier can say it before ringing up, not after.

### 6.5 General rules

| Rule | Reason |
|---|---|
| An account may be **open** (anyone naming the company can charge) or **restricted** (only listed persons). | Small tenants want convenience; large tenants want control. `CAPPED_PER_EMPLOYEE` implies restricted — an unnamed person has no cap to check against. |
| Every charge records **which authorized person** took it. | The only way to answer "who drank 40,000 IQD of coffee last week" — the first question every tenant asks when the invoice arrives. |
| `per_order_limit` and `daily_limit` are checked **in addition to** the cap. | Stops a whole monthly allowance disappearing into one order for the office. |
| Optional **signature capture** per charge, attached and reprinted on the statement. | Some tenants require proof before they pay. |
| A person can be authorized on **more than one account** (e.g. their employer and the building operator). The cashier picks which account to charge. | Real in a shared building; caps are tracked per (person, account) pair. |
| Offline: caps are checked against the **locally cached** consumption. Charges taken offline are marked `provisional` and reconciled at sync. | Same treatment as credit limits (§5). |

---

## 7. Loyalty (optional, Phase 4)

Only meaningful for **walk-ins and individuals** — company accounts get negotiated pricing
instead.

- Identify by **phone number**. No app, no password, no card.
- Simple stamp model: *buy N, get 1 free* per item group. Points models are harder to
  explain across five branches and generate disputes.
- **Balance is shared across all branches** — that is the point of running it centrally.
- Offline redemption is allowed but flagged for reconciliation; the risk of one duplicate
  free coffee is far smaller than the cost of refusing a customer during an outage.

---

## 8. Customer-facing operations

| Operation | Who | Notes |
|---|---|---|
| Create account | Manager / Accountant / Owner | Credit limit requires Accountant or Owner |
| Attach authorized persons | Manager / Accountant | |
| Change billing terms | Accountant / Owner | Effective next cycle; audited |
| Put on hold / release | Accountant / Owner | Reason required |
| Statement on demand | Any back-office role | Current period, printable/exportable |
| Charge history lookup | Cashier (own branch, read-only) | So a cashier can answer "how much do we owe?" at the counter |
| Close account | Owner | Blocked while balance ≠ 0 |
