---
description: GDesk Enterprise Help Desk – Full development rules for Cursor AI Agent
globs: ["**/*.php", "**/*.blade.php", "**/*.js", "**/*.css", "**/*.vue", "**/*.json", "**/*.env*", "database/**", "routes/**", "app/**", "resources/**", "tests/**"]
alwaysApply: true
---

# GDesk — Cursor AI Agent Rules
**Enterprise Help Desk & Ticketing System**
GulfTech Saudi Arabia | www.gulftech.sa | info@gulftech.sa

---

## 1. PROJECT IDENTITY

| Field | Value |
|---|---|
| Product Name | GDesk — Enterprise Help Desk |
| Client / Owner | GulfTech Saudi Arabia |
| Version | v1.0 |
| Contact | info@gulftech.sa · +966 560 635 771 |
| Website | www.gulftech.sa |

---

## 2. TECHNOLOGY STACK (Non-Negotiable)

| Layer | Technology | Notes |
|---|---|---|
| Backend Framework | Laravel 11 (PHP 8.3) | Latest stable Laravel |
| Frontend Templating | Blade Templates + Bootstrap 5 | NO Vue/React unless explicitly added |
| CSS Framework | Bootstrap 5.3 | Custom design system on top |
| Database | MySQL 8.0 | Primary persistent store |
| Cache / Queues | Redis | Sessions, cache, job queues |
| Auth | Laravel Sanctum + Azure AD OIDC | SSO + local fallback |
| SMS | Twilio | 2FA OTP + SLA breach alerts |
| Email | Laravel Mail (SMTP / SendGrid) | Notifications + OTP |
| File Storage | AWS S3 / Azure Blob | Ticket attachments + exports |
| Queue Driver | Redis (Laravel Queues) | All async jobs |
| CI/CD | GitHub Actions | Automated tests + deploy |
| Hosting | AWS or Azure | Docker + Forge / Vapor |

**Do NOT introduce** React, Vue, Next.js, Inertia, Livewire, or any SPA framework unless the user explicitly asks. The UI is server-side rendered Blade + Bootstrap 5 with vanilla JS only.

---

## 3. DESIGN SYSTEM & BRANDING

### Color Palette (CSS Variables — defined in `public/assets/css/gdesk.css`)
```css
--g-900: #0b3d2e;   /* Sidebar background */
--g-800: #0f5132;
--g-700: #14693f;
--g-600: #1a8049;
--g-500: #1A6B3C;   /* Primary green — buttons, links, active states */
--g-400: #22a05a;
--g-300: #3cc06f;
--g-100: #e6f4ec;
--g-50:  #f3faf5;
--d-900: #7a5800;
--d-700: #a17a07;
--d-600: #B8860B;   /* Primary gold — accents, highlights */
--d-500: #d4af37;
--d-400: #e2c25a;
--d-300: #f0d77d;
--d-100: #fbf4dd;
--d-50:  #fffdf3;
--ink:   #1c2a23;   /* Body text */
--muted: #5c6b63;   /* Secondary text */
--border: #dde7e1;  /* Default border */
--bg:    #ffffff;
--bg-soft: #f6faf7;
--bg-dark: #0b3d2e;
```

### Status Badge Colors
```css
--s-new:       #3b82f6;
--s-open:      #1A6B3C;
--s-pending:   #d97706;
--s-progress:  #7c3aed;
--s-escalated: #dc2626;
--s-hold:      #6b7280;
--s-resolved:  #059669;
--s-closed:    #374151;
--s-reopened:  #b45309;
```

### Typography
- **Font**: Poppins (Google Fonts) — weights 300, 400, 500, 600, 700
- **Headings font**: Poppins 600/700
- **Base font size**: 14px
- **Line height**: 1.6

### Layout Constants
- Sidebar width: 260px (fixed left)
- Topbar height: 60px (fixed top)
- Main content: `margin-left: 260px; padding-top: 60px`

### UI Component Classes (from `gdesk.css`)
Always use these existing classes — do not duplicate with Bootstrap defaults:
- `.gd-sidebar` — fixed left navigation
- `.gd-topbar` — fixed top header bar
- `.gd-main` — main content wrapper
- `.gd-content` — inner content padding
- `.gd-card` — white card with border-radius
- `.gd-card-header` — card header row
- `.stat-card` — KPI metric card (icon + value + label)
- `.gd-table` — styled data table
- `.gd-input` — form input
- `.gd-select` — form select
- `.gd-btn`, `.gd-btn-primary`, `.gd-btn-outline`, `.gd-btn-gold` — buttons
- `.gd-search` — search bar with icon
- `.gd-filter-bar` — horizontal filter row
- `.gd-tabs` — tab navigation (Bootstrap nav-tabs styled)
- `.gd-toggle` — iOS-style toggle switch
- `.badge-status.bs-open`, `.bs-new`, `.bs-pending`, `.bs-escalated`, `.bs-hold`, `.bs-resolved`, `.bs-closed` — ticket status badges
- `.badge-p1`, `.badge-p2`, `.badge-p3`, `.badge-p4` — priority badges
- `.sla-bar-wrap`, `.sla-bar`, `.sla-bar-fill` — SLA progress bar
- `.upload-zone` — drag-and-drop file upload area
- `.user-avatar` — circular user avatar
- `.sidebar-badge` — notification count badge in sidebar

---

## 4. DATABASE SCHEMA

### 4.1 Core Tables

#### `users`
```sql
id, name, email, password (nullable — SSO users), employee_id,
phone, job_title, avatar, role (enum: employee|agent|dept_admin|super_admin),
department_id (FK, nullable), azure_id (nullable), is_sso_user (bool),
is_active (bool), 2fa_enabled (bool), 2fa_method (enum: totp|sms|email),
2fa_secret (nullable), last_login_at, created_at, updated_at
```

#### `departments`
```sql
id, name, slug, description, icon, email_alias, head_user_id (FK users),
sla_policy_id (FK), default_form_id (FK forms, nullable),
is_active (bool), is_visible (bool), created_at, updated_at
```

#### `tickets`
```sql
id, ticket_number (e.g. GT-2025-0001), subject, description,
status (enum: new|open|pending|in_progress|escalated|on_hold|resolved|closed|reopened),
priority (enum: p1|p2|p3|p4),
user_id (FK — submitter), department_id (FK — current dept),
assigned_agent_id (FK users, nullable), form_id (FK, nullable),
form_data (JSON — submitted form values), sla_due_at,
first_response_at, resolved_at, closed_at,
reopen_count (int default 0), merged_into_ticket_id (FK, nullable),
created_at, updated_at, deleted_at (soft delete)
```

#### `ticket_replies`
```sql
id, ticket_id (FK), user_id (FK), body (text),
is_internal (bool — internal note, not visible to submitter),
created_at, updated_at
```

#### `ticket_attachments`
```sql
id, ticket_id (FK), reply_id (FK, nullable), user_id (FK),
filename, original_filename, file_path (S3 key), mime_type,
file_size, created_at
```

#### `ticket_transfers`
```sql
id, ticket_id (FK), from_user_id (FK), to_user_id (FK, nullable),
from_department_id (FK, nullable), to_department_id (FK, nullable),
transfer_type (enum: agent|department), reason (text),
transferred_at, accepted_at, created_at
```

#### `ticket_status_history`
```sql
id, ticket_id (FK), changed_by_user_id (FK),
old_status, new_status, note (nullable), created_at
```

#### `ticket_time_logs`
```sql
id, ticket_id (FK), user_id (FK), department_id (FK),
action (enum: assigned|replied|transferred|resolved),
duration_seconds (int), logged_at, created_at
```

#### `forms`
```sql
id, name, description, department_id (FK), created_by (FK users),
mode (enum: basic|advanced), is_active (bool),
fields (JSON — full form schema), version (int default 1),
published_at, created_at, updated_at
```

#### `sla_policies`
```sql
id, name, department_id (FK, nullable — null = global default),
p1_response_minutes, p1_resolution_minutes,
p2_response_minutes, p2_resolution_minutes,
p3_response_minutes, p3_resolution_minutes,
p4_response_minutes, p4_resolution_minutes,
p1_escalation_action (JSON), p2_escalation_action (JSON),
is_active (bool), created_at, updated_at
```

#### `saved_replies`
```sql
id, title, body (text — supports {{merge_fields}}),
user_id (FK — owner), department_id (FK, nullable — null = personal),
is_shared (bool), category, use_count (int), created_at, updated_at
```

#### `notifications`
```sql
id, user_id (FK), type, title, body, data (JSON),
read_at (nullable), created_at
```

#### `audit_logs`
```sql
id, user_id (FK, nullable), actor_type (enum: user|system),
action (string), entity_type, entity_id, old_values (JSON),
new_values (JSON), ip_address, user_agent, created_at
```

#### `knowledge_base_articles`
```sql
id, title, slug, body (longtext), category_id (FK),
author_id (FK users), department_id (FK, nullable — null = all),
status (enum: draft|published), view_count, helpful_count,
not_helpful_count, published_at, created_at, updated_at
```

#### `kb_categories`
```sql
id, name, slug, icon, color, department_id (FK, nullable),
visibility (enum: public|internal), sort_order, created_at, updated_at
```

#### `csat_surveys`
```sql
id, ticket_id (FK unique), user_id (FK), rating (1-5),
comment (text, nullable), submitted_at, created_at
```

#### `notification_templates`
```sql
id, name, event_key (e.g. ticket.created), channel (enum: email|sms|inapp),
subject (nullable — email only), body (text), language (en|ar),
is_active (bool), created_at, updated_at
```

#### `api_keys`
```sql
id, name, key (hashed), plain_key (shown once), user_id (FK),
scopes (JSON), last_used_at, expires_at, is_active (bool),
created_at, updated_at
```

#### `webhooks`
```sql
id, name, url, secret (nullable), events (JSON array),
is_active (bool), failure_count (int), last_delivery_at,
created_at, updated_at
```

#### `webhook_deliveries`
```sql
id, webhook_id (FK), event, payload (JSON), response_status,
response_body, duration_ms, delivered_at, created_at
```

#### `sessions` (Laravel standard + custom fields)
```sql
id, user_id, ip_address, user_agent, payload, last_activity, device_name
```

#### `password_reset_tokens` (Laravel standard)

#### `otp_codes`
```sql
id, user_id (FK), code (hashed), method (email|sms|totp),
expires_at, used_at, created_at
```

---

## 5. USER ROLES & PERMISSIONS

GDesk has **4 roles**. Always check roles using Laravel Gates/Policies — never hardcode role checks in controllers.

| Role Constant | Value | Description |
|---|---|---|
| `ROLE_EMPLOYEE` | `'employee'` | Company employee — submits and tracks own tickets only |
| `ROLE_AGENT` | `'agent'` | Department agent — manages and resolves tickets |
| `ROLE_DEPT_ADMIN` | `'dept_admin'` | Department admin — all agent caps + team & form management |
| `ROLE_SUPER_ADMIN` | `'super_admin'` | Full platform access |

### Permission Matrix

| Action | Employee | Agent | Dept Admin | Super Admin |
|---|---|---|---|---|
| Submit ticket | ✅ | ✅ | ✅ | ✅ |
| View own tickets | ✅ | ✅ | ✅ | ✅ |
| View all dept tickets | ❌ | ✅ | ✅ | ✅ |
| View all tickets (global) | ❌ | ❌ | ❌ | ✅ |
| Reply to ticket | ✅ (own) | ✅ | ✅ | ✅ |
| Add internal note | ❌ | ✅ | ✅ | ✅ |
| Change ticket status | ❌ | ✅ | ✅ | ✅ |
| Transfer ticket (agent) | ❌ | ✅ | ✅ | ✅ |
| Transfer ticket (dept) | ❌ | ✅ | ✅ | ✅ |
| Close / reopen ticket | ✅ (own) | ✅ | ✅ | ✅ |
| Manage team agents | ❌ | ❌ | ✅ | ✅ |
| Build dept forms | ❌ | ❌ | ✅ | ✅ |
| Configure dept SLA | ❌ | ❌ | ✅ | ✅ |
| View dept reports | ❌ | ✅ (own) | ✅ | ✅ |
| View global reports | ❌ | ❌ | ❌ | ✅ |
| Manage departments | ❌ | ❌ | ❌ | ✅ |
| Manage all users | ❌ | ❌ | ❌ | ✅ |
| Configure SSO / 2FA | ❌ | ❌ | ❌ | ✅ |
| Access audit logs | ❌ | ❌ | ❌ | ✅ |
| Manage API keys | ❌ | ❌ | ❌ | ✅ |
| Manage webhooks | ❌ | ❌ | ❌ | ✅ |
| Manage templates | ❌ | ❌ | ❌ | ✅ |
| Manage KB (global) | ❌ | ❌ | ❌ | ✅ |
| Manage KB (dept) | ❌ | ❌ | ✅ | ✅ |

---

## 6. TICKET LIFECYCLE

### Ticket States (enum values)
```
new → open → in_progress → pending → resolved → closed
                ↓                         ↑
            escalated              reopened
                ↓
            on_hold
```

### State Transition Rules
- `new` → `open` when agent first views or accepts the ticket
- `open` → `in_progress` when agent first replies
- Any state → `pending` when agent is waiting for user response
- `pending` → `in_progress` when user replies
- Any active state → `escalated` when transferred to another dept
- Any active state → `on_hold` by agent/admin
- Any active state → `resolved` by agent
- `resolved` → `closed` after 7 days of no user response (configurable), or user confirms
- `resolved`/`closed` → `reopened` by user or admin
- `reopened` behaves as `open`

### SLA Tiers
```php
// app/Enums/Priority.php
enum Priority: string {
    case P1 = 'p1'; // Critical — 15min response, 4h resolution
    case P2 = 'p2'; // High — 1h response, 8h resolution
    case P3 = 'p3'; // Medium — 4h response, 24h resolution
    case P4 = 'p4'; // Low — 8h response, 72h resolution
}
```

### SLA Clock Rules
- Starts when ticket is created
- Pauses during `on_hold` and `pending` states
- Pauses during inter-department transfer (until receiving dept accepts)
- Resumes on state change back to `open` or `in_progress`
- Breached = `NOW() > sla_due_at`

---

## 7. ROUTES STRUCTURE

```
routes/
  web.php          → Main routes file (imports all route groups)
  auth.php         → Login, SSO, 2FA, logout
  employee.php     → All /portal/* routes (role: employee+)
  agent.php        → All /agent/* routes (role: agent+)
  admin.php        → All /admin/* routes (role: dept_admin+)
  superadmin.php   → All /superadmin/* routes (role: super_admin)
  api.php          → REST API routes (/api/v2/*)
```

### URL Conventions

#### Employee Portal (`/portal`)
```
GET  /portal/dashboard
GET  /portal/tickets
GET  /portal/tickets/create
POST /portal/tickets
GET  /portal/tickets/{id}
POST /portal/tickets/{id}/reply
POST /portal/tickets/{id}/close
POST /portal/tickets/{id}/reopen
GET  /portal/kb
GET  /portal/kb/{category}
GET  /portal/kb/{category}/{slug}
GET  /portal/profile
GET  /portal/notifications
GET  /portal/survey/{token}
POST /portal/survey/{token}
```

#### Agent Backend (`/agent`)
```
GET  /agent/dashboard
GET  /agent/queue
GET  /agent/queue/all
GET  /agent/queue/unassigned
GET  /agent/queue/escalated
GET  /agent/tickets/{id}
POST /agent/tickets/{id}/reply
POST /agent/tickets/{id}/status
POST /agent/tickets/{id}/priority
POST /agent/tickets/{id}/transfer/agent
POST /agent/tickets/{id}/transfer/department
POST /agent/tickets/{id}/merge
POST /agent/tickets/bulk
GET  /agent/tickets/{id}/audit
GET  /agent/tickets/{id}/report
GET  /agent/saved-replies
POST /agent/saved-replies
GET  /agent/saved-replies/{id}/edit
PUT  /agent/saved-replies/{id}
DELETE /agent/saved-replies/{id}
GET  /agent/profile
GET  /agent/performance
GET  /agent/security
GET  /agent/notifications/preferences
```

#### Dept Admin (`/admin`)
```
GET  /admin/dashboard
GET  /admin/team
POST /admin/team/invite
GET  /admin/team/{id}
DELETE /admin/team/{id}
GET  /admin/routing-rules
PUT  /admin/routing-rules
GET  /admin/forms
GET  /admin/forms/create
POST /admin/forms
GET  /admin/forms/{id}/edit
PUT  /admin/forms/{id}
DELETE /admin/forms/{id}
GET  /admin/forms/{id}/preview
GET  /admin/forms/{id}/submissions
GET  /admin/sla
GET  /admin/sla/{priority}/edit
PUT  /admin/sla/{priority}
GET  /admin/reports/overview
GET  /admin/reports/agents
GET  /admin/reports/categories
GET  /admin/reports/sla-breaches
GET  /admin/reports/transfers
GET  /admin/reports/peak-hours
```

#### Super Admin (`/superadmin`)
```
GET  /superadmin/dashboard
GET  /superadmin/alerts

GET  /superadmin/settings
PUT  /superadmin/settings
GET  /superadmin/settings/email
PUT  /superadmin/settings/email
POST /superadmin/settings/email/test
GET  /superadmin/settings/sms
PUT  /superadmin/settings/sms
GET  /superadmin/settings/locale
PUT  /superadmin/settings/locale

GET  /superadmin/departments
GET  /superadmin/departments/create
POST /superadmin/departments
GET  /superadmin/departments/{id}
GET  /superadmin/departments/{id}/edit
PUT  /superadmin/departments/{id}
POST /superadmin/departments/{id}/deactivate

GET  /superadmin/users
GET  /superadmin/users/create
POST /superadmin/users
GET  /superadmin/users/{id}
GET  /superadmin/users/{id}/edit
PUT  /superadmin/users/{id}
POST /superadmin/users/import
GET  /superadmin/roles
PUT  /superadmin/roles
GET  /superadmin/users/deactivated

GET  /superadmin/sso
PUT  /superadmin/sso
GET  /superadmin/sso/test
GET  /superadmin/sso/groups
PUT  /superadmin/sso/groups
GET  /superadmin/sso/provisioning
PUT  /superadmin/sso/provisioning
GET  /superadmin/2fa/policy
PUT  /superadmin/2fa/policy
GET  /superadmin/2fa/methods
PUT  /superadmin/2fa/methods
GET  /superadmin/2fa/adoption
POST /superadmin/2fa/reset/{userId}

GET  /superadmin/security/sessions
DELETE /superadmin/security/sessions/{id}
GET  /superadmin/security/ip-allowlist
POST /superadmin/security/ip-allowlist
DELETE /superadmin/security/ip-allowlist/{id}
GET  /superadmin/security/password-policy
PUT  /superadmin/security/password-policy
GET  /superadmin/security/lockout-policy
PUT  /superadmin/security/lockout-policy
GET  /superadmin/security/data-retention
PUT  /superadmin/security/data-retention

GET  /superadmin/reports/{type}  → {type}: overview|cross-dept|comparison|leaderboard|trends|sla-breach|aging|bottlenecks
GET  /superadmin/reports/scheduled
POST /superadmin/reports/scheduled
DELETE /superadmin/reports/scheduled/{id}
GET  /superadmin/reports/exports

GET  /superadmin/templates/email
GET  /superadmin/templates/email/{id}/edit
PUT  /superadmin/templates/email/{id}
GET  /superadmin/templates/sms
GET  /superadmin/templates/sms/{id}/edit
PUT  /superadmin/templates/sms/{id}
GET  /superadmin/templates/inapp
PUT  /superadmin/templates/inapp/{id}

GET  /superadmin/kb
GET  /superadmin/kb/categories
POST /superadmin/kb/categories
GET  /superadmin/kb/articles/create
POST /superadmin/kb/articles
GET  /superadmin/kb/articles/{id}/edit
PUT  /superadmin/kb/articles/{id}
DELETE /superadmin/kb/articles/{id}
GET  /superadmin/kb/analytics

GET  /superadmin/audit
GET  /superadmin/audit/auth
GET  /superadmin/audit/admin-actions

GET  /superadmin/api-keys
POST /superadmin/api-keys
DELETE /superadmin/api-keys/{id}

GET  /superadmin/webhooks
POST /superadmin/webhooks
PUT  /superadmin/webhooks/{id}
DELETE /superadmin/webhooks/{id}
GET  /superadmin/webhooks/deliveries

GET  /superadmin/integrations/teams
PUT  /superadmin/integrations/teams
GET  /superadmin/api-docs

GET  /superadmin/system/health
GET  /superadmin/system/storage
GET  /superadmin/system/queue
GET  /superadmin/system/backups
POST /superadmin/system/backups/run
POST /superadmin/system/backups/{id}/restore

GET  /superadmin/profile
PUT  /superadmin/profile
GET  /superadmin/profile/security
PUT  /superadmin/profile/security
GET  /superadmin/profile/audit-trail
```

---

## 8. DIRECTORY STRUCTURE

```
app/
  Console/
    Commands/
      ProcessSlaBreaches.php    → Scheduled: checks and escalates breached tickets
      SendScheduledReports.php  → Scheduled: sends weekly/monthly email reports
      CleanExpiredOtps.php      → Scheduled: purges expired OTP codes
  Enums/
    TicketStatus.php
    Priority.php
    UserRole.php
    TransferType.php
    TwoFactorMethod.php
  Events/
    TicketCreated.php
    TicketReplied.php
    TicketStatusChanged.php
    TicketTransferred.php
    TicketEscalated.php
    TicketResolved.php
    TicketClosed.php
    SlaBreached.php
    SlaNearBreach.php
  Http/
    Controllers/
      Auth/
        LoginController.php
        SsoController.php          → Azure AD OIDC callback
        TwoFactorController.php
        LogoutController.php
      Employee/
        DashboardController.php
        TicketController.php
        KnowledgeBaseController.php
        ProfileController.php
        SurveyController.php
        NotificationController.php
      Agent/
        DashboardController.php
        TicketQueueController.php
        TicketReplyController.php
        TicketTransferController.php
        TicketMergeController.php
        BulkActionController.php
        SavedReplyController.php
        PerformanceController.php
        AgentProfileController.php
      Admin/
        TeamController.php
        FormBuilderController.php
        SlaController.php
        ReportController.php
        RoutingRuleController.php
      SuperAdmin/
        DashboardController.php
        OrgSettingsController.php
        DepartmentController.php
        UserController.php
        SsoController.php
        TwoFactorPolicyController.php
        SecurityController.php
        ReportController.php
        TemplateController.php
        KnowledgeBaseController.php
        AuditController.php
        ApiKeyController.php
        WebhookController.php
        IntegrationController.php
        SystemController.php
        ProfileController.php
      Api/V2/
        TicketApiController.php
        UserApiController.php
        DepartmentApiController.php
        ReportApiController.php
    Middleware/
      RoleMiddleware.php          → Checks user role, redirects unauthorized
      EnsureTwoFactorPassed.php   → Checks if 2FA challenge completed this session
      EnsureSsoLinked.php         → Redirects SSO-required users to SSO login
      AuditLogMiddleware.php      → Logs every authenticated request
      ApiKeyMiddleware.php        → Validates API key for /api/* routes
    Requests/
      StoreTicketRequest.php
      ReplyTicketRequest.php
      TransferTicketRequest.php
      StoreFormRequest.php
      StoreUserRequest.php
      StoreDepartmentRequest.php
      UpdateSlaRequest.php
  Jobs/
    SendTicketNotification.php
    SendSmsOtp.php
    SendEmailOtp.php
    DeliverWebhook.php
    GenerateScheduledReport.php
    SyncScimUsers.php
    ProcessSlaAutoEscalation.php
  Listeners/
    SendTicketCreatedNotifications.php
    SendTicketRepliedNotifications.php
    SendTicketTransferredNotifications.php
    LogSlaBreachEvent.php
    TriggerWebhooksForEvent.php
  Mail/
    TicketCreatedMail.php
    TicketReplyMail.php
    TicketResolvedMail.php
    OtpMail.php
    WelcomeMail.php
    SlaBreachAlertMail.php
    ScheduledReportMail.php
  Models/
    User.php
    Department.php
    Ticket.php
    TicketReply.php
    TicketAttachment.php
    TicketTransfer.php
    TicketStatusHistory.php
    TicketTimeLog.php
    Form.php
    SlaPolicy.php
    SavedReply.php
    Notification.php
    AuditLog.php
    KbArticle.php
    KbCategory.php
    CsatSurvey.php
    NotificationTemplate.php
    ApiKey.php
    Webhook.php
    WebhookDelivery.php
    OtpCode.php
  Notifications/
    TicketUpdated.php            → Laravel Notification class (email + database)
    SlaBreachNotification.php
  Policies/
    TicketPolicy.php
    FormPolicy.php
    UserPolicy.php
    DepartmentPolicy.php
    KbArticlePolicy.php
  Providers/
    AppServiceProvider.php
    AuthServiceProvider.php      → Registers all Policies + Gates
    EventServiceProvider.php     → Maps Events → Listeners
  Services/
    SlaService.php               → Calculate SLA due dates, check breaches
    TicketNumberService.php      → Generate GT-YYYY-NNNN ticket numbers
    FormBuilderService.php       → Validate and render dynamic form schemas
    TwoFactorService.php         → Generate, send, verify OTP codes
    SsoService.php               → Azure AD OIDC flow handling
    ReportService.php            → Build report data arrays for views
    NotificationService.php      → Dispatch multi-channel notifications
    WebhookService.php           → Dispatch webhook deliveries
    ScimService.php              → User provisioning via Azure SCIM
    StorageService.php           → S3/Azure Blob upload/download abstraction

database/
  migrations/
    2025_01_01_000001_create_users_table.php
    2025_01_01_000002_create_departments_table.php
    2025_01_01_000003_create_sla_policies_table.php
    2025_01_01_000004_create_forms_table.php
    2025_01_01_000005_create_tickets_table.php
    2025_01_01_000006_create_ticket_replies_table.php
    2025_01_01_000007_create_ticket_attachments_table.php
    2025_01_01_000008_create_ticket_transfers_table.php
    2025_01_01_000009_create_ticket_status_history_table.php
    2025_01_01_000010_create_ticket_time_logs_table.php
    2025_01_01_000011_create_saved_replies_table.php
    2025_01_01_000012_create_audit_logs_table.php
    2025_01_01_000013_create_kb_categories_table.php
    2025_01_01_000014_create_kb_articles_table.php
    2025_01_01_000015_create_csat_surveys_table.php
    2025_01_01_000016_create_notification_templates_table.php
    2025_01_01_000017_create_api_keys_table.php
    2025_01_01_000018_create_webhooks_table.php
    2025_01_01_000019_create_webhook_deliveries_table.php
    2025_01_01_000020_create_otp_codes_table.php
    2025_01_01_000021_create_notifications_table.php
  seeders/
    DatabaseSeeder.php
    DepartmentSeeder.php
    SlaPolicySeeder.php
    NotificationTemplateSeeder.php
    SuperAdminSeeder.php

resources/
  views/
    layouts/
      app.blade.php              → Base layout (head, CSS, JS)
      employee.blade.php         → Employee portal layout (sidebar + topbar)
      agent.blade.php            → Agent portal layout
      admin.blade.php            → Dept admin layout
      superadmin.blade.php       → Super admin layout
      auth.blade.php             → Auth pages (no sidebar)
    auth/
      login.blade.php
      sso-callback.blade.php
      two-factor.blade.php
      forgot-password.blade.php
      reset-password.blade.php
      account-locked.blade.php
    employee/
      dashboard.blade.php
      tickets/index.blade.php
      tickets/create.blade.php
      tickets/show.blade.php
      tickets/confirmation.blade.php
      kb/index.blade.php
      kb/category.blade.php
      kb/article.blade.php
      profile/index.blade.php
      profile/security.blade.php
      survey/show.blade.php
    agent/
      dashboard.blade.php
      queue/index.blade.php
      queue/all.blade.php
      queue/unassigned.blade.php
      queue/escalated.blade.php
      tickets/show.blade.php
      tickets/reply.blade.php
      saved-replies/index.blade.php
      performance.blade.php
      profile.blade.php
    admin/
      dashboard.blade.php
      team/index.blade.php
      forms/index.blade.php
      forms/builder.blade.php
      forms/preview.blade.php
      sla/index.blade.php
      reports/index.blade.php
    superadmin/
      dashboard.blade.php
      settings/general.blade.php
      settings/email.blade.php
      settings/sms.blade.php
      settings/locale.blade.php
      departments/index.blade.php
      departments/create.blade.php
      departments/edit.blade.php
      departments/show.blade.php
      users/index.blade.php
      users/create.blade.php
      users/edit.blade.php
      users/show.blade.php
      sso/settings.blade.php
      sso/test.blade.php
      sso/groups.blade.php
      2fa/policy.blade.php
      2fa/adoption.blade.php
      security/sessions.blade.php
      security/ip-allowlist.blade.php
      reports/show.blade.php
      templates/email.blade.php
      templates/sms.blade.php
      kb/index.blade.php
      kb/article-editor.blade.php
      audit/index.blade.php
      api-keys/index.blade.php
      webhooks/index.blade.php
      integrations/teams.blade.php
      system/health.blade.php
      system/backups.blade.php
      profile.blade.php
    components/
      ticket-status-badge.blade.php
      priority-badge.blade.php
      sla-bar.blade.php
      stat-card.blade.php
      ticket-row.blade.php
      user-avatar.blade.php
      notification-bell.blade.php
      pagination.blade.php
      filter-bar.blade.php
      upload-zone.blade.php
  js/
    app.js
    ticket-form.js              → Dynamic form renderer
    form-builder.js             → Drag-and-drop form builder canvas
    notifications.js            → Real-time notification polling
    sla-countdown.js            → Live SLA countdown timers
  css/
    gdesk.css                   → Shared design system (all CSS variables + components)

public/
  assets/
    css/gdesk.css               → Compiled/copied from resources/css/gdesk.css
    js/
    img/
      logo.svg
      logo-white.svg

config/
  gdesk.php                     → App-level config (ticket prefix, SLA defaults, features)
  sso.php                       → Azure AD / OIDC config
  twilio.php                    → Twilio SMS config
```

---

## 9. AUTHENTICATION & SSO IMPLEMENTATION

### 9.1 Login Flow
1. User visits `/login` — shown email + password form + "Sign in with Microsoft" button
2. If SSO-only mode is on: redirect directly to `/auth/sso`
3. After successful login (either method): check if 2FA is required
4. If 2FA required: redirect to `/auth/two-factor` — user enters OTP
5. After 2FA passed: set `session('2fa_passed', true)`, redirect to appropriate dashboard

### 9.2 Azure AD SSO
```php
// Use socialite-like pattern with custom Azure provider
// Package: steverhoades/oauth2-azure or custom OIDC implementation
// config/sso.php keys:
// azure_tenant_id, azure_client_id, azure_client_secret, azure_redirect_uri

// On callback:
// 1. Exchange code for tokens
// 2. Decode ID token, get email + display_name + groups
// 3. Find or create User record
// 4. Map Azure groups to GDesk roles using group_mapping config
// 5. Continue to 2FA if required
```

### 9.3 2FA Implementation
```php
// OTP generation → store hashed in otp_codes table → dispatch SMS/Email job
// TOTP: use pragmarx/google2fa package
// Verify: compare submitted code, check not expired, check not already used
// On success: session(['2fa_passed' => true, '2fa_passed_at' => now()])
// EnsureTwoFactorPassed middleware checks this session key
```

---

## 10. NOTIFICATION SYSTEM

### Channels per event
Every notification dispatches a Laravel Job that:
1. Looks up the `notification_templates` table for the correct template + language
2. Replaces merge fields: `{{ticket_id}}`, `{{ticket_subject}}`, `{{agent_name}}`, `{{department_name}}`, `{{portal_url}}`, `{{org_name}}`, `{{sla_due_at}}`, `{{employee_name}}`
3. Sends via appropriate channel (email via Mail, SMS via Twilio, in-app via `notifications` table insert)

### Merge Fields Reference
```
{{ticket_id}}          → e.g. GT-2025-0892
{{ticket_subject}}     → Ticket subject line
{{ticket_url}}         → Full URL to ticket in portal
{{ticket_status}}      → Current status label
{{ticket_priority}}    → P1/P2/P3/P4
{{employee_name}}      → Submitter's full name
{{employee_email}}     → Submitter's email
{{agent_name}}         → Assigned agent's full name
{{department_name}}    → Current department name
{{org_name}}           → Organization name from settings
{{portal_url}}         → App base URL
{{support_email}}      → From settings
{{sla_due_at}}         → Formatted SLA deadline datetime
{{sla_remaining}}      → Human-readable time remaining
{{created_at}}         → Ticket creation datetime
{{otp_code}}           → 2FA one-time password (only for OTP templates)
```

---

## 11. DYNAMIC FORM SYSTEM

### Form Schema (JSON stored in `forms.fields`)
```json
{
  "fields": [
    {
      "id": "field_abc123",
      "type": "dropdown",
      "label": "Issue Category",
      "required": true,
      "options": ["Hardware Problem", "Software / App", "Network / VPN", "Access"],
      "help_text": "",
      "conditional": null
    },
    {
      "id": "field_def456",
      "type": "short_text",
      "label": "Ticket Subject",
      "required": true,
      "min_length": 5,
      "max_length": 200,
      "conditional": {
        "show_if_field": "field_abc123",
        "show_if_value": "Hardware Problem"
      }
    },
    {
      "id": "field_ghi789",
      "type": "file_upload",
      "label": "Attachment",
      "required": false,
      "allowed_types": ["image/png", "image/jpeg", "application/pdf"],
      "max_size_mb": 10,
      "max_files": 5
    }
  ]
}
```

### Supported Field Types
`short_text` | `long_text` | `dropdown` | `multi_select` | `radio` | `checkbox` | `date` | `date_range` | `file_upload` | `number` | `email` | `phone` | `divider`

### Form Rendering
- `FormBuilderService::renderFormHtml($form)` → outputs Blade-compatible HTML
- `FormBuilderService::validateSubmission($form, $data)` → validates against schema, returns errors
- `FormBuilderService::applyConditionalLogic($form, $data)` → filters visible fields based on values
- Form data stored as JSON in `tickets.form_data`
- Old submissions always reference the form version at time of submission (`forms.version`)

---

## 12. SLA SERVICE

```php
// app/Services/SlaService.php

class SlaService {
    // Calculate SLA due date when ticket is created or priority changes
    public function calculateDueDate(Ticket $ticket): Carbon;

    // Check if ticket is breached
    public function isBreached(Ticket $ticket): bool;

    // Check near-breach (default: 80% of time elapsed)
    public function isNearBreach(Ticket $ticket, float $threshold = 0.8): bool;

    // Pause SLA (on_hold, pending, in transfer)
    public function pauseClock(Ticket $ticket): void;

    // Resume SLA clock
    public function resumeClock(Ticket $ticket): void;

    // Get remaining time as human-readable string
    public function getRemainingTime(Ticket $ticket): string;

    // Get percentage elapsed (for SLA bar display)
    public function getElapsedPercentage(Ticket $ticket): float;
}
```

### Scheduled Command
```php
// app/Console/Commands/ProcessSlaBreaches.php
// Runs every 5 minutes via scheduler
// 1. Find tickets where sla_due_at < NOW() and status not in [resolved, closed]
// 2. For P1: auto-escalate (change status to 'escalated', notify Dept Admin via SMS+email)
// 3. For all: dispatch SlaBreached event → triggers notifications + webhook
// 4. Find near-breach tickets (80% threshold) → dispatch SlaNearBreach event
```

---

## 13. CODING STANDARDS

### PHP / Laravel
- Always use **typed properties** and **return types** in PHP 8.3
- Use **Enums** for all status/type/role constants (PHP 8.1+ backed enums)
- Use **Form Request** classes for all validation — never validate in controllers directly
- Use **Resource classes** for all API responses (`php artisan make:resource`)
- Use **Service classes** for business logic — keep controllers thin (10-20 lines max)
- Use **Policy classes** for authorization — never check roles inline in controllers
- Always use **Eloquent relationships** with eager loading (`with()`) to avoid N+1
- Use `DB::transaction()` for any multi-step write operations (e.g. ticket transfer)
- Soft delete tickets with `SoftDeletes` trait
- All timestamps stored in UTC; convert to org timezone in views using `app('settings')->timezone`

### Blade Templates
- Extend the correct layout: `@extends('layouts.employee')`, `@extends('layouts.agent')`, etc.
- Use `@can('permission', $model)` for all permission checks in views
- Use Blade components for repeated UI: `<x-ticket-status-badge :status="$ticket->status" />`
- Never put business logic in Blade files — only display logic
- Use `{{ }}` for escaped output, `{!! !!}` only for trusted HTML (e.g. KB article body)

### JavaScript
- Vanilla JS only — no jQuery, no npm build pipeline for the main app
- Use `fetch()` for AJAX calls with CSRF token header: `'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content`
- All interactive screens use event delegation on `document` not direct element listeners
- SLA countdowns update every 30 seconds using `setInterval`

### Migrations
- Each migration file has both `up()` and `down()` methods
- Add foreign key indexes explicitly: `$table->index('department_id')`
- Use `->after('column')` for column ordering clarity
- Never modify existing migration files — always create new ones

### API (REST)
- Base URL: `/api/v2/`
- Authentication: Bearer token (API key) in `Authorization` header
- Always return JSON with consistent structure:
```json
{
  "success": true,
  "data": { ... },
  "meta": { "total": 100, "page": 1 }
}
```
- Errors:
```json
{
  "success": false,
  "message": "Descriptive error message",
  "errors": { "field": ["validation error"] }
}
```
- Rate limit headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`

---

## 14. ENVIRONMENT VARIABLES (.env)

```env
# App
APP_NAME="GDesk"
APP_URL=https://gdesk.gulftech.sa
APP_TIMEZONE=Asia/Riyadh
APP_LOCALE=en

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=gdesk
DB_USERNAME=gdesk_user
DB_PASSWORD=

# Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.office365.com
MAIL_PORT=587
MAIL_USERNAME=noreply@gulftech.sa
MAIL_PASSWORD=
MAIL_FROM_ADDRESS=support@gulftech.sa
MAIL_FROM_NAME="GDesk Support"
MAIL_ENCRYPTION=tls

# SMS (Twilio)
TWILIO_SID=
TWILIO_TOKEN=
TWILIO_FROM=

# Azure AD SSO
AZURE_TENANT_ID=
AZURE_CLIENT_ID=
AZURE_CLIENT_SECRET=
AZURE_REDIRECT_URI="${APP_URL}/auth/sso/callback"

# File Storage
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=me-south-1
AWS_BUCKET=gdesk-attachments
AWS_URL=

# GDesk App Config
GDESK_TICKET_PREFIX=GT
GDESK_SSO_FORCED=false
GDESK_2FA_REQUIRED_FOR=agents,admins
GDESK_AUTO_CLOSE_DAYS=7
GDESK_MAX_ATTACHMENT_MB=10
GDESK_CSAT_DELAY_HOURS=24
```

---

## 15. KEY BUSINESS LOGIC RULES

1. **Ticket Numbers**: Format `GT-YYYY-NNNN` (prefix configurable). Auto-increment per year, zero-padded to 4 digits. Prefix from `config('gdesk.ticket_prefix')`.

2. **Internal Notes**: Replies with `is_internal = true` are NEVER shown to the ticket submitter. Only agents and admins of the department see them.

3. **Form Data Immutability**: Once a ticket is submitted, `tickets.form_data` is never modified. The form schema at submission time is snapshotted in `tickets.form_id` + version.

4. **Cross-Dept Transfer**: When a ticket transfers between departments, the receiving dept admin must explicitly accept it. Until accepted, SLA clock is paused.

5. **Merge Tickets**: When tickets are merged, the secondary ticket gets `merged_into_ticket_id` set, status set to `closed`, and all its replies/attachments are linked to the primary ticket. The secondary ticket is soft-deleted.

6. **Auto-Close**: A cron job runs daily to close `resolved` tickets that haven't been reopened within `config('gdesk.auto_close_days')` days.

7. **CSAT Survey**: Triggered `config('gdesk.csat_delay_hours')` hours after ticket closure. One unique token per ticket. Survey can only be submitted once.

8. **Routing Rules**: Round-robin assignment considers only agents who are `is_active = true` and whose open ticket count is below `config('gdesk.max_tickets_per_agent', 15)`.

9. **Audit Log**: The `AuditLogMiddleware` logs every POST/PUT/PATCH/DELETE request with before/after values. The `AuditLog::record()` static helper is used in services for semantic events.

10. **Data Residency**: All file uploads go to the configured S3 bucket. Never store user data in `public/` except compiled assets.

---

## 16. SCHEDULED TASKS (app/Console/Kernel.php)

```php
$schedule->command('gdesk:process-sla-breaches')->everyFiveMinutes();
$schedule->command('gdesk:auto-close-tickets')->dailyAt('02:00');
$schedule->command('gdesk:send-scheduled-reports')->hourly();
$schedule->command('gdesk:clean-expired-otps')->hourly();
$schedule->command('queue:work --stop-when-empty')->everyMinute()->withoutOverlapping();
```

---

## 17. HTML PROTOTYPE REFERENCE

A complete **136-screen HTML prototype** of the full application exists at `prototype/`:
```
prototype/
  index.html          → Master hub (start here)
  assets/gdesk.css    → Full design system
  frontend/           → 7 files, 30 screens (Employee portal)
  backend/            → 7 files, 40 screens (Agent portal)
  superadmin/         → 12 files, 66 screens (Super Admin panel)
```

**Always reference the prototype** for:
- Exact UI layout, component structure, and field names
- Screen flow and navigation patterns
- Sidebar structure and active states per section
- Form field labels and placeholder text
- Table column names and sort order
- Dashboard KPI cards and their data sources
- Status badge colors and text

The prototype CSS (`gdesk.css`) is the **single source of truth** for all design tokens. Do not deviate from it.

---

## 18. TESTING

```
tests/
  Unit/
    SlaServiceTest.php
    FormBuilderServiceTest.php
    TicketNumberServiceTest.php
    TwoFactorServiceTest.php
  Feature/
    Auth/
      LoginTest.php
      SsoTest.php
      TwoFactorTest.php
    Employee/
      SubmitTicketTest.php
      ViewTicketTest.php
      ReplyTicketTest.php
    Agent/
      TicketQueueTest.php
      TransferTicketTest.php
      BulkActionTest.php
    Admin/
      FormBuilderTest.php
      SlaConfigTest.php
    SuperAdmin/
      DepartmentManagementTest.php
      UserManagementTest.php
      AuditLogTest.php
    Api/
      TicketApiTest.php
      AuthApiTest.php
```

- Use `RefreshDatabase` trait for Feature tests
- Factory classes for all models: `php artisan make:factory`
- Test SLA calculations with fixed `Carbon::setTestNow()` dates
- Mock Twilio + Mail in tests using `Mail::fake()` and service mocking

---

## 19. SECURITY CHECKLIST

Every feature must satisfy:
- [ ] Route protected by correct `RoleMiddleware`
- [ ] Controller action uses `$this->authorize()` via Policy
- [ ] Form inputs validated via Form Request class
- [ ] No direct `$request->all()` → always use `$request->validated()`
- [ ] File uploads: validate MIME type + size server-side (never trust client)
- [ ] API endpoints: rate-limited via `throttle:api` middleware
- [ ] Sensitive data (API keys, 2FA secrets): encrypted in DB using Laravel's `Crypt` or `Hash`
- [ ] SQL: always Eloquent or `DB::select()` with bindings — zero raw concatenated queries
- [ ] XSS: always `{{ }}` in Blade, never `{!! !!}` on user-submitted content
- [ ] CSRF: all forms use `@csrf`, all AJAX uses X-CSRF-TOKEN header

---

## 20. CONTACT & SUPPORT

**GulfTech Saudi Arabia**
- Website: www.gulftech.sa
- Email: info@gulftech.sa
- Phone: +966 560 635 771 · +966 569 158 569
- Product: GDesk v1.0

*This rules file is the single source of truth for all Cursor AI agent development decisions on the GDesk project.*
