- 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.
552 lines
36 KiB
Markdown
552 lines
36 KiB
Markdown
# 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/<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 (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: <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 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-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 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: <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` 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: <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 (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.
|