# Websockets Migration — Master Event Map & Plan

*Generated 2026-08-02 from a 6-agent code survey of: Central (Filament/controllers, routes, console/jobs/cron), the Magento storefront, the Ship app, the old Phalcon Central, and all five crontabs (goparts, centralgoparts, gopartsb2b, opsgoparts, shipgoparts). File:line evidence for every claim lives in the agent reports; this doc is the consolidated design.*

## 0. What the /orders page polls today (the stale-data surface)

| Poll | Endpoint | Watches |
|---|---|---|
| status-poll | `GET /api/orders/status-updates` | `orders.order_status_id` |
| assignments-poll (5s) | `GET /api/assignments/new` | `order_assignments`, `order_department_assignments` |
| notes | `GET /api/orders/notes/changes` | `notes` (via trigger-fed `order_notes_changes`) |
| autofulfill-states | `POST /api/items/autofulfill-states` | `orders_items.can_auto_fulfill` + DERIVED state |
| needs-order-counts | `GET /api/orders/needs-order-counts` | count of `orders` in status 1 |
| batch-items | `POST /api/orders/batch-items` | items, statuses, tracking, returns, costs, assignments |
| new-orders (5 min) | page re-fetch | new `orders` rows |

## 1. Who writes the data (writer census)

**A. Central Laravel — user actions (~50 sites).** Filament actions/pages, `routes/web.php` closures, API controllers, Livewire modals. Key choke points that many sites already funnel through:
- `checkAndUpdateOrderStatus()` (routes/web.php:25) — order-status rollup used by tracking/status routes and `ShipmentController`.
- `Order::updateStatusBasedOnItems()` (app/Models/Order.php:272) — rollup used by webhooks, fulfillment pages, used-parts autofulfill.
- `OrderObserver::updating()` — fires on every Eloquent order save (but NOT on raw `DB::table()` writes — and many writers are raw).

**B. Central Laravel — background (11 scheduled commands).** `orders:process-tracks`, `shipstation:update-shipments`, `monitoring:sync-tracking`, `usedparts:sync` (every minute; qualifier + processor), `pams:process-orders`, `meyer:sync-tracking`, `ps:process-orders`, `usauto:generate-reports`, `gopartsship:sync-refunds`, `orders:detect-duplicates`. All run in-app → can emit events directly.

**C. Ship app (ship.go-parts.com).** Writes NOTHING directly — every mutation arrives as an HTTP webhook into three Central controllers: `ShipDevWebhookController` (labels/SHIP_NOTIFY), `LtlWebhookController`, `HazmatWebhookController`. Instrument those three and the entire Ship domain is covered.

**D. Magento storefront (`/home/goparts/www`) — writes the DB DIRECTLY (no HTTP channel exists today).**
- `Saleshook::createGopartsOrder()` (Model/Saleshook.php:155) — **the primary retail order creator** (fires on Bolt capture / invoice pay). Also PS-submit status advance.
- `Helper/Data::checkandcreateGopartOrder()` — credit-memo → order create/refresh path; also the admin "Sync to Central" button and a public `mage-scripts/create-order-in-central.php` form.
- `Commentshook` — buyer notes on checkout success.
- Magento cron `goparts_goparts_order_history_sync` — notes import (config-gated).
- Bolt recovery crons (`bolt_payment_update_batch.php` hourly) — inherit Saleshook.

**E. Old Phalcon Central (`opsgoparts`, verified via targeted reads):**
- `LkqtrackingTask` — `orders_items.current_status=6` + tracking (6 write sites) + order rollup.
- `AmazonTask` — dropshipper tracking + item saves (cancel path reverts items to needs-order).
- `RpmWareHelper::createOrder()` — **imports new orders** (hin/racing accounts) every 4h.
- `b2b/createOrders` HTTP endpoint — B2B (source=500) order creation, fed by gopartsb2b cron every 5 min.
- `scheduler main` cron — **dormant**: its `scheduler`/`CronJob` tables don't exist in the DB (verified 2026-08-02).
- Keystone/LKQ + Amazon exact column lists verified; note **neither writes `orders_items_status` audit rows**.

**F. Supplier invoice loggers (goparts cron, ~16 entries)** → `orders_additional_costs` only. Ten share one helper (`scripts/dan/supplier invoices logger/_oac_supplier_credit.php:63`) — one instrumentation point; 4–5 others are direct SQL parsers.

**Unaudited residue (permission-blocked, low risk):** `/home/argoparts` AR-sync commands (comments say read-only on goparts), `/home/oldgoparts` COGS python (likely margin tables only), `/home/goparts/www/agents/`. Verify when convenient — goparts user has ACLs to read most of these.

## 2. Event design (keep it small)

Rather than 30 bespoke events, use **four**, all `ShouldBroadcastNow` (no queue worker exists; QUEUE_CONNECTION=database with no worker — queued events would never send):

| Event | Payload | Emitted when | Client reaction |
|---|---|---|---|
| `OrderCreated` | `{order_id, external_order_id, source}` | any new `orders` row | refresh needs-order counts; show the existing "new orders" banner |
| `OrderDataChanged` | `{order_id, scope[]}` — scope ⊆ {status, items, tracking, notes, assignments, returns, costs, autofulfill} | any mutation of that order's polled data | if the order is on screen: re-fetch just that order via the existing `/api/orders/batch-items` (85 ms warm) and patch the row using the existing updater functions |
| `CountsChanged` | `{}` (coalesced server-side, ≤1/5s) | order enters/leaves status 1 | refresh the Needs-Order badge |
| `SupplierDataRefreshed` | `{supplier_code?}` | supplier imports / inventory sync complete | one-shot re-poll of autofulfill-states for visible items (covers the DERIVED-state problem: imports change computed autofulfill eligibility without touching any order row) |

Channel: `private-orders` (all staff; auth via existing session through `/broadcasting/auth`). Client: Filament's bundled Echo, configured for Reverb.

**Server helper:** `App\Support\OrdersBroadcast::changed(int|array $orderIds, array $scope)` and `::created($orderId)` — try/catch-wrapped, never throws into the calling request; no-ops if Reverb is down.

**Non-Laravel writers** (Magento, Phalcon, invoice loggers) get a tiny authenticated ping endpoint: `POST /api/broadcast/order-changed {key, order_id, scope}` → emits the same events. One shared PHP snippet added at: Saleshook (after commit), `checkandcreateGopartOrder`, `Commentshook`, `OrderHistorySync`, `_oac_supplier_credit.php`, the direct-SQL parsers, and the 3 Phalcon tasks (or, if Phalcon is being retired per Phase-II plan, a DB-outbox shim instead).

**Reconciliation fallback:** keep one slow client poll (every 3–5 min) of status-updates + counts so a dropped socket can never strand stale data. NOTE: a reconciler keyed on `orders.date_updated` will miss storefront-created orders (Saleshook sets only `date_placed`) — key on `order_id > lastSeen` + `last_updated` of items instead.

## 3. Rollout phases

1. ~~Infra~~ **done 2026-08-02**: Reverb 1.x installed, daemon on 127.0.0.1:6001 with cron keepalive; Apache wss proxy config prepared (needs root install); server-side publish verified.
2. **Proof of concept — assignments** (smallest surface: 6 UI write sites, zero background writers): emit `OrderDataChanged{scope:[assignments]}`, consume with existing `updateOrderAssignments()`, run the 5s poll in parallel as checksum, then retire that poll.
3. **Status + items**: instrument the choke points (checkAndUpdateOrderStatus, updateStatusBasedOnItems, the shared `orders_items_status` insert sites, 3 webhook controllers, ItemActionsController) + the 11 background commands. Retire status-poll and shrink batch-items polling.
4. **Notes + counts + autofulfill**: notes sites + Magento ping; CountsChanged; SupplierDataRefreshed from the import commands.
5. **New orders**: ping from Saleshook, retire the 5-min page re-fetch. *(Owner decision 2026-08-02: RPMWare + B2B imports on old Phalcon are DESCOPED — those orders arrive via the reconciliation poll instead.)*

## 4. Bugs found during the survey (fix independently of websockets)

1. **`GET /api/orders/{id}/toggle-radioactive` (web.php:4162) mutates on GET with NO auth/CSRF** — any prefetcher can flip radioactive status. (The live UI actually posts to `public/toggle-radioactive.php`; the two Laravel implementations are dead, one broken — wrong PK.)
2. `mark-no-part` writes `current_status = 4` ("Sent to SS") with comment "Cancelled" — likely wrong status ID (web.php:4980).
3. `MeyerSyncTracking`, `KeystoneImportTracking`, and Phalcon `LkqtrackingTask` set item status 6 **without an `orders_items_status` audit row** — history gaps.
4. `orders_items_status` column drift: two routes insert `user_id`/omit `doer` (web.php:4999, 5041) — possibly silently failing inside try/catch.
5. Dead/dormant code: `api.php` assign/unassign shadowed by web.php duplicates; `AutoFulfillController::autoFulfill/markNoPart` unrouted; `ReturnEntriesModal` unmounted (and skips history writes); `ProcessBulkShipment` job never dispatched, no worker; `RETURN_CREDIT_NOTIFY` handler implemented in Central but Ship never sends it; Magento `signifyd` observer references a class that doesn't exist.
6. Return labels purchased in Ship produce **no** Central write at all (no webhook) — confirm intentional.
7. `web.php:9159` does a global `Cache::flush()` from the yard-attribution endpoint.
8. Storefront sets `date_placed` but never `date_updated` on new orders (breaks `date_updated`-keyed queries).
9. `scripts/seller` models point at `goparts_dev_sellbrite` today but are latent uninstrumented `orders` writers if repointed.
