# Architecture

## Overview

This is a **Laravel 12** monolithic CRM API backend. It follows a traditional MVC architecture with a Service Layer for business logic. The application exposes a RESTful JSON API consumed by mobile apps and a frontend (SPA).

## Design Patterns

- **MVC** - Controllers handle HTTP, Models handle data, Views are minimal (SPA frontend)
- **Service Layer** - Business logic extracted into `app/Services/`
- **Repository Pattern** - `app/Repositories/DemoRepository.php` (only one)
- **Trait-based Reuse** - Common functionality via traits (`ApiResponse`, `SendsPushNotifications`, `ResolvesAccessibleUserIds`, `ManagesLeadContacts`, `FiltersLeadQueries`, `FormatsEntityData`, `RecordsActivity`, `RequiresSuperAdmin`)
- **Queue-based Async Processing** - Database-driven queue for notifications, calendar sync, attendance backfill
- **RBAC** - Spatie Laravel Permission for role-based access control
- **Activity Log** - Spatie Laravel Activitylog for audit trail

## Module Structure

```
app/
├── Console/Commands/       # 6 Artisan commands
├── Events/                 # NewChatMessage (broadcasting)
├── Exceptions/             # Custom exceptions (InsufficientExpenseBalanceException)
├── Exports/                # 5 Excel exports (Leads, Products, Sales, PhoneCalls, IvrCalls)
├── Http/
│   ├── Controllers/        # 73 controllers + Meta/ subdirectory (3)
│   ├── Middleware/         # AccountValidation, CheckPermission, EnsureClientAuth
│   ├── Requests/          # 25 Form Request classes (Expense/, Incentive/, Setting/, Task/)
│   └── Resources/         # API Resource classes
├── Jobs/                   # 13 queue jobs + Meta/ subdirectory (1)
├── Models/                 # 123 Eloquent models
├── Providers/              # AppServiceProvider only
├── Repositories/           # DemoRepository only
├── Services/               # 33 service classes + Meta/ subdirectory (4)
├── Support/                # PermissionCatalog, AttendanceSchedule, DateHelper
└── Traits/                 # 8 traits
```

(Counts last verified: August 2026. Includes Campaign/Marketing, Lead Group, Meta Lead Ads, Expense,
Incentive, and Helpdesk features.)

## Controllers

73 controllers extend `App\Http\Controllers\Controller` (+ 3 in `Meta/` subdirectory). Business logic
SHOULD be in services (per AGENTS.md), but in practice many controllers contain significant logic inline.

### Key Controller Groups

| Group | Count | Primary Controllers |
|-------|-------|-------------------|
| Core | 3 | AuthController, RegistrationController, ClientAuthController |
| Lead Management | 8 | LeadController, LeadSaleController, LeadOverviewController, LeadDashController, LeadGroupController, CompanyController, ProductController, TagController |
| Meetings | 2 | MeetingController (largest), MeetingSettingController |
| Demos | 1 | DemoController |
| Projects | 2 | ProjectController, ProjectChatController |
| Tasks | 2 | TaskController, TimesheetController |
| HR | 7 | UserController, AttendanceController, LeaveController, ShiftController, HolidayController, PayrollPaymentController, userAssetsController |
| Finance | 5 | FinanceController, PaymentTypeController, LeadSaleController, PayrollPaymentController, ExpenseController |
| Communication | 6 | NotificationController, NotificationIntegrationController, PhoneCallLogController, IvrCredentialController, SmsCredentialController, WabaCredentialController, RcsCredentialController |
| Marketing/Campaigns | 6 | CampaignController, CampaignApiCredentialController, SmsTemplateController, RcsTemplateController, WabaTemplateController |
| Meta Lead Ads | 3 | Meta\MetaWebhookController (public, signature-verified), Meta\MetaConnectionController, Meta\MetaAuthController (OAuth stub) |
| Client Portal | 4 | ClientAuthController, ClientProfileController, ClientProjectController, ClientChatController, ClientPortalAdminController |
| Settings | 9 | RoleController, SaleSettingController, IncentiveSettingController, MeetingSettingController, CreditSettingController, NotificationSettingController, SmtpController, AccountController, AccountPolicyController |
| Analytics | 4 | DashboardController, UserAnalyticsController, UserActivityKpiController, LeadDashController |
| Incentives | 2 | SaleIncentiveSlabController, MonthlyIncentiveController |
| Location | 2 | LocationTrackingController, LocationFencingController |
| Utilities | 6 | AppVersionController, AppSettingsController, VisitController, QuotationController, QuotationTermsController, GoogleAuthController, LinkedDevicesController, FileController |
| Helpdesk | 1 | HelpdeskTicketController |
| Credits | 2 | CreditRequestController, CreditSettingController |

### Controller Pattern

Most controllers are **not RESTful resource controllers**. They use a custom verb-first naming convention:
- `POST /module/action` -> `controller->action_method()`
- Example: `POST /lead/create` -> `LeadController::create_lead()`
- Example: `POST /meeting/otp/send` -> `MeetingController::meeting_otp_send()`

## Services

33 service classes in `app/Services/` (+ 4 in `Meta/` subdirectory). The list below covers the most
significant ones — see the directory itself for the full set:

| Service | Purpose |
|---------|---------|
| `AttendanceBackfillService` | Fills missing attendance records, handles holidays/week-offs/leaves |
| `GoogleCalendarService` | Base Google Calendar integration (create/update/delete events) |
| `OptimizedGoogleCalendarService` | Enhanced version with circuit breaker, caching, exponential backoff |
| `LeadDistributionService` | Builds user distribution queues for lead import (percentage/count) |
| `LeadAnalyticsService` | Lead funnel analytics and reporting |
| `LeadColumnMappingService` | Excel/CSV lead import with column mapping, validation, distribution, file analysis (per-field formats/counts), per-field required-skip and format normalization. Optimized for large files (synchronous HTTP): chunked reading (`WithChunkReading`, 500/chunk), per-chunk batched duplicate phone/email lookups (one `whereIn` each instead of two queries per row), in-memory source/company caches, in-file duplicate tracking, and raised `set_time_limit`/`memory_limit`. Duplicate checks ignore soft-deleted leads; when an incoming phone/email is held by a soft-deleted lead, that lead's contact is freed (nulled + `lead_notes` entry) so the new lead imports without purging the FK-referenced deleted lead |
| `FieldFormatNormalizer` | Normalizes lead-import field values (phone/email/country_code/text) to a chosen preset or custom format spec; also detects/strips disallowed special characters in string fields (name/company/etc.) via allowed-character profiles; used by the import analyze + apply flow |
| `LeadGroupService` | Builds/maintains reusable, fixed-snapshot lead groups (multi-select membership) |
| `PushNotificationService` | Firebase Cloud Messaging HTTP v1 implementation. Dual-project: mobile (`fcm_token`, `android`/`apns` payload) and browser web push (`web_fcm_token`, `webpush` payload) resolve separate service-account JSONs / project IDs / OAuth tokens per platform; `sendToUser` delivers to both tokens when present |
| `QuotationAttachmentService` | File upload validation and storage for quotations |
| `QuotationEmailService` | Sends quotation emails with dynamic SMTP config |
| `QuotationPdfService` | Generates quotation PDFs via DomPDF |
| `UserActivityKpiService` | Manages KPI target assignment and summary metrics |
| `SettingService` | Lists/updates per-module behavior settings |
| `CampaignService` | Campaign CRUD, audience import (lead group/filters/Excel), variable mapping, launch/pause/resume/stop, stats |
| `CampaignExecutionService` | Executes a launched campaign: failover/parallel strategy, per-channel send (SMS/RCS/WhatsApp), variable resolution |
| `CampaignExcelImportService` | Two-step Excel audience upload/import for campaigns |
| `TemplateVariableService` | Extracts/substitutes `{{n}}` template variables, channel-aware |
| `SmsTemplateService` / `RcsTemplateService` / `WabaService` | Fetch/sync provider templates into local cache tables |
| `PaymentReminderService` | Finds unpaid/partial sales, flags overdue and credit-exceeded |
| `ClientReportService` | Builds client-wise comprehensive report aggregating all records tied to a lead |
| `SaleIncentiveService` | Incentive calculations for sales staff |
| `MonthlyIncentiveService` | Monthly incentive generation and bracket-based calculations |
| `ExpenseService` | Expense allocations, reimbursement workflows, ledger entries |
| `JourneyApiClient` | External Journey API integration |
| `Meta\MetaWebhookService` | Extracts `leadgen` change events from Meta webhook payloads |
| `Meta\MetaApiService` | Thin Graph API client for fetching lead data |
| `Meta\MetaLeadService` | Maps Meta lead fields to CRM lead, creates/deduplicates leads |
| `Meta\MetaApiException` | Custom exception for Meta API errors |

Full detail on the campaign-related services: [campaign-management.md](campaign-management.md).

`App\Traits\ManagesLeadContacts` (`duplicateLeadByPhone`, `duplicateLeadByEmail`, `syncLeadContacts`)
was extracted out of `LeadController` so both `LeadController` and `Meta\MetaLeadService` share one
implementation of phone/email dedupe and contact syncing instead of duplicating it per lead source.

## Middleware

### `AccountValidation` (`account_validation`)
- Applied to all authenticated routes
- Validates the user's account has a valid status and is not expired

### `CheckPermission` (`permission`)
- Checks the authenticated user has a specific Spatie permission
- Usage: `->middleware('permission:lead_management.raw_lead.create')`

### `EnsureClientAuth` (`client_auth`)
- Applied to Client Portal routes
- Validates client authentication (separate from internal user auth)

## Jobs

| Job | Schedule | Purpose |
|-----|----------|---------|
| `DispatchUpcomingActivityNotifications` | Every minute | Finds activities within next 15min, dispatches notifications |
| `SendUpcomingActivityNotification` | Dispatched | Sends single push notification for an activity |
| `DispatchMeetingStateNotifications` | Dispatched | Sends notifications on meeting state changes |
| `GenerateRecurringTasks` | Daily at 1:00 AM, or on demand via `POST /api/task/recurrence/generate` | Creates next occurrences of recurring tasks |
| `SyncDemoToGoogleCalendar` | On demand | Syncs demo events to Google Calendar |
| `BackfillAttendanceJob` | On demand | Backfills attendance for a specific user |
| `CalculateMonthlyIncentives` | On demand | Calculates monthly incentive payouts |
| `RefreshUserMonthIncentive` | On demand | Recalculates a single user's incentive for a month |
| `ExecuteCampaignJob` | Dispatched on launch | Runs a campaign's send loop (chunked by 500 recipients) on the `campaigns` queue |
| `ExecuteCampaignChunkJob` | Dispatched on launch (>50,000 recipients) | Staggered parallel chunk processing for very large campaigns |
| `SendPaymentRemindersJob` | Daily at 9:00 AM | Pushes payment reminders for unpaid/partial sales, deduped per recipient/day via `scheduled_notification_logs` |
| `SendRunningTasksReminder` | Scheduled | Reminds users of tasks still in running state |
| `Meta\ProcessMetaLead` | Dispatched per `leadgen` webhook event, on the `meta` queue | Fetches the full lead from the Graph API and runs it through `MetaLeadService` |

## Queue

- **Driver**: Database (`QUEUE_CONNECTION=database`)
- **Worker**: Runs via `queue:work --stop-when-empty --tries=3 --timeout=90` every minute
- **Failed Jobs**: Database-uuids driver
- **Batching**: Supported via `job_batches` table
- **`meta` queue**: dedicated queue for `Meta\ProcessMetaLead`, same worker/driver as above

## Scheduled Tasks

| Task | Frequency | Command/Job |
|------|-----------|-------------|
| Dispatch activity notifications | Every minute | `DispatchUpcomingActivityNotifications` |
| Generate recurring tasks | Daily 1:00 AM | `GenerateRecurringTasks` |
| Process daily attendance | Daily 2:00 AM | `attendance:process-daily` |
| Cleanup old attendance selfies | Daily 3:00 AM | `attendance:cleanup-old-selfies` (deletes check-in/check-out selfie files older than `config('attendance.selfie_retention_days')`, default 60 days, and nulls the DB columns) |
| Send payment reminders | Daily 9:00 AM | `SendPaymentRemindersJob` |
| Queue worker | Every minute | `queue:work --stop-when-empty` |

## Authentication

**Dual Auth System:**
1. **Sanctum Token-based** - API tokens via `Bearer` header for mobile apps
2. **Sanctum Stateful** - SPA authentication via cookies (`statefulApi()` in bootstrap)

Flow:
1. User registers via `POST /api/register` (with OTP verification)
2. User logs in via `POST /api/login` -> receives Sanctum token
3. Token sent as `Authorization: Bearer <token>` for subsequent requests
4. `auth:sanctum` middleware protects all routes

**Account Validation**: Before any authenticated request, `AccountValidation` middleware checks the user's account is active and not expired.

## Authorization

**Dual Authorization:**
1. **Spatie Laravel Permission** - Fine-grained permissions defined in `PermissionCatalog` (dot-notation: `module.sub_module.action`)
2. **`CheckPermission` middleware** - Applied per-route

Permission structure:
```
{module}.{sub_module}.{action}
e.g., lead_management.raw_lead.create
       meeting_management.meeting.override
```

## Data Flow

```
Request -> Middleware (CORS -> AccountValidation -> auth:sanctum -> CheckPermission)
       -> Controller (validates via inline $request->validate())
       -> Service (business logic, DB transactions)
       -> Model (Eloquent ORM)
       -> Response (ApiResponse trait: success/error JSON)
```

Key observations:
- Most controllers use **inline validation** (`$request->validate()`) rather than Form Requests
- Most controllers return JSON **directly** rather than through API Resources
- Business logic often lives in controllers despite AGENTS.md guidance

## Storage

- **Local disk**: `storage/app/private` (default)
- **Public disk**: `storage/app/public` (symlinked to `public/storage`)
- **File types**: Profile images, logos, visit images, project files, attendance images, documents, APK files, quotation PDFs
- **S3**: Configured but not default

## Caching

- **Default store**: Database (`CACHE_STORE=database`)
- **Failover**: Database -> Array
- Used for: OTP in registration (cache key `registration_{phone}`), Google Calendar tokens

## Session

- **Driver**: Database
- **Lifetime**: 120 minutes
- Used for: Sanctum stateful SPA authentication

## Multi-tenancy

**Account-based isolation**. The `accounts` table defines tenant boundaries. Every user belongs to an
account via `users.account_id`. The `AccountValidation` middleware wraps all authenticated routes and
scopes data to the user's account. This is not a full multi-tenant setup (single database, no schema
separation) but provides logical data isolation per account.
