HVAR

One organism, four systems: a phone-OTP storefront wired live into a Laravel ERP, a reconciliation hub running four state machines across shipping and accounting, and an MCP server exposing all of it as 101 AI tools — bound together by one identity key, the customer's phone number.

Four systems, one database, one identity key. Solid edges are code-verified; the dashed one is documented but the repo was never opened.

Storefront

phone-OTP commerce

The seam that’s most of the work

HVAR Portal is the storefront and admin console behind hvarstore.com — Arabic-first, RTL, one codebase serving both customers and the team running /admin. Stock and product truth live in an external ERP the storefront doesn’t own: Portal reads that ERP live, layers its own pricing and content on top, and pushes every order back out through a durable webhook queue — 8 retries, exponential backoff. Three MySQL pools, deliberately unmixed: the site DB owns everything the storefront writes, the ERP DB is read-only truth for what the warehouse holds, joined at query time with no foreign keys across the boundary.

The honest specifics

Installment plans (ValU, Souhoola, Aman) · Kashier card payments · Bosta delivery · passwordless phone-OTP login · a webhook-outbox monitor in /admin for catching stuck ERP syncs before a customer notices. Repair and warranty tickets sync straight into the same call-center queue Hub runs — one customer record, not three systems pretending to agree. No automated test suite or CI yet: verification today is bun run lint plus hands-on flows against the live site.

Customers

Phone-OTP passwordless auth, optional Facebook enrichment — browse, buy, track, open service tickets. Arabic-first RTL throughout.

pos/README.md:49,163-165

Admin / Owner

Gated by ADMIN_PHONES / OWNER_PHONES comma-separated env allowlists — no DB-backed role table, literally a phone allowlist.

admin.ts:28,940,969-971

ERP bridge

Laravel, two-way

The system of record

Sales, purchases, multi-location inventory, and accounting for HVAR’s warehouse — the source of truth every other HVAR system treats as read-only. Portal reads its stock and product data live; Hub reconciles it against Bosta shipping and the customer on the phone.

What shipped on it

OrderSyncService resolves Arabic shipping addresses down to governorate and district IDs — schema-aware column detection across variant spreadsheet formats, not a fixed mapping — then auto-creates the Bosta shipment from that resolved address. That’s the piece that lets a website order become a tracked shipment without someone re-typing an address by hand.

seamStock truth is a hard rule: only variation_location_details.qty_available is authoritative. A legacy product_stocks table, synced daily and up to 48h stale, is treated as if it doesn’t exist for new code.

Ops hub

four state machines

The gap

ERP knows the order. Bosta knows the tracking. The customer knows what they need. Nobody was connecting all three. When a customer called saying “it’s broken” or “I never got it,” the agent had no unified view — three separate realities, zero reconciliation.

The bridge

HVAR Hub is the reconciliation layer — a three-way engine connecting ERP (Laravel), Bosta (shipping API), and every customer interaction into one organism. The system has two hearts:

  • Call Center — ERP sell orders auto-enriched with Bosta tracking data. Agent calls, confirms, ticket created, leader approves, Hub takes over.
  • Hub — four parallel state machines governing the entire post-sale journey. Every state transition touches three inventory dimensions atomically inside a single MySQL transaction.

Four machines, one organism

MachineJourneyThe hard part
Replacement7 states: Confirm → Prepare → Dispatch → Receive → ValidateReserve at confirm, commit at dispatch, receive at validation — three different stock operations in one flow
Maintenance6 states + 3 internal sub-states (Arabic)Status stays IN_PROCESS while three internal actions happen — tracked via history counting, not status flags
Return4 states — the shortest pathClean by design: receive, validate condition, done
Sell6 states — products reference-only, parts reservedProducts don’t touch inventory, only parts — different rules per item type

56 state transitions. All atomic. Zero tolerance for inconsistency.

Why this is different

This isn’t a CRUD app — every bug here has a real consequence: a customer doesn’t get their money back, inventory counts diverge from reality, a package ships to the wrong address. The machines are interlocked — confirming a replacement must reserve stock, dispatching must commit, receiving must validate condition. One broken link and the chain corrupts.

Without Hub, the business has accounting software and a shipping API. With it, it has a nervous system.

By the numbers

  • 79,000+ lines of production code, 126+ commits over 7 months
  • 40+ API endpoints, 4 parallel state machines, 56 atomic transitions
  • Arabic-first RTL throughout — not translated, built that way from line one

Call-center agents

Work the queue: ERP sell orders auto-enriched with Bosta tracking, confirm with the customer, create the ticket.

compass/products/mcrm.md:29-36 — documented, MCRM repo not directly inspected (medium confidence)

Team leaders

Approval gate between a confirmed ticket and Hub taking over execution.

compass/products/mcrm.md:29-36 — medium confidence

Hub technicians

Execute the four ticket types — Replacement, Maintenance, Return, Sell (HVR/HVM/HVT/HVS) — through their state machines.

compass/products/mcrm.md:29-36 — medium confidence

MCP server

101 tools

Why this exists

Hub and Portal both run on real-time delivery and service data — but that data lived behind two separate APIs, each needing its own client code to query. HVAR MCP puts a single Model Context Protocol server in front of both: 29 tools over Bosta (tracking, analytics, wallet, pickups, pricing) and 72 over MCRM (customers, tickets, stock, call center, ERP bridge), namespaced (bosta_*, mcrm_*) so there’s zero collision between them. Any MCP-compatible client — Claude Desktop, an agent framework, anything speaking the protocol — wires in with one config block and gets all 101 tools immediately.

Writes are opt-in by default

Every mutation tool — create, update, delete — stays locked behind HVAR_WRITES=true. Read access is the default because most of what an AI agent needs from a business’s operational data is visibility: tracking status, customer history, stock levels — not the ability to change it.

AI agents / MCP clients

Claude Desktop or any MCP-speaking agent framework — one config block, 101 namespaced tools, read access by default.

hvar-mcp README.md:3,44-51