# TechTitans POS — Customer CRM Phase C2 Implementation

**Date:** 2026-11-12 · **Branch:** `techtitans/integration` (backend + frontend) · Local commits only (no push/deploy).
**Builds on:** the completed legacy customer audit (`backend/docs/techtitans/customer-legacy/01–06`). One shared Customer CRM — no second customer store, no rebuild of working modules.

**Guardrails honoured:** no legacy migration, no oldportal PHP executed, no legacy attachments imported, NH POS untouched, no existing customer data wiped/replaced, nothing pushed. The one schema change is purely additive (a nullable, indexed, backfilled column).

## 1. Smarter customer search
`CustomerService::applyTermSearch()` now matches, in one parameter-bound query:
- **Name / email / phone** (existing, escaped LIKE).
- **Normalized phone** — the term is reduced to digits and matched against the new `customers.normalized_phone`, so `(416) 555-1234`, `416-555-1234` and `+1 416 555 1234` all find the same record (and `555-1234` partial-matches).
- **Customer reference** — an exact `id` lookup (the stable, human-quotable reference shown on the profile).
- **IPTV MC ID / user login** — a bounded `EXISTS` on the tenant-unique `(tenant_id, account_identifier)` index, **only** when the caller holds `iptv.view` (resolved server-side in the controller via `searchFilters()`), so MC-ID matching never leaks IPTV linkage to a customers-only user.

Pagination, sort whitelist, tenant scope and `payments.view` financial gating are unchanged. Both `GET /customers` (picker) and `GET /customers-directory` benefit. The list search box now advertises *"Name, email, phone, reference or IPTV MC ID"*.

## 2. Phone normalization
- New migration `2026_11_12_130000_add_normalized_phone_to_customers_table` adds an **app-maintained** `normalized_phone` (nullable, indexed `(tenant_id, normalized_phone)`) and **backfills** every existing row.
- `PhoneNumber::normalizeDigits()` (on the existing libphonenumber value object — the single source of truth) returns the **E.164 digits** for the default region (CA) when the number parses, else the raw digits, so formatting variations collapse to one match key.
- The `Customer::setPhoneAttribute()` mutator derives `normalized_phone` on every write path (create / update / find-or-create / order resolution). **The original `phone` is never overwritten** — display formatting is preserved; only the search key is derived.

## 3. Safe duplicate suggestions
- `CustomerService::duplicateSuggestions(name, email, phone)` returns up to 6 candidates matched by **normalized email (exact)**, **normalized phone (exact)**, or **name LIKE** — non-guests, bounded, ordered by name. It **never merges or mutates** anything.
- `GET /customers/duplicate-suggestions` (`customers.view`) returns a **masked** projection via `CustomerResource::suggestion()`: display name, type, status, `email_masked` (`g•••@n•••.mil`), `phone_masked` (`•••-•••-9999`), and `matched_on` (email > phone > name) — **never raw PII**. The full record is fetched through the normal `show()` endpoint once selected.
- **Frontend:** the shared `CustomerFormDrawer` (used by Customers, IPTV and Repairs) now debounces this lookup as staff type (≥2-char name, an `@` email, or ≥4-digit phone), shows *"Possible existing customers"* with a **Use** button, and on select fetches the full customer and hands it to the caller — avoiding a duplicate. **POS** keeps its by-design server-side resolve-or-create snapshot.
- **Shared family/business numbers stay separate:** a shared phone yields several suggestions, never an automatic combine (covered by a test).

## 4. Unified Customer Overview
`CustomerOverviewPanel` replaces the old two-card overview with a composed 360° screen, **reusing the existing module APIs** — it never recomputes money:
- **Service summary cards** (soft-coloured): Orders (count + last order, from the authoritative customer stats), Repairs (open tickets), IPTV (active connections), Outstanding (from `CustomerFinancialService`, coral when owed / emerald when settled).
- **Identity & contact** (incl. the customer **reference**) + notes.
- **Quick actions**: New order / New repair / New IPTV — each permission-gated, deep-linking the module with `customer_id` prefilled.
- **Recent activity** timeline: a chronological merge of the customer's **real** recent orders, payments, repair tickets and IPTV connections (real timestamps from each ledger — nothing fabricated), each linking to its detail. Sections only appear — and only fetch — for modules the user may view.

## 5. Security
- Every new path is tenant-scoped (`BelongsToTenant` + composite FKs) and permission-gated: directory/suggestions `customers.view`; MC-ID search `iptv.view`; money `payments.view`; module panels `orders/repairs/iptv.view`.
- Suggestions expose only masked contact; no passwords, encrypted IPTV credentials or attachments are ever surfaced.
- Duplicate detection surfaces candidates only — **no auto-merge**, preserving legitimate shared contacts.

## 6. Testing

### Backend (`php artisan test --filter=Customer`) → **106 passed**
New `CustomerSearchC2Test` (7): phone search matches formatting variations; `normalized_phone` derived while display preserved; reference (id) search; **MC-ID search gated by `iptv.view`**; duplicate suggestions match email/phone/name with masking + `matched_on`; **shared phone → multiple suggestions, never merged**; suggestions require `customers.view` + tenant isolation. All existing customer tests still green.
- **Pint** clean; **PHPStan** clean on every changed file (`CustomerService`, `CustomerResource`, `Customer`, `PhoneNumber`, `CustomerController`).

### Frontend → **60 customers+iptv tests passed**
- New `CustomerFormDrawer.test.tsx` (2): suggestions appear as staff type and *Use* selects the existing record (no `create`); single-char input is below the threshold and a normal create still works.
- Updated `CustomerProfilePage.test.tsx` for the new Overview (service cards, Identity & contact, Recent activity; module ledgers stubbed).
- `tsc -b`, `eslint`, `npm run build` all clean (pre-existing bundle-size advisory only).

### Live Playwright (real dev UI, techtitans tenant)
- Customer **Overview**: four service cards (Orders 5 · Repairs 0 · IPTV 0 · Outstanding CA$181.18), Identity & contact with reference, Quick actions, Recent activity — **no horizontal overflow** at 1440; **mobile 390** no overflow.
- **Duplicate suggestions**: typing "Dinesh" in the New-customer drawer surfaces the masked existing customer (`d•••@v•••.com`, *Similar name*, **Use**).
- **Smarter search** placeholder present on the list.
- **0 console errors.** Screenshots: `screenshots/customer-crm-c2/c2-{overview,overview-mobile,duplicate-suggestions,list-search}.png`.

## 7. Files changed
- **Backend:** `database/migrations/2026_11_12_130000_add_normalized_phone_to_customers_table.php` (new), `app/Support/Phone/PhoneNumber.php` (`normalizeDigits`), `app/Models/Customer.php` (phone mutator + guard), `app/Customers/CustomerService.php` (search + `duplicateSuggestions`), `app/Customers/CustomerResource.php` (`suggestion` + masking), `app/Http/Controllers/Api/V1/Customers/CustomerController.php` (`suggestions`, `searchFilters`), `routes/api.php` (suggestions route), `tests/Feature/Customers/CustomerSearchC2Test.php` (new).
- **Frontend:** `src/lib/api/customers.ts` (`CustomerSuggestion` + `duplicateSuggestions`), `src/pages/customers/CustomerFormDrawer.tsx` (suggestions), `src/pages/customers/CustomerOverviewPanel.tsx` (new), `src/pages/customers/CustomerProfilePage.tsx` (Overview wiring), `src/pages/customers/CustomersListPage.tsx` (search hint), `src/pages/customers/customers.css` (C2 styles), `CustomerFormDrawer.test.tsx` (new) + `CustomerProfilePage.test.tsx` (updated).

## 8. Remaining gaps (next increments)
- **Server-side audited activity endpoint** — the timeline is currently composed client-side from the module ledgers; a single paginated `GET /customers/{id}/activity` merging the append-only audit log would add customer create/update events and deeper history.
- **Customer-wide outstanding across modules** — IPTV/repair financials are not yet folded into the one customer balance (orders+payments only).
- **Merge-review tool** — suggestions avoid duplicates at creation; a `customers.manage` review to reconcile pre-existing duplicates is still open.
- **Communications channel (SMS/consent)** and the **Mobile-services** section await their modules.
- **Legacy data migration** remains design-only (`customer-legacy/05-MIGRATION-PLAN.md`).

## 9. Commits (local only — no push)
- **Backend:** this commit — C2 search + phone normalization + duplicate suggestions + tests + this report + screenshots.
- **Frontend:** `bc2e632` — duplicate-suggestions drawer + unified Overview + search hint + tests.
