# 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 2–8 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 1–8, 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//`. - Each resource gets: `-form.tsx`, `.schema.ts` (Zod), optional `-actions.tsx`, optional inline section components (e.g. `-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)///page.tsx` (`"use client"` + `ResourcePage`). - Detail page: `app/(authenticated)///[id]/page.tsx` (async server component, uses `getServerApi()` + `DashboardPage`). - Sub-tabs of a detail (e.g. notes, documents): `[id]//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 `.schema.ts`. - Use `useResourceForm({ 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` (path: `packages/api/src/clients/.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/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` → 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 `.` 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/.php` MUST have the matching `lang/ar/.php` key in the same change. - Use `__('api..')` 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()` method on the controller. 3. Add `resources/views/pdf/document-print-.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 (2–3 wks) | 3 resources + sections + permissions + i18n | 3 modules + pipelines | reuses TaskSection pattern | | 6 | Marketing (Reminders email+SMS, Reviews) | L (1–2 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 (1–2 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: } ``` - 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`: `` - `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//page.tsx`. Each is a thin wrapper that imports and renders the existing module list component from `modules/settings//`. 4. **Edit** `config/navGroups.tsx`: add `{ title: "Master", href: "/settings/master", icon: }` 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: }` 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 0–30 / 31–60 / 61–90 / 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.tsx` data view. 2. **Routes**: - `app/(authenticated)/reports/page.tsx` — index grid of report cards grouped by category. - `app/(authenticated)/reports///page.tsx` — individual report view with filters + table + export buttons. 3. **Sidebar**: add a new top-level group `{ title: "Reports", href: "/reports", icon: }` 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_`. --- # 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 1–8. ### 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: , 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` 1–5, `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: , 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//`. 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: , 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/`. - 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 (0–30 / 31–60 / 61–90 / 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.