# Shipprex — Feature Knowledge Base

> **Status:** COMPLETE (§1–§49 + Appendix). This is the full business-oriented feature knowledge
> base for Shipprex, written section-by-section from the code. Each section explains **why a feature
> exists, the business payoff, and how it works** (with code/setting references), rather than being a
> line-by-line code reference. Pair it with the code refs cited inline for implementation detail.
>
> **How to use this file:** read top-to-bottom for a guided tour, or jump to a section via the
> Progress Tracker at the bottom. Cross-references (e.g. "§9.3") link related concepts; `@path:line`
> citations point at the authoritative code. Keep this index current as features evolve.

---

## Legend
- **[V1]** = Server-rendered monolith (templates, plugins, hooks)
- **[V2]** = API-first JSON layer under `/api/v2` + server-driven UI
- **[Core]** = Lives in `src/` (first-class domain)
- **[Plugin]** = Lives in `plugins/<Name>`
- **[Setting]** = Toggle/config registered via `easy_new_setting`
- **[Integration]** = 3rd-party / external system

---

# PART I — Platform Overview & Architecture

## 1. System Overview ✅

### 1.1 What Shipprex is
**Shipprex** (branded **ShiprexNow** — `PROJECT_COPY` in `@c:\xampp\htdocs\cake\shipping\config\app_local.php:11-13`)
is a **last-mile delivery & logistics operations platform** for courier/shipping companies. It is the
back-office a delivery company uses to run its whole operation: onboarding merchants (sellers),
capturing orders, pricing shipments by geographic zone, assigning drivers, running pickups, tracking
order status through its lifecycle, collecting **Cash-on-Delivery (COD)**, and settling money back to
merchants and drivers.

**Product identity**
- Codebase is the `cakephp/app` skeleton, but the product is **Shipprex / ShiprexNow** (historically also **ShipRex** / **FasTrak**).
- Version `PROJECT_VERSION = 1.5.0` (`@c:\xampp\htdocs\cake\shipping\config\app_local.php:13`).

**Technology foundation**

| Concern | Implementation | Source |
|---------|----------------|--------|
| Framework | **CakePHP `^5.0`** | `@c:\xampp\htdocs\cake\shipping\composer.json:11` |
| Language | **PHP `>=8.2`** | `@c:\xampp\htdocs\cake\shipping\composer.json:8` |
| AuthN/AuthZ | `cakephp/authentication` + `cakephp/authorization` + custom `UsersManager.Auth`/`UserAuth` | `composer.json:15-16`, `@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php:66-95` |
| API tokens | `firebase/php-jwt` (V2 API) | `composer.json:17` |
| PDF / printing | `dompdf/dompdf` | `composer.json:18` |
| Excel import/export | `phpoffice/phpspreadsheet` | `composer.json:26` |
| Phone validation | `giggsey/libphonenumber-for-php` | `composer.json:20` |
| Auto-translation | `google/cloud-translate` | `composer.json:21` |
| File uploads | `josegonzalez/cakephp-upload` | `composer.json:23` |
| UI shell | `friendsofcake/bootstrap-ui` + `CustomTheme` theme | `composer.json:19`, `AppController.php:219` |

**The "two systems, one database" model** — Shipprex ships as **two front-ends over a single MySQL schema**:
1. **V1 — Monolith:** server-rendered CakePHP app (`src/Controller`, `templates/`, ~28 plugins). The mature admin/seller/driver web dashboard.
2. **V2 — API-first:** JSON REST API under `/api/v2` (`src/Controller/Api/V2/*`) secured with JWT, driving a React front-end via **server-driven UI** (forms/tables/actions described by the backend). See `@c:\xampp\htdocs\cake\shipping\docs\API_V2_README.md`.

Both layers read/write the same tables, so any feature is expected to be reflected in **both** systems (or explicitly declared N/A). This duality is the single most important architectural fact about the platform.

**Extensibility model (why the app is "big")** — almost every capability is a **toggleable, plugin-delivered feature** wired by two WordPress-style mechanisms:
- **Settings engine** — `easy_new_setting(...)` to register, `get_option_value(...)` to read (see §3).
- **Hooks system** — `do_action()` / `apply_filters()` + CakePHP `EventManager` (see §4).

The core is intentionally thin; breadth lives in plugins that switch on/off per customer.

### 1.2 Primary actors
Authentication uses the custom **`UsersManager.Auth`** component (login by `email` + `password` against `Users`) with permissions enforced by **`UsersManager.UserAuth`** (`@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php:66-95,112`).
Authorization is **group-based (RBAC)**; groups are hierarchical (`parent_id`) and own `Permissions`, `ProfileFields`, `Users` (`@c:\xampp\htdocs\cake\shipping\plugins\UsersManager\src\Model\Table\GroupsTable.php:54-73`).

> **Nuance:** "Actor" ≠ always "group". Some actors are **groups** (Admin, Seller, Accountant, Company, Branch manager); the **Driver** actor is a **separate first-class entity** (`Drivers` table) with its own portal, not a user group.

| Actor | How it is modeled | Notes |
|-------|-------------------|-------|
| **Admin / Super-admin** | **`group_id = 1`** (hard-coded full-access group) | Bypasses permission checks; gets balance/recharge warnings (`AppController.php:125-132`); API treats group 1 as full access. |
| **Seller / Merchant** | Regular user `Group` | The courier's customer; creates orders, owns COD, sees own data only. Per-seller pricing via `zones_users`. |
| **Driver / Courier** | **`Drivers` entity** (not a group) | Online driver portal (OTP/hash security), per-zone payout (`zones_drivers`), restricted status-update permissions. Mobile app served by `Apiv1`. |
| **Accountant** | User `Group` selected via setting | `accountant_label_user_group` (default `4`) — Accountants dashboard, daily-bank tools, GL access (`@c:\xampp\htdocs\cake\shipping\plugins\Accountants\config\settings.php:16-38`). |
| **Company (multi-tenant merchant)** | `Group` flagged `is_companies` + `Companies` plugin | Merchant umbrella owning sub-users; gated by `companies_enabled` (`@c:\xampp\htdocs\cake\shipping\plugins\Companies\config\settings.php:8-14`). |
| **Branch manager** | User `Group` selected via setting | `MultiBranches` branch-management group; manages per-branch currency/inventory/banks (`@c:\xampp\htdocs\cake\shipping\plugins\MultiBranches\config\settings.php:14`). |
| **End customer (consignee)** | Not an authenticated user | The recipient; interacts only via public **tracking** and notifications (SMS/email). |

Default landing after login is **Orders index** (`loginRedirect` → `Orders::index`, `AppController.php:75-79`), reflecting that order management is the operational center.

### 1.3 High-level capability map
- **A. Order lifecycle (core):** capture (single/bulk/Excel/API), typed **status workflow** via the single choke point `OrdersTable::actionUpdates()` emitting `Model.UpdatedOrderState`; order **types** (Forward, Exchange, Cash Collection, Customer Return Pickup, Refund); returns, duplication, bulk fee/COD edits, audit trail (`Actions`).
- **B. Geographic pricing engine ⭐:** hierarchical **Zones** with default prices; **per-merchant** overrides (`zones_users`) and **per-driver** payout (`zones_drivers`); AI fuzzy city matching (DeepSeek) + fallback default zone.
- **C. Field operations:** **Drivers** (assignment, online portal, per-zone payout, status permissions); **Pickups** (requests, status history, driver workflow).
- **D. Financial back-office:** **Invoices** (COD reconciliation, terminated-order handling), **Transactions** (settlement), **Wallet** (balances + withdrawals), **General Ledger** (banks + Money-Flow dashboard), **Accountants** workspace; multi-currency/per-branch finance via **MultiBranches**.
- **E. Inventory:** **Stocks** (products, warehouses, status-driven stock movement, out-of-stock blocking).
- **F. Multi-tenancy:** **Companies** (merchant sub-accounts) and **MultiBranches** (per-branch currency/inventory/banks).
- **G. Communication:** **Notifications** (in-app), **SMS** (per-status templates), **Comments** (image upload), email sharing (Brevo), public **OrderTracking**.
- **H. Extensibility & integrations:** **API v2** (JSON/JWT, server-driven UI), **API v1** (driver app); integrations **Odoo** (FasTrak), **Google Maps** (Locations), **Brevo** (email), **SMS** provider, **DeepSeek AI** (zones); **Webhooks** (outbound + logs).
- **I. Platform/admin:** Settings engine, Hooks/Events, RBAC (UsersManager), Menus, Theming (CustomTheme), Translation/i18n, OrderRevisions (version history), reporting, maintenance/data-restore.

### 1.4 Naming & legacy quirks
Non-obvious conventions that are easy to break — treat as ground truth.

**Database / domain naming**
- **`statues` (sic):** the order status column is intentionally misspelled `statues` throughout schema and code (`@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php:177-180`). Never "fix" it.
- **Statuses are constants, not strings:** use `state_active_type`, `state_unactive_types`, `types_for_state` (`config/bootstrap.php`) and the enum `src/Enum/OrderStatus.php`. Do not hardcode status text.
- **`AppController::ShippingStuff()` legacy status list** (`AppController.php:202-213`) exposes an *older, different* status set (`On Hold`, `On Route`, `Created`, `Returned`). Legacy — **not** the canonical workflow.

**ID obfuscation**
- **`DID()` / `UNDID()`** offset displayed order IDs by **+8,000,000 / −8,000,000** (`@c:\xampp\htdocs\cake\shipping\config\bootstrap.php:389-394`). Public/printed IDs are "DID-ed"; incoming IDs are "UNDID-ed" before DB lookups (`AppController::handelIdsVar()`, `AppController.php:226-239`).

**Misspelled-but-canonical identifiers**
- **`SUSTEM_CURRENCY`** (sic) — system currency constant; defaults to **`EGP`** when no `intl_local_currency` setting (`config/bootstrap.php:523-524`).
- **`Notifictions`** (sic) — the notifications plugin dir/namespace is spelled this way; the settings group is `Settings.Notifications` (correct). Mind the difference.

**Licensing / balance metering (currently OFF)**
- `USE_BALANCE = false`, `ORDER_BALANCE = 0.3` (`@c:\xampp\htdocs\cake\shipping\config\app_local.php:14-15`). When enabled, meters a pay-per-order "balance" and nags `group_id = 1` to recharge (`AppController.php:115-133`). Disabled by default / largely dormant.

**CakePHP 4 → 5 migration shims**
- Migrated 4→5; `config/bootstrap.php:38-115` defines polyfills/aliases for `env()`, `h()`, and the `__()` I18n family, with `loadModel()`→`fetchTable()` and removed `RequestHandler`/`AuthComponent` noted throughout. Prefer CakePHP 5 patterns in new code.
- **Note:** repo docs sometimes say "CakePHP 4", but the constraint is `cakephp/cakephp: ^5.0` — treat as CakePHP 5.

**Theming default**
- Backend pages render with the **`CustomTheme`** theme and `backend/main` layout (`AppController.php:107,219`); AJAX requests use the `ajax` layout (`AppController.php:108-110`).

## 2. Architecture ✅

### 2.1 Dual-system model (V1 monolith + V2 API-first, one DB)
Shipprex runs **two independent front-ends over the same MySQL schema**, with **two different auth
models** and **two routing trees** living in the same `config/routes.php`.

| Aspect | V1 Monolith | V2 API-first |
|--------|-------------|--------------|
| Entry | Server-rendered HTML (`templates/`, plugins) | JSON under `/api/v2` (`src/Controller/Api/V2/*`) |
| Routing | Root `/` scope + `DashedRoute` fallbacks + `ADmad/I18n` routes (`routes.php:637-690`) | `Api/V2` prefix, `path => /api/v2` (`routes.php:62-635`) |
| Auth | **Session-based** via `UsersManager.Auth` (login by email/password) | **Stateless JWT** (Bearer) via `JwtAuthMiddleware` |
| State | `$_SESSION['Auth']['User']` | `identity` request attribute injected from JWT payload |
| Consumer | Admin/seller/driver web dashboard | React front-end with server-driven UI |

The root path `/` redirects to the V1 login (`UsersManager::Users::login`, `routes.php:655`). Both
systems must reflect a feature (or declare it N/A) — see `docs/FEATURE_DELIVERY_PROMPT.md`.

### 2.2 CakePHP application layout
```
config/      bootstrap.php · routes.php · app.php · app_local.php (secrets/DB) ·
             plugins_config.php (ACTIVE_PLUGINS) · settings.php · events.php · Migrations/
src/         Application.php (bootstrap + middleware) · Controller/ (V1) ·
             Controller/Api/V2/ (V2, grouped by domain) · Model/Table/ + Model/Entity/ ·
             Middleware/Api/ (Cors, RateLimit, JwtAuth) · Service/ (Auth/JwtService, UI/*) ·
             Hooks/ (Hooks.php) · Enum/ (OrderStatus) · Event/ (*Listener) · Command/ (backfills) ·
             DTO/ · Exception/Api/
plugins/     ~28 modular feature plugins (each: src/, config/, templates/)
templates/   V1 server-rendered views + element/ (e.g. orders_table.php) + backend/main layout
webroot/     public assets + uploads/
tests/       PHPUnit + tests/e2e (API v2 E2E)
docs/, PRPs/ documentation, ADRs, and Project Requirement Prompts (incl. version_2 epics)
```
Core domain lives in `src/` ([Core]); breadth lives in `plugins/` ([Plugin]).

### 2.3 Request lifecycle & middleware
The middleware queue is built in `@c:\xampp\htdocs\cake\shipping\src\Application.php:124-153`
(**order matters**):

1. **`AssetMiddleware`** — serves plugin/theme assets. (CakePHP 5: `ErrorHandlerMiddleware` removed; errors handled by `ErrorTrap` in bootstrap.)
2. **`CorsMiddleware`** — must run first to answer CORS preflight for the API.
3. **`RateLimitMiddleware(60, 60, 'default', ...)`** — 60 req/min general; `/api/v2/extensions/manifest` gets a higher 240 bucket (it's hit on every page load — PRP-2.12).
4. **`JwtAuthMiddleware`** — validates Bearer JWT **only for `/api/v2`** paths.
5. **`RoutingMiddleware`** — dispatches to controllers.

**JWT middleware behavior** (`@c:\xampp\htdocs\cake\shipping\src\Middleware\Api\JwtAuthMiddleware.php`):
- Skips non-`/api/v2` paths entirely (V1 is untouched and stays session-based).
- **Public routes** (no token): `/health`, `/auth/login`, `/auth/refresh`, `/auth/register`, `/auth/password/*`, `/auth/email/verify`, `/orders/tracking` (`JwtAuthMiddleware.php:24-34`).
- On success, injects an **`identity`** request attribute `{id, email, group_id, role}` from the token payload — this `group_id` drives V2 RBAC (`JwtAuthMiddleware.php:70-77`).
- On failure → `401 {success:false, message}`.

**V1 lifecycle** then layers `AppController` concerns per request: load `UsersManager.Auth`/`UserAuth`, `UserAuth->checkPremissions()`, menu/notification init, locale selection, `CustomTheme` theme + `backend/main` layout (`AppController.php:56-134,216-221`).

### 2.4 Bootstrap & global constants
`config/bootstrap.php` is where the platform "wakes up". Key responsibilities:
- **CakePHP 4→5 polyfills**: `env()`, `h()`, and the `__()` I18n family aliases (`bootstrap.php:38-115`).
- **Loads `config/events.php` then `config/settings.php`** (`bootstrap.php:499-500`) — i.e. event listeners and the settings engine are initialized at boot.
- **Defines canonical domain constants** (single source of truth — never hardcode):
  - `LANG_LIST` (`en/ar/fr`), `state_active_type`, `state_unactive_types`, `types_for_state`, `types_colors`, `PickupSTATUS`.
  - `allOrderTypes` (Forward, Exchange, Cash Collection, Customer Return Pickup, Refund) → filtered into `OrderTypes` by per-type settings (`bootstrap.php:502-520`).
  - `SUSTEM_CURRENCY` (default `EGP`), `DID()`/`UNDID()` ID offset helpers (`bootstrap.php:389-394,523-524`).
- **Registers core event listeners** (e.g. `OrdersListener` via `EventManager`, `bootstrap.php:335-336`).

**Plugin loading** happens in `Application::bootstrap()` (`Application.php:42-116`), in three tiers:
1. **Hard-coded always-on** plugins (OrderExtera, Translation, Delegations, CustomTheme, Apiv1, Search, GeneralLedger, Sms, Settings, ADmad/I18n, Notifictions, Menus, Utils, UsersManager, BootstrapUI, OrderTracking, Webhooks, MultiBranches, Companies, Locations, Wallet, OrderRevisions, …).
2. **Config-driven optional** plugins from `config/plugins_config.php` → `ACTIVE_PLUGINS` (currently **Stocks, Comments, Accountants, FasTrak**) — toggled per install (`Application.php:58-69`).
3. **CLI-only** plugins (`Bake`, `Migrations`) in `bootstrapCli()` (`Application.php:163-174`).

### 2.5 Database & ORM conventions
- **Connection**: configured in `config/app_local.php` `Datasources.default` (MySQL). Per user rules, the working DB is `ftrak` on `127.0.0.1:3306` (root/no-pass in dev).
- **Table/model placement**: core tables in `src/Model/Table/*Table.php`; plugin tables in `plugins/<X>/src/Model/Table`. Plugin models are referenced with the plugin prefix, e.g. `UsersManager.Users`, `GeneralLedger.Banks`, `OrderExtera.Tags`.
- **Naming**: lowercase, underscored tables; plugin tables often prefixed by domain (`zones_users`, `tags_orders`, `drivers_orders`, `zones_drivers`). Models are CamelCase plurals.
- **Behaviors in active use**: `Timestamp` (created/modified), `Tree` (hierarchical `Zones`, `Groups` via `parent_id`), custom **`Active`** (soft active/inactive filtering with a `StopActive` finder option), and **`OrderExtera.Meta`** (EAV-style custom fields on Orders).
- **Association style**: declared in `Table::initialize()`; many cross-plugin associations are added dynamically at runtime via events/hooks (e.g. `OrderExtera\Config\MainEvents::addAssociation()` adds `Tags`/`Meta`/`Pickups` to `Orders` with `hasAssociation()` guards for CakePHP 5).
- **Status writes go through one choke point** — `OrdersTable::actionUpdates()` — never via raw `save` of `statues` (see §9/§10).
- **Migrations**: managed by `cakephp/migrations` (Phinx) under `config/Migrations/`; schema can drift from migrations, so verify live schema against MySQL when in doubt (per user rules).

## 3. Settings Engine ✅

**Why it exists.** Every courier company runs its operation differently — different fees, different
workflows, different enabled features. The settings engine is the **control panel** that lets admins
reshape the platform's behavior from the UI, **without code changes or redeploys**. It's also the
on/off switch behind nearly every plugin: most features are "dark" until a setting turns them on.

**The business payoff:** one codebase serves many customers; sales/onboarding can configure a tenant
by flipping toggles; and risky behaviors (auto-suspension, out-of-stock blocking, fee handling) are
opt-in per customer.

### 3.1 What a "setting" is, and where it lives
Think of a setting as having two halves:
- **A definition** — its label, type, default, and which tab it appears on. Defined in code so the platform knows what to render.
- **A value** — what the admin actually chose. Stored in the database (`options` table), one row per setting.

Reads are served from an in-memory cache built once per request, so checking a setting is cheap. The
practical consequence to remember: **a saved change takes effect on the next request**, not mid-request.

### 3.2 Defining a setting (`easy_new_setting`)
A plugin declares a setting in its `config/settings.php` with **`easy_new_setting(path, slug, label,
type, default, …)`**. The `type` decides the control the admin sees — toggle (`on_off`), `text_input`,
`number`, `select`, `checkbox`, `radio`, `color`, file `upload`, rich-text editor, or a `label` (a
visual heading that groups related settings). The full how-to lives in
`@c:\xampp\htdocs\cake\shipping\config\settings.md` (read it before adding settings — project rule).

> **Practical gotcha:** a plugin must initialize its settings group (`Configure::write('Settings.<Group>', [])`) before its first `easy_new_setting(...)`, or the setting is silently dropped.

### 3.3 Reading a setting (`get_option_value`)
Anywhere in the app — controller, model, view, plugin — code asks **`get_option_value('slug')`** to get
the current value, automatically falling back to the defined default if the admin never changed it. This
is the single most common settings call in the codebase (e.g. "is the Wallet enabled?",
"is SMS active?"). It always returns *something* usable, which keeps feature code simple and safe.

### 3.4 How settings are organized for admins
Settings are grouped into **tabs** (Orders, Zones, Drivers, Wallet, SMS, Notifications, Companies,
etc.), and `label` entries act as sub-headings inside a tab. The unified settings screen renders every
plugin's settings together, and saving routes through one place that updates-or-creates each value —
so there are never duplicate or orphaned settings.

### 3.5 Settings in the V2 (API) world
V2 adds an important business capability V1 never had: **settings can differ per group and per user**,
not just platform-wide. When the API resolves a setting it checks, in order: the **individual user's
preference**, then their **group's setting**, then the **platform default**. This is what lets the new
front-end tailor forms and tables to *who is looking* (e.g. a merchant sees different order-form fields
than an admin) while still honoring a sensible global default.

### 3.6 Reference
- Canonical how-to: `@c:\xampp\htdocs\cake\shipping\config\settings.md`.
- Engine internals: `@c:\xampp\htdocs\cake\shipping\config\settings.php` and `plugins/Settings/src/Model/Table/OptionsTable.php`.
- V2 cascade: `@c:\xampp\htdocs\cake\shipping\src\Service\UI\SettingsResolverService.php`.

## 4. Hooks & Events System ✅

**Why it exists.** Shipprex is sold to many courier companies, and each one enables a *different* mix
of features. If every feature edited the core order screens and logic directly, the product would be
impossible to maintain or upgrade. The hooks & events system is the answer: it lets a feature
**plug into well-known moments** in the application — a screen being rendered, an order being saved,
a status changing — and add its behavior there, **without modifying core code**. This is what keeps
the core thin and lets ~28 plugins coexist and be toggled on/off per customer (it's the same idea as
WordPress hooks, deliberately so).

**The business payoff:** new capabilities ship as self-contained plugins; turning a customer's
feature on or off is a setting, not a code branch; and a single business action (e.g. "order
delivered") can safely ripple into many modules (invoice, SMS, stock, wallet, audit) that don't know
about each other.

### 4.1 Two complementary mechanisms (and when each is used)
The platform uses **two** extension buses for two different jobs:

| Mechanism | Used for | Business question it answers |
|-----------|----------|------------------------------|
| **Hooks** (`do_action` / `apply_filters`) | Screen & content extension, menus, shortcodes | *"Add something to this page / change this displayed value."* |
| **CakePHP `EventManager`** | Domain/business-logic reactions to data changes | *"When this business thing happens, what else must happen?"* |

As a rule of thumb: **hooks shape what the user sees**; **events drive what the system does** when
data changes.

### 4.2 Hooks — extending screens & menus (the "what the user sees" layer)
Hooks let a plugin inject UI or alter values at named points without touching the core templates:
- **Actions** are "do something here" slots — e.g. a plugin adds an **Extra Weight** or **Partial
  Delivery** button to the order action list, or appends custom fields to the order form. Core just
  announces the slot; plugins decide what fills it.
- **Filters** let a plugin **modify a value** as it passes through — e.g. restyling a status badge or
  adjusting a computed figure.
- **Menus**: the left-hand navigation is built through the same system, so a plugin can add its own
  menu entries — and they automatically respect the viewer's **permissions**.
- **Shortcodes**: small `[tag]`-style tokens can be registered and expanded inside content.

Business value: features feel native (their buttons, columns, and menu items appear in the right
places) even though they live in separate plugins. Reference: `@c:\xampp\htdocs\cake\shipping\src\Hooks\README.md`.

### 4.3 Events — reacting to business moments (the "what the system does" layer)
When core domain data changes, it **announces an event**, and any module can listen and react. This is
how one action produces all its downstream consequences consistently. Real, wired-up examples
(`@c:\xampp\htdocs\cake\shipping\config\events.php`):
- **Audit trail** — virtually every save/delete is logged to the `Logger` table (who, what, before/after, URL, IP). *Why:* accountability and dispute resolution in a cash-heavy business.
- **Invoice recalculation** — saving an order or a user keeps financials in sync. *Why:* money figures must never drift from operational reality.
- **Driver assignment reactions** — assigning a driver updates driver-side obligations. *Why:* keep driver workload current.
- **Status-change ripple** — the order status choke point announces `Model.UpdatedOrderState`, which downstream modules (SMS, notifications, stock, wallet, COD transactions) react to. *Why:* "delivered" or "collected" must trigger the customer SMS, the stock movement, and the money settlement together — never half of them.
- **Settings-driven cache busting** — changing certain settings refreshes the V2 UI manifest. *Why:* config changes show up without manual cache clears.

### 4.4 The golden rule: one choke point per critical action
For high-stakes operations, all writers go through **one** function that emits the event, so reactions
can never be skipped. The prime example is order status: everything (web UI, driver app, bulk tools,
imports, the API) routes status changes through `OrdersTable::actionUpdates()`. *Business reason:* it
guarantees the customer notification, the audit entry, the stock movement and the financial settlement
all happen, regardless of which screen triggered the change.

### 4.5 When you'd touch this system
- **React to an existing business event** (e.g. "email the merchant when an order is returned") → add an **event listener** in a plugin; don't edit core.
- **Add a button, column, form field, or menu entry** → use a **hook/action**; don't edit core templates.
- **Introduce a brand-new business moment others should react to** → emit a **new event** from the relevant choke point so future features can subscribe.

### 4.6 Reference
- Hooks guide & API: `@c:\xampp\htdocs\cake\shipping\src\Hooks\README.md`.
- Core event wiring: `@c:\xampp\htdocs\cake\shipping\config\events.php` and `src/Event/*Listener.php`.
- Example plugin event maps: `plugins/OrderExtera/config/MainEvents.php`, `plugins/Wallet/config/events.php`.

## 5. Plugin Architecture ✅

**Why it exists.** Shipprex deliberately keeps its core small and pushes almost every *feature* into a
**plugin**. A plugin is a self-contained capability — Wallet, Stocks, SMS, Companies, etc. — that can
be shipped, enabled, disabled, or sold independently. This is the structural reason the platform can
serve very different customers from one codebase: each tenant is essentially "core + the plugins they
paid for."

**The business payoff:** features are **packaged and priced as units**; a customer's footprint is just
a list of active plugins; new functionality can be built without destabilizing the core or other
features; and an unfinished feature can sit in the repo switched off until it's ready.

### 5.1 What a plugin contains (and why it's organized that way)
Every plugin follows the same predictable shape so the platform can wire it up automatically. In
business terms, a plugin bundles everything a feature needs to stand on its own:
- its **settings** (the admin toggles that turn it on/off and configure it),
- its **reactions** (the events/hooks it listens to so it can join in core workflows),
- its **screens & routes** (its own pages and URLs),
- its **data** (its own tables/models),
- and, for the new system, its **UI contributions** (the fields/buttons/widgets it adds to V2).

The takeaway: a feature is never scattered across the core — it lives in one folder under `plugins/`.

### 5.2 How plugins plug into the core (without touching it)
Plugins extend the platform through the mechanisms from §4 plus data relationships:
- **Settings** decide *whether* the feature is active for a tenant.
- **Events** let the plugin react to business moments (order placed, status changed, user saved).
- **Hooks** let the plugin add UI — buttons, columns, menu entries — into core screens.
- **Associations** let the plugin attach its own data to core records (e.g. tags, custom fields, or
  pickups hanging off an order) — added at runtime so the core `Orders` model doesn't need to know
  about them in advance.

This is *why* you can remove a plugin and the core still runs: the connections are additive, not
hard-wired.

### 5.3 How plugins are turned on
There are two tiers of activation, reflecting two business realities:
- **Always-on plugins** are foundational (orders extras, users/permissions, settings, menus, theming, translation, search, ledger, notifications). They're part of "what Shipprex *is*."
- **Optional plugins** are enabled per install via a config list (`config/plugins_config.php` → `ACTIVE_PLUGINS`) — currently **Stocks, Comments, Accountants, FasTrak**. These are the "sold/installed per customer" features.

So enabling a purchased feature for a tenant is, in most cases, a configuration change — not a code release.

### 5.4 How plugins contribute to the new front-end (V2)
For the API-first system, a plugin can declare its UI contributions **declaratively**, so the React
front-end renders them without bespoke code:
- **`config/field_registrations.php`** — registers the plugin's **form fields, table columns, and action buttons** through the field registry. This is the primary, preferred way to add UI to V2.
- **`config/Apiv2Contributor.php`** — returns a pure list of **extra "slot" contributions** the declarative config doesn't cover (read-only display widgets, print-layout injections). It's auto-discovered the same way the platform loads `events.php`/`settings.php`.

Business value: a plugin's presence in V2 is data the backend *describes*, so the front-end stays
generic and any enabled plugin's UI appears automatically.

### 5.5 Plugin inventory & roadmap
- The **full catalog** of plugins (each with its purpose, settings, and behaviors) is documented in **Part III** of this knowledge base (§22 onward).
- Some plugins are **placeholders / work-in-progress** — present in the repo but not yet delivering functionality (e.g. CRM, Rex2, Reporting, AdditionalCosts). They represent the **roadmap**: planned capabilities that can be switched on once built, without disrupting what's already shipping.

## 6. Permissions & RBAC ✅

**Why it exists.** A courier operation has very different people touching the same system — the owner/
admin, office staff, accountants, merchants, branch managers — and they must **not** all see or do the
same things. A merchant shouldn't see other merchants' orders; an accountant shouldn't reassign
drivers; a branch manager is scoped to their branch. The permissions system (delivered by the
**UsersManager** plugin) is what enforces these boundaries so the platform can be safely shared by
many roles at once.

**The business payoff:** access is controlled centrally and per-role; new staff get the right access
by being placed in the right group; and sensitive actions (deleting orders, editing money, managing
users) can be locked down without custom code.

### 6.1 The model: Users belong to Groups, Groups hold Permissions
The building blocks are deliberately simple:
- **Users** are individual logins.
- **Groups** are roles (Admin, Seller/Merchant, Accountant, Branch manager, etc.). Every user belongs to one group, and groups can be **hierarchical** (a parent/child structure).
- **Permissions** are granted **to the group, not the user** — each permission row says "this group may perform this action on this screen." Change the group, and every member's access changes with it.

This means access is managed by **role**, which is how a growing operation actually thinks about it
("give this person accountant access"), rather than configuring each person individually.

### 6.2 How a permission check happens
On essentially every request, the system asks a single question: *"Is this user's group allowed to do
this action on this screen?"* (`UserAuth::checkPremissions()`). If yes, the request proceeds; if no,
the user is bounced to an **"access denied"** page (or to login if their session expired). Because the
check is centralized, a screen is protected automatically — developers don't re-implement access logic
per page.

There are a few sensible exceptions baked in: **public pages** (login, register, password reset, terms)
are always reachable, and the legacy **driver app API** is handled separately. Plugins can also extend
the decision through a `permissions_extander` filter — so a feature can grant or restrict access
without editing the core checker.

### 6.3 The Admin shortcut (group 1)
**Group `1` is the super-admin role and bypasses all permission checks** — it can do everything. This
is a deliberate, well-known rule across both the web app and the API. *Business reason:* the operation
owner always needs unobstructed access, and it guarantees no one can accidentally lock out the
administrator. Everyone else is governed by their group's explicit permissions.

### 6.4 Permissions in the V2 (API) world
The API enforces the **same role model**. When a user logs in, their identity (including their group)
travels with each request via the security token, and the API checks that group against the allowed
action — with group `1` again treated as full access. The API also exposes endpoints for **reading and
managing permissions**, so the new front-end can both *respect* a user's access (hiding what they
can't use) and *administer* it. The net effect: V1 and V2 agree on who can do what.

### 6.5 Profile fields — tailoring the user record per role
Beyond access control, the same plugin lets admins add **custom profile fields** to users (Pfields),
optionally **scoped to a specific group**. *Why it matters:* different customers and different roles
need to capture different information about their people (IDs, tax numbers, vehicle details for
drivers, etc.) without changing the database schema — the platform stays flexible per tenant.

### 6.6 Reference
- RBAC engine: `@c:\xampp\htdocs\cake\shipping\plugins\UsersManager\src\Controller\Component\UserAuthComponent.php` (the `checkPremissions()` check and `adminGroup => 1` rule).
- Data model: `UsersManager` `Users` / `Groups` / `Permissions` / `Pfields` tables.
- V1 wiring: `@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php` (loads `UserAuth`, runs the check per request).

## 7. Localization & Internationalization ✅

**Why it exists.** Shipprex is sold across regions — notably Arabic-speaking markets alongside English
and French — so the same platform must present itself in the user's language, including
**right-to-left (RTL)** layout for Arabic. Just as importantly, the *content* couriers see (status
names, button labels, custom field names) needs to be translatable **per customer**, not hard-coded.
Localization is what lets one product feel native to each market and tenant.

**The business payoff:** a single deployment serves multilingual teams; customers can re-word the
interface to match their own terminology; and dates, numbers, and currency display correctly for the
region — reducing confusion and support load.

### 7.1 Supported languages & how the language is chosen
The platform ships with **English, Arabic, and French** (`LANG_LIST` = `en/ar/fr`). The active language
is chosen per session: a user's selection wins, otherwise the platform's **default language**
(`intl_local_default_lang`, defaulting to English) applies. Arabic automatically drives **RTL**
presentation. *Business value:* staff and merchants each work in their preferred language without
separate installs.

### 7.2 Translatable content (the Translation plugin)
Unlike a typical app that locks translations into developer files, Shipprex stores translatable
messages **in the database** via the **Translation** plugin. This is a deliberate business choice: an
admin (not a developer) can open the translations screen, **search-and-replace wording**, and adjust
any label — so a customer can rename things to their own vocabulary and fixes ship instantly, no code
release needed.

### 7.3 Auto-translate (optional)
To avoid manually translating every new term, the platform can **auto-translate** newly encountered
words across all supported languages using **Google Translate** (enabled by providing a Google API
key). When a new label appears, it's captured and pre-filled for each language, leaving admins to
refine rather than translate from scratch. *Business value:* faster rollout of new features into all
languages; it's optional and key-gated, so customers who don't want the dependency simply leave it off.

### 7.4 Dates, numbers & currency
Locale affects more than words — it changes how **dates and numbers** read. Because localized date/number
parsing can cause subtle data bugs, there's a safety toggle
(`datetime_field_escape_localizations`) that forces a consistent English-format for date/number
handling regardless of display language. Currency is governed by the system currency setting
(default `EGP`), so money is shown in the operation's own currency. *Business value:* the operator
chooses between fully-localized formatting and predictable, consistent formatting — whichever causes
fewer errors for their team.

### 7.5 Reference
- Language list & locale selection: `@c:\xampp\htdocs\cake\shipping\config\bootstrap.php` (`LANG_LIST`) and `@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php` (per-request `I18n::setLocale`, the date/number escape toggle).
- DB-backed translations: the **Translation** plugin (`plugins/Translation`) and the `Translations` table.
- Settings involved: `intl_local_default_lang`, `intl_local_currency`, `datetime_field_escape_localizations`, and the auto-translate Google API key.

## 8. Theming & UI Customization ✅

**Why it exists.** Shipprex is white-labelled: the same back-office is resold by different courier
companies under their own names and brand colors, and each operator wants the dashboard to *look like
theirs* — not like a generic admin tool. Just as importantly, different roles want a navigation menu
that reflects how *they* work (an accountant's menu is not a merchant's menu). The theming and UI layer
is what lets an operator restyle the dashboard and reshape its navigation **from settings and an admin
screen — without touching code.**

**The business payoff:** a tenant can be visually branded in minutes (top bar, sidebar, brand area,
accent — plus the site name); the navigation each user sees is **role-aware** and **plugin-extensible**,
so enabling a paid feature automatically adds its menu entries; and presentation overrides live in one
theme so they survive framework upgrades.

### 8.1 Theme colors & branding (the white-label controls)
The dashboard is built on the **AdminLTE** admin skin, and Shipprex exposes its color scheme as plain
settings on a dedicated **Themes** tab. Four color pickers drive the look (`Settings.Themes` in
`@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:436-441`):

| Setting | What it paints |
|---------|----------------|
| `theme_basic_color_top_bar` | The top navigation bar background. |
| `theme_basic_color_left_bar` | The left sidebar background. |
| `theme_basic_color_brand_bg` | The brand/logo block at the top of the sidebar. |
| `theme_basic_color_accent` | The main text/accent color applied across the dashboard. |

These values are read at render time and dropped straight into AdminLTE's CSS class names — e.g. the
sidebar becomes `bg-<left_bar>`, the top bar `navbar-<top_bar>`, the brand block `bg-<brand_bg>`, and
the page body gets `accent-<accent>` (`templates/element/backend/left_navbar.php:2-4`,
`templates/element/backend/navbar.php:30`, `templates/layout/backend/main.php:4`). Because they're
ordinary settings, a change takes effect on the next page load with no deploy. The displayed **site
name** comes from the separate global site setting (`site_settings_.site_name`), and the brand logo is
rendered in that same sidebar block. *Business value:* an operator self-serves their branding instead
of filing a change request.

> **Scope note:** these are palette/skin choices within AdminLTE's named-color system, not a free-form
> design system. They're meant for fast, safe brand alignment — deeper visual changes belong in the
> CustomTheme plugin (§8.2).

### 8.2 The CustomTheme plugin (presentation overrides that survive upgrades)
**CustomTheme** is a CakePHP **view theme** — the always-on plugin that owns how pages actually look.
Every back-office request renders through it: `AppController::beforeRender()` calls
`viewBuilder()->setTheme('CustomTheme')` (`@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php:219`),
and pages use its `backend/main` layout. Its job is to be the **one place** where the product's
presentation lives, separate from business logic.

In practice it does two things:
- **Hosts the dashboard shell** — the AdminLTE layout, header, top bar, sidebar, and the elements that
  the color settings in §8.1 feed into.
- **Selectively overrides specific screens** — because it's a theme, dropping a template at the
  matching path replaces the default for that screen only (e.g. the driver print layout and the
  merchant self-registration page under `plugins/CustomTheme/templates/...`). Everything not overridden
  falls through to the core/plugin templates unchanged.

*Business value:* the look-and-feel is a swappable layer. Overriding one screen doesn't fork the whole
app, and keeping presentation in a theme (rather than edited into core/plugin views) is what lets the
platform take framework and plugin upgrades without losing customizations.

### 8.3 The Menus plugin (role-aware, plugin-extensible navigation)
The left-hand sidebar is **not** a hard-coded list — it's assembled per request from two sources and
filtered to the viewer's role, which is what makes the same install present a different menu to an
admin, an accountant, a merchant, or a branch manager.

**Two sources, merged:**
1. **Database-defined menus** — admins build menus and menu items in an admin screen. Each **Menu** is
   tied to a **group** (role), and each **Menu item** carries a display name, URL, icon, sort order,
   parent (for nesting), and an optional **badge** with a `count_model` for live counts (e.g. a number
   next to "Pending pickups"). Items form a hierarchy (parent/child), so sub-menus are just nested
   items (`plugins/Menus` — `MenusTable`/`MenuitemsTable`, README in the plugin folder).
2. **Plugin-contributed menus (via hooks)** — a feature plugin registers its own navigation in code
   with `create_new_root_menu(id, name, url, icon, permissions)` and
   `add_sub_menu_item_to_root(parent, name, url, permissions)`
   (`@c:\xampp\htdocs\cake\shipping\src\Hooks\Hooks.php:143-182`). The `permissions` argument is a list
   of group IDs allowed to see the entry. *This is why enabling a purchased plugin makes its menu
   appear automatically* — the plugin brings its own navigation (e.g. Stocks adds a "Stock" menu for
   admin/accountant groups).

**How it's assembled and shown:** on each request `TreelistComponent::setmenu()` pulls the current
user's group, lets the `menu_group_id` hook adjust it if needed, queries the DB menu items as a
threaded tree, and merges in the hook-registered items — already filtered to the group's permissions —
caching the result per group (`plugins/Menus/src/Controller/Component/TreelistComponent.php`). The
view's `TreelistHelper::generateUls()` then renders that structure into the nested sidebar lists
(`plugins/Menus/src/View/Helper/TreelistHelper.php`). Around the rendered menu, the sidebar fires the
hooks `before_main_menu`, `after_main_menu`, and `after_main_menu_seller`
(`templates/element/backend/left_navbar.php:18-34`), giving plugins extra injection points — including
a seller-specific slot, since merchants (group 3) get a deliberately different tail.

*Business value:* navigation is **data + role**, not code. Operators reorganize menus themselves;
access is enforced at the menu level (you don't see what you can't use); and the menu stays in lock-step
with which features a tenant has switched on.

### 8.4 Reference
- Theme color settings: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:436-441` (`Settings.Themes` tab), consumed in `templates/element/backend/navbar.php`, `templates/element/backend/left_navbar.php`, and `templates/layout/backend/main.php`.
- Theme application: `@c:\xampp\htdocs\cake\shipping\src\Controller\AppController.php:219` (`setTheme('CustomTheme')`); plugin at `plugins/CustomTheme` (layout + per-screen template overrides).
- Menus engine: `plugins/Menus` (`MenusTable`/`MenuitemsTable`, `TreelistComponent`, `TreelistHelper`) and the menu hooks in `@c:\xampp\htdocs\cake\shipping\src\Hooks\Hooks.php:143-205` (`create_new_root_menu`, `add_sub_menu_item_to_root`, `get_all_menu_array`, `menu_per_permissions`).
- Sidebar hook slots: `before_main_menu`, `after_main_menu`, `after_main_menu_seller` (`templates/element/backend/left_navbar.php`).

**This completes Part I — Platform Overview & Architecture (§1–§8).**

---

# PART II — Core Domain Modules (`src/`)

## 9. Orders Engine ✅

**Why it exists.** The order is the **atomic unit of the whole business** — every other module
(pricing, drivers, pickups, invoices, COD settlement, stock, SMS, reports) exists to move an order
through its life and get money back to the merchant. The Orders engine is the spine: it captures the
shipment, prices it, drives it through a status workflow, records who did what, and fans the
consequences out to every dependent module. Get this right and the rest of the platform stays
consistent; get it wrong and money and stock drift from reality.

**The business payoff:** orders can be captured many ways (single, bulk, Excel, API) yet always priced
and recorded identically; every status change ripples the *same* downstream effects no matter who
triggered it; and a complete, tamper-evident history exists for every dispute.

### 9.1 The order data model & key fields
An order bundles the shipment's commercial and logistical facts on one row (`orders` table, modeled in
`@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php`). The fields that carry business meaning:
- **Money:** `cod` (Cash-on-Delivery to collect from the consignee) and `fees` (what the courier
  charges the merchant for the delivery). These two numbers drive the entire financial back-office.
- **Who:** `user_id` (the merchant who owns the order), `driver_id` (the assigned courier), plus a
  many-to-many `AssignedDrivers` (`drivers_orders`) for assignment history.
- **Where:** `city` (resolved to a pricing **zone**), receiver name/phone/address, and the pickup-side
  address fields used for returns.
- **Lifecycle:** `statues` *(sic)* — the current status, only ever written through the choke point
  (§9.3); `invoice_id` (which COD invoice it settled into); `pickup_id` (the pickup it belongs to).
- **Reverse logistics:** `return_of` — a self-reference; when set, this order is the *return* of another
  order (drives `OriginalOrder`/`ReturnOrder` associations, §17).

Custom, per-tenant fields hang off the order without schema changes via the **Meta** behavior
(`OrderExtera.Meta`, §22.2), so different customers can capture different attributes.

### 9.2 Order creation & automatic zone-pricing
The headline behavior: **a merchant never sets the delivery fee — the system prices it.** On every new
order, `OrdersTable::beforeSave()` looks the destination `city` up against the zone table (including
AI-generated alternative names, §12.5). If it matches a zone, the order's `fees` is set to that zone's
`default_price` — then, if a **per-merchant override** exists for that user+zone (`zones_users`), the
merchant-specific price wins, and the typed city is normalized to the canonical zone name
(`OrdersTable.php:139-172`). If the city matches *nothing* (common on Excel imports with free-typed
cities), it falls back to the configured **default zone** (`zones_default_zone`), so an order is never
left unpriced. *Business value:* consistent, contract-correct pricing regardless of who typed the
address or how the order entered the system.

### 9.3 The status choke point & its side-effects ⭐
This is the single most important rule in the codebase. **All status changes flow through one
function**, `OrdersTable::actionUpdates($id, $status, $description)` (`OrdersTable.php:174-198`), no
matter the entry point (web UI, driver app, bulk tools, Excel import, V2 API). It does four things in
order:
1. Writes the new `statues` (refusing to move an order that's already `Collected` — a terminal money
   state).
2. Stamps an **audit action** (§9.4) recording the change.
3. Dispatches the **`Model.UpdatedOrderState`** event, which downstream modules subscribe to — SMS to
   the consignee, in-app notifications, stock movement, wallet/COD effects (§4.3).
4. On `Collected`, triggers the COD **settlement transaction** (`Transactions::HandelOrder`, §16).

*Business reason:* "delivered/collected" must trigger the customer notification, the stock decrement,
the audit entry **and** the money settlement together — never a subset. Centralizing guarantees it.

### 9.4 Audit trail (the `Actions` table)
Every order carries a chronological log of what happened to it in the **`Actions`** table (`hasMany`,
newest-first). The status choke point writes one, and so do bulk operations via
`Actions::add_action(orderId, name, description)` — e.g. a bulk fee/COD edit records exactly what
changed (`OrdersTable.php:282-295`). On top of this order-level log, the global event system writes a
broader who/what/before-after/URL/IP entry to the `Logger` table on virtually every save (§4.3).
*Business value:* in a cash-heavy operation, disputes ("I never got my COD", "who reassigned this
driver?") are answerable from the record.

### 9.5 The detailed order view & its associations
An order is a hub: it `belongsTo` its merchant (`Users`), `Invoice`, `Pickup`, and `Driver`; it
`hasMany` `Actions`, `Transactions`, and driver-pickup rows; and it gains `Tags`, `Meta`, and `Pickups`
dynamically at runtime via plugin events (so the core model needn't know about optional plugins,
§5.2). The self-referential `OriginalOrder`/`ReturnOrder` pair links an order to its return. This is
why the order detail screen can show pricing, custom fields, tags, comments, history, money and the
return chain in one place — each is a wired association, not a bespoke query.

### 9.6 Order duplication
Operators can clone an order to re-ship without re-keying it (V2 `POST /orders/{id}/duplicate`). The
related but distinct **location-flip** (`flipLocations`, `OrdersTable.php:324-370`) builds a *reversed*
order — pickup and receiver swapped — which is the mechanism behind reverse/return shipments (§17).

### 9.7 [V2] Orders API surface
The API-first layer exposes the full order lifecycle as JSON under `/api/v2/orders`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:132-202`), all funneling into the same engine above:
- **CRUD & lists:** `GET/POST /orders`, `GET/PUT/DELETE /orders/{id}`, plus `search`, `summary`,
  `export`, `unassigned`, `returns`.
- **Status & workflow:** `POST /orders/{id}/status`, `markPickup`, `markDelivered`, `GET .../timeline`
  and `.../transitions` (allowed next states) — all routed through `actionUpdates()`.
- **Assignment:** `POST/DELETE /orders/{id}/assign`, `unassign`.
- **Bulk:** `POST /orders/bulk` (create), `PATCH /orders/bulk` (fee/COD edits), `POST /orders/bulk/status`, `import`.
- **Returns & docs:** `POST /orders/{id}/return`, `GET .../returns`, `invoice`, `pdf`, `addNote`,
  `duplicate`; public `GET /orders/tracking/{trackingId}` (no auth).

*Business value:* the new front-end and external integrators drive orders through the exact same
guarded path as the legacy UI — one engine, two doors.

### 9.8 Reference
- Engine: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php` (`beforeSave` pricing, `actionUpdates` choke point, bulk/return/flip helpers); `ActionsTable` (`add_action`).
- Status workflow & enum: `@c:\xampp\htdocs\cake\shipping\src\Enum\OrderStatus.php` and `config/bootstrap.php` status sets (see §10).
- Events: `Model.UpdatedOrderState` consumers in `@c:\xampp\htdocs\cake\shipping\config\events.php` and plugin event maps.
- V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:132-202`.

## 10. Order Statuses & Workflow ✅

**Why it exists.** A parcel's journey *is* a sequence of statuses — Processing → Picked up → In
warehouse → On route → Delivered → Collected (money settled), with branches for returns and
cancellations. The status is what every actor reads to know "where is my order and what happens next,"
and it's what triggers money and stock movement. So statuses are treated as **first-class, controlled
vocabulary**, not free text: defined once, colored consistently, and constrained in how they may
change.

**The business payoff:** everyone (merchant, driver, accountant, customer) sees the same status
language; reports and dashboards can reliably split "still in flight" from "done"; and illegal jumps
(e.g. "On route" straight to "Collected") are prevented so money isn't settled on a parcel that was
never delivered.

### 10.1 Canonical status sets (active vs. inactive)
The status vocabulary is defined **once** in `config/bootstrap.php` and must never be hardcoded
elsewhere (`bootstrap.php:338-362`):
- **`state_active_type`** — orders still "in flight": *Processing, Picked up, In warehouse, On route,
  Delivered, Returned to warehouse*. These are what an operator works day-to-day.
- **`state_unactive_types`** — terminal/closed states: *Collected* (delivered **and** money settled),
  *Terminated*, *Canceled by client*.
- **`types_for_state`** — the union, used wherever a complete status list is needed (filters,
  dropdowns).

The active/inactive split is the backbone of dashboards and reports: "active orders" = work remaining,
"inactive" = closed. *Business meaning:* note that **Delivered ≠ done** — an order is only truly closed
at **Collected**, when the COD has been reconciled. That distinction is the heart of a cash-on-delivery
business.

### 10.2 The typed enum (developer-facing helper)
`src/Enum/OrderStatus.php` provides a typed `OrderStatus` enum with helpers — `label()` (translated
display), `color()`, `isActive()`, `isFinal()`, and `toArray()` for dropdowns. It's the safe,
modern way for code to ask "is this a final state?" without string-matching. *Caveat for readers:* the
enum carries a slightly broader, older case list (e.g. `On delivery`, `Returned`, `Cancelled`) than the
operational bootstrap sets — the **bootstrap constants plus the settings-driven colors are the
canonical operational source**; the enum is a convenience layer.

### 10.3 Status colors (admin-configurable)
Each status has a color, used for the badges shown across the app. Defaults live in `types_colors`
(`bootstrap.php:363-377`), but every color is **overridable per tenant** through settings —
`order_status_color_<status>` on the Orders tab (`plugins/Settings/config/settings.php:326-329`). The
resolved palette feeds the `HSTT()` badge renderer, which also runs the badge through the `order_status`
filter so plugins can restyle it (`settings.php:448-475`). *Business value:* operators can match status
colors to their own conventions, and the customer-facing tracking page reads the same palette.

### 10.4 Transition rules (allowed next states)
The V2 API enforces an explicit **transition graph** — each status declares which statuses it may move
to (`OrdersController::$statusTransitions`, `src/Controller/Api/V2/OrdersController.php:47-57`):
Processing → {Picked up, Canceled by client}; On route → {Delivered, Returned to warehouse, In
warehouse}; Delivered → {Collected}; Collected/Terminated/Canceled → {} (terminal). A status update
that isn't in the allowed list is rejected — **unless the caller is an admin (group 1), who may
override** (`OrdersController.php:400-403`). The `GET /orders/{id}/transitions` endpoint returns the
legal next states so the front-end can show only valid buttons. Underneath, the actual write still goes
through `actionUpdates()` (§9.3), and the engine independently refuses to move an already-`Collected`
order. *Business value:* the workflow is guided and hard to corrupt, while the operation owner retains
an escape hatch for exceptions.

### 10.5 Status visibility per role
What statuses (and status-driven actions) a given role sees is itself configurable — e.g. the client
dashboard's status visibility and the cancel button are governed by Orders settings
(`plugins/Settings/config/settings.php:224`). Combined with RBAC (§6) and the driver's restricted
status permissions (§13.3), this means a merchant, a driver, and an admin each see a status picture
appropriate to their role. *Business value:* customers aren't exposed to internal warehouse states they
don't need, and drivers can only set the statuses they're trusted to.

### 10.6 Reference
- Canonical sets & default colors: `@c:\xampp\htdocs\cake\shipping\config\bootstrap.php:338-388`.
- Typed helper: `@c:\xampp\htdocs\cake\shipping\src\Enum\OrderStatus.php`.
- Configurable colors & badge renderer: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:326-329,448-475`.
- Transition graph & admin override: `@c:\xampp\htdocs\cake\shipping\src\Controller\Api\V2\OrdersController.php:47-57,400-403`.

## 11. Order Types ✅

**Why it exists.** Not every shipment is a simple "deliver this box." Couriers also do exchanges,
money-only collections, and reverse pickups. Rather than build separate modules for each, Shipprex
models them as **order types** on the same order — so the whole engine (pricing, statuses, drivers,
invoicing) is reused, and a tenant only turns on the types they actually sell.

**The business payoff:** one order pipeline covers several commercial products; a courier that only does
forward delivery isn't cluttered with exchange/refund options; and adding a service line is a setting,
not a development project.

### 11.1 The type catalog
Five types are defined centrally in `allOrderTypes` (`config/bootstrap.php:502-510`):
- **Forward** — the standard case: deliver a parcel to the consignee and (usually) collect COD.
- **Exchange** — deliver a new item *and* take back an old one in the same visit.
- **Cash Collection** — collect money from the customer with no parcel handed over (e.g. installment/debt collection).
- **Customer Return Pickup** — collect an item *from* the customer to return to the merchant (reverse logistics).
- **Refund** — return money to the customer.

### 11.2 Enabling/disabling types
Types are **opt-in per tenant**. A master toggle, `open_types_enable`, turns the whole feature on, and
`open_types_added_to_create_form` decides whether the type picker appears on the new-order screen
(`plugins/OrderExtera/config/settings.php:156-158`). Each individual type then has its own on/off
setting, `order_type_<slug>` (e.g. `order_type_cash_collection`), generated automatically from the
catalog (`settings.php:163-175`). At boot, bootstrap filters `allOrderTypes` down to the **enabled**
set, `OrderTypes` (`bootstrap.php:512-520`), and the rest of the app only ever offers those. *Business
value:* the order form and reports show exactly the service lines a customer offers — nothing more.

### 11.3 Type-specific behaviors
The type tags an order's commercial intent and drives the differences that matter downstream — whether
COD is collected or refunded, whether a reverse pickup/return is created, and how the order reads on
invoices and the customer's tracking page. Forward and Cash Collection move money *in*; Refund moves it
*out*; Exchange and Customer Return Pickup involve the reverse-logistics path (location flip / return
order, §17). Because the type lives on the same order record, all of these still flow through the one
status choke point and the one financial pipeline. *Business value:* new service lines inherit the
platform's guarantees (audit, settlement, notifications) for free.

### 11.4 Reference
- Catalog & enabled-set filtering: `@c:\xampp\htdocs\cake\shipping\config\bootstrap.php:502-520` (`allOrderTypes` → `OrderTypes`).
- Type settings: `@c:\xampp\htdocs\cake\shipping\plugins\OrderExtera\config\settings.php:156-175` (`open_types_enable`, `order_type_<slug>`).

## 12. Zones & Pricing ⭐ (flagship feature)

**Why it exists.** Delivery pricing is the courier's core commercial lever, and it's inherently
**geographic** — delivering across town costs less than delivering to the next governorate. Shipprex
turns geography into money through **zones**: named areas, each with a price. On top of that, real
contracts are negotiated *per merchant* ("you get cheaper rates because you ship volume") and drivers
are paid *per zone*. The zones-and-pricing engine is what encodes all of this so that the right fee and
the right driver payout are applied automatically on every single order.

**The business payoff:** sales can negotiate per-customer rates that the system enforces without manual
work; driver payouts are computed by where they delivered; and even messy, free-typed destination names
(from Excel imports or APIs) resolve to the correct price — with AI helping the system *learn* new
spellings so unknown cities shrink over time.

### 12.1 The zone model
A **Zone** (`zones` table, `@c:\xampp\htdocs\cake\shipping\src\Model\Table\ZonesTable.php`) is a named
area with a **`default_price`** (the standard delivery fee) and these traits:
- **Hierarchical** — zones form a tree (`Tree` behavior, `parent_id`/`ParentZones`/`ChildZones`), so a
  country → governorate → district structure is natural.
- **Activatable** — the custom `Active` behavior soft-hides retired zones; `findAllZones()` (with
  `StopActive`) sees everything, `findActiveZones()` only live ones (`ZonesTable.php:144-157`).
- **Alternative names** — a free-text field of synonyms/spellings, used by fuzzy matching (§12.5).

### 12.2 Per-customer zone pricing (`zones_users`)
A merchant can have a **negotiated price for a zone** that overrides the default, stored in the
`zones_users` join table. `findUserZonesOrReturnAllZones($userId)` returns the full zone list but swaps
in the merchant's special price wherever one exists (`ZonesTable.php:111-121`). *Business value:* this
is the contract engine — "Merchant A pays 35, Merchant B pays 45, to the same zone" — applied
automatically at order time (§12.4).

### 12.3 Per-driver zone pricing (`zones_drivers`)
Symmetrically, drivers are **paid per zone**. `findDriverZonesOrReturnAllZones($driverId)` returns each
zone's payout for that driver: the driver+zone rate from `zones_drivers` if set, otherwise the driver's
flat `cost` as the fallback (`ZonesTable.php:123-137`). *Business value:* a driver's earnings reflect
where they actually delivered, and special arrangements (a driver who covers a hard area for more) are
captured without code.

### 12.4 Price resolution at order creation
This is where it all comes together — and it's automatic. On a new order, `OrdersTable::beforeSave()`
(§9.2) resolves the fee in a strict order of precedence:
1. Match the destination city to a zone (by name **or** alternative name, §12.5).
2. Start from that zone's `default_price`.
3. If a `zones_users` override exists for **this merchant + this zone**, use that price instead.
4. Normalize the stored city to the canonical zone name.
5. If no zone matched at all → fall back to the **default zone** (§12.6).

*Business value:* the merchant never picks a price; the system applies the contract-correct fee every
time, regardless of capture channel.

### 12.5 Fuzzy city matching & AI alternative names (DeepSeek)
Customers and Excel sheets type city names inconsistently ("Cairo", "القاهرة", "Cairo - Nasr City").
`findZoneByNameOrAlternative()` matches on the zone name **or** a `LIKE` against the zone's
`alternative_names` list (`ZonesTable.php:160-170`), so known synonyms resolve correctly. To grow that
synonym list without manual data entry, an admin can have the **DeepSeek AI** service generate plausible
alternative spellings/names for a zone (`src/Services/AI/DeepSeekService.php`; V2 `POST
/logistics/zones/{id}/generate-alternatives`). *Business value:* the platform gets better at
auto-pricing free-typed addresses over time, cutting the manual cleanup that plagues bulk imports.

### 12.6 Default/fallback zone for unknown locations
A delivery fee must never be blank. When a city matches nothing, the engine prices the order from the
configured **default zone** (`zones_default_zone`), optionally renaming the order's city to that zone's
name so it's visibly flagged (`zones_default_zone_name_show`) — see `OrdersTable.php:159-169`. *Business
value:* messy imports still produce billable orders, and the "default zone" bucket is an obvious place
to find and re-classify problem addresses.

### 12.7 [V2] Zones API
The full pricing surface is exposed under `/api/v2/logistics/zones`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:356-375`): list/CRUD, `lookup` (resolve a city to a
zone), `stats`, `toggle-active`, `bulk-status`, `generate-alternatives` (AI), and the pricing endpoints
`GET/PUT /zones/{id}/pricing` plus `GET /zones/user-pricing/{userId}` for per-merchant rates. *Business
value:* the new front-end manages the entire pricing model — including per-customer contracts — through
the API.

### 12.8 Reference
- Zone model & finders: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\ZonesTable.php` (`findUserZonesOrReturnAllZones`, `findDriverZonesOrReturnAllZones`, `findZoneByNameOrAlternative`, `Active`/`Tree`).
- Price resolution: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php:139-172`.
- AI: `@c:\xampp\htdocs\cake\shipping\src\Services\AI\DeepSeekService.php`.
- Settings: `zones_default_zone`, `zones_default_zone_name_show` (`@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:386-389`).
- V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:356-375`.

## 13. Drivers ✅

**Why it exists.** Drivers are the people who actually move parcels and **handle the cash**. They're
also the riskiest touchpoint: a courier wants drivers to update statuses and collect COD from the
field, but only within tight limits, and without giving them a full back-office login. Shipprex models
the driver as its **own first-class entity** (not a user group) with a dedicated, locked-down online
portal — so field work is enabled while exposure is controlled.

**The business payoff:** drivers get a simple mobile-friendly view of just their work; what they can
see and change is fully governed by settings; their pay is computed automatically from where they
delivered; and the office keeps a clean audit of every field action.

### 13.1 Driver model & payout
A **Driver** (`drivers` table, `@c:\xampp\htdocs\cake\shipping\src\Model\Table\DriversTable.php`) is a
separate entity that `hasMany` Orders and Pickups, plus a `drivers_orders` join for assignment history.
Payout is driven by a flat **`cost`** per driver, overridden **per zone** via `zones_drivers` when
`driver_multi_zones` is enabled (§12.3). *Business value:* "what do we owe this driver" is derived from
their deliveries and zone rates, not entered by hand.

### 13.2 The driver online portal & security
Drivers work through a dedicated **online view** (separate from the admin/seller dashboard), gated by
the `allow_driver_screen` master toggle. Because the link is shared with people who aren't full users,
access is protected by a selectable **security module**, `driver_online_sec` — `none`, `otp` (one-time
password), or `hash` (secure-hash link) (`plugins/Settings/config/settings.php:411`). *Business value:*
the operator chooses how much friction vs. security the portal needs for their drivers.

### 13.3 Driver-allowed status updates (orders vs. pickups)
Drivers may only set the statuses the operator trusts them with — and the allowed set is configured
**separately for orders and for pickups**: `drivers_update_status_allowed` and
`drivers_update_status_allowed_pickups` (both checkbox lists over `types_for_state`,
`settings.php:403-405`). So a driver might be allowed to mark "Delivered" but never "Collected"
(a money state reserved for the office). *Business value:* field staff can advance the workflow without
being able to touch sensitive financial transitions.

### 13.4 Assignment rules
Two settings shape how orders reach drivers: `driver_assign_show_status` controls **which order statuses
appear in the assignment list** (so you assign only relevant orders), and
`driver_unassigned_before_re_assign` **force-unassigns the old driver** whenever an order is reassigned,
preventing two drivers from thinking they own the same parcel (`settings.php:304,409`). Non-admins can
be allowed to assign drivers via `bulk_update_allow_driver_assign`. *Business value:* clean hand-offs
and no double-assignment disputes.

### 13.5 Online-view controls
The portal is tuned for focus and performance: `driver_limit_orders` caps how many orders load,
`driver_hide_completed_orders` removes finished work from view, and `drivers_show_comments_in_online`
(plus `driver_comments_limit_for_widget`) controls the comments widget drivers use for special
instructions (`settings.php:401-417`). *Business value:* a driver sees a short, current task list rather
than an unbounded history.

### 13.6 [V2] Drivers API & the driver app (Apiv1)
The modern surface is `/api/v2/logistics/drivers`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:311-337`): list/CRUD, `available`, `workload`,
`{id}/orders`, `{id}/route`, `activate`/`deactivate`, `performance`, plus bulk-assign endpoints. The
**existing mobile driver app** is served by the legacy **Apiv1** plugin (§44). *Business value:* the
back-office and the driver's phone read/write the same orders through APIs, while status writes still
funnel through the order choke point (§9.3).

### 13.7 Reference
- Model & payout: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\DriversTable.php`; zone payout in `ZonesTable::findDriverZonesOrReturnAllZones` (§12.3).
- Driver settings: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:399-417` (`Settings.Driver` tab) plus `allow_driver_screen` (`:159`), `driver_unassigned_before_re_assign` (`:304`).
- V2 routes & driver app: `@c:\xampp\htdocs\cake\shipping\config\routes.php:311-337`; Apiv1 plugin (§44).

## 14. Pickups ✅

**Why it exists.** Before a parcel can be delivered, the courier has to **collect it from the
merchant**. A pickup is that first-mile event: a scheduled visit to a seller's location to take in one
or more orders. Modeling it explicitly lets the operation plan driver routes for collection, charge for
the service, and give merchants a self-service way to request "come get my parcels."

**The business payoff:** collection is schedulable and chargeable like a mini-delivery; merchants
self-serve pickup requests within operator-set limits; and the pickup has its own status trail so the
office knows exactly where collection stands.

### 14.1 Pickup model & status history
A pickup moves through its own status vocabulary, **`PickupSTATUS`** — *Pending → Assigned → En Route →
Arrived → Picked Up* (or *Cancelled*) (`config/bootstrap.php:379-387`). Each change is recorded as
status history (exposed via `GET /logistics/pickups/{id}/history`), giving the same accountability for
collection that orders get for delivery.

### 14.2 Pickup creation (manual + via order "Has Pickup")
Pickups are created two ways: **directly** (an admin or — if `pickup_seller_create_enabled` — a
merchant requests one), or **implicitly from an order** via the OrderExtera **"Has Pickup"** meta field
(§22.5), so capturing an order can automatically generate its collection task. Seller self-service is
bounded by settings — `pickup_seller_create_enabled`, `pickup_seller_cancel_enabled`,
`pickup_seller_reschedule_enabled`, `pickup_seller_max_daily`, plus booking windows
(`pickup_min_hours_before`, `pickup_max_days_ahead`, `pickup_weekend_enabled`)
(`plugins/Settings/config/pickup_settings.php`). *Business value:* merchants book their own collections
without the office losing control of capacity and timing.

### 14.3 Driver pickup workflow & status permissions
Drivers carry out pickups from their portal, and — as with orders — they may only set the pickup
statuses they're trusted with: `pickup_driver_controlled_statuses` (a multi-select over `PickupSTATUS`,
`pickup_settings.php:46-65`), complementing the order-side `drivers_update_status_allowed_pickups`
(§13.3). Assignment can be automated (`pickup_auto_assign_enabled`) within per-driver daily caps and a
radius (`pickup_driver_max_daily_pickups`, `pickup_driver_assignment_radius`). *Business value:*
collection runs are dispatched and progressed in the field with the right guardrails.

### 14.4 Pricing & zone/area mapping
Pickups are charged via a base cost (`pickup_base_cost`) with optional **zone-based pricing**
(`pickup_enable_zone_pricing`) and cancellation fees (`pickup_cancellation_fee`,
`pickup_late_cancellation_hours`) governed by a stated `pickup_cancellation_policy`. Because pickups map
to the same zone/area geography as deliveries (§12, §31), collection can be priced and routed by area.
*Business value:* first-mile cost is recovered and varies sensibly by distance.

### 14.5 [V2] Pickups API
The surface is `/api/v2/logistics/pickups`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:289-307`): list/CRUD, `pending`, `scheduled`,
`statuses`, `bulk-assign`, and per-pickup `complete`, `assign`, `orders`, `history`. *Business value:*
the new front-end manages the whole collection lifecycle, including which orders ride on each pickup.

### 14.6 Reference
- Status vocabulary: `@c:\xampp\htdocs\cake\shipping\config\bootstrap.php:379-387` (`PickupSTATUS`).
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\pickup_settings.php` (`Settings.Pickup` tab).
- "Has Pickup" order meta: OrderExtera (§22.5).
- V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:289-307`.

## 15. Invoices ✅

**Why it exists.** In a cash-on-delivery business the courier collects the customer's money, then owes
the merchant their cut. An **invoice** is the settlement document that answers "how much do we pay this
merchant, for which orders, less our fees?" It's the bridge between operations (orders delivered) and
money (merchant paid). Getting invoices right is how the courier proves what it owes and closes the
loop on every COD.

**The business payoff:** each merchant has a running tab of collected orders that closes into a clean,
auditable payout; fees are netted automatically; and edge cases (terminated orders) are handled by
policy rather than by ad-hoc spreadsheet math.

### 15.1 Invoice model & lifecycle (open → close)
Every merchant has at most **one open invoice** at a time — a row with `cleared_at = null`
(`@c:\xampp\htdocs\cake\shipping\src\Model\Table\InvoicesTable.php`). As orders settle they're
**attached to that open invoice** (`attache_order_to_active_invoice`, `:170`). Closing it
(`CloseAnActiveInvoice`, `:131`) freezes the totals, stamps `cleared_at`, and a fresh empty invoice is
opened for the next cycle (`closeAnyOpenInvoiceForUser`). *Business value:* the open invoice is the
merchant's live balance; closing it is the act of paying them out.

### 15.2 COD collection & totals calculation
The invoice totals are computed straight from its orders (`getInvoiceTotalSum`, `:157`): `total_amount`
= sum of **COD** collected, `total_fees` = sum of **fees**, and the headline **`total_payout` = COD −
fees** — exactly what the merchant receives (`:135-138`). *Business value:* the payout figure is a
direct, checkable function of delivered orders, so disputes resolve against the order list.

### 15.3 Terminated-order handling
Terminated orders (delivery abandoned/failed) are a known accounting edge case, governed by Orders
settings (`plugins/Settings/config/settings.php:316-324`): `order_invoices_should_collect_terminated`
decides whether they appear on the invoice at all; `order_invoices_terminated_default_fees` and
`order_invoices_terminated_use_the_current_fees` decide whether to charge **no fee, a default fee, or
the order's own fee**; and `order_invoices_terminated_capture_the_old_cod` preserves the original COD in
notes for the record. *Business value:* the operator sets a consistent policy for failed deliveries
instead of negotiating each one.

### 15.4 Invoice printing
Invoices print for handing to merchants, controlled by the print/label settings
(`settings.php:188+`, e.g. `order_serial_in_print`) and the PDF endpoint below. (The broader printing
system — 4x6 vs A4, fonts, signatures, serials — is covered in §48.1.)

### 15.5 [V2] Financial invoices API
The surface is `/api/v2/financial/invoices`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:202-231`): list/CRUD, `statuses`, `summary`,
`estimate` (preview a payout before closing), per-invoice `approve`/`clear`/`void`, `orders`, `pdf`,
plus the **AdditionalCosts** extensions `costs`, `deductions`, and `applyDiscount` (§47.5). *Business
value:* the full settlement workflow — preview, approve, pay, document — runs through the API.

### 15.6 Reference
- Engine: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\InvoicesTable.php` (open/close, totals, attach).
- Terminated policy: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:316-324`.
- V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:202-231`. Cross-ref §47 (financial system).

## 16. Transactions ✅

**Why it exists.** An invoice is a *summary*; the **transaction ledger** is the line-by-line money
trail underneath it. Every time money is owed or moved for an order — the COD owed to the merchant, the
fee the courier keeps — a transaction records it. This is the raw material from which balances and
payouts are computed, and the evidence trail for any financial dispute.

**The business payoff:** every figure on an invoice or a merchant's balance traces back to dated,
attributed transaction rows; "clear balance" events mark when a merchant was paid; and reconciliation
has a single source of truth.

### 16.1 Transaction model & order handling (`HandelOrder`)
A transaction (`transactions` table,
`@c:\xampp\htdocs\cake\shipping\src\Model\Table\TransactionsTable.php`) belongs to a user, and
optionally an order or pickup, with a `type` (`in`/`out`), `amount`, and `details`. When an order
reaches **Collected**, the status choke point calls **`HandelOrder($order)`** (`:118-135`), which writes
**two** rows: an `out` line for the COD owed to the merchant (zero for a **Refund** type) and an `in`
line of **−fees** (the courier's cut deducted). *Business value:* the moment money is collected in the
field, the merchant's ledger reflects both what they're owed and what was deducted.

### 16.2 COD / settlement transactions
Because `HandelOrder` is triggered only from the `Collected` transition, transactions are created
**exactly once per settled order**, through the one choke point — never on mere "Delivered." This is the
mechanism that keeps the money ledger in lock-step with operational reality (§9.3). Manual/adjustment
transactions can also be entered directly.

### 16.3 Balance & "clear balance" (`cbalance`)
A merchant's payable balance is the running net of their transactions. The **clear-balance** action
(`TransactionsController::cbalance`, `:130`) computes the settlement figures via `CoreInvoicesService`
and records a `Clear Balance` `in` transaction marking the cut-off; `get_user_clearnces` /
`get_user_transaction_for_this_clarence` (`:140-167`) then group transactions **between successive
clearances** — i.e. "what was settled in this payout period." *Business value:* the operator can show a
merchant exactly which orders made up each payment and when balances were zeroed.

### 16.4 [V2] Financial transactions API
The surface is `/api/v2/financial/transactions`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:255-265`): list/CRUD, `types`, `summary`, `export`,
`unreconciled`, `reconcile`, `report`, and `order/{orderId}`. *Business value:* reconciliation and
financial reporting are fully API-driven.

### 16.5 Reference
- Engine: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\TransactionsTable.php` (`HandelOrder`, clearance grouping).
- Settlement trigger: `OrdersTable::actionUpdates()` on `Collected` (§9.3).
- V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:255-265`. Cross-ref §47.

## 17. Returns / Reverse Logistics ✅

**Why it exists.** Deliveries fail and customers send things back, so the parcel has to travel the
other way — from the customer (or the courier's warehouse) back to the merchant. Returns are
first-class because they carry their own money implications (return fees, COD reversal) and must stay
**linked to the original order** for a complete history.

**The business payoff:** a return is traceable to the order it came from; reverse trips are priced by
policy; and the reverse shipment reuses the entire delivery engine instead of a parallel system.

### 17.1 The return order model (`return_of`)
A return is just another order with its **`return_of`** field pointing at the original
(`OrdersTable.php:59-68`), exposing `OriginalOrder` and `ReturnOrder` associations. `isReturnOrder()`
checks whether an order is a return (`:305`). *Business value:* the original and its return are one
linked chain, so the office sees the full round trip.

### 17.2 Eligibility (`canBeReturned`) & window
An order may be returned only when it's **`Collected` and not already a return**
(`canBeReturned`, `:310`). *Business value:* you can't return something that wasn't completed, and you
can't recursively return a return.

### 17.3 Return fees, reasons & policy
Return trips can carry a fee, computed via `calculateReturnFees()` against the `default_return_fee`
setting (`:315`). *Business value:* the cost of reverse logistics is recovered by policy rather than
absorbed silently.

### 17.4 Location flipping (`flipLocations`)
The reverse shipment is generated by **swapping pickup and receiver** details — the customer becomes the
sender, the merchant's pickup location becomes the destination (`flipLocations`, `:324-370`). This is
what turns "deliver A→B" into "return B→A" without re-keying addresses. *Business value:* a return is
created in one click and routed/priced like any other order.

### 17.5 [V2] Returns surface & auto return-tag
Returns are created and listed via the Orders API — `POST /orders/{id}/return`, `GET /orders/{id}/returns`,
and the global `GET /orders/returns` list (`@c:\xampp\htdocs\cake\shipping\config\routes.php:137,192-194`).
Return orders are typically auto-tagged (via OrderExtera tags, §22.1) so they're easy to filter.
*Business value:* reverse logistics is visible and manageable alongside forward orders.

### 17.6 Reference
- Return helpers: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php:305-370` (`isReturnOrder`, `canBeReturned`, `calculateReturnFees`, `flipLocations`) and the `OriginalOrder`/`ReturnOrder` associations (`:59-68`).
- Settings: `default_return_fee`. V2 routes: `@c:\xampp\htdocs\cake\shipping\config\routes.php:137,192-194`.

## 16. Transactions
- 16.1 Transaction model & order handling (`HandelOrder`)
- 16.2 COD/settlement transactions
- 16.3 Balance calculation (cbalance) & invoice estimate summary
- 16.4 [V2] Financial transactions API

## 17. Returns / Reverse Logistics
- 17.1 Return order model (`return_of` self-reference)
- 17.2 Eligibility (`canBeReturned`) & return window
- 17.3 Return fees, reasons, policy
- 17.4 Location flipping (`flipLocations`)
- 17.5 Auto return-tag

## 18. Bulk Operations ✅

**Why it exists.** Couriers process orders by the hundred, and many actions need to happen to *many*
orders at once — reprice a batch, push a stack of orders to the next status, assign a run to a driver.
Doing these one-by-one is slow and error-prone. Bulk operations make mass changes a single, audited
action — while keeping them safe to delegate to non-admin staff.

**The business payoff:** office staff process big batches in seconds; each bulk change is recorded per
order; and exactly which bulk powers junior staff have is a setting, not a code change.

### 18.1 Bulk fee/COD updates (fixed vs. percentage)
`OrdersTable::bulkUpdateFeesAndCod($orderIds, $data)` (`OrdersTable.php:228-303`) applies a fee and/or
COD change across many orders, each either **`fixed`** (set to a value) or **`percentage`** (adjust by a
percentage). It returns a before/after summary (original vs. new COD/fees totals, success/failure
counts) and stamps an audit **action** on every order describing exactly what changed (§9.4). *Business
value:* batch repricing with a clear paper trail of the old and new numbers.

### 18.2 Bulk status updates
Statuses can be advanced in bulk (`POST /api/v2/orders/bulk/status`). Crucially these still route
through the status workflow, so the downstream ripple (SMS, stock, settlement, audit) fires per order
just as it would individually. *Business value:* "mark this whole route delivered" is one click but
loses none of the per-order consequences.

### 18.3 Non-admin permissions & display config (Bulk Update V2)
Bulk power is finely gated for non-admins via Orders settings (`settings.php:348-363`):
`bulk_update_enabled_for_non_admin` is the master switch, then `bulk_update_allow_cod_changes`,
`_allow_fees_changes`, `_allow_driver_assign`, and `_allow_status_changes` grant each capability
individually. A parallel set of `bulk_update_show_*` toggles (area, city, customer name/phone, …)
controls which columns appear in the bulk screen. *Business value:* a courier can let staff bulk-update
statuses but never touch COD, and tailor the bulk grid to what their team needs to see.

### 18.4 Reference
- Engine: `@c:\xampp\htdocs\cake\shipping\src\Model\Table\OrdersTable.php:228-303` (`bulkUpdateFeesAndCod`).
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:348-363`.
- V2 routes: `POST /orders/bulk`, `PATCH /orders/bulk`, `POST /orders/bulk/status` (`routes.php:154-156`).

## 19. Data Import / Export ✅

**Why it exists.** Merchants live in spreadsheets. They arrive with a day's orders in Excel and expect
them loaded in bulk; the office needs to export orders, transactions, and reports back out for
accounting and analysis. Import/export is the bridge between Shipprex and the spreadsheet world its
customers actually work in.

**The business payoff:** a merchant onboards a batch of orders without re-keying; messy spreadsheet
cities still get priced (via zone resolution); and operational data flows out to whatever downstream
tools the business uses.

### 19.1 Excel order import
`ExcelController::createOrders($file, $userId, $isOldFormat)` (`ExcelController.php:182`) reads an
uploaded spreadsheet and creates orders for a merchant, supporting both a current and a legacy column
format (`$isOldFormat`). The key behavior: imported orders flow through the **same `beforeSave`
pricing** as any order, so each free-typed destination is resolved to a zone and priced — falling back
to the default zone when unmatched (§9.2/§12.6). *Business value:* bulk onboarding is reliable even when
the sheet's city names don't match the zone list exactly.

### 19.2 CSV / Excel exports
Operational data exports across the app — orders (`GET /orders/export`), transactions
(`/financial/transactions/export`), and reports (`/reports/export`). *Business value:* accounting and
analysis happen in the tools finance teams already use.

### 19.3 Product Excel import (Stocks)
Inventory has its own spreadsheet importer for loading products in bulk (Stocks plugin, §24.5).
*Business value:* warehouses stock up their catalog without manual entry.

### 19.4 Excel templates (deprecated paths)
`ExcelController::generateTemplate()` (`:40`) produces a blank import template matching the expected
columns. Note that some template/format paths are legacy (the `$isOldFormat` switch exists precisely
because an older layout is still supported) — prefer the current template. *Business value:* merchants
start from a correct template instead of guessing columns.

### 19.5 Reference
- Importer: `@c:\xampp\htdocs\cake\shipping\src\Controller\ExcelController.php` (`createOrders`, `generateTemplate`); pricing reuse in `OrdersTable::beforeSave` (§9.2).
- Exports: `routes.php` (`/orders/export`, `/financial/transactions/export`, `/reports/export`).
- Product import: Stocks plugin (§24.5).

## 20. Reports & Dashboards ✅

**Why it exists.** Running a courier means knowing the numbers: how many orders are in flight, how much
money is owed and owing, which drivers and zones perform. Reports and dashboards turn the raw order and
transaction data into the operational and financial picture the operator manages by.

**The business payoff:** the owner sees profit and throughput at a glance; staff see what needs action
today (active orders, unassigned, returns); and the new front-end gets the same numbers via API.

### 20.1 Profit reports & timelines
The V1 `ReportsController` (`orders`, `invoices`) drives profit and order reporting, with the timeline
granularity tuned by `reports_duration_for_profit_timeline` (days per block, §3 settings). *Business
value:* the operator can see profit trends at the resolution that suits them.

### 20.2 Order status widgets
Dashboards summarize the order book by status — counts per status and an "active in the last 24h"
widget — built directly on the active/inactive status split (§10.1). *Business value:* the team
instantly sees the shape of the workload.

### 20.3 Money Flow dashboard
The financial command center (available cash, customer/driver breakdowns, CSV export) is delivered by
the **GeneralLedger** plugin and documented in §25.3. It cross-references invoices, transactions, and
banks. *Business value:* one screen answers "how much cash do we actually have and who does it belong
to?"

### 20.4 [V2] Reports API
The surface is `/api/v2/reports` (`routes.php:582-590`): `orders`, `financial`, `drivers`, `customers`,
`zones`, `custom`, `export`, plus `dashboard/summary` and `dashboard/stats`. *Business value:* the React
dashboard renders the full reporting suite from the API.

### 20.5 Reference
- V1: `src/Controller/ReportsController.php`.
- Settings: `reports_duration_for_profit_timeline` (§3). Money Flow: GeneralLedger (§25.3).
- V2 routes: `config/routes.php:582-590`.

## 21. Maintenance & Data Recovery ✅

**Why it exists.** Real operations accumulate messes — duplicate orders from double submits, invoices
that drifted from their orders, records that need restoring. The Maintenance tooling is the **admin
repair shop**: a set of guarded utilities to diagnose and fix data problems without going into the
database by hand.

**The business payoff:** data integrity issues are fixable in-app by an admin; fixes can be **simulated
before applying**; and there's a backup/diagnostic path for when things go wrong.

### 21.1 Maintenance controller capabilities
`MaintainceController` (note the spelling) bundles the repair tools
(`@c:\xampp\htdocs\cake\shipping\src\Controller\MaintainceController.php`): order recovery
(`retriveOdersFromLog`, `moveOrderBack`), duplicate detection (`findDuplicates`, `duplicateReports`),
invoice repair (`fixInvoices`, and the safe `simulateInvoiceFix` → `applyInvoiceFix` pair, plus
`revokeInvoice`), database tools (`backup`, `analyzeDb`), and integration review (`reviewOdoo`).
*Business value:* an admin can investigate and correct data problems with audit and simulation, rather
than risky raw SQL.

### 21.2 Data restore tooling
Because virtually every change is logged (§4.3), orders can be **reconstructed from the log**
(`retriveOdersFromLog`) and pushed back to a prior state (`moveOrderBack`). *Business value:* an
accidental deletion or bad status change is recoverable from the audit history.

### 21.3 Duplicate orders handling
`findDuplicates`/`duplicateReports` surface orders that look like accidental repeats (a common result of
double form submits or re-imports) so staff can resolve them. *Business value:* merchants aren't billed
and parcels aren't shipped twice.

### 21.4 Reference
- `@c:\xampp\htdocs\cake\shipping\src\Controller\MaintainceController.php` (recovery, duplicates, invoice fix/simulate/apply, backup, analyzeDb, reviewOdoo).
- Invoice repair leans on `CoreInvoicesService` (§16.3). Audit log basis: §4.3.

## 19. Data Import / Export
- 19.1 Excel order import (mapping, zone resolution on import)
- 19.2 CSV/Excel exports
- 19.3 Product Excel import (Stocks)
- 19.4 Excel templates (deprecated paths noted)

## 20. Reports & Dashboards
- 20.1 Profit reports & timelines
- 20.2 Order status widgets (24h active widget, status count table)
- 20.3 Money Flow dashboard (cross-ref GeneralLedger)
- 20.4 [V2] Reports API

## 21. Maintenance & Data Recovery
- 21.1 Maintenance controller capabilities
- 21.2 Data restore tooling
- 21.3 Duplicate orders handling

---

# PART III — Plugin Modules

> Each plugin gets its own section: purpose, settings, data model, events/hooks emitted &
> consumed, UI surfaces, [V2] contributions, dependencies.

## 22. OrderExtera (order super-plugin) ✅

**Why it exists.** Couriers keep asking for *one more thing* on the order — a tag, a custom field, an
extra-weight charge, partial deliveries, an open-parcel flag. Baking each into the core would bloat it
and force every customer to carry every feature. **OrderExtera** is the always-on "super-plugin" that
hangs all these optional order enhancements off the order via hooks, events, and the Meta engine — each
independently toggleable — so the core `Orders` model stays clean while the order screen can grow
arbitrarily rich per tenant.

**The business payoff:** an operator switches on exactly the order capabilities they sell; new order
attributes appear without schema changes; and money-affecting extras (extra weight, partial delivery)
are handled by configurable rules, not custom code.

### 22.1 Tags
Tags are colored labels on orders for triage and filtering (returns, fragile, VIP, …). Display is
**per-role and per-mode**: separate toggles for admin and driver views (`tags_enable_for_admin`,
`tags_enable_for_driver_online`), and admins choose between a **tag button** or a **status-dropdown**
tagging UI (`tags_enable_for_admin_type`) — settings at
`@c:\xampp\htdocs\cake\shipping\plugins\OrderExtera\config\settings.php:121-143`. Tags attach via hooks
into the order detail and action lists (`view_order_after_receiver_details`,
`orders_action_after_buttons_list`). *Business value:* teams organize the order book in their own
vocabulary, with each role seeing tags where it helps them.

### 22.2 Custom fields / Meta engine (`easy_add_custom_field`, MetaBehavior)
The platform's extensibility workhorse: the **Meta** behavior (added to `Orders` in `OrdersTable`)
stores arbitrary key/value attributes per order **EAV-style — no schema change**. Plugins/admins
declare fields with `easy_add_custom_field(...)`
(`plugins/OrderExtera/config/meta_settings.php`), and the fields render into the create/edit forms and
print/detail views through hooks (`orders_add_form_before_end`, `view_Order_Driver_Info`,
`orders_print_under_bills_info`). *Business value:* every customer can capture the data *their* business
needs on an order without a development cycle — this is the same mechanism behind "Has Pickup," country
support, and most optional order data.

### 22.3 Extra-weight billing
Charges for parcels over a free weight allowance, with full pricing rules
(`settings.php:36-57`): a `extra_weight_free_up_to` allowance, `extra_weight_cost_per_extra_kg`, and a
choice of whether the surcharge lands on **COD, fees, or both** (`extra_weight_cost_added_to`). It can
show on the create/edit forms and list view, and optionally stamp a note explaining the charge
(`extra_weight_added_note_for_extra_kg`); the button wires in via `orders_action_buttons_list`. *Business
value:* heavier shipments are priced correctly and transparently, by policy.

### 22.4 Partial delivery
**The business problem.** The consignee opens the parcel and says *"I'll take 1 of these 2 t-shirts, send
the rest back."* The driver must collect the right (lower) money and route the refused items back to the
merchant. This is harder than it looks because ~90% of orders ship with **no item description** (the
shipper packs a parcel, prints the policy, hands it to the courier — the warehouse never learns what's
inside), so the driver has no way to know *which item maps to which money* — especially with **bundle
discounts** (3 items listed at 500 each, COD discounted to 1000).

**[V1] — money-only (the original).** `PartialController` shows a modal with *New COD* + *notes*, rewrites
`order.cod`, logs an `Action`, sets `partial_status`, flags a `partially=1` meta field, and fires
`Orders.partially_delivered` (`MainEvents.php:560-595`). It tracks **no items, touches no stock, creates
no return** — the driver just types a number. Settings (`settings.php:5-26`): `partial_module_enable`,
`partial_status` / `partial_add_partial_statues`, `partial_add_driver_partial_workflow` (adds the button
to the driver portal via `driver_view_action_status_update` / `driver_online_order_actions`),
`broadcast_notifications_on_partial_delivered`.

**[V2] — item-level, stock-backed, reverse-logistics (PRP-2.17, in design).** The redesign turns a
partial into a proper reverse-logistics event, gated to **stock-backed orders only** (so items + prices
are known). The flow:
1. **Gate** (`partial_require_stock_items` [NEW], default on): only orders with `Stocks.ProductsOrders`
   lines can be partialed — the forcing function that pushes sellers to attach items (incl. **virtual
   stock**, §24, relying on `allow_negative_stock`).
2. **Driver picks kept quantity per line** (item/qty picker) and **types the collected COD** — *manual but
   item-informed*: the screen shows a *suggested* number (proportional allocation of the discounted COD +
   the delivery fee), but the human is authoritative. This deliberately sidesteps the bundle-discount
   math rather than guessing.
3. The **un-kept items become a return order** — type **Customer Return Pickup** with `return_of` →
   original (§17), its address **inverted to the original pickup location** via `flipLocations()`, a **new
   barcode/patch_number** assigned at the office, and **auto-attached to the seller's next pickup**
   (`partial_auto_create_return`, `partial_return_attach_next_pickup` [NEW]).
4. **Stock double-movement** for the returned units (the trickiest part): **+1 when the return reaches the
   warehouse** (`partial_return_received_status` [NEW], default *In warehouse*) and **−1 when handed back
   to the seller** on the next pickup (`partial_return_handover_status` [NEW], default *Delivered*) — so
   the company's custody nets out. Return orders need an **inverted** stock rule, special-cased so they
   don't double-count against the normal `out_statues`/`in_statues` path.
5. **Money on the invoice:** the original order shows the partial collected COD; the return shows the
   returned items + a **return fee** (reuses `default_return_fee`, §17.3, with optional
   `partial_return_fee_override` [NEW]), reducing the seller's payout.

*Business value:* the driver is never "lost," money and items always reconcile, refused goods flow back to
the merchant automatically, and inventory stays correct. See **PRP-2.17** for the full contract, the
worked stock example, edge cases, and the V2 API (`/orders/{id}/partial`, `/orders/{id}/partial/eligibility`).
Cross-ref: returns/reverse logistics §17, Stocks §24, order products PRP-2.1.1.

### 22.5 Open Parcel / Payment Method / and deprecated extras
A family of small order flags follows the same enable / show-on-create / show-on-edit / show-in-list
pattern: **Open Parcel** (let the customer inspect before paying) and **Payment Method** are active;
**Pickup Time, Has Pickup, Locations** are **deprecated** in OrderExtera (`settings.php:84-117`) — their
responsibilities have moved (pickups → §14, locations → the Locations plugin §31). *Business value:* the
order form exposes only the flags a tenant uses; the deprecated set is flagged so no one builds on it.

### 22.6 Country support
An optional country field set for international shipping (`country_support`, backed by
`config/country.php`). *Business value:* operators who ship cross-border capture destination country
without it cluttering domestic-only tenants.

### 22.7 Templates management
`TemplatesController` manages reusable message templates that **SMS and notifications** render
per-status (§32, §33). *Business value:* admins edit the wording customers receive in one place,
decoupled from code.

### 22.8 Automation handler
OrderExtera also carries automation wiring (its `events.php`, `event_associations.php`,
`phone_events.php`, `users_events.php`) that reacts to order/user events — and it registers the dynamic
`Tags`/`Meta`/`Pickups` associations on `Orders` at runtime (`MainEvents::addAssociation`, §5.2).
*Business value:* the plugin joins core workflows automatically when enabled, and cleanly detaches when
not.

### 22.9 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\OrderExtera\config\settings.php` and `meta_settings.php` (`easy_add_custom_field`).
- Hooks/events map: `@c:\xampp\htdocs\cake\shipping\plugins\OrderExtera\config\MainEvents.php` (actions/filters listed above), plus `events.php`, `field_registrations.php`, `Apiv2Contributor.php` (V2 contributions, §5.4).
- Controllers: `ExtraController`, `PartialController`, `TagsController`, `TemplatesController` under `plugins/OrderExtera/src/Controller`.

## 23. Wallet ✅

**Why it exists.** By default, paying merchants is an office-driven process (the courier closes
invoices and pays out). The **Wallet** plugin turns that into a **self-service account**: each merchant
sees a running balance and can request withdrawals themselves. It shifts routine payout work onto the
merchant while giving the operator guardrails (minimums, frequency, and a credit-risk auto-suspension).

**The business payoff:** merchants self-serve their money (less office workload and fewer "where's my
payout?" tickets), and the courier limits its exposure to merchants who run a negative balance.

**Architecture.** The Wallet plugin (`plugins/Wallet/`) has three database tables, three service
classes, three web controllers, and an event file that hooks into the invoice lifecycle.

- **Tables:**
  - `wallet_transactions` — `id`, `user_id`, `amount`, `request_id`, `submitted_by`, `payment_method`,
    `created`, `modified`. The actual payment record when admin settles a withdrawal.
    (`@plugins/Wallet/config/Migrations/20240110084810_CreateWalletTransactions.php`)
  - `withdrawal_requests` — `id`, `user_id`, `amount`, `status` (varchar 255, default `'pending'`),
    `invoice_id`, `created`. The merchant's "pay me" request, linked to the invoice that triggered it.
    Status values in practice: `pending`, `paid`, `cancelled`.
    (`@plugins/Wallet/config/Migrations/20240110084847_CreateWithdrawalRequests.php`; status changed
    from enum to varchar in `20240121082848_ChangeTheEnumOfStatus.php`)
  - `wallet_banks` — `id`, `user_id`, `integration_id`, `name`, `type`, `account_number`, `bank_name`,
    `account_name`. Merchant bank/payment accounts.
    (`@plugins/Wallet/config/Migrations/20240126075426_AddBanks.php`)
    Types defined as `BANK_TYPES` constant: `bank`, `mobile_wallet`, `cash`, `other`
    (`@plugins/Wallet/config/bootstrap.php:10-15`).

- **Services:**
  - `WalletService` (`@plugins/Wallet/src/Services/WalletService.php`) — balance calculations:
    `getUserCodAmount` (sum `invoices.total_payout`), `getUserFeesAmount` (sum `invoices.total_fees`),
    `getUserWalletAmount` (sum `wallet_transactions.amount`), `getUserTotalPayout`,
    `getUserUnpaidAmount` (= totalPayout − walletAmount), `calculateNetWalletBalance`
    (= walletAmount − pendingRequests). Also: `addRequest` (creates withdrawal request from invoice),
    `changeRequestStatus`, `cancelRequest`, `validateRequestCreation`, `processWalletTransaction`,
    `notifyUserOfTransaction`, `generateMonthlyStatement` (HTML).
  - `InvoicesService` (`@plugins/Wallet/src/Services/InvoicesService.php`) — invoice-close validation
    (`validateInvoiceCreation`), user balance across open invoices (`calculateUserBalance`),
    negative-balance detection (`checkAndHandleNegativeBalance`). Note:
    `createInvoiceRequestForUser()` exists but its save logic is currently commented out.
  - `RequestService` (`@plugins/Wallet/src/Services/RequestService.php`) — search/filter for withdrawal
    requests by status (Pending/Paid/All) and user (by ID or username).

- **Controllers** (legacy web, plugin-scoped — **not** API v2):
  - `RequestsController` — index (paginated, filtered), view, add (with balance summary), edit, delete,
    resync (recalculates after cancelled orders).
  - `TransactionsController` — CRUD for `wallet_transactions`, linked to requests via `request_id`.
  - `BanksController` — CRUD for `wallet_banks`.

### 23.1 Enabling the wallet
The whole module is gated by `wallet_enable` (`@c:\xampp\htdocs\cake\shipping\plugins\Wallet\config\settings.php:7`),
which switches on the balance view and withdrawal-request flow for users/sellers. *Business value:* the
operator decides whether merchants get self-service finance at all.

### 23.2 Self-closing invoices & withdrawal requests
With `wallet_self_closing_invoices` on (`settings.php:10`), a merchant can close their own invoice and
file a withdrawal request — i.e. "pay me what I'm owed now." This is the self-service counterpart to the
office-side invoice close (§15). The actual close is executed by `TransactionsController::dbalance($userId)`
(`@src/Controller/TransactionsController.php:104`) which calls `CoreInvoicesService::PayInvoice()`.

The invoice→wallet bridge works via events (`@plugins/Wallet/config/events.php`):
1. `CoreInvoicesService::PayInvoice()` (`@src/Services/CoreInvoicesService.php:68`) retrieves the user's
   active invoice, calculates `totalFees` (SUM `orders.fees`) and `totalCODs` (SUM `orders.cod`), runs
   the `validation_for_closing_invoice` hook, creates two core transactions (in=fees, out=−COD payout),
   fires `Model.Transactions.ClearBalance` event, then closes the invoice (sets `cleared_at`).
2. Wallet event listener (`events.php:16-27`) catches `ClearBalance` → calls
   `WalletService::addRequest($invoice, $inTransaction, $outTransaction)` → creates a
   `withdrawal_request` with `user_id`, `invoice_id`, `amount=abs(outTransaction.amount)`,
   `status='pending'`.

*Business value:* invoice clearing automatically generates the payout request — no manual step.

### 23.3 Merchant self-service: creating a withdrawal request
A merchant (or admin on behalf of a merchant) creates a request via `RequestsController::add($userId)`
(`@plugins/Wallet/src/Controller/RequestsController.php:66`). The add form
(`templates/Requests/add.php`) shows a **balance summary panel** displaying four figures: COD, fees,
wallet (already paid out), and unpaid (what is still owed). The amount field is capped at the unpaid
balance (`max=$amounts['unpaid']`). If the user is admin (`group_id=1`), a user dropdown appears to
select any merchant; if seller, `user_id` is auto-set to self. Status is hardcoded to `'pending'` in
the controller — the merchant cannot set it. *Business value:* merchant requests exactly the amount
owed, within system limits.

### 23.4 Admin receives and reviews requests
Admin navigates to Wallet → Requests → index (`templates/Requests/index.php`). The page provides:
- **Filter bar:** filter by status (All / Pending / Paid) and search by username or user ID
  (`RequestService.php` performs the query).
- **Table:** ID, User (linked to user profile), Amount, Status, Created.
- **Actions per row:**
  - **Pay** — navigates to `Wallet TransactionsController::add($request_id)` to settle (§23.5).
  - **View** — shows request detail (user, status, amount, created).
  - **Edit** — lets admin change `user_id`, `amount`, and `status` (free-text input).
  - **Delete** — removes the request entirely.
  - **Resync** — recalculates totals after post-clearing cancellations (§23.9).

*Business value:* admin has a single queue of all pending merchant withdrawal requests with one-click
settlement.

### 23.5 Admin settles (pays) a merchant request
Admin clicks "Pay" on a pending request → navigates to `Wallet TransactionsController::add($request_id)`
(`@plugins/Wallet/src/Controller/TransactionsController.php:54`). The form
(`templates/Transactions/add.php`) pre-fills:
- `request_id` — hidden, auto-set from URL parameter.
- `submitted_by` — hidden, auto-set to current admin's user ID from session.
- `payment_method` — dropdown: cash (0), bank (1), mobile (2).

The admin selects only the payment method and clicks Submit. The controller:
1. Loads the request by `request_id`.
2. Copies `amount` and `user_id` from the request to the new `wallet_transaction`
   (`TransactionsController.php:60-62`).
3. Saves the `wallet_transaction` to the DB.
4. `Model.afterSave` event on `wallet_transactions` (`events.php:33-39`) fires → calls
   `WalletService::changeRequestStatus($requestId, 'paid')` → the request is now settled.

The settlement record (`wallet_transaction`) tracks: who paid (`submitted_by`), how (`payment_method`),
how much (`amount` from request), and which request (`request_id`). The Transactions index
(`templates/Transactions/index.php`) lists all settlements with columns: id, user, amount, request_id,
submitted_by, payment_method, created, modified.

> **Note:** there is no separate "approve" step in the current V1 codebase. The flow is a direct
> **pending → paid** path. The admin either pays (creating a transaction) or manually edits the status
> via the request edit form. The V2 API routes define separate `approve`/`pay`/`cancel` actions, but no
> V2 controller exists yet (§23.12).

*Business value:* one-click settlement with full audit trail (who, when, how, how much).

### 23.6 Invoice-close validation hook
The Wallet plugin hooks into `validation_for_closing_invoice` (`events.php:7-13`) via the App Hooks
system. `InvoicesService::validateInvoiceCreation()` (`InvoicesService.php:30-67`) can **block** an
invoice from closing if:
- Payout amount (COD − fees) < `wallet_min_amount` (`settings.php:12`)
- Payout amount > `wallet_max_amount` (`settings.php:13`)
- An open withdrawal request already exists for this user
- Last request was created < `wallet_invoice_close_days` ago (`settings.php:15`)
- If caller is a seller (`group_id=3`) and invoice doesn't belong to them

If any check fails, an exception is thrown and `PayInvoice()` aborts. *Business value:* the courier
avoids a flood of tiny daily payout requests and controls cash-out cadence — enforced at system level,
not just UI.

### 23.7 Negative-balance auto-suspension (+ per-user override)
Triggers on `Model.afterSave` of the **Orders** table when `statues == 'Collected'`
(`events.php:44-73`):
1. `InvoicesService::calculateUserBalance($userId)` sums (COD − fees) across all open/uncleared
   invoices for the user.
2. Loads the user record; checks `user.negative_balance_threshold` first (per-user override). Falls
   back to global setting `negative_balance_threshold` (default −500, `settings.php:22`).
3. If balance ≤ threshold → `user.active = 0` (suspended).
4. Calls `createInvoiceRequestForUser($userId)` — method exists but its DB save is currently commented
   out (`InvoicesService.php:113-116`), so only suspension executes.

`user_threshold_override` (`settings.php:29`) enables per-user thresholds for trusted/risky merchants.
`clear_balance_disabled` (`settings.php:37`) can temporarily switch off the clear-balance feature
entirely. *Business value:* the platform protects the courier from runaway merchant debt automatically,
with room for case-by-case credit terms.

### 23.8 Invoice recalculation → withdrawal request sync
When an invoice is recalculated (e.g. orders cancelled after clearing),
`InvoicesController::recalculate($id)` directly loads `Wallet.Requests` and finds the matching pending
withdrawal request (same `user_id`, same day as `cleared_at`) → updates its `amount` to the new
`total_payout` (`@src/Controller/InvoicesController.php:261-278`).

Additionally, `RequestsController::resync($id)` (`RequestsController.php:151-270`) handles post-clearing
cancellations:
- Finds the last cleared invoice for the seller (matching request creation date).
- Identifies orders with status `'Canceled by client'` within that invoice.
- Removes them (sets `invoice_id=null`, appends note).
- Recalculates invoice totals (`total_amount`, `total_fees`, `total_payout`, `number_of_orders`).
- Creates a new withdrawal request with corrected amount, deletes the old one.
- All wrapped in a DB transaction (rollback on failure).

*Business value:* payouts stay accurate even when orders are retroactively cancelled.

### 23.9 Invoice revocation → withdrawal request cleanup
`MaintainceController` (`@src/Controller/MaintainceController.php:601-646`) lets an admin fully revoke a
cleared invoice. This:
- Deletes the matching pending `withdrawal_request` (same user, same amount, same cleared day).
- Moves orders back from the closed invoice to the open one.
- Resets the closed invoice (nulls `cleared_at`, `total_payout`, `total_fees`, `total_amount`).
- Deletes the open invoice.
- All in a DB transaction.

*Business value:* admin can undo an accidental invoice close and its associated withdrawal request in
one atomic operation.

### 23.10 Banks & balance formula
Merchant balances derive from the `wallet_transactions` ledger. The formula:
- **Unpaid** = `getUserTotalPayout` (sum `invoices.total_payout`) − `getUserWalletAmount` (sum
  `wallet_transactions.amount`)
- **Net available** = walletAmount − pending requests

Withdrawals are paid through configured `wallet_banks` (types: `bank`, `mobile_wallet`, `cash`,
`other`). Each `wallet_transaction` records: `amount`, `request_id` (links to `withdrawal_request`),
`submitted_by` (admin who processed), `payment_method` (0=cash, 1=bank, 2=mobile).

### 23.11 Known code issues (as of current source)
- `wallet_invoice_close_days` is registered **twice** in `settings.php` (lines 15 and 39).
- `createInvoiceRequestForUser()` save logic is **commented out** — auto-suspension works but
  auto-invoice-creation does not execute.
- `withdrawal_requests.invoice_id` is set in code (`WalletService::addRequest`) but no migration
  explicitly adds this column — likely added directly to production DB.
- `WalletService::validateRequestCreation()` has inverted logic (rejects active users).

### 23.12 [V2] Wallet API
Routes are **defined** in `config/routes.php:234-252` mapping to controller `FinancialWallet`:
`balance`, `summary`, `transactions`, `deposit`/`deposits`, `withdrawals` (list/create/statuses), and
per-request `approve`/`pay`/`cancel`. However, the `FinancialWalletController` class does **not exist**
in the codebase — these routes will 404 until the V2 controller is implemented. Only the legacy web
controllers (in `plugins/Wallet/src/Controller/`) are functional today.

> V2 routes define separate `approve` and `pay` actions — this is an upgrade from the V1 flow which has
> no explicit approval step (pending → paid directly).

*Business value:* the merchant-facing front-end runs the entire self-service wallet, while the office
approves and pays through the same API — once implemented.

### 23.13 Reference
- **Settings:** `@plugins/Wallet/config/settings.php` (`Settings.Wallet` tab, 8 settings).
- **Events:** `@plugins/Wallet/config/events.php` (3 listeners + 1 hook).
- **Services:** `@plugins/Wallet/src/Services/` (`WalletService`, `InvoicesService`, `RequestService`).
- **Models:** `@plugins/Wallet/src/Model/Table/` (`RequestsTable`→`withdrawal_requests`,
  `TransactionsTable`→`wallet_transactions`, `BanksTable`→`wallet_banks`).
- **Controllers:** `@plugins/Wallet/src/Controller/` (`RequestsController`, `TransactionsController`,
  `BanksController` — web only, not API v2).
- **Templates:** `@plugins/Wallet/templates/` (`Requests/`, `Transactions/`, `Banks/` — 4 views each).
- **Core dependency:** `@src/Services/CoreInvoicesService.php` (`PayInvoice`, `ClearBalance` event).
- **Invoice touchpoint:** `@src/Controller/InvoicesController.php:261-278` (recalc→request sync).
- **Invoice revocation:** `@src/Controller/MaintainceController.php:601-646` (undo close→delete request).
- **Clear-balance trigger:** `@src/Controller/TransactionsController.php:104` (`dbalance` action).
- **V2 routes (non-functional):** `config/routes.php:234-252`. Ledger basis: §16.

## 24. Stocks (Inventory) ✅

**Why it exists.** Some couriers also **fulfil** — they hold the merchant's products in a warehouse and
ship from stock, not just move parcels around. **Stocks** adds inventory management so the platform
knows what's on hand, decrements it as orders ship, and returns it when orders come back. It's an
optional, install-time plugin (`ACTIVE_PLUGINS`) for tenants who offer fulfilment.

**The business payoff:** stock levels stay in sync with order flow automatically; the operator can stop
merchants from overselling; and warehouse data flows in/out by spreadsheet for practicality.

### 24.1 Products & warehouses
The model is products held in one or more **warehouses**, with a configurable
**`stocks_default_warehouse`** used when none is specified
(`@c:\xampp\htdocs\cake\shipping\plugins\Stocks\config\settings.php:18`). *Business value:* multi-location
fulfilment is supported, with a sensible default so single-warehouse tenants don't have to think about
it.

### 24.2 Stock transactions
Inventory changes are recorded as **stock movements** (receive, dispatch, transfer, adjust), giving a
full audit of how quantities changed — the inventory analogue of the financial ledger (§16). *Business
value:* "why is this product short?" is answerable from the movement history.

### 24.3 In/out status mapping to inventory movement
The clever bit: which **order statuses** move stock is configurable. `out_statues` lists the statuses
that **decrement** stock (product leaves the warehouse) and `in_statues` lists those that **return** it
(`settings.php:5-7`). So inventory moves automatically as orders progress through their workflow — wired
to the same `Model.UpdatedOrderState` event (§9.3). *Business value:* the operator decides exactly when
a parcel counts as "out of stock" (on pickup? on delivery?) to match their physical process.

### 24.4 Guards: count-collected-only, negative-stock, block out-of-stock
Three safety controls (`settings.php:9-20`): `count_collected_only` counts only **collected** orders as
truly "out" for invoicing; `allow_negative_stock` (off by default, "not recommended") permits or
forbids negative quantities; and `stocks_block_none_stock_orders` **blocks stock-tracked merchants from
creating orders for products they don't have**. *Business value:* prevents overselling and keeps
inventory honest.

### 24.5 Default warehouse & product import
Products load in bulk via a spreadsheet importer, with the template link configurable
(`product_excel_template`, `settings.php:13`). Combined with the default warehouse, a tenant stocks up
their catalog quickly. *Business value:* onboarding a fulfilment catalog is a spreadsheet upload, not
manual data entry.

### 24.6 [V2] Inventory API
The surface is `/api/v2/inventory`
(`@c:\xampp\htdocs\cake\shipping\config\routes.php:425-464`): `products` (CRUD, `search`, `low-stock`,
`{id}/stock`, `adjust`, `history`), `warehouses` (CRUD, `{id}/stock`, `{id}/products`), and `movements`
(`receive`, `dispatch`, `transfer`, `adjust`, `report`). *Business value:* full inventory control from
the new front-end.

### 24.7 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Stocks\config\settings.php` (`Settings.Stocks` tab).
- Status-driven movement ties to `Model.UpdatedOrderState` (§9.3). V2 routes: `routes.php:425-464`.
- Activation: `config/plugins_config.php` → `ACTIVE_PLUGINS` (§5.3).

## 25. GeneralLedger ✅

**Why it exists.** The wallet and invoices tell you what you owe *merchants*; they don't tell you
whether the **company itself** is solvent — how much cash is actually in the bank versus how much is
already promised to merchants and drivers. **GeneralLedger** is the company's own books: banks,
categorized money movements, driver settlement, profit tracking, and a Money-Flow dashboard that
computes true available cash.

**The business payoff:** the owner sees real liquidity (not just gross collections), money is
categorized for accounting, obligations to merchants and drivers are netted against bank balances in
one view, and driver settlements are handled end-to-end.

### 25.1 Banks & categories
The ledger tracks one or more **banks** (cash accounts, table `gl_banks`) and **categories** that
classify money movements (table `gl_categories`) via `BanksController` and `CategoriesController`.
Both provide standard CRUD (index, view, add, edit, delete).

Banks have predefined roles via constants in `plugins/GeneralLedger/config/bootstrap.php:16-18`:

| Constant | Bank ID | Purpose |
|----------|---------|---------|
| `GL_CONFIG_MAIN_BANK` | 1 | Main operating bank |
| `GL_CONFIG_COD_BANK` | 2 | COD collections bank |
| `GL_CONFIG_PROFIT_BANK` | 3 | Revenue/profit bank |
| *(hardcoded)* | 5 | Driver fees bank |

Banks belong to `UsersManager.Users` and have many `Transes`. Bank fields: `name`, `description`,
`type`, `minimum_amount`. *Business value:* income and expenses are bucketed the way the finance team
reports them, with well-known bank roles enabling automated money movement between accounts.

### 25.2 Transactions (Transes)
Ledger entries — **Transes** (`TransesController`, table `gl_transes`) — are the company-level money
movements (distinct from the per-order merchant transactions of §16). Transes associate with Banks,
FromBank (for transfers), Invoices, Categories, Users, Orders, and Drivers.

#### 25.2.1 Basic CRUD
Standard actions: `index`, `view`, `add`, `edit`, `delete`.

#### 25.2.2 Driver settlement
- **`clearBalance($driverId)`** — Displays outstanding unpaid orders for a driver, computes total fees
  owed using zone-based pricing (`DriversTable::getDriverNextPaidAmountOrders()`), shows COD sum collected.
- **`clearDriverBalance($driverId)`** — Executes settlement: creates a driver-fee transaction for each
  unpaid order via `TransesTable::add_driver_fees_transaction()`, then redirects to driver view.
- **`driverBalance($driverId)`** — Driver balance history with time-period filtering (3m/6m/12m/all),
  grouped by day with COD and fees summaries per day.

#### 25.2.3 Profit tracking
- **`profits()`** — Net profits dashboard using `TransesReportesComponent`. Displays profit sums, driver
  sums, income sums, and a profit timeline chart (revenue vs driver fees vs margins).
- **`netProfitPerDay($driverId)`** — Daily net profit breakdown across drivers.

#### 25.2.4 Inter-bank transfers
- **`bankTransfer()`** — Moves money between banks by creating paired debit/credit transactions via
  `TransesTable::move_from_bank_to_another()`.
- **`transfer()`** — Generic transfer action.

#### 25.2.5 Automated transaction logic (TransesTable)
The `TransesTable` model (`plugins/GeneralLedger/src/Model/Table/TransesTable.php`) contains business
logic that automatically creates transactions:

| Method | Trigger | Effect |
|--------|---------|--------|
| `add_cod_transaction($order)` | Order status → "Collected" (event-driven) | Credits COD bank (id=2) with order COD amount |
| `add_driver_fees_transaction($driverId, $orderID, $fees, $moveOutFromCOD)` | Driver balance clearing | Credits driver fees bank (id=5); optionally debits COD bank. Fires `Model.Transes.DriverTransactionsIsDone` event |
| `move_from_bank_to_another($trans)` | Bank transfer action | Creates paired +/− transactions across two banks |
| `reset_bank_to_zero($bankId)` | Manual reset | Creates negative transaction to zero out a bank |
| `cod_burn_for_invoice($out, $in, $invoice)` | Invoice balance clearing (event-driven) | Creates paired transactions on profit bank and COD bank |

#### 25.2.6 Event listeners
Defined in `plugins/GeneralLedger/config/bootstrap.php:171-188`:
- **`Model.Order.UpdatedOrderState`** — When an order state becomes "Collected", auto-creates a COD
  transaction via `add_cod_transaction()`.
- **`Model.Transactions.ClearBalance`** — When an invoice balance is cleared, triggers
  `cod_burn_for_invoice()` to reconcile profit and COD banks.

#### 25.2.7 Reporting component
`TransesReportesComponent` (extends `ReportsComponent`) provides:
- `get_full_profit_sums()` — Net profit = income bank (3) − driver fees bank (5)
- `get_full_income_sums()` — Total income from bank 3
- `get_full_drivers_sums()` — Total driver fees from bank 5
- `get_profit_time_line()` — Timeline data with revenue, driver_fees, and profit_margins per period

*Business value:* a complete record of cash in and out of the business's own accounts, with automated
transaction creation on key business events and full driver settlement workflow.

### 25.3 Money-Flow dashboard ⭐
The headline feature (`MoneyFlowService`,
`plugins/GeneralLedger/src/Service/MoneyFlowService.php`). It computes the number that matters most:
**Available cash = COD bank balance − pending merchant payouts − unpaid driver fees**
(`MoneyFlowService.php:159-162`). It breaks obligations down by **customer** (what each merchant is
owed via open invoices) and **driver** (zone-priced driver fees on collected orders), supports CSV
export of customer breakdown, provides AJAX endpoints for drill-down and refresh, and caches results
for 5 minutes (`CACHE_DURATION = 300`).

#### Dashboard endpoints

| Route | Action | Description |
|-------|--------|-------------|
| `/general-ledger/money-flow/dashboard` | `dashboard` | Main dashboard view |
| `/general-ledger/money-flow/hard-refresh` | `hardRefresh` | Clears cache, regenerates all data (AJAX or redirect) |
| `/general-ledger/money-flow/invoice-orders/{id}` | `invoiceOrders` | AJAX: Expandable order details with status breakdown |
| `/general-ledger/money-flow/refresh-summary` | `refreshSummary` | AJAX: Lightweight summary refresh |
| `/general-ledger/money-flow/export-csv` | `exportCsv` | Download customer breakdown as CSV |

#### Key service methods

| Method | Purpose |
|--------|---------|
| `getSummaryMetrics()` | Pending payouts, active COD, total fees, unpaid driver fees, available cash, bank balances, open invoice count |
| `getCustomerBreakdown()` | Open invoices grouped by customer with order count, COD, fees, net payout |
| `getDriverObligations()` | Unpaid fees per driver using zone-based pricing |
| `getInvoiceOrderStatusBreakdown($id)` | Active/inactive order count within a specific invoice |
| `getBankBalancesOptimized()` | All bank balances via single GROUP BY query |
| `clearCache()` | Purges all cached money flow data |

#### Extensibility hooks
Applied in `MoneyFlowController::dashboard()`:
- `GeneralLedger.money_flow_summary` (filter) — modify summary metrics before display
- `GeneralLedger.money_flow_customers` (filter) — modify customer breakdown data

Template-level action hooks in `dashboard.php`:
- `GeneralLedger.money_flow_dashboard_top`
- `GeneralLedger.money_flow_after_summary_cards`
- `GeneralLedger.money_flow_after_customers`
- `GeneralLedger.money_flow_dashboard_bottom`

*Business value:* one screen answers "how much of the cash we're holding is actually ours?" — the
difference between feeling flush and being solvent.

### 25.4 Driver integration via hooks
The GL plugin extends the Drivers UI via hooks (`config/bootstrap.php:20-68` and `config/events.php`):
- Adds `cost` field to driver add/edit forms
- Adds driver cost column to drivers index table
- Adds **"Clear Balance"** button to driver view page (links to `transes/clear_balance/{id}`)
- Adds **"Driver Balance"** button to driver view page (links to `transes/driver_balance/{id}`)

Menu items are role-restricted to groups `[1, 4]` (admin and accountant).

### 25.5 Reference
- **Service:** `plugins/GeneralLedger/src/Service/MoneyFlowService.php` — available-cash formula, driver/customer breakdowns, caching.
- **Controllers:** `MoneyFlowController` (dashboard, AJAX endpoints, CSV export), `BanksController` (bank CRUD), `CategoriesController` (category CRUD), `TransesController` (transaction CRUD, driver settlement, profit tracking, inter-bank transfers).
- **Models:** `TransesTable` (`gl_transes`), `BanksTable` (`gl_banks`), `CategoriesTable` (`gl_categories`).
- **Component:** `TransesReportesComponent` — profit/income/driver sum aggregations and timeline.
- **Config:** `plugins/GeneralLedger/config/bootstrap.php` (bank constants, event listeners, driver UI hooks), `config/events.php` (menu registration, driver view buttons), `config/routes.php` (MoneyFlow routes).
- **Templates:** `templates/MoneyFlow/dashboard.php` (main view), 4 modular elements under `templates/element/MoneyFlow/` (`summary_cards`, `customer_breakdown`, `driver_obligations`, `bank_reconciliation`).
- Cross-ref: §16 (Merchant Transactions), §20.3 (Money Flow dashboard cross-ref), §47 (Hooks & Extensibility).

## 26. Accountants ✅

**Why it exists.** Bookkeeping is a distinct job with distinct needs: a focused workspace for
reconciling daily cash, not the full operations dashboard. **Accountants** is an install-time plugin
that gives the finance role its own landing page and daily-bank tooling.

**The business payoff:** accountants get a purpose-built view (fewer mistakes, faster reconciliation),
and which user group counts as "accountant" is configurable per tenant.

### 26.1 The accountant user group
`accountant_label_user_group` selects which group **is** the accountant role (default group 4)
(`@c:\xampp\htdocs\cake\shipping\plugins\Accountants\config\settings.php:18`). That group gets the
accountants workspace and GL access. *Business value:* the finance role is mapped to the tenant's actual
group structure (§6).

### 26.2 Daily bank activity
`accountant_daily_bank_activated` enables a daily-bank workflow against a chosen bank
(`accountant_bank_daily`) — the day's cash reconciliation tool (`settings.php:21-27`). *Business value:*
end-of-day cash is reconciled against a designated account systematically.

### 26.3 Dashboard window & redirect
`accountant_widgets_days` sets how many days back the dashboard queries look, and
`accountant_redirect_url` sets where accountants land on login (`settings.php:30-34`). *Business value:*
the workspace is tuned to the finance team's reporting window and drops them straight into their work.

### 26.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Accountants\config\settings.php` (`Settings.Accountant`).
- V2 routes: `/api/v2/accountants/*` (`routes.php:628-631`). Activation: `ACTIVE_PLUGINS` (§5.3).

## 27. Companies (multi-tenant merchants) ✅

**Why it exists.** A single "merchant" is sometimes really an **organization** with several people who
should share one account, one balance, and one set of orders. **Companies** models that umbrella: a
company owns sub-users, so a business customer can have multiple logins under one commercial
relationship.

**The business payoff:** larger merchants onboard as organizations with team logins, while still being
billed and settled as one entity.

### 27.1 Company model & sub-accounts
A company is a `Group` flagged `is_companies` plus the Companies plugin, owning sub-users under one
umbrella. The whole feature is gated by `companies_enabled`
(`@c:\xampp\htdocs\cake\shipping\plugins\Companies\config\settings.php:8`). *Business value:* multi-user
merchant accounts without duplicating the customer relationship.

### 27.2 Company users in user lists
`companies_show_company_users_in_user_list` controls whether a company's sub-users appear in the admin
user list (`settings.php:14`). *Business value:* the office can choose to see the people inside a company
account or just the company itself.

### 27.3 Company-scoped data
Orders, balances, and reports roll up to the company, so members share the same operational and
financial view. *Business value:* a team works a shared order book and one consolidated balance.

### 27.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Companies\config\settings.php` (`Settings.Companies`).
- V2 routes: `/api/v2/companies/*` (`routes.php:616-624`). Actor model: §1.2.

## 28. MultiBranches ✅

**Why it exists.** A courier operating in several cities/regions may run each as a semi-independent
**branch** — its own currency, its own inventory, its own bank accounts — under one platform.
**MultiBranches** adds that dimension so a multi-region operator isn't forced into one shared
configuration.

**The business payoff:** regional branches keep their own money and stock while the head office sees
everything in one system.

### 28.1 Branch model
The plugin introduces branches as a first-class scoping dimension, enabled by `ENABLE_PLUGIN`
(`@c:\xampp\htdocs\cake\shipping\plugins\MultiBranches\config\settings.php:10`). *Business value:* one
deployment serves a multi-branch organization.

### 28.2 Per-branch currencies / inventories / banks
Three independent toggles decide how much branches diverge: `CURRENCIES` (different currency per
branch), `INVENTORIES` (separate stock per branch), and `BANKS` (separate financial banks per branch)
(`settings.php:11-13`). *Business value:* a branch in another country can run its own currency and
books, or share the parent's — operator's choice.

### 28.3 Branch-management user group
`USER_GROUP` selects the group that manages branches (`settings.php:14`). *Business value:* branch
managers (§1.2) are scoped to their branch's currency/inventory/banks.

### 28.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\MultiBranches\config\settings.php` (via `MultiBranchesSettingEnum`). Multi-currency finance: §47.4.

## 29. Delegations ✅

**Why it exists.** Sometimes a courier can't fulfil an order itself and hands it to **another carrier**
(a partner or sub-contractor). **Delegations** captures that hand-off so a delegated order is visibly
marked, moved to an appropriate status, and tracked as "someone else has this now."

**The business payoff:** overflow or out-of-area orders are passed to partners without losing visibility
or messing up the workflow.

### 29.1 Delegating orders
The feature is gated by `delegations_active_state`
(`@c:\xampp\htdocs\cake\shipping\plugins\Delegations\config\settings.php:10`). Delegating an order
records who it went to. *Business value:* a clear record of which orders are being handled by a partner.

### 29.2 Auto status change & note stamping
On delegation the order can be auto-moved to a configured status
(`delegations_order_change_statues`) and its **notes field stamped** with `[DELEGATED TO : DATE]`
(`delegations_mark_order_note_field`) — `settings.php:12-14`. *Business value:* delegated orders are
unmistakable at a glance and drop into the right workflow bucket automatically.

### 29.3 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Delegations\config\settings.php` (`Settings.Delegations`).
- V2 routes: `/api/v2/delegations`, `POST /orders/{orderId}/delegate`, `accept` (`routes.php:605-612`).

## 30. FasTrak (3rd-party courier / ERP) ✅

**Why it exists.** Larger operators run an **ERP (Odoo)** as their system of record and need orders to
flow between Shipprex and that ERP — and they need a way for **external systems to push orders in**.
**FasTrak** is the integration plugin (install-time) that handles both directions.

**The business payoff:** Shipprex slots into an existing ERP-centric back office instead of replacing
it, and partner systems can inject orders programmatically.

### 30.1 Odoo integration (outbound)
Configured under the FasTrak settings (`@c:\xampp\htdocs\cake\shipping\plugins\FasTrak\config\settings.php:16-22`):
`odoo_integration_active_state`, the Odoo `odoo_integration_url`, and an `odoo_integration_login_token`.
*Business value:* orders/events sync out to the customer's ERP.

### 30.2 Incoming integration API (token auth)
A token-authenticated inbound API lets external systems create/update orders
(`incoming_api_token`, with a kill-switch `incoming_api_disabled`) — `settings.php:25-27`. *Business
value:* partners and storefronts push orders into Shipprex securely.

### 30.3 Order handoff flows
The plugin maps Shipprex orders to/from the ERP's representation so a handoff in either direction lands
as a proper order in the target system (and is reviewable via Maintenance `reviewOdoo`, §21.1).
*Business value:* a reliable, auditable bridge rather than manual re-entry.

### 30.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\FasTrak\config\settings.php` (`Settings.FasTrak`).
- V2 ERP routes: `/api/v2/integrations/erp/*` (`routes.php:557-562`). Activation: `ACTIVE_PLUGINS` (§5.3). Cross-ref §46.1.

## 31. Locations ✅

**Why it exists.** Plain free-typed addresses are error-prone. **Locations** adds a structured,
optional address layer — saved pickup/delivery locations, an Areas→Zones hierarchy, and Google Maps —
so addresses become accurate, reusable, and map-pinned.

**The business payoff:** fewer failed deliveries from bad addresses, faster order entry from saved
locations, and map-based precision where it's worth paying for.

### 31.1 Advanced locations mode
`locations_active_advanced_locations` switches on the structured-location features
(`@c:\xampp\htdocs\cake\shipping\plugins\Locations\config\settings.php:9`). *Business value:* operators
who need address structure turn it on; simple tenants stay lightweight.

### 31.2 Google Maps integration
`locations_activate_google_maps` plus an API key (`locations_activate_google_maps_api`) enable
map-based location selection (`settings.php:10-11`). *Business value:* pinpoint addresses on a map —
optional and key-gated so there's no dependency for those who don't want it.

### 31.3 Areas → Zones hierarchy
Locations introduces **Areas** that map up into the pricing **Zones** (§12), so a precise area still
resolves to the correct delivery price. *Business value:* granular addressing without breaking the
zone-based pricing model.

### 31.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Locations\config\settings.php` (`Settings.Locations`).
- V2 routes: `/api/v2/logistics/locations`, `/logistics/areas` (`routes.php:341-352`). Cross-ref §12, §46.2.

## 32. Notifictions ✅  *(note: dir/namespace is misspelled — §1.4)*

**Why it exists.** People need to know when things happen — admins when new orders arrive, customers
when their parcel moves. **Notifictions** is the in-app + email notification fabric (the SMS side lives
in §33), driven by the event system so any business moment can raise an alert.

**The business payoff:** the office reacts to new orders promptly, customers get proactive updates
(fewer "where is my order?" calls), and notifications are configurable per channel.

### 32.1 Admin notifications on new orders
`notifications_on_new_orders` sends the admin group an in-app notification when orders are created
(`@c:\xampp\htdocs\cake\shipping\plugins\Notifictions\config\settings.php:16`). *Business value:* new
business is noticed immediately.

### 32.2 Share order via email (Brevo)
`enable_order_email_sharing` adds a "Share via Email" button on the order view, using the **Brevo**
email service (`settings.php:17`). *Business value:* order details are emailed to customers/partners in
one click.

### 32.3 Event-driven notifications
Beyond these, the plugin listens to domain events (e.g. partial delivery, §22.4) to raise notifications
automatically — the "what the system does" side of the hooks/events split (§4.3). *Business value:*
relevant parties are kept informed without manual messaging.

### 32.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Notifictions\config\settings.php` (`Settings.Notifications` — note the *settings group* is spelled correctly even though the plugin dir isn't, §1.4).
- V2 routes: `/api/v2/notifications/*` (`routes.php:572-579`). Brevo: §46.3.

## 33. Sms ✅

**Why it exists.** SMS is the most reliable way to reach a delivery customer who isn't in the app. The
**Sms** plugin sends the consignee a message at key points in the order's journey, using
operator-editable templates — so customers get timely, branded updates in their own words.

**The business payoff:** proactive delivery SMS cuts failed deliveries and support calls; the operator
controls exactly which status changes message customers and what each message says.

### 33.1 Provider integration config
A generic HTTP SMS provider is configured under the Sms settings
(`@c:\xampp\htdocs\cake\shipping\plugins\Sms\config\settings.php:5-21`): `sms_integration_active_state`
(master switch), `sms_url`, `sms_username`/`sms_password`, sender ID (`sms_SMSSender`), and language.
*Business value:* works with the operator's chosen SMS gateway.

### 33.2 Per-status SMS templates
The heart of the plugin: a **template per order status** — created, processing, picked up, on route,
delivered, collected, canceled, terminated — each chosen from the managed templates
(`sms_template_created`, `_pickedup`, `_onroute`, `_delivered`, …) at `settings.php:28-35`. Wired to the
`Model.UpdatedOrderState` event (§9.3), the right message fires automatically as an order advances.
*Business value:* customers get the right message at the right moment, every time, without staff effort.

### 33.3 Template toggles
Because each status maps to a template (and an unset template means no message), the operator can enable
SMS for only the milestones that matter to them. *Business value:* avoids over-messaging — e.g. SMS on
"out for delivery" and "delivered" only.

### 33.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Sms\config\settings.php` (`Settings.Sms`); templates managed via OrderExtera `TemplatesController` (§22.7).
- Trigger: `Model.UpdatedOrderState` (§9.3). V2 routes: `/api/v2/integrations/sms/*` (`routes.php:565-568`). Cross-ref §46.4.

## 34. Comments ✅

**Why it exists.** Orders generate conversation — special instructions, proof-of-delivery photos,
notes between office and driver. **Comments** gives each order a discussion thread with image upload, so
context travels with the order instead of living in chat apps or someone's memory.

**The business payoff:** every order carries its own notes and photos (useful for disputes and proof of
delivery), and which roles can attach images is controlled.

### 34.1 Order comments
Each order has a comment thread, with a per-order cap (`Comments_limit_for_order`)
(`@c:\xampp\htdocs\cake\shipping\plugins\Comments\config\settings.php:28`). *Business value:* a focused,
bounded record of communication per order.

### 34.2 Image uploads (per-role, order-creation)
Image upload is independently enableable for **seller** (`image_upload_for_seller`), **driver**
(`image_upload_for_driver_online`), and even at **order creation** (`image_upload_for_order_creation`),
all under a master `images_upload_enabled` (`settings.php:12-23`). *Business value:* drivers can capture
proof-of-delivery photos and sellers can attach product images, scoped to who you trust.

### 34.3 Notifications & display placement
`image_upload_send_notifications` alerts on new images, and
`image_upload_show_image_inside_comment_or_image_section` controls where images render
(`settings.php:18-25`). *Business value:* the right people see new evidence, laid out the way the tenant
prefers.

### 34.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Comments\config\settings.php` (`Settings.Comments`).
- V2 routes: `/api/v2/extensions/comments`, `/orders/{orderId}/comments` (`routes.php:492-527`). Activation: `ACTIVE_PLUGINS`.

## 35. OrderRevisions ✅

**Why it exists.** Orders get edited — addresses, COD, fees — and sometimes a change was wrong or
disputed. **OrderRevisions** keeps a **version history** of each order and allows rolling back, so "who
changed the COD and can we undo it?" is answerable.

**The business payoff:** edits are reversible and attributable, which matters when money fields change.

### 35.1 Version-history capture
Gated by `order_revisions_enable` (off by default,
`@c:\xampp\htdocs\cake\shipping\plugins\OrderRevisions\config\settings.php:17-25`), the plugin snapshots
order changes over time. *Business value:* a full edit trail beyond the action log (§9.4), with
before/after field values.

### 35.2 Rollback & compare
Revisions can be viewed, compared, and **reverted** to a prior version. *Business value:* a bad edit is
undone in one step rather than reconstructed by hand.

### 35.3 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\OrderRevisions\config\settings.php`.
- V2 routes: `/api/v2/orders/{orderId}/revisions`, `/revert`, `/compare` (`routes.php:593-601`).

## 36. Search ✅

**Why it exists.** Operators need to find an order or customer fast — by ID, phone, name, brand. The
**Search** plugin provides global search, tuned so it's responsive without hammering the database.

**The business payoff:** staff locate records instantly; the operator can balance search power against
performance.

### 36.1 Global search
`allow_search_on_global` switches on the cross-entity search bar
(`@c:\xampp\htdocs\cake\shipping\plugins\Search\config\settings.php:7`). *Business value:* one box to
find anything.

### 36.2 Minimum-character threshold
`min_amount_of_characters` (default 3, "recommended greater than 3") sets how many characters trigger a
search (`settings.php:8`). *Business value:* avoids expensive 1–2 character queries that would scan huge
result sets.

### 36.3 Username/brand search (performance)
Searching by username/brand is powerful but heavier; the threshold above is the main lever to keep it
performant on large datasets. *Business value:* useful search without degrading the app under load.

### 36.4 Reference
- Settings: `@c:\xampp\htdocs\cake\shipping\plugins\Search\config\settings.php` (`Settings.Search`).

## 37. Webhooks ✅

**Why it exists.** External systems (a merchant's store, a partner platform) want to be **told** when
something happens in Shipprex rather than poll for it. **Webhooks** pushes outbound HTTP events on order
changes, with per-user configuration and delivery logging.

**The business payoff:** merchants integrate Shipprex into their own systems in real time; failures are
visible and retryable, so integrations are reliable.

### 37.1 Webhook configuration
Users register webhook endpoints and the events they care about. Each webhook can be activated,
deactivated, and test-fired. *Business value:* self-service, scoped integrations.

### 37.2 Delivery logs
Every delivery attempt is logged (success/failure, payload), with **retry** for failures
(`deliveries`, `logs`, `retry` endpoints). *Business value:* integration problems are diagnosable and
recoverable instead of silently lost.

### 37.3 Per-user webhooks & events
Webhooks are scoped per user and subscribe to a catalog of events (`webhook-events`), so each merchant
gets only the events relevant to them. *Business value:* clean, isolated integrations per customer.

### 37.4 Reference
- V2 routes: `/api/v2/integrations/webhooks/*` and `/webhook-events` (`routes.php:534-553`).
- Plugin: `plugins/Webhooks` (always-on, §2.4). Event basis: the hooks/events system (§4).

## 38. Menus ✅

**Why it exists.** The left-hand navigation must adapt to each role and to which features a tenant has
enabled. **Menus** is the dynamic navigation builder — covered in depth in **§8.3**.

**The business payoff:** role-aware, plugin-extensible navigation that operators edit themselves, with
access enforced at the menu level.

- **Database menus** scoped per group + **hook-registered plugin menus** (`create_new_root_menu` /
  `add_sub_menu_item_to_root`), merged and permission-filtered by `TreelistComponent`, rendered by
  `TreelistHelper`. See **§8.3** for the full treatment.

### Reference
- Full detail and code refs: **§8.3**. Plugin: `plugins/Menus`; menu hooks in `src/Hooks/Hooks.php`.

## 39. CustomTheme ✅

**Why it exists.** Presentation must be brandable and override-able without forking core templates.
**CustomTheme** is the always-on view theme — covered in depth in **§8.2**.

**The business payoff:** the dashboard shell and per-screen overrides live in one swappable layer that
survives upgrades; brand colors are settings (§8.1).

### Reference
- Full detail and code refs: **§8.2** (and theme colors §8.1). Applied via `AppController::beforeRender()`.

## 40. Translation ✅

**Why it exists.** Customer-facing wording must be editable by admins and translatable per market.
**Translation** is the DB-backed message store — covered in depth in **§7.2–§7.3**.

**The business payoff:** admins re-word the UI instantly (no release), across en/ar/fr, with optional
Google auto-translate to pre-fill new terms.

- **DB-backed translations** (admins search-and-replace wording), **language management** for en/ar/fr
  with RTL for Arabic, optional key-gated auto-translate. See **§7**.

### Reference
- Full detail and code refs: **§7.2–§7.5**. Plugin: `plugins/Translation`; `Translations` table.

## 41. UsersManager ✅

**Why it exists.** Identity, roles, and access control for the whole platform live here. The RBAC model
is covered in depth in **§6**; this entry summarizes the plugin's surface.

**The business payoff:** centralized, role-based access plus flexible per-tenant user fields.

- **Users CRUD** — individual logins (the `Auth` login-by-email component, §1.2).
- **Groups** — hierarchical roles owning permissions (group 1 = super-admin, §6.3).
- **Permissions** — granted to groups; checked per request via `UserAuth::checkPremissions()` (§6.2),
  extensible through the `permissions_extander` filter.
- **Profile fields (Pfields)** — custom, optionally group-scoped user fields without schema change (§6.5).

### Reference
- Full detail and code refs: **§6**. Plugin: `plugins/UsersManager`; V2 routes `/api/v2/users`, `/groups`, `/permissions` (`routes.php:85-128`).

## 42. Utils / Media / OrderTracking (support plugins) ✅

**Why it exists.** A set of small, foundational plugins that other features lean on: shared helpers,
file handling, and the public tracking page. Individually minor, collectively load-bearing.

**The business payoff:** common needs (caching helpers, uploads, customer tracking) are solved once and
reused everywhere.

### 42.1 Utils — shared helpers & components
Cross-cutting helper functions and components used across the app (e.g. the `cacheOrFetch` utility
behind menu caching, §8.3). *Business value:* consistent behavior and less duplicated code.

### 42.2 Media handling
File/image storage and retrieval (built on `josegonzalez/cakephp-upload`, §1.1), exposed via the V2
media endpoints (`/api/v2/extensions/media/*`, `routes.php:500-503`). *Business value:* one place for
uploads (comment images, signatures, product images).

### 42.3 OrderTracking — public tracking
A **public, no-login** page where a consignee tracks their parcel by ID/reference + phone — the only
order surface exposed to end customers (§1.2). It's an explicitly public route in the API
(`/api/v2/orders/tracking`, bypasses JWT auth, §2.3) and V1. *Business value:* customers self-serve
"where is my order?" without an account, cutting support load.

### 42.4 Reference
- Plugins: `plugins/Utils`, `plugins/OrderTracking` (always-on, §2.4); media via `cakephp-upload`.
- Public tracking: `JwtAuthMiddleware` public routes (§2.3); `OrdersController::tracking` (§9.7).

---

# PART IV — APIs & Integrations

## 43. API v2 (API-first layer) ✅

**Why it exists.** The V2 API is the **modern face of the entire platform**: a clean, stateless JSON
REST layer that drives the React front-end and lets external systems do anything the web app can. It
exists so the product can evolve its UI independently and integrate with the outside world — over the
*same* database and business rules as the V1 monolith (§2.1).

**The business payoff:** one backend, many clients (web, mobile, partners); a consistent, documented
contract; and security/rate-limiting enforced uniformly.

### 43.1 Auth & JWT
Stateless **JWT** auth (`@c:\xampp\htdocs\cake\shipping\config\routes.php:69-82`): `POST /auth/login`
returns `access_token` + `refresh_token`; `refresh`, `logout`, `GET/PATCH /auth/me`, password reset
(`password/request|verify|reset`) and email verify (`email/request|verify`). The token carries
`{id, email, group_id, role}`, validated by `JwtAuthMiddleware` and injected as the request `identity`
that drives V2 RBAC (§2.3, §6.4). *Business value:* secure, scalable, sessionless access.

### 43.2 Response envelope & error codes
Every response uses a uniform envelope from `AppApiController`
(`@c:\xampp\htdocs\cake\shipping\src\Controller\Api\V2\AppApiController.php`): `success(data, message,
meta)`, `created()`, `error(message, errors, status)`, `notFound()`, `unauthorized()`, and `paginated()`
— so clients parse `{success, data|message, meta}` the same way everywhere. Shared helpers `requireAuth()`
and `isAdmin()` enforce access consistently. *Business value:* predictable integration; clients write
error handling once.

### 43.3–43.9 Domain surface
The API mirrors the whole platform (all in `routes.php`), each domain documented in its own section:
- **Orders** (§9.7) — CRUD, status/workflow, bulk, assignment, returns, tracking (`:132-202`).
- **Users / Groups / Permissions** (§6, §41) — `:85-128`.
- **Financial** — Invoices (§15.5), Transactions (§16.4), Wallet (§23.6) — `:202-265`; plus AdditionalCosts/ledger extensions (§47.5).
- **Logistics** — Pickups (§14.5), Drivers (§13.6), Zones (§12.7), Locations/Areas (§31.4) — `:289-375`.
- **Inventory** (§24.6) — `:425-464`.
- **Reports / Settings / Preferences / Notifications** (§20.4, §3.5, §32) — `:390-419,572-590`.
- **Companies / Delegations / Accountants** (§27, §29, §26) — `:605-631`.
- **Extensions** — UI manifest, tags, meta, comments, media (§45) — `:471-527`.

### 43.10 Health checks
A public `/health` route (no auth) for uptime monitoring (§2.3). *Business value:* load balancers and
monitors can probe liveness.

### 43.11 Reference
- `@c:\xampp\htdocs\cake\shipping\docs\API_V2_README.md` and the Postman collection.
- Routing: `@c:\xampp\htdocs\cake\shipping\config\routes.php:62-635`. Envelope: `AppApiController`.

## 44. API v1 (legacy driver app) ✅

**Why it exists.** The **existing mobile driver app** predates V2 and talks to its own **Apiv1** plugin.
It's kept because real drivers run the shipped app; it's the field client for assignments, status
updates, and pickups.

**The business payoff:** drivers keep working through the app they have, while the rest of the platform
modernizes around them.

### 44.1 Driver app endpoints
`Apiv1` exposes driver-facing controllers — `DriversController`, `OrdersController`, `PickupsController`,
`StatusController`, `TagsController`, plus storefront/`ShopifyController` and `WebhooksController`
(`plugins/Apiv1/src/Controller`). *Business value:* a complete field API for the mobile app.

### 44.2 Auth model
Apiv1 uses its **own** auth (separate from V2's JWT and V1's sessions) and is explicitly exempted from
the standard permission checker (§6.2). *Business value:* the driver app authenticates in its own way
without entangling the web RBAC.

### 44.3 Status-update flows
Driver status changes still funnel into the order **choke point** (`actionUpdates`, §9.3), so app-driven
updates trigger the same SMS, stock, settlement, and audit effects as any other channel. *Business
value:* the field app can't bypass the consequences the business depends on.

### 44.4 Reference
- Plugin: `plugins/Apiv1` (always-on, §2.4). Driver model & permissions: §13. Choke point: §9.3.

## 45. Server-Driven UI (V2 frontend extensibility) ⭐ ✅

**Why it exists.** The biggest architectural bet of V2: the **backend describes the UI** (which form
fields, table columns, and action buttons exist for a given screen and user) and the React front-end
renders it generically. This is what lets a plugin add UI to the new front-end **without front-end code**
— the same "features feel native though they live in plugins" promise as the V1 hooks (§4), reimagined
for an API-first world.

**The business payoff:** enabling a plugin makes its fields/columns/actions appear in the new UI
automatically; forms and tables tailor themselves to *who is looking*; and the front-end stays generic
and stable.

### 45.1 FormConfigService
`getFormConfig(formId, user)` returns the field list, types, and rules for a form — resolved against the
viewer via the settings cascade (§3.5). *Business value:* a merchant and an admin see different
order-form fields from one definition.

### 45.2 TableConfigService
`getTableConfig(tableId, user)` returns the columns (and visibility) for a list/table per user.
*Business value:* each role gets a relevant grid without bespoke screens.

### 45.3 ActionsConfigService
`getActions(context, user, entity)` and `getBulkActions(context, user)` return the buttons/actions a user
may perform in a context — honoring RBAC and entity state. *Business value:* the UI only ever offers
actions the user can actually do.

### 45.4 FieldRegistryService
The registry plugins write into via `config/field_registrations.php` (§5.4) — the primary way a plugin
contributes form fields, table columns, and action buttons to V2. `Apiv2Contributor.php` covers extra
"slot" contributions the declarative config doesn't. *Business value:* a plugin's V2 UI is **data the
backend describes**, discovered automatically.

### 45.5 UiController & manifest slots
The `Ui` controller serves these configs (`/api/v2/ui/forms`, `/ui/tables`, `/ui/actions/{context}`,
`/ui/settings/resolve`, `:379-386`), and the **Extensions manifest** (`/api/v2/extensions/manifest`,
plus `slot-catalog` and `hooks-catalog`, §2.3/PRP-2.12) tells the front-end what plugin slots and hooks
exist — hit on every page load (hence its higher rate-limit bucket). *Business value:* the front-end
boots by asking the backend "what should I render?"

### 45.6 Reference
- Services: `@c:\xampp\htdocs\cake\shipping\src\Service\UI\` (`FormConfigService`, `TableConfigService`, `ActionsConfigService`, `FieldRegistryService`, `SettingsResolverService`).
- Routes: `routes.php:379-386,471-527`. Design: `@c:\xampp\htdocs\cake\shipping\PRPs\version_2\EPIC-2.0-Frontend-Extensibility-Integration.md`.

## 46. Third-Party Integrations ✅

**Why it exists.** Shipprex doesn't do everything itself — it leans on best-of-breed external services
for maps, messaging, email, ERP, and AI. Each is **optional and key-gated**, so a tenant only takes on a
dependency (and its cost) when they want the capability.

**The business payoff:** powerful features (maps, AI pricing, ERP sync) without building them in-house;
and no forced dependencies for customers who don't need them.

| Integration | Used for | Gate / config | Section |
|-------------|----------|---------------|---------|
| **Odoo (FasTrak)** | ERP order sync (in/out) | `odoo_integration_active_state`, URL, token | §30 |
| **Google Maps (Locations)** | Map-based addressing | `locations_activate_google_maps` + API key | §31 |
| **Brevo (Notifictions)** | Transactional email / share order | `enable_order_email_sharing` | §32.2 |
| **SMS provider (Sms)** | Per-status customer SMS | `sms_integration_active_state` + provider creds | §33 |
| **DeepSeek AI (Zones)** | Generate zone alternative names | `DeepSeekService` | §12.5 |

> **Security note:** the DeepSeek API key is currently **hardcoded** in `src/Services/AI/DeepSeekService.php`
> and should be moved to config and rotated (flagged as a follow-up task). Treat integration credentials
> as secrets belonging in `config/app_local.php` / env, not in source.

### 46.6 Reference
- Per-integration detail and settings: §30 (Odoo), §31 (Maps), §32 (Brevo), §33 (SMS), §12.5 (DeepSeek).

---

# PART V — Financial System (cross-cutting) ✅

**Why this part exists.** Money in a COD courier moves through several modules (orders, invoices,
transactions, wallet, ledger), and the single biggest source of bugs and disputes is letting those
figures drift apart. This part is the **map of how money flows end-to-end**, so the pieces documented
separately above are understood as one system.

## 47.1 Money model overview
Four quantities drive everything: **COD** (collected from the consignee), **fees** (the courier's charge
to the merchant), **merchant payout** (COD − fees), and **driver fees** (zone-priced payout to the
courier). Each is set or derived automatically — fees by the zone engine (§12.4), driver fees by
zone-driver rates (§12.3), COD on the order, payout on the invoice (§15.2). *Business value:* the money
math is a function of operational data, not manual entry.

## 47.2 Invoice ↔ Transaction ↔ Wallet ↔ GeneralLedger
The chain of responsibility:
- **Order → Transaction** — `Collected` writes the per-order `in`/`out` ledger rows (§16.1).
- **Transactions → Invoice** — orders attach to the merchant's open invoice; closing nets COD − fees into a payout (§15).
- **Invoice/Transactions → Wallet** — the merchant's self-service balance and withdrawals read the same ledger (§23).
- **All → GeneralLedger** — the company's own books and the Money-Flow dashboard aggregate across merchants, drivers, and banks (§25).

*Business value:* one settled order propagates consistently from line-item to merchant payout to company
liquidity.

## 47.3 Available-cash formula & reconciliation
The reconciliation north-star (§25.3): **Available cash = COD bank balance − pending merchant payouts −
unpaid driver fees**. Maintenance tools simulate-then-apply invoice fixes when figures drift (§21).
*Business value:* the operator always knows true liquidity, and drift is correctable safely.

## 47.4 Multi-currency & multi-branch finance
With **MultiBranches** (§28), currency, inventory, and banks can be scoped per branch, and the system
currency defaults to **EGP** (`SUSTEM_CURRENCY`, §1.4). *Business value:* multi-region operators keep
separate books while head office sees the whole.

## 47.5 Additional costs / discounts / deduction rules
The V2 financial layer extends invoices with **cost types**, **deduction rules**, and **discounts**
(`/api/v2/financial/cost-types`, `/deduction-rules`, invoice `costs`/`deductions`/`applyDiscount`,
`routes.php:221-285`) — the **AdditionalCosts** capability. Note the **AdditionalCosts plugin itself is a
placeholder/WIP** (§5.5; see `PRPs/version_2/ADDITIONALCOSTS-INVESTIGATION.md`); the API hooks exist
ahead of the full plugin. *Business value:* surcharges and discounts on a payout can be modeled per
policy as the feature matures.

---

# PART VI — Cross-Cutting Features ✅

**Why this part exists.** Some capabilities aren't a single module — they touch every order and every
screen. This part gathers those threads.

## 48.1 Printing system
Couriers print **shipping labels** (the parcel sticker) and **invoices**. The print layout is highly
configurable via Orders settings (`@c:\xampp\htdocs\cake\shipping\plugins\Settings\config\settings.php:188-220`):
**paper size** `print_size` (`4x6` label vs `A4`), per-field visibility (`print_show_sender_*`,
`print_show_receiver_*`, `print_show_ship_*`), `print_show_signature`, `print_font_size_number_in_pixels`,
and `order_serial_in_print` (sequential serials on prints). PDFs are produced with **dompdf** (§1.1) and
plugins inject extra print content via hooks (e.g. `orders_print_under_bills_info`, §22.2). *Business
value:* labels and invoices match the operator's stationery and disclosure preferences exactly.

## 48.2 Currency & system defaults
The system currency is `SUSTEM_CURRENCY` *(sic)*, derived from `intl_local_currency` and defaulting to
**EGP** (§1.4, §7.4). The `datetime_field_escape_localizations` toggle keeps date/number handling
predictable (§7.4). *Business value:* money and dates render consistently for the operation's region.

## 48.3 Notifications fabric
Three channels, one event backbone: **in-app** notifications and **email** (Brevo) via Notifictions
(§32), and **SMS** per status via the Sms plugin (§33) — all driven off `Model.UpdatedOrderState` and
other domain events (§4.3). *Business value:* the right people and customers are informed automatically
through whichever channel fits.

## 48.4 Audit & history
Three complementary layers: the per-order **`Actions`** log (§9.4), full-record **`Logger`** audit on
nearly every save (who/what/before-after/URL/IP, §4.3), and optional **OrderRevisions** version history
with rollback (§35). *Business value:* in a cash business, every change is attributable and, where
needed, reversible.

## 48.5 Terms & conditions integration
A Terms integration (`Terms_integration_label`, `settings.php:163`) surfaces terms/acceptance to users.
*Business value:* the operator can present and (where required) capture agreement to their terms.

# PART VII — Operations & Admin Reference ✅

**Why this part exists.** A consolidated index for operators and developers — the catalogs you reach for
when configuring or extending a tenant. (These are pointers; the authoritative definitions live in code.)

## 49.1 Settings catalog
All settings are declared via `easy_new_setting(...)` across `config/settings.php` and each plugin's
`config/settings.php`, grouped into tabs (`Settings.Orders`, `.Zones`, `.Driver`, `.Wallet`, `.Sms`,
`.Themes`, `.Pickup`, …). How-to: `@c:\xampp\htdocs\cake\shipping\config\settings.md` (§3). Read values
with `get_option_value('slug')`.

## 49.2 Permissions catalog
RBAC is group→permission on controller/action, checked by `UserAuth::checkPremissions()` (§6.2), with
group 1 as super-admin (§6.3) and the `permissions_extander` filter for plugins. V2 manages permissions
via `/api/v2/permissions` (§43.4).

## 49.3 Event & hook catalog
Two buses (§4): **hooks** (`do_action`/`apply_filters`, screen/menu extension — see `src/Hooks/README.md`)
and **CakePHP events** (domain reactions — `config/events.php`, `src/Event/*Listener`, plugin
`config/*Events.php`). The keystone event is `Model.UpdatedOrderState` from the order choke point (§9.3).
V2 self-documents slots/hooks via `/extensions/slot-catalog` and `/hooks-catalog` (§45.5).

## 49.4 Database schema reference
Core tables in `src/Model/Table` (orders, zones, drivers, invoices, transactions, actions, …); plugin
tables under each plugin; join tables `zones_users`, `zones_drivers`, `drivers_orders`. Mind the
canonical misspellings (`statues`, `SUSTEM_CURRENCY`, `Notifictions`) and the `DID()/UNDID()` ID offset
(§1.4). Verify live MySQL when migrations may have drifted (§2.5).

## 49.5 Migrations & console commands
Schema via `cakephp/migrations` (Phinx) under `config/Migrations/`; CLI plugins `Bake`/`Migrations` load
in `bootstrapCli()` (§2.4). Backfill/maintenance commands live in `src/Command` and the Maintenance
controller (§21).

---

# PART VII — Operations & Admin Reference
- 49.1 Settings catalog (grouped reference of all `easy_new_setting` keys)
- 49.2 Permissions catalog (controller/action map)
- 49.3 Event & hook catalog
- 49.4 Database schema reference (key tables)
- 49.5 Migrations & console commands (backfill)

---

# Appendix ✅

## A. Glossary
- **COD (Cash on Delivery)** — money the driver collects from the consignee on delivery; the core of the business (§9.1, §47).
- **Zone** — a named geographic area with a delivery price; basis of all pricing (§12).
- **Fees** — what the courier charges the merchant to deliver an order (§12.4, §47.1).
- **Payout** — what the merchant receives: COD − fees (§15.2).
- **Driver fee / cost** — zone-priced payout owed to a driver (§12.3, §13.1).
- **Invoice** — settlement document closing a merchant's collected orders into a payout (§15).
- **Transaction** — a single money ledger row (`in`/`out`) per order (§16).
- **Available cash** — COD bank − pending payouts − unpaid driver fees (§25.3, §47.3).
- **Delegation** — handing an order to another carrier (§29).
- **Partial delivery** — delivering part of an order with partial payment (§22.4).
- **Return / reverse logistics** — sending an order back, via `return_of` + location flip (§17).
- **Pickup** — first-mile collection of orders from a merchant (§14).
- **Meta field** — EAV custom field on an order, no schema change (§22.2).
- **Choke point** — the single function all status writes pass through: `OrdersTable::actionUpdates()` (§9.3).
- **`statues` (sic)** — the intentionally misspelled order-status column (§1.4).
- **`DID()/UNDID()`** — ±8,000,000 display-ID offset (§1.4).
- **V1 / V2** — the server-rendered monolith vs. the API-first JSON layer (§2.1).

## B. Deprecated features
- OrderExtera: **Pickup Time, Has Pickup, Locations** order-extras (moved to Pickups §14 / Locations §31) — §22.5.
- **Payment Method** order-extra — marked deprecated (§22.5).
- Reports: `reports_add_search_for_profit_table` — deprecated (§3 settings).
- Legacy Excel template paths / `$isOldFormat` import format — prefer current template (§19.4).
- `AppController::ShippingStuff()` legacy status list — **not** canonical (§1.4).
- Balance metering (`USE_BALANCE=false`) — dormant (§1.4).

## C. Roadmap / WIP modules
Present in the repo but not yet delivering full functionality (§5.5): **CRM, Rex2, Reporting,
AdditionalCosts** (the last has V2 API hooks ahead of the plugin — §47.5). They represent planned
capabilities that can be switched on once built, without disrupting shipping features.

## D. Related docs index
- **API:** `@c:\xampp\htdocs\cake\shipping\docs\API_V2_README.md`, Postman collection.
- **Feature delivery rule (V1+V2 parity):** `@c:\xampp\htdocs\cake\shipping\docs\FEATURE_DELIVERY_PROMPT.md`.
- **Settings how-to:** `@c:\xampp\htdocs\cake\shipping\config\settings.md`.
- **Hooks guide:** `@c:\xampp\htdocs\cake\shipping\src\Hooks\README.md`.
- **ADRs:** `@c:\xampp\htdocs\cake\shipping\docs\ADR\` (e.g. CakePHP 4→5 migration).
- **PRPs (Project Requirement Prompts):** `@c:\xampp\htdocs\cake\shipping\PRPs\version_2\` (EPIC-2.0 frontend extensibility, PRP-2.x domains, AdditionalCosts investigation).
- **Jira / knowledge base:** `@c:\xampp\htdocs\cake\shipping\docs\jira\`, `@c:\xampp\htdocs\cake\shipping\docs\knowledge-base\`.

---

## Progress Tracker
| # | Section | Status | Detailed doc |
|---|---------|--------|--------------|
| — | Master outline | ✅ Done | this file |
| 1 | System Overview | ✅ Done | inline (§1 above) |
| 2 | Architecture | ✅ Done | inline (§2 above) |
| 3 | Settings Engine | ✅ Done | inline (§3 above) |
| 4 | Hooks & Events System | ✅ Done | inline (§4 above) |
| 5 | Plugin Architecture | ✅ Done | inline (§5 above) |
| 6 | Permissions & RBAC | ✅ Done | inline (§6 above) |
| 7 | Localization & i18n | ✅ Done | inline (§7 above) |
| 8 | Theming & UI Customization | ✅ Done | inline (§8 above) |
| 9 | Orders Engine | ✅ Done | inline (§9 above) |
| 10 | Order Statuses & Workflow | ✅ Done | inline (§10 above) |
| 11 | Order Types | ✅ Done | inline (§11 above) |
| 12 | Zones & Pricing ⭐ | ✅ Done | inline (§12 above) |
| 13 | Drivers | ✅ Done | inline (§13 above) |
| 14 | Pickups | ✅ Done | inline (§14 above) |
| 15 | Invoices | ✅ Done | inline (§15 above) |
| 16 | Transactions | ✅ Done | inline (§16 above) |
| 17 | Returns / Reverse Logistics | ✅ Done | inline (§17 above) |
| 18 | Bulk Operations | ✅ Done | inline (§18 above) |
| 19 | Data Import / Export | ✅ Done | inline (§19 above) |
| 20 | Reports & Dashboards | ✅ Done | inline (§20 above) |
| 21 | Maintenance & Data Recovery | ✅ Done | inline (§21 above) |
| 22 | OrderExtera (super-plugin) | ✅ Done | inline (§22 above) |
| 23 | Wallet | ✅ Done | inline (§23 above) |
| 24 | Stocks (Inventory) | ✅ Done | inline (§24 above) |
| 25 | GeneralLedger | ✅ Done | inline (§25 above) |
| 26 | Accountants | ✅ Done | inline (§26 above) |
| 27 | Companies | ✅ Done | inline (§27 above) |
| 28 | MultiBranches | ✅ Done | inline (§28 above) |
| 29 | Delegations | ✅ Done | inline (§29 above) |
| 30 | FasTrak | ✅ Done | inline (§30 above) |
| 31 | Locations | ✅ Done | inline (§31 above) |
| 32 | Notifictions | ✅ Done | inline (§32 above) |
| 33 | Sms | ✅ Done | inline (§33 above) |
| 34 | Comments | ✅ Done | inline (§34 above) |
| 35 | OrderRevisions | ✅ Done | inline (§35 above) |
| 36 | Search | ✅ Done | inline (§36 above) |
| 37 | Webhooks | ✅ Done | inline (§37 above) |
| 38 | Menus | ✅ Done | inline (§38; depth in §8.3) |
| 39 | CustomTheme | ✅ Done | inline (§39; depth in §8.2) |
| 40 | Translation | ✅ Done | inline (§40; depth in §7) |
| 41 | UsersManager | ✅ Done | inline (§41; depth in §6) |
| 42 | Utils / Media / OrderTracking | ✅ Done | inline (§42 above) |
| 43 | API v2 | ✅ Done | inline (§43 above) |
| 44 | API v1 (driver app) | ✅ Done | inline (§44 above) |
| 45 | Server-Driven UI ⭐ | ✅ Done | inline (§45 above) |
| 46 | Third-Party Integrations | ✅ Done | inline (§46 above) |
| 47–49 | Parts V–VII (cross-cutting/ops) | ✅ Done | inline below |
| App | Appendix A–D | ✅ Done | inline below |
