هفار — four systems that read as one, joined by a single fact: the customer’s phone number. A storefront, a warehouse ERP, a call-center reconciliation hub, and an AI tool server, all resolving the same identity across three MySQL pools and one webhook queue. Here’s how they connect — then switch between the four systems below.
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
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.
seam
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.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
| Machine | Journey | The hard part |
|---|---|---|
| Replacement | 7 states: Confirm → Prepare → Dispatch → Receive → Validate | Reserve at confirm, commit at dispatch, receive at validation — three different stock operations in one flow |
| Maintenance | 6 states + 3 internal sub-states (Arabic) | Status stays IN_PROCESS while three internal actions happen — tracked via history counting, not status flags |
| Return | 4 states — the shortest path | Clean by design: receive, validate condition, done |
| Sell | 6 states — products reference-only, parts reserved | Products 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
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