garage-erp/Reparee Full Version .md
humam kerdiah 3c55be5052 feat: add payroll, time clocks, and leave request modules
- Implemented PayrollPage for managing payroll runs with CRUD operations.
- Created TimeClocksPage for tracking employee clock-ins and clock-outs.
- Developed TimeSheetsPage for displaying employee timesheets.
- Added EmployeeCertificationForm and EmployeeDocumentForm for managing employee certifications and documents.
- Introduced LeaveRequestForm for submitting and managing leave requests.
- Added usePermissions hook for UI-side permission checks.
- Created LeaveRequestsClient and PayrollClient for API interactions.
2026-05-21 13:30:06 +04:00

552 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Reparee → Garage Box Feature Parity — Implementation Roadmap
> **Purpose of this doc.** Single source of truth for closing the gap between Reparee's current dashboard and Garage Box. Captures the gap analysis, the canonical Reparee patterns to mimic, and a per-phase implementation spec detailed enough for a fresh agent to pick up any phase and ship it without re-discovering conventions.
> **Status (2026-05-21).** Phase 1 (Integrations placeholder) is the only phase ready to execute. Phases 28 are spec'd here but not started. Pick one, re-open it in a focused planning session, and implement.
---
## 0. Reading order
1. **Part A — Gap analysis** (what's missing and why).
2. **Part B — Project conventions cheatsheet** (the patterns every phase MUST follow).
3. **Part C — Phase index** (the at-a-glance table).
4. **Part D — Per-phase implementation specs** (Phases 18, in recommended build order).
---
## Part A — Gap analysis (Garage Box vs Reparee)
Legend: ✅ exists ⚠️ partial / different location ❌ missing
### Already covered
- **Sales** (Customers, Vehicles, Inspections, Estimates, Job Cards, Invoices, Payments Received, Credit Notes) — ✅
- **Purchases** (Vendors, Expenses, POs, Bills, Payments Made, Vendor Credits) — ✅
- **Employees / Productivity** (Employees, Time Clocks, Time Sheets, Payroll, Shop Calendars, Shop Timing, Holidays, Tasks, Leave Requests) — ✅ (Reparee adds Leave Requests beyond Garage Box)
- **Items** (Services, Parts, Expense Item, Service Group, Inventory Adjustments) — ✅
- **Settings** Company / Shop Types / Tax & Rates / Configurations — ✅
### Gaps
| Garage Box | Status in Reparee | Phase |
|---|---|---|
| Settings → Integrations | ❌ → placeholder | **Phase 1** |
| Settings → Master (hub for body types + make & model + other lookups) | ❌ as a hub (data scattered) | **Phase 2** |
| Settings → Templates (notification template library, ~35 templates × 4 channels) | ⚠️ only inspection templates | **Phase 3** |
| Reports (top-level module) | ❌ | **Phase 4** |
| CRM (Leads pipeline, Calls log + calendar, Tasks pipeline) | ❌ (Reparee has only productivity tasks, not CRM tasks) | **Phase 5** |
| Marketing (Service Reminders, Rating & Reviews, Google Business Reviews) | ❌ | **Phase 6** |
| Settings → Integrations (real connectors: Google, Microsoft, Zoho, Interakt, Respond, Vonage, Geidea, MSG91, Wati, Xero, WhatsApp) | ❌ | **Phase 7** |
| Accountants (Chart of Accounts + Manual Journals + default account wiring) | ❌ | **Phase 8** |
### Cross-cutting Garage Box conventions to mimic
- **Pipelines** (Leads, Tasks) have user-editable column **sections** (add / rename / reorder). Default sections seeded on first run.
- **`+ Label`** chip on every record (taxonomy / colored tags).
- **Configurable doc-number sequences** with a gear icon next to the `#` field (`LD-000001`, `CL-000001`, `TK-000001`, `JN-000001`, etc.).
- **Notification templates** are channel-aware (SMS / WhatsApp / Email / Push) with per-channel toggles.
- **Per-row Department filter** is universal — every list view has a Department dropdown in the toolbar.
---
## Part B — Project conventions cheatsheet (MUST follow in every phase)
> Every phase below assumes these patterns. Don't re-invent.
### B1. Frontend module structure
- **Modules live at** `garage-erp/apps/dashboard/modules/<area>/`.
- Each resource gets: `<resource>-form.tsx`, `<resource>.schema.ts` (Zod), optional `<resource>-actions.tsx`, optional inline section components (e.g. `<resource>-services-section.tsx`).
- **Reference**: `garage-erp/apps/dashboard/modules/estimates/` is the canonical example. Mirror its file layout for any new resource.
### B2. Routes (Next.js App Router)
- List page: `app/(authenticated)/<area>/<resource>/page.tsx` (`"use client"` + `ResourcePage<Client>`).
- Detail page: `app/(authenticated)/<area>/<resource>/[id]/page.tsx` (async server component, uses `getServerApi()` + `DashboardPage`).
- Sub-tabs of a detail (e.g. notes, documents): `[id]/<subroute>/page.tsx`.
- **Reference**: `app/(authenticated)/sales/estimates/page.tsx` (list) + `[id]/page.tsx` (detail).
### B3. Tables & row actions
- Use TanStack Table via `ResourcePage` wrapper.
- Row actions via `actionsColumn({ extraItems: (row) => [...] })`.
- Print buttons via `useDocumentPrint(type, id)` from `apps/dashboard/shared/hooks/use-document-print.ts`.
### B4. Forms
- React Hook Form 7 + Zod 4. Schema lives in `<resource>.schema.ts`.
- Use `useResourceForm<FormValues, ApiPayload>({ schema, resourceId, initialize, mapToFormValues })` from `apps/dashboard/shared/hooks/`.
- Mutations: `useFormMutation` for create/update; invalidate via `tableQuery.invalidateQuery()` or the resource's query key.
### B5. Sidebar navigation
- Single source: `garage-erp/apps/dashboard/config/navGroups.tsx` — hard-coded, **no i18n layer**.
- Add a new group entry or a sub-item; pick a `lucide-react` icon already imported (or add to the import block at top of file).
### B6. API client (`@garage/api`)
- All clients extend `CrudClient<INDEX_ROUTE, BY_ID_ROUTE>` (path: `packages/api/src/clients/<name>.ts`).
- After backend changes, **regenerate**: `pnpm --filter @garage/api generate` (runs `generate:openapi && generate:types`).
- The generated files in `packages/api/` are NOT hand-edited; only the thin client wrappers are.
### B7. Backend routes
- **Legacy string controller resolution** in `reparee_backend/routes/api.php`:
```php
Route::get('/leads', 'LeadController@index');
Route::post('/leads', 'LeadController@store');
// etc.
```
- **Every route is gated** by `permission.check` middleware (auto-applied group-wide).
### B8. Backend controllers
- Standard CRUD shape: `index`, `show`, `store`, `update`, `destroy` returning `JsonResponse`.
- Path: `reparee_backend/app/Http/Controllers/Api/<Name>Controller.php`.
- Use `vyuldashev/laravel-openapi` annotations so OpenAPI spec stays in sync (mirror `EstimateController`).
### B9. Models & migrations
- Models in `reparee_backend/app/Models/`. Use `$fillable`, `$casts`, relations.
- Migrations: `php artisan make:migration create_<table>_table` → standard `Schema::create` with `$table->id()` + `timestamps()`.
### B10. Permissions
- Permissions are **columns on the `roles` table**: `can_{view|create|update|delete}_{resource}`.
- To add a new resource:
1. Create a migration that adds the four boolean columns to `roles` (default `false`).
2. Update `database/seeders/RoleSeeder.php` so Super Admin gets `true` for all new columns (it auto-discovers `can_*` columns — verify).
3. The `permission.check` middleware reads the column name from the route name; ensure route is named `<resource>.<ability>` or matches its existing convention.
- **Reference migration**: `2026_04_06_120000_create_roles_table_and_refactor_employee_permissions.php`.
### B11. i18n (backend)
- Pairs required: every new key in `lang/en/<file>.php` MUST have the matching `lang/ar/<file>.php` key in the same change.
- Use `__('api.<area>.<key>')` in controllers and responses.
- Dashboard nav labels are NOT translated (hard-coded in `navGroups.tsx`).
### B12. Document-print (PDF)
- Single entry: `POST /api/document-print` → `DocumentPrintController@handle`.
- To add a printable type:
1. Add string to `DocumentPrintController::TYPES`.
2. Add `payload<Type>()` method on the controller.
3. Add `resources/views/pdf/document-print-<type>.blade.php`.
4. Add the type to the `DocumentPrintType` union in `packages/api/src/clients/document-print.ts`.
### B13. Pipeline-with-sections precedent
- `TaskSection` model + `task-section-form.tsx` already exist. Fields: `title`, `arrangement` (sort order), `is_default`.
- Reuse this pattern for `LeadSection`, `CallSection` (if needed), `JournalSection`, etc.
### B14. House rules (from `CLAUDE.md`)
- Never `git commit / push / tag / reset --hard / rebase` — stop at file edits.
- Don't add new dependencies without explicit approval.
- After edits, run `pnpm --filter dashboard typecheck`, `pnpm --filter dashboard lint`, and `php -l` on changed PHP files, then report results.
- Match existing conventions before introducing new patterns.
---
## Part C — Phase index
| # | Phase | Effort | Backend changes | Frontend changes | Depends on |
|---|---|---|---|---|---|
| 1 | Integrations placeholder | XS (1 hr) | none | 2 files | — |
| 2 | Settings → Master hub | S (1 day) | none (regroup) | 1 layout + tabs | — |
| 3 | Notification Templates library | M (3 days) | new table + CRUD + permission | full module | — |
| 4 | Reports (4 reports locked) | M (1 wk) | 4 aggregation endpoints | reports module + 4 reports w/ Recharts | reads existing data |
| 5 | CRM (Leads, Calls, Tasks-CRM) | XL (23 wks) | 3 resources + sections + permissions + i18n | 3 modules + pipelines | reuses TaskSection pattern |
| 6 | Marketing (Reminders email+SMS, Reviews) | L (12 wks) | reminder engine + Twilio integration + reviews | 3 sub-pages | needs Phase 3 (templates); Twilio composer dep |
| 7 | ~~Integrations (real)~~ | DEFERRED | — | — | — |
| 8A | Accountants — COA + Manual Journals | L (1.5 wks) | ledger UI only, no auto-post | 3 sub-pages | — |
| 8B | Accountants — auto-posting | L (12 wks) | observers on Invoice/Bill/Payment/Expense + reversal | linkage panels on source docs | requires 8A stable |
---
## Part D — Per-phase implementation specs
# Phase 1 — Settings → Integrations (Coming Soon)
**Goal.** Add an Integrations entry under Settings that renders a "Coming soon" placeholder.
### Backend
None.
### Frontend
1. **Edit** `garage-erp/apps/dashboard/config/navGroups.tsx` (around line 183):
- There is already a commented-out entry:
```tsx
// { title: "Integrations", href: "/settings/integrations/providers", icon: <PlugZapIcon /> }
```
- Uncomment it but change `href` to `/settings/integrations` (single page, no sub-tabs yet).
- Insert between `Inspection Templates` and `Configurations`. Confirm `PlugZapIcon` is in the top-of-file imports; if not, add `PlugZapIcon` to the `lucide-react` import.
2. **Create** `garage-erp/apps/dashboard/app/(authenticated)/settings/integrations/page.tsx`:
- Use `DashboardPage` with `headerProps={{ title: "Integrations" }}` (match `settings/insurance-types/page.tsx`).
- Centered `Empty` block (from `@/shared/components/ui/empty`):
- `EmptyMedia`: `<PlugZapIcon className="size-12 text-muted-foreground" />`
- `EmptyTitle`: "Coming soon"
- `EmptyDescription`: "Connect WhatsApp, payments, accounting and email providers. Available in an upcoming release."
### Verification
1. `pnpm --filter dashboard dev`.
2. Sidebar → Settings expands → new "Integrations" entry visible between Inspection Templates and Configurations.
3. Click → page renders with the Empty state.
4. `pnpm --filter dashboard typecheck && pnpm --filter dashboard lint`.
### Out of scope
Sub-tabs (Providers / Integrations), any provider logic, backend.
---
# Phase 2 — Settings → Master hub
**Goal.** Group existing lookup data screens under one "Master" sidebar entry with tabs, matching Garage Box's UX.
### Tabs (in order)
1. Body Types (existing `/settings/...`)
2. Make & Model (existing `/settings/make-and-models`)
3. Insurance Types (existing `/settings/insurance-types`)
4. Departments (existing `/settings/departments`)
5. Shop Types (move from top-level OR alias)
6. Vehicle Transmissions, Fuel Types, Colors, Unit Types, Payment Terms, Payment Modes, Labor Rates, Reasons, Referral Sources, Document Types, Labels — surface those that currently lack a dedicated screen.
### Backend
None new. Verify every tab's data source has a controller in `app/Http/Controllers/Api/` (most already do: `MakeAndModelController`, `InsuranceTypeController`, `DepartmentController`, `VehicleBodyTypeController`, `VehicleTransmissionController`, `VehicleFuelTypeController`, `VehicleColorController`, `UnitTypeController`, `PaymentTermController`, `PaymentModeController`, `LaborRateController`, `ReasonController`, `ReferralSourceController`, `DocumentTypeController`, `LabelController`).
### Frontend
1. **Create** `app/(authenticated)/settings/master/layout.tsx` with a `Tabs` header (use existing `Tabs` from `packages/ui`) and the 11+ tab labels above.
2. **Create** `app/(authenticated)/settings/master/page.tsx` that redirects to `./body-types`.
3. **Create** `app/(authenticated)/settings/master/<each-tab>/page.tsx`. Each is a thin wrapper that imports and renders the existing module list component from `modules/settings/<existing-folder>/`.
4. **Edit** `config/navGroups.tsx`: add `{ title: "Master", href: "/settings/master", icon: <DatabaseIcon /> }` after Integrations. **Keep** the existing top-level entries (Insurance Types, Departments, Make & Models) in the Settings list — they coexist as aliases for backwards-compat (per Part F decision).
### Verification
1. Sidebar → Settings → Master → tabs render and each tab CRUDs correctly.
2. Direct URL `/settings/master/insurance-types` works.
3. Removed/aliased entries still resolve to the same screen.
---
# Phase 3 — Notification Templates library
**Goal.** Manage ~35 notification templates (per Garage Box screenshot 16) with per-channel toggles and bodies. Required for Phase 6 (Marketing) and any reminder/automation.
### Data model
| Table | Columns |
|---|---|
| `notification_templates` | `id`, `key` (unique e.g. `appointment_created`), `title`, `description` (nullable), `created_at`, `updated_at` |
| `notification_template_channels` | `id`, `notification_template_id` (FK), `channel` enum(`sms`,`whatsapp`,`email`,`push`), `enabled` bool, `subject` (nullable, for email), `body` text, `created_at`, `updated_at` |
Seed all template `key`s from this list (extracted from Garage Box image 16):
- `appointment_assigned`, `appointment_cancelled`, `appointment_confirmed`, `appointment_create`, `appointment_no_shows`, `appointment_reminder`, `appointment_requested`, `appointment_reschedule`, `appointment_unconfirmed`
- `attendance_details`, `attendance_reminder`
- `call_assigned`, `call_cancelled`, `call_completed`, `call_rescheduled`, `call_scheduled`
- `estimate_approved_by_admin`, `estimate_approved_by_customer`, `estimate_send`
- `job_card_customer`, `job_card_technician`
- `login_otp`
- `new_lead_notification`
- `part_requisition_create`
- `purchase_order_create`
- `ratings_reviews_link`
- `salary_change_notification`
- `send_customer_statement`, `send_inspection`, `send_invoice`, `send_vendor_statement`
- `service_reminder_cancelled`, `service_reminder_reschedule`, `service_reminder_scheduled`, `service_reminder_send`
- `work_request_form_send`, `workorder_create`
### Backend
- `NotificationTemplateController` (`index`, `show`, `update` only — templates are seeded, not user-created).
- Routes (string-resolution style):
```php
Route::get('/notification-templates', 'NotificationTemplateController@index');
Route::get('/notification-templates/{key}', 'NotificationTemplateController@show');
Route::put('/notification-templates/{key}', 'NotificationTemplateController@update');
```
- Permissions: `can_view_notification_templates`, `can_update_notification_templates` (skip create/delete — seed-managed).
- Lang keys in `lang/en/api.php` + `lang/ar/api.php` under `notification_templates`.
### Frontend
1. **Module** `apps/dashboard/modules/notification-templates/` with `notification-template-row.tsx`, `notification-template-edit-dialog.tsx`, `notification-template.schema.ts`.
2. **Route** `app/(authenticated)/settings/templates/notifications/page.tsx` — TanStack Table with columns: Title, SMS (✓/—), WhatsApp, Email, Push, Actions.
3. **Sidebar entry**: `config/navGroups.tsx` add `{ title: "Templates", href: "/settings/templates/notifications", icon: <FileTextIcon /> }` between Inspection Templates and Integrations. (Or restructure existing `/settings/templates` if free.)
4. **Edit dialog**: tabs per channel, body editor with **variable picker** (e.g. `{{customer.name}}`, `{{vehicle.plate}}`, `{{appointment.date}}`). Variables list lives in a `notification-template-variables.ts` constants file co-located with the module.
5. **API client**: `packages/api/src/clients/notification-templates.ts` extends `CrudClient`.
### Verification
1. Seeded list shows all 35+ rows.
2. Toggle a channel → persists.
3. Edit body → re-render shows updated body.
4. Backend `__('api.notification_templates.updated')` returns Arabic when locale is `ar`.
---
# Phase 4 — Reports
**Goal.** Top-level Reports module with categorized reports across Sales, Purchases, Inventory, Employees, Accountants.
### Reports to ship (v1 — locked per Part F)
1. **Sales by Customer** — revenue grouped by customer with date range + department filter.
2. **Invoices Aging** — outstanding invoices bucketed 030 / 3160 / 6190 / 90+ days.
3. **Technician Productivity** — hours logged vs billable per technician, derived from `TimeSheet` + job-card service rows.
4. **Inventory Valuation + Low-Stock** — current stock value by part + list of parts under reorder threshold.
All other reports candidates (Sales by Service/Vehicle, Payments Summary, Estimates Conversion, Purchases by Vendor, Bills Aging, Stock Movement, Attendance, Payroll, Job Card Turnaround/Status) are deferred to a follow-up phase. Architect the reports module so adding the 5th report is trivial.
### Charting
Use **Recharts** via the already-installed wrapper `garage-erp/apps/dashboard/shared/components/ui/chart.tsx`. No new dependency. Wrapper exports `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, etc. — mimic shadcn's chart usage docs.
### Backend
- `app/Http/Controllers/Api/Reports/` directory — one controller per report category (`SalesReportController`, `PurchasesReportController`, etc.).
- Each report = single GET endpoint accepting `from`, `to`, optional `department_id`, `customer_id`, etc. Returns aggregated rows.
- Use Eloquent + `selectRaw` aggregations. Index any frequently-grouped columns via migration.
- Routes grouped under `/api/reports/...`. New permission per category: `can_view_reports_sales`, `can_view_reports_purchases`, ...
- **No new models or tables** — pure read aggregation over existing data.
- Lang keys for report titles in `lang/en/reports.php` + `lang/ar/reports.php`.
### Frontend
1. **Module** `apps/dashboard/modules/reports/` with:
- `report-card.tsx` (preview tile in the index).
- `report-filters.tsx` (date range, department, customer/vendor).
- One subdirectory per category: `sales/`, `purchases/`, `inventory/`, `employees/`, `job-cards/`. Each contains a `<report-name>-report.tsx` data view.
2. **Routes**:
- `app/(authenticated)/reports/page.tsx` — index grid of report cards grouped by category.
- `app/(authenticated)/reports/<category>/<slug>/page.tsx` — individual report view with filters + table + export buttons.
3. **Sidebar**: add a new top-level group `{ title: "Reports", href: "/reports", icon: <BarChart3Icon /> }` after Items/Employees in `navGroups.tsx`.
4. **Export**: reuse `useDocumentPrint` pattern for PDF; for Excel use existing `maatwebsite/excel` backend integration (add new export classes in `app/Exports/Reports/`).
5. **Charts**: prefer `recharts` if already in `packages/ui` dependencies; otherwise propose to user before adding.
### Verification
1. Each report renders with sample data filtered by date range.
2. PDF export uses the same layout shell as other prints (`resources/views/pdf/layouts/document.blade.php`).
3. Excel export downloads a valid `.xlsx`.
4. Permissions block access when role lacks `can_view_reports_<category>`.
---
# Phase 5 — CRM (Leads, Calls, Tasks-CRM)
**Goal.** Add the missing CRM module with three pipelines: Leads, Calls, Tasks (distinct from existing productivity tasks). Mirrors Garage Box screenshots 18.
### Sub-phase 5a — Leads
#### Data model
| Table | Key columns |
|---|---|
| `lead_sections` | `id`, `title`, `arrangement`, `is_default`, `kind` enum(`open`,`won`,`lost`) (kind drives default behavior). Reuse `TaskSection` pattern. |
| `leads` | `id`, `number` (unique, e.g. `LD-000001`), `title`, `date`, `lead_owner_id` (FK users), `referral_source_id` (FK), `lead_status_id` (FK), `department_id`, `description`, `lead_section_id` (FK), `customer_id` (nullable), `vehicle_id` (nullable), salutation, first/last/company name, phone, email, address fields, vehicle fields (make, model, plate, body_type_id). |
| `lead_services` | pivot to services |
| `lead_parts` | pivot to parts |
| `lead_labels` | pivot to `labels` table |
#### Backend
- `LeadController`, `LeadSectionController`, `LeadServiceController`, `LeadPartController` (mirror Estimate's split).
- Permissions: `can_view_leads`, `can_create_leads`, `can_update_leads`, `can_delete_leads`, plus `can_manage_lead_sections`.
- Number sequence: reuse `InvoiceSequenceController` pattern (add `LeadSequenceController`).
- Lang keys under `lang/en/api.php` `leads` section + `lang/ar/api.php`.
- OpenAPI annotations on each method.
#### Frontend
- **Module** `apps/dashboard/modules/leads/` with `lead-form.tsx`, `lead.schema.ts`, `lead-pipeline.tsx` (Kanban view), `lead-actions.tsx`, inline sections for services/parts.
- **Routes** `app/(authenticated)/crm/leads/page.tsx` (Kanban + table toggle), `[id]/page.tsx` (detail).
- **Sidebar** new group `{ title: "CRM", icon: <UsersIcon />, items: [Leads, Calls, Tasks] }` after Purchases in `navGroups.tsx`.
- **Pipeline UI**: Kanban with `@dnd-kit` (check if already in deps before adding). Each column = a `LeadSection`. "+ Section" button opens a section dialog (reuse `task-section-form.tsx` pattern).
- **API client** `packages/api/src/clients/leads.ts`.
### Sub-phase 5b — Calls
#### Data model
| Table | Key columns |
|---|---|
| `calls` | `id`, `number` (`CL-000001`), `call_for` (polymorphic morphs to Customer/Lead), `vehicle_id`, `status` enum(`requested`,`scheduled`,`completed`,`cancelled`), `type` enum(`inbound`,`outbound`), `subject`, `call_owner_id`, `call_date`, `from_time`, `to_time`, `department_id`, `purpose_id` (FK to lookup), `agenda` text, `outcome_id` (nullable FK), `description`, `duration_minutes`, `duration_seconds`, `voice_recording_path`, `send_notification` bool. |
| `call_reminders` | `id`, `call_id`, `unit` int, `unit_type` enum(`minute`,`hour`,`day`), `event` enum(`before`,`after`), `channel` enum(`sms`,`email`,`whatsapp`). |
| `call_purposes`, `call_outcomes` | lookups |
#### Backend
- `CallController` (CRUD + special endpoints: `complete`, `cancel`, `reschedule`).
- `CallReminderController` (nested).
- Reminder dispatcher: a Laravel scheduled job in `app/Console/Commands/SendDueCallReminders.php` runs every minute, sends pending reminders via the relevant notification template (Phase 3) through the provider configured in Phase 7. If Phase 7 not yet shipped, log-only.
- Permissions: standard four + `can_log_calls`, `can_complete_calls`.
#### Frontend
- **Module** `apps/dashboard/modules/calls/` with `call-form.tsx` (schedule), `call-log-form.tsx` (log), `call.schema.ts`, `call-calendar.tsx`.
- **Routes** `app/(authenticated)/crm/calls/page.tsx` (table + tabs Requested/Scheduled/Completed/Cancelled), `crm/calls/calendar/page.tsx` (month view), `[id]/page.tsx`.
### Sub-phase 5c — Tasks (CRM)
**Approach locked per Part F: Option A — extend existing `tasks` table.**
#### Migration
Add to the existing `tasks` table:
- `context` enum (`productivity`, `crm`), default `productivity`, NOT NULL.
- `customer_id` nullable FK to `customers`.
- `vehicle_id` nullable FK to `vehicles`.
- Index on `(context, status)` for the pipeline filter.
#### Backend
- Backfill existing rows to `context = 'productivity'`.
- All existing `TaskController` queries get a `->where('context', 'productivity')` filter so `/productivity/tasks` keeps showing only what it shows today.
- Add `CrmTaskController` (thin) that scopes to `context = 'crm'` and exposes the same CRUD + section endpoints. Reuse the `TaskSection` table for sections (add a `context` column there too, same enum).
- Permissions: introduce `can_view_crm_tasks`, `can_create_crm_tasks`, `can_update_crm_tasks`, `can_delete_crm_tasks` separate from the existing productivity-task permissions.
#### Frontend
- New module `apps/dashboard/modules/crm-tasks/` mirroring Leads pipeline pattern (Kanban with sections).
- Route `app/(authenticated)/crm/tasks/page.tsx` (Kanban + table toggle).
- Re-use schema fragments from `modules/tasks/` where possible — extract shared bits to a `modules/tasks/shared/` if duplication grows.
### Verification (Phase 5 overall)
1. Pipeline drag-and-drop persists section + arrangement.
2. New lead with vehicle info creates a corresponding draft Customer + Vehicle (or links existing) — confirm desired behavior.
3. Call scheduled with a 10-min reminder → cron job fires at expected time.
4. Number sequences advance correctly when a record is created.
---
# Phase 6 — Marketing (Service Reminders + Reviews)
**Goal.** Automate service reminders based on mileage / time, plus collect and surface customer reviews.
### Service Reminders
- New table `service_reminders`: `id`, `customer_id`, `vehicle_id`, `reminder_type` (`mileage` / `time`), `interval_value`, `interval_unit`, `last_triggered_at`, `next_due_at`, `status` enum(`scheduled`,`sent`,`cancelled`), `channels` JSON (subset of `['email','sms']`).
- Scheduled command `app/Console/Commands/SendDueServiceReminders.php` runs daily; sends via Phase 3 templates (`service_reminder_send`).
- **Channels (per Part F)**: **Email** via Laravel `Mail::to(...)->send(...)` using the configured mail sender; **SMS** via Twilio.
- Install `twilio/sdk` (Composer) — flag this dependency add for user approval per CLAUDE.md house rules.
- Add a `NotificationDispatcher` service in `app/Services/Notifications/` with `sendEmail()` and `sendSms()` methods. Reads Twilio credentials from `config/services.php` (`twilio.sid`, `twilio.token`, `twilio.from`) which read from `.env` (`TWILIO_SID`, `TWILIO_TOKEN`, `TWILIO_FROM`).
- WhatsApp/Push channel toggles render but are disabled with a "Requires integration" tooltip — wiring up to providers happens in deferred Phase 7.
- Frontend: `app/(authenticated)/marketing/service-reminders/page.tsx` — list with status tabs, "Create reminder" form with Email + SMS channel checkboxes.
### Rating & Reviews
- New table `customer_reviews`: `id`, `customer_id`, `job_card_id`, `rating` 15, `comment`, `submitted_at`, `source` enum(`internal`,`google`).
- Public-facing review form already partially served by `PublicInspectionController` pattern — extend with `PublicReviewController`.
- After job-card completion, fire `ratings_reviews_link` template (Phase 3).
- Frontend list at `app/(authenticated)/marketing/reviews/page.tsx`.
### Google Business Reviews
- Read-only sync via Google Business Profile API (requires Phase 7 Google integration).
- Background job polls every 6 h.
- Render in same Reviews list with `source = google` badge.
### Sidebar
- Add CRM-style group `{ title: "Marketing", icon: <MegaphoneIcon />, items: [Service Reminders, Reviews, Google Reviews] }` after CRM.
### Verification
1. Create a mileage-based reminder → next_due_at calculated.
2. Cron tick at due time → template send is recorded (or logged if Phase 7 incomplete).
3. Submit a public review → appears in admin list.
---
# Phase 7 — Integrations (real connectors) — DEFERRED
**Status (per Part F decision): NOT IN ROADMAP.** The Phase 1 "Coming soon" placeholder is the long-term state for now. Twilio (SMS) and Laravel mail (email) used by Phase 6 are configured via `.env` only — no user-facing integrations UI.
Keep this spec as reference for when integrations become a priority later.
**Goal (when revived).** Replace the Phase 1 placeholder with a real Providers/Integrations page connecting Google, Microsoft, Zoho, Interakt, Respond, Vonage, Geidea, MSG91, Wati, Xero, WhatsApp. Twilio also moves into the UI at that point.
### Data model
| Table | Key columns |
|---|---|
| `integration_providers` | seeded list: `id`, `key` (slug), `name`, `purpose`, `icon_path`, `auth_type` (`oauth2`/`api_key`/`webhook`), `is_active`. |
| `integration_connections` | per-tenant connection: `id`, `provider_id`, `credentials` (encrypted JSON), `status` (`connected`/`disconnected`/`error`), `last_synced_at`. |
### Backend
- `IntegrationProviderController` (list), `IntegrationConnectionController` (CRUD + `connect`, `disconnect`, `test`).
- One service class per provider in `app/Services/Integrations/<Provider>/`. Each implements a common contract `IntegrationDriver` with `connect()`, `disconnect()`, `sendMessage()`, `pullData()`.
- OAuth callback routes: `/integrations/{provider}/callback`.
- Permissions: `can_manage_integrations`.
- Lang keys for connection status messages.
### Frontend
- **Replace** the Phase 1 placeholder page with `app/(authenticated)/settings/integrations/page.tsx` showing a `Tabs` of "Providers" (catalog) + "Integrations" (connected list).
- **Provider catalog tile** with "Connect" button → opens OAuth popup or credentials dialog.
- Update `navGroups.tsx`: `href: "/settings/integrations/providers"` (the original commented-out path).
### Verification
1. Connect Google → OAuth popup → callback persists tokens → "Connected" badge appears.
2. Test SMS via Vonage → message delivered (in sandbox).
3. Disconnect → credentials zeroed.
### Out of scope for first cut
Real-time webhook handlers for each provider (do them incrementally, one provider per follow-up PR).
---
# Phase 8 — Accountants
**Goal.** Introduce a real general ledger. Split into two sub-phases per Part F: ship the ledger UI first (8A), wire auto-posting into existing modules afterward (8B).
## Phase 8A — Chart of Accounts + Manual Journals + Default Configuration
Ship the standalone ledger. No existing controllers are touched. Users can manually post journal entries; nothing auto-posts.
### Data model
| Table | Key columns |
|---|---|
| `chart_of_accounts` | `id`, `name`, `code` (nullable), `account_type` enum(`asset`,`liability`,`equity`,`income`,`expense`,`cogs`), `parent_id` (nullable, for sub-accounts), `is_system` bool (lock icon in UI), `note`. |
| `journals` | `id`, `number` (`JN-000001`), `date`, `reference`, `notes`, `total_amount` (must balance), `posted_by`, `posted_at`. |
| `journal_lines` | `id`, `journal_id`, `account_id`, `debit`, `credit`, `description`. Constraint: sum(debit) == sum(credit) per journal. |
| `default_account_settings` | single-row config table for: `sales_account_id`, `purchase_account_id`, `inventory_account_id`, `adjustment_account_id`, `customer_advance_account_id`, `deposit_to_account_id`, `vendor_advance_account_id`, `paid_through_account_id`, `purchase_discount_account_id`, `expense_account_id`. |
### Seed
Seed ~80 standard accounts matching Garage Box screenshot 9 (Accounts Payable, Accounts Receivable, Advance Tax, … VAT Payable). Mark system accounts (`is_system = true`).
### Backend (8A only)
- `ChartOfAccountController`, `JournalController`, `DefaultAccountSettingsController`.
- Permissions: `can_view_accountants`, `can_manage_chart_of_accounts`, `can_post_journals`.
- OpenAPI annotations.
- **No changes to existing controllers** in this sub-phase — Invoices/Bills/Payments/Expenses are untouched.
### Frontend
- **Sidebar group** `{ title: "Accountants", icon: <BookOpenIcon />, items: [Manual Journals, Chart Of Accounts] }` after Purchases.
- **Module** `apps/dashboard/modules/accountants/` with `chart-of-account-list.tsx`, `chart-of-account-form.tsx`, `journal-form.tsx` (multi-line debit/credit with running balance check), `default-account-settings-form.tsx`.
- **Routes**: `app/(authenticated)/accountants/chart-of-accounts/page.tsx`, `accountants/manual-journals/page.tsx`, `[id]/page.tsx`.
### Verification (8A)
1. Seed list matches Garage Box exactly (account names + types).
2. Create a manual journal with unbalanced lines → backend rejects.
3. Default Configuration dialog persists.
4. Invoices/Bills/Payments/Expenses still work unchanged (no regression).
## Phase 8B — Auto-posting + cross-doc linking
Wire the ledger into operational modules. **Only start after 8A is stable in production.**
### Backend
- New service `app/Services/Accounting/JournalPoster.php` with methods like `postInvoice(Invoice $i)`, `postBill(Bill $b)`, `postPaymentReceived(...)`, `postPaymentMade(...)`, `postExpense(...)`.
- Hook into existing controllers via Eloquent `created` / `updated` model observers (cleaner than editing every controller). Observers live in `app/Observers/`, registered in `EventServiceProvider`.
- Each posted journal gets a `source_type` + `source_id` morphable pair so it links back to the originating document.
- Reversal logic: when a source doc is deleted or voided, post the reverse journal (don't soft-delete the original).
- New permission: `can_post_automatic_journals` (default true for finance roles).
### Frontend
- On Invoice / Bill / Payment / Expense detail pages: add a "Journal Entry" panel showing the posted journal lines (read-only) with a deep link to `/accountants/manual-journals/<id>`.
- On Journal detail: show "Source Document" link back to the originating doc.
### Verification (8B)
1. Create an Invoice → Journal auto-posts (debit AR, credit Sales, credit Tax).
2. Edit Invoice amount → existing journal reversed + new one posted.
3. Delete Invoice → reversal posted; original journal preserved for audit.
4. Default Configuration changes are respected on next post.
5. Audit trail: every Journal has a non-null `source_type`/`source_id` when auto-posted.
---
## Part E — Cross-cutting follow-ups (every phase finishes with)
1. Run **`pnpm --filter dashboard typecheck && pnpm --filter dashboard lint`** — must pass.
2. Run **`php -l`** on every changed PHP file.
3. Regenerate API client if backend changed: **`pnpm --filter @garage/api generate`**.
4. Update lang pairs (`lang/en/*.php` + `lang/ar/*.php`).
5. Update permission migration + seeder for any new permission columns.
6. Use the `code-review-graph` MCP `detect_changes_tool` + `get_review_context_tool` before opening the PR to self-review.
7. Update `claude-timeline/YYYY-MM-DD.md` with a one-line note about the phase shipped (per global memory rule).
8. **Do not commit** — leave the diff for the user to review.
---
## Part F — Resolved decisions (2026-05-21)
All open questions answered by the user. These are now binding for the phases referenced.
- **Phase 2 (Master)** → **Keep top-level entries as aliases.** Insurance Types, Departments, Make & Models remain in the Settings sidebar AND appear under Master tabs. Backwards-compat for bookmarks. No redirects, no removals.
- **Phase 4 (Reports) — charts** → **Use Recharts (already installed)** via the existing wrapper at `garage-erp/apps/dashboard/shared/components/ui/chart.tsx`. No new dependency.
- **Phase 4 (Reports) — v1 scope** → ship exactly these four reports:
1. Sales by Customer
2. Invoices Aging (030 / 3160 / 6190 / 90+ days)
3. Technician Productivity (hours logged vs billable per technician)
4. Inventory Valuation + Low-Stock
All other reports listed in Phase 4 are deferred.
- **Phase 5c (CRM Tasks)** → **Extend the existing `tasks` table** (Option A). Add `context` enum (`productivity` | `crm`), nullable `customer_id`, nullable `vehicle_id`. All existing task queries get a `context = 'productivity'` filter.
- **Phase 6 (Marketing reminders) — channels** → **Email + SMS in v1; WhatsApp deferred.**
- Email sends via Laravel's configured mail sender (`config/mail.php`) — works out of the box, no Integrations UI required.
- SMS sends via **Twilio**. Configured server-side via `.env` (`TWILIO_SID`, `TWILIO_TOKEN`, `TWILIO_FROM`) — not surfaced in the Integrations page yet.
- WhatsApp channel toggles are visible but disabled with a "Requires integration" tooltip.
- **Phase 7 (Integrations real)** → **Stays "coming soon."** The Phase 1 placeholder is the long-term state for now. Twilio + Laravel mail are configured server-side only; no user-facing connector UI in this roadmap. Phase 7's detailed spec is preserved as a future reference but no work is planned.
- **Phase 8 (Accountants)** → **Split into two sub-phases:**
- **Phase 8A**: Chart of Accounts + Manual Journals + Default Configuration UI. No automatic posting from other modules.
- **Phase 8B**: Auto-post journals on Invoice / Bill / Payment / Expense create + link from each source doc to its posted journal entry. Big blast radius — ship only after 8A is stable.