# CRM and Lead Management module: install notes

Builds on the core package and the Inventory module (vehicles, branches, RBAC, settings, audit log, document sequences).

## 1. Copy files
Copy `app/`, `config/crm.php`, `database/`, `resources/`, `routes/crm.php`, `tests/` into the app.
These files REPLACE earlier versions: `config/navigation.php`, `database/seeders/DatabaseSeeder.php`.

## 2. Wire up
```php
// end of routes/web.php
require __DIR__.'/crm.php';
```
```php
// bootstrap/providers.php  (hooks the vehicle observer and the scheduler)
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\CrmServiceProvider::class,
];
```
```bash
php artisan migrate
php artisan db:seed --class=CrmPermissionSeeder
php artisan test --filter=CrmTest
php artisan queue:work        # wishlist matching runs on the queue (QUEUE_CONNECTION=database recommended)
```
The scheduler entries are registered by the provider; the server still needs the standard cron line:
`* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1`

## 3. Before first use
- **Indemnity wording is a DRAFT** in `config/crm.php`. Have your lawyer and insurer approve it. Change `version` whenever the text changes.
- **Lead rotation** only includes active users with the Sales Executive role and `accepts_leads` on. Set them up before going live,
  otherwise new leads stay unassigned and managers are notified.
- **Public form:** `/enquiry` (optionally `/enquiry?vehicle=STK-NBO-2026-00001`). Link it from your website. It has CSRF, throttling,
  a honeypot and a minimum-fill-time check. If the website lives on another domain, you will want a token-protected API instead (not built).
- **Data Protection:** ID numbers, KRA PINs and licence numbers are encrypted. Search uses keyed hashes (exact match only), so
  rotating `APP_KEY` makes stored values unreadable and breaks searches: back the key up. Reveals are audit-logged.
  Test-drive signatures are stored on the private `local` disk, never served publicly.

## How it fits together
- **Capture:** `LeadService::capture()` is the single entry point (staff form and website form). It finds or creates the customer by phone,
  merges repeat enquiries into the open lead (14-day window), starts the response clock, and assigns an owner.
- **SLA:** deadlines count business minutes only (hours in CRM settings). Staff-taken walk-ins and calls have no deadline.
  `crm:check-leads` (every 5 min) notifies the owner on breach, then the managers after the escalation window, and reminds of due follow-ups.
- **Assignment:** first matching rule by priority (round-robin, fewest open leads, or a named person); fallback is fewest open leads.
- **Pipeline:** New, Contacted, Test drive, Negotiation, Finance, then Won or Lost. Winning sells the vehicle through the inventory
  status service; losing needs a reason and cancels booked test drives. Only lost leads can be reopened.
- **Test drives:** booking checks vehicle and salesperson clashes and moves the lead to Test drive. The customer can sign on their own phone
  through a 48-hour signed link (sent by WhatsApp) or on a staff tablet. Completion requires a signed indemnity and an unexpired licence.
- **Wishlists:** a vehicle entering stock, or an in-stock vehicle's price changing, is matched against active wishlists. The salesperson gets
  an in-app alert with a ready WhatsApp message, once per customer per vehicle.
- **WhatsApp:** click-to-chat links only. WhatsApp opens with text ready and a person taps send. Automatic sending needs the WhatsApp Business API (not built).

## Known gaps
No notification bell UI yet (alerts are stored with `$user->unreadNotifications`), no automatic SMS, no Deals/finance application link,
no Customer documents upload, and the dashboard has no CRM widgets yet.
Not run in the authoring environment (no PHP available): run migrations and tests before relying on it.
