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

36 KiB
Raw Blame History

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:
    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-printDocumentPrintController@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:
      // { 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 keys 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):
    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) — chartsUse 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) — channelsEmail + 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.