# Gap Analysis — A&B Hub Frontend Review

This is the Task 1 deliverable: a page-by-page review of the completed
frontend templates, what a professional CRM needs that wasn't visually
obvious from the UI alone, and where each gap is addressed in this
backend foundation.

## Method

Every one of the 30 delivered pages was reviewed for: what data it
displays, what actions a user can take on it, and what that implies
about records, relationships, and permissions that must exist behind
it — even where the frontend (correctly, for a static template) doesn't
show it.

## Gaps found and how Phase 1 addresses them

| Gap | Why it matters | Where it's addressed |
|---|---|---|
| **No password reset flow** | Login page has a "Forgot password?" link with no backend | `password_resets` table, ready for a future `AuthController::sendResetLink()` / `resetPassword()` |
| **No invited-user onboarding flow** | "Invite User" modal creates a user, but they need a way to set their own password | `user_invitations` table (token-based, time-limited) + `users.status = 'invited'` |
| **"Keep me signed in" has no mechanism** | Checkbox exists on login, nothing implements it | `remember_tokens` table (selector/validator pattern — never a raw long-lived session id in a cookie) |
| **No brute-force protection** | Any login form without this is a real production risk | `login_attempts` table + `AuthService::isRateLimited()`, configurable via `config/security.php` |
| **No generic rate limiting for AJAX endpoints** | Table search/filter, notification polling, etc. all hit the server repeatedly | `rate_limit_hits` table + `RateLimiter` service + `RateLimitMiddleware` (`throttle:*`) |
| **"Files" tab on Lead Details is an empty state** | The UI has the tab; nothing stores what goes in it | `attachments` table (polymorphic `entity_type`/`entity_id`) + `UploadHelper` with real MIME sniffing |
| **Labels in Email Inbox ("Homecare Leads" / "Janitorial Leads")** | Implies a general tagging concept, not just email-specific | `tags` + `taggables` tables, usable by any entity later (leads, customers, companies) |
| **Notification toggles have no persistence** | Notifications page renders switches with no state | `notification_preferences` table, opt-out model (absence of a row = enabled), read by `NotificationService::channelEnabled()` |
| **No audit trail for admin actions** | Activity Logs page shows a "Security"/"Admin" category filter | `activity_logs.category` enum (`user_action`/`system`/`security`/`admin`) written by `ActivityLogger::logAction()` |
| **No per-record timeline storage** | Lead Details / Customer Details both render an "Activity Timeline" | `activities` table (polymorphic), written by `ActivityLogger::logEntityActivity()`, read by `ActivityLogger::timelineFor()` |
| **Roles & Permissions matrix has no backing model** | The frontend renders a static checkbox grid | `permissions` + `role_permissions` + `user_permission_overrides`, driven by `config/permissions.php` |
| **"Both Brands" / "Homecare" / "Janitorial" access column in User Management** | Implies users are scoped to one or both business lines | `business_lines` + `user_business_lines` pivot, read via `Auth::businessLineSlugs()` |
| **Settings > Billing tab shows a plan/price/card, unrelated to customer invoicing** | Two different "billing" concepts were being conflated | Split into `billing_accounts` (the Hub's own SaaS subscription) vs. `invoices`/`invoice_items`/`payments` (A&B billing *their* customers) |
| **Integrations page needs real connection state** | Static "Connected" / "Not Connected" badges | `integrations` table with a `status` enum + encrypted credential storage column |
| **API key shown in Integrations ("Live API Key" / Regenerate)** | Needs real generation, hashing, and rotation | `api_keys` table — only a prefix is ever shown again after creation; the full key is hashed at rest |
| **AI Assistant / Voice Agent / Chatbot all need a shared data model** | Three separate-looking features are really one "conversation" concept with a channel flag | Unified `conversations` + `conversation_messages` tables (`channel` = `chatbot` \| `voice_agent`), so Conversation Logs can list both from one query |
| **Service plans / pricing implied by "Weekly Care Plus", "Nightly Commercial" labels** | Customer/company records reference a plan by name only in the UI | `service_plans` and `service_types` tables, seeded with realistic defaults per business line |
| **No search index consideration** | Global search bar exists in the topbar on every page | `FULLTEXT` indexes added on `leads`, `customers`, `companies` name/email columns from day one, not bolted on later |
| **Currency is CAD by default, but the schema must support USD too** | Business requirement stated explicitly for this project | Every money column pairs a `DECIMAL(12,2)` amount with a `CHAR(3) currency DEFAULT 'CAD'`, never a bare float |
| **Multi-tenant-shaped data without being multi-tenant** | Two real companies share one Hub | Every business-owning table carries `business_line_id`; RBAC and future query scoping both key off it — this is *not* the same as multi-tenancy (there's one shared user base, one shared database), but every list query a future controller writes must filter by the user's allowed business lines |

## Explicitly NOT addressed in Phase 1 (by design)

Per the project brief, these remain architecture-only (schema + naming
exist; no controllers, services, or UI logic were built):

- Lead/Customer/Company CRUD business logic
- Appointment scheduling logic (conflict detection, recurring visits)
- Campaign sending logic / email queue worker
- AI Assistant / Chatbot / Voice Agent runtime behavior
- Reports/analytics query logic
- The login page and any other authentication UI

Each of these has a clear landing spot already prepared (see
`docs/ARCHITECTURE.md`, "How a new module plugs in") — Phase 2 should
never need to modify `app/core`, `database/schema.sql`'s existing
tables, or the routing/middleware system to add them.
