# 🚀 خطة المرحلة السادسة — النسخة النهائية المحسومة
# Apex Logistics Dashboard — Phase 6 Final Implementation Plan

> **آخر تحديث:** 2026-08-01
> **حالة الأسئلة المفتوحة:** ✅ كلها محسومة — لا توجد أسئلة معلقة

---

## ✅ القرارات المحسومة

| السؤال | القرار النهائي |
|--------|---------------|
| Redis | `predis/predis` + Docker (الـ `compose.yaml` فيه Redis أصلاً!) |
| WebSocket | Laravel Reverb (رسمي، مجاني) |
| خريطة التتبع | Leaflet.js + OpenStreetMap (مجاني، بدون API Keys) |
| Push Notifications | Firebase Cloud Messaging (FCM) — تنفيذ فوري وكامل |

---

## 📊 حالة المشروع الحالية

> [!NOTE]
> ### المرحلة الخامسة مكتملة ✅
> كل التيكتات السبعة منفذة. كل الملفات موجودة ومُصلَّحة.

### البنية الحالية الجاهزة:
| المكوّن | الحالة | ملاحظات |
|---------|--------|--------|
| Docker Compose + Redis | ✅ جاهز | `compose.yaml` فيه service `redis` بالفعل |
| `.env` REDIS_HOST | ⚠️ يشير لـ `redis` (اسم Docker service) | سنغيره لـ `127.0.0.1` عند التشغيل بدون Sail |
| `.env` REDIS_CLIENT | ⚠️ `phpredis` | سنغيره لـ `predis` |
| Broadcasting config | ✅ `reverb` connection موجود | بس `BROADCAST_CONNECTION=log` حالياً |
| `routes/channels.php` | ❌ غير موجود | سيُنشأ |
| `resources/js/echo.js` | ❌ غير موجود | سيُنشأ |
| `laravel-echo` / `pusher-js` | ❌ مش في `package.json` | سيُثبت |
| FCM | ❌ لا يوجد أي بنية | سيُبنى بالكامل |
| AdminPanelProvider | ✅ `discoverWidgets` مفعّل | أي Widget في `app/Filament/Widgets/` يتسجل أوتوماتيك |
| LocationController | ✅ يخزن في Cache | يحتاج إضافة broadcast فقط |

---

## 📋 هيكل التيكتات (9 تيكتات)

| التيكت | الوصف | الوقت | الاعتماديات |
|--------|-------|------|-------------|
| **1** | Redis عبر Docker + predis | ساعة | — |
| **2** | بنية WebSockets (Reverb + Events + Channels) | 3 ساعات | 1 |
| **3** | دمج Broadcasting مع الكود الحالي | ساعتين | 2 |
| **4** | Firebase Cloud Messaging (FCM) | 3 ساعات | 1 |
| **5** | خريطة التتبع الحي (Live Map) | 3 ساعات | 2, 3 |
| **6** | محرك التوزيع الآلي (Auto-Dispatch) | 4 ساعات | 1, 2, 3 |
| **7** | حماية التوزيع والتكامل | ساعتين | 6 |
| **8** | Widgets وإحصائيات KPIs | 3 ساعات | 1 |
| **9** | الرسوم البيانية (Charts) | ساعتين | 8 |
| | **الإجمالي** | **~24 ساعة** | |

> [!TIP]
> **تيكتات 4, 8, 9 مستقلة عن 5, 6, 7** — يمكن العمل عليها بالتوازي.

---

## Proposed Changes

---

### 🎟️ التيكت 1: Redis عبر Docker + predis

**الهدف:** تشغيل Redis وتحويل Cache/Queue إليه.
**الاعتماديات:** لا شيء.
**التقدير الزمني:** ساعة.

---

#### 1.1 — تشغيل Redis من Docker

> [!IMPORTANT]
> الـ [compose.yaml](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/compose.yaml) فيه Redis **أصلاً**!
> مش محتاجين نعدل فيه ولا نضيف حاجة.

**الأمر المطلوب من المستخدم:**
```bash
cd "D:\Important Projects\Apex_Logistics\Dashboard"
docker compose up -d redis
```

**فحص:**
```bash
docker compose exec redis redis-cli ping
# → PONG
```

---

#### 1.2 — تثبيت predis/predis

```bash
composer require predis/predis
```

---

#### 1.3 — تعديل `.env`

#### [MODIFY] [.env](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/.env)

```diff
- REDIS_CLIENT=phpredis
+ REDIS_CLIENT=predis

- REDIS_HOST=redis
+ REDIS_HOST=127.0.0.1

- CACHE_STORE=file
+ CACHE_STORE=redis

- QUEUE_CONNECTION=sync
+ QUEUE_CONNECTION=redis
```

**⚠️ ملاحظة دقيقة:** الـ `REDIS_HOST` حالياً `redis` (اسم Docker service). لما نشغل `php artisan serve` من خارج Docker، لازم يكون `127.0.0.1` عشان PHP يتصل بـ Redis المعروض على `localhost:6379`. لو شغلنا عبر Sail، يبقى `redis` صح.

**⚠️ ممنوع تغيير:**
- `SESSION_DRIVER` — يفضل `database` (تحويله لـ Redis هيطرد كل الجلسات)
- `DB_CONNECTION` — يفضل `sqlite`

---

#### 1.4 — فحص التيكت الأولى

| الفحص | الأمر | النتيجة المتوقعة |
|-------|-------|----------------|
| Redis متصل | `php artisan tinker --execute="Cache::put('p6test', 'ok', 60); echo Cache::get('p6test');"` | `ok` |
| Queue شغال | `php artisan queue:work --once --stop-when-empty` | بدون Error |
| Lock شغال عبر Redis | افتح طلب في EditOrder → فحص Redis key | `redis-cli GET "order_lock:{id}"` يرجع الـ user ID |
| الداشبورد شغال | `php artisan serve` | بدون أخطاء |

#### المخاطر:
| المخاطرة | الاحتمال | الحل |
|---------|---------|------|
| Docker مش شغال | منخفض | المستخدم أكد إن Docker متاح |
| Port 6379 مشغول | منخفض | `docker compose down` أولاً ثم `up` |
| `predis` مش متوافق | منخفض جداً | تم اختباره مع Laravel 12 |

---

### 🎟️ التيكت 2: بنية WebSockets (Reverb + Events + Channels)

**الهدف:** تسطيب Reverb، إنشاء Events، تسجيل Channels، إعداد Echo في الـ Frontend.
**الاعتماديات:** التيكت 1.
**التقدير الزمني:** 3 ساعات.

---

#### 2.1 — تثبيت Laravel Reverb

```bash
composer require laravel/reverb
php artisan reverb:install
```

هذا الأمر سيضيف تلقائياً في `.env`:
```
REVERB_APP_ID=...
REVERB_APP_KEY=...
REVERB_APP_SECRET=...
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
```

#### [MODIFY] [.env](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/.env)
```diff
- BROADCAST_CONNECTION=log
+ BROADCAST_CONNECTION=reverb
```

---

#### 2.2 — تثبيت Frontend Dependencies

```bash
npm install --save-dev laravel-echo pusher-js
```

---

#### 2.3 — إنشاء Echo Setup

#### [NEW] `resources/js/echo.js`
```javascript
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 8080,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});
```

#### [MODIFY] [resources/js/bootstrap.js](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/resources/js/bootstrap.js)
- إضافة `import './echo';` في آخر الملف

---

#### 2.4 — إنشاء الـ Events

#### [NEW] `app/Events/OrderStatusChanged.php`
- `implements ShouldBroadcastNow`
- Properties: `$orderId`, `$newStatus`, `$oldStatus`, `$driverId`, `$batchId`, `$orderType`
- `broadcastOn()`: `[new PrivateChannel("admin.dashboard")]` + `new PrivateChannel("driver.{$this->driverId}")` (لو دايفر موجود) + `new PrivateChannel("order.{$this->orderId}")`
- `broadcastAs()`: `'order.status.changed'`
- `broadcastWith()`: كل الـ Properties + `'timestamp' => now()->toISOString()`

#### [NEW] `app/Events/NewOrderCreated.php`
- `implements ShouldBroadcastNow`
- Properties: `$orderId`, `$orderType`, `$customerName`
- `broadcastOn()`: `[new PrivateChannel("admin.dashboard")]`
- `broadcastAs()`: `'order.created'`

#### [NEW] `app/Events/DriverLocationUpdated.php`
- `implements ShouldBroadcastNow`
- Properties: `$driverId`, `$latitude`, `$longitude`, `$driverName`, `$activeBatchId`, `$activeOrdersCount`
- `broadcastOn()`: `[new PrivateChannel("admin.dashboard")]`
- `broadcastAs()`: `'driver.location.updated'`
- **⚠️ لا يبث على قنوات عامة** — المشرف بس يشوف مواقع المناديب

---

#### 2.5 — إنشاء Channel Authorization

#### [MODIFY] [routes/channels.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/routes/channels.php)
> **⚠️ تصحيح:** الملف موجود أصلاً (8 أسطر). المحتوى أدناه **يُضاف بعد** المحتوى الحالي — لا يستبدله.
```php
<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('driver.{id}', function ($user, $id) {
    return $user->driver && $user->driver->id === (int) $id;
});

Broadcast::channel('admin.dashboard', function ($user) {
    return $user->hasRole('super_admin');
});

Broadcast::channel('order.{id}', function ($user, $id) {
    if ($user->hasRole('super_admin')) return true;
    if ($user->driver) {
        return \App\Models\Order::where('id', $id)
            ->where('driver_id', $user->driver->id)->exists();
    }
    return false;
});
```

---

#### 2.6 — فحص التيكت الثانية

| الفحص | الأمر | النتيجة |
|-------|-------|---------|
| Reverb يشتغل | `php artisan reverb:start` (في terminal منفصل) | يطبع "Starting server..." بدون Error |
| Events تتبث | `php artisan tinker --execute="broadcast(new App\Events\NewOrderCreated(1, 'standard', 'Test'));"` | بدون Error |
| Vite يبني | `npm run build` | بدون Error |

#### المخاطر:
| المخاطرة | الحل |
|---------|------|
| Port 8080 مشغول | غيّر `REVERB_PORT` في `.env` |
| `reverb:install` يفشل | فحص نسخة Laravel + PHP 8.2+ |

---

### 🎟️ التيكت 3: دمج Broadcasting مع الكود الحالي

**الهدف:** ربط الـ Events بالـ Observers والـ Controllers الموجودة.
**الاعتماديات:** التيكت 2.
**التقدير الزمني:** ساعتين.

---

#### 3.1 — ربط تغيير الحالة

#### [MODIFY] [app/Observers/OrderObserver.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Observers/OrderObserver.php)
**الموجود:** يراقب `isDirty('status')` ويعمل restock عند الإلغاء.
**المطلوب:** إضافة broadcast **بعد** بلوك الـ restock وقبل نهاية `updated()`:
```php
// === Broadcasting (Phase 6) ===
if ($order->isDirty('status')) {
    $oldStatus = $order->getOriginal('status');
    broadcast(new \App\Events\OrderStatusChanged(
        orderId: $order->id,
        newStatus: $order->status instanceof \App\Enums\OrderStatus ? $order->status->value : (string) $order->status,
        oldStatus: $oldStatus instanceof \App\Enums\OrderStatus ? $oldStatus->value : (string) $oldStatus,
        driverId: $order->driver_id,
        batchId: $order->batch_id,
        orderType: $order->order_type instanceof \App\Enums\OrderType ? $order->order_type->value : (string) $order->order_type,
    ));
}
```

**⚠️ دقة عالية:** `getOriginal('status')` ممكن يرجع Enum أو String حسب توقيت الاستدعاء. الكود أعلاه يتعامل مع الحالتين.

---

#### 3.2 — ربط إنشاء الطلب

#### [MODIFY] [app/Filament/Resources/Orders/Pages/CreateOrder.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Filament/Resources/Orders/Pages/CreateOrder.php)
**الموجود في `afterCreate()`:** PricingManager::snapshot().
**المطلوب إضافته بعده:**
```php
broadcast(new \App\Events\NewOrderCreated(
    orderId: $order->id,
    orderType: $order->order_type instanceof \App\Enums\OrderType ? $order->order_type->value : (string) $order->order_type,
    customerName: $order->customer?->name ?? 'غير محدد',
));
```

---

#### 3.3 — ربط تحديث الموقع

#### [MODIFY] [app/Http/Controllers/Api/Driver/LocationController.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Http/Controllers/Api/Driver/LocationController.php)
**الموجود (31 سطر):** يستخدم `Redis::setex()` مباشرة (مش `Cache::put`). المتغير `$driverId = $request->user()->driver->id` — مفيش متغير `$driver`.
**المطلوب (خطوتين):**
1. تغيير `$driverId = $request->user()->driver->id;` لـ `$driver = $request->user()->driver; $driverId = $driver->id;`
2. إضافة broadcast بعد `Redis::setex()`:
```php
broadcast(new \App\Events\DriverLocationUpdated(
    driverId: $driver->id,
    latitude: (float) $request->latitude,
    longitude: (float) $request->longitude,
    driverName: $driver->name ?? "Driver #{$driver->id}",
    activeBatchId: $driver->deliveryBatches()
        ->where('status', \App\Enums\BatchStatus::Active)
        ->first()?->id,
    activeOrdersCount: $driver->orders()
        ->whereNotIn('status', [
            \App\Enums\OrderStatus::Delivered->value,
            \App\Enums\OrderStatus::Cancelled->value,
        ])->count(),
));
```
> **⚠️ تصحيحات:** `$validated['latitude']` → `$request->latitude` | `$activeBatch?->id` → `$driver->deliveryBatches()->...->first()?->id`

---

#### 3.4 — إضافة Echo في الداشبورد

#### [MODIFY] [app/Providers/Filament/AdminPanelProvider.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Providers/Filament/AdminPanelProvider.php)
**المطلوب:** إضافة RenderHook ثاني بعد الـ Heartbeat hook الموجود لتحميل Echo وإظهار Toasts:
```php
->renderHook('panels::body.end', function () {
    // === Echo + Real-time Notifications (Phase 6) ===
    return view('filament.hooks.echo-notifications');
})
```

#### [NEW] `resources/views/filament/hooks/echo-notifications.blade.php`
- يحمّل Echo
- يستمع لـ `admin.dashboard` channel
- عند `order.created` → يعرض Toast "طلب جديد #{id}"
- عند `order.status.changed` → يعرض Toast "الطلب #{id} تغيّر لـ {status}"

---

### 🎟️ التيكت 4: Firebase Cloud Messaging (FCM)

**الهدف:** بناء نظام Push Notifications كامل عبر FCM.
**الاعتماديات:** التيكت 1 (Redis).
**التقدير الزمني:** 3 ساعات.

> [!IMPORTANT]
> هذا التيكت يتطلب Firebase project + Server Key.
> المستخدم لازم يوفّر:
> - Firebase Project → Settings → Cloud Messaging → Server Key (أو Service Account JSON لـ FCM v1)
> - Firebase Config Object للـ Frontend (apiKey, authDomain, projectId, messagingSenderId, appId)

---

#### 4.1 — إنشاء جدول `fcm_tokens`

#### [NEW] Migration: `create_fcm_tokens_table`
```bash
php artisan make:migration create_fcm_tokens_table
```

```php
Schema::create('fcm_tokens', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->string('token', 512)->unique();        // FCM device token
    $table->string('device_type', 20)->nullable();  // 'web', 'android', 'ios'
    $table->string('device_name')->nullable();      // "Chrome on Windows"
    $table->timestamp('last_used_at')->nullable();
    $table->timestamps();

    $table->index('user_id');
});
```

---

#### 4.2 — إنشاء Model

#### [NEW] `app/Models/FcmToken.php`
```php
- fillable: user_id, token, device_type, device_name, last_used_at
- casts: last_used_at → datetime
- علاقة: user() → BelongsTo(User::class)
- ⚠️ لا SoftDeletes — التوكن المنتهي يُحذف مباشرة
```

#### [MODIFY] [app/Models/User.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Models/User.php)
- إضافة علاقة: `fcmTokens() → HasMany(FcmToken::class)`

---

#### 4.3 — إنشاء FCM Service

#### [NEW] `app/Services/FcmNotificationService.php`
```
المنطق:
1. يستخدم FCM HTTP v1 API (https://fcm.googleapis.com/v1/projects/{project}/messages:send)
2. يقرا Service Account JSON من config('services.fcm.credentials_path')
3. يولّد OAuth2 Access Token من الـ Service Account
4. يبعث notification لقائمة tokens
5. لو token غير صالح (UNREGISTERED/NOT_FOUND) → يحذفه من DB تلقائياً
6. كل الاستدعاءات عبر Laravel HTTP Client (مفيش حزمة خارجية)

الدوال:
- sendToUser(User $user, string $title, string $body, array $data = []): void
- sendToRole(string $role, string $title, string $body, array $data = []): void
- sendToTokens(array $tokens, string $title, string $body, array $data = []): void
- cleanupInvalidTokens(array $invalidTokens): void
```

---

#### 4.4 — API لتسجيل/إلغاء Token

#### [NEW] `app/Http/Controllers/Api/FcmTokenController.php`
```
Route POST /api/fcm/register:
  - يستقبل: token, device_type, device_name
  - يحفظ أو يحدّث (updateOrCreate by token)

Route DELETE /api/fcm/unregister:
  - يستقبل: token
  - يحذف التوكن
```

#### [MODIFY] [routes/api.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/routes/api.php)
```php
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/fcm/register', [\App\Http\Controllers\Api\FcmTokenController::class, 'register']);
    Route::delete('/fcm/unregister', [\App\Http\Controllers\Api\FcmTokenController::class, 'unregister']);
});
```

---

#### 4.5 — Service Worker

#### [NEW] `public/firebase-messaging-sw.js`
```javascript
importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js');

firebase.initializeApp({
    apiKey: '...',          // ← من Firebase Console
    authDomain: '...',
    projectId: '...',
    messagingSenderId: '...',
    appId: '...',
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage(function(payload) {
    const notificationTitle = payload.notification?.title || 'Apex Logistics';
    const notificationOptions = {
        body: payload.notification?.body || '',
        icon: '/favicon.ico',
        data: payload.data,
    };
    self.registration.showNotification(notificationTitle, notificationOptions);
});
```

---

#### 4.6 — Frontend Firebase Init

#### [NEW] `resources/views/filament/hooks/fcm-init.blade.php`
```html
<script type="module">
    import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js';
    import { getMessaging, getToken, onMessage } from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging.js';

    const app = initializeApp({/* Firebase Config */});
    const messaging = getMessaging(app);

    // طلب إذن الإشعارات
    Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
            getToken(messaging, { vapidKey: '...' }).then(token => {
                // إرسال التوكن للسيرفر
                fetch('/api/fcm/register', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
                    },
                    body: JSON.stringify({
                        token: token,
                        device_type: 'web',
                        device_name: navigator.userAgent.substring(0, 100),
                    }),
                });
            });
        }
    });

    // استقبال الإشعارات في الـ Foreground
    onMessage(messaging, payload => {
        new Notification(payload.notification.title, {
            body: payload.notification.body,
        });
    });
</script>
```

#### [MODIFY] [app/Providers/Filament/AdminPanelProvider.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Providers/Filament/AdminPanelProvider.php)
- إضافة RenderHook لتحميل FCM:
```php
->renderHook('panels::body.end', fn () => view('filament.hooks.fcm-init'))
```

---

#### 4.7 — ربط FCM بالأحداث

#### [MODIFY] [app/Observers/OrderObserver.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Observers/OrderObserver.php)
- بعد الـ broadcast → إرسال FCM push:
```php
// إشعار المندوب عبر FCM
if ($order->driver_id && $order->driver?->user) {
    app(\App\Services\FcmNotificationService::class)->sendToUser(
        $order->driver->user,
        'تحديث الطلب #' . $order->id,
        'الحالة الجديدة: ' . $order->status->value,
        ['order_id' => $order->id, 'status' => $order->status->value]
    );
}
```

---

#### 4.8 — Config

#### [MODIFY] `config/services.php`
```php
'fcm' => [
    'credentials_path' => env('FCM_CREDENTIALS_PATH', storage_path('app/firebase/service-account.json')),
    'project_id' => env('FCM_PROJECT_ID'),
],
```

#### [MODIFY] `.env`
```
FCM_PROJECT_ID=your-firebase-project-id
FCM_CREDENTIALS_PATH="storage/app/firebase/service-account.json"
```

---

#### المخاطر:
| المخاطرة | الاحتمال | الحل |
|---------|---------|------|
| المستخدم مش عنده Firebase project | متوسط | الخطة بتوضح بالظبط اللي مطلوب — ممكن يتأنشأ في 5 دقائق |
| VAPID Key مش صحيح | منخفض | إعادة التوليد من Firebase Console |
| Token expires | متوسط | `cleanupInvalidTokens()` يحذف التوكنات المنتهية تلقائياً |
| Service Worker مش بيتسجل (HTTP) | عالي في Dev | Service Workers تحتاج HTTPS أو localhost |

---

### 🎟️ التيكت 5: خريطة التتبع الحي (Live Map)

**الهدف:** صفحة Filament تعرض مواقع المناديب على خريطة Leaflet.js.
**الاعتماديات:** التيكتات 2, 3.
**التقدير الزمني:** 3 ساعات.

---

#### [NEW] `app/Filament/Pages/LiveMapPage.php`
- صفحة Custom Filament
- `$view = 'filament.pages.live-map'`
- Icon: `heroicon-o-map`
- Navigation Group: 'المراقبة'
- `getViewData()`: جلب مواقع المناديب الأولية من Redis

#### [NEW] `resources/views/filament/pages/live-map.blade.php`
- تحميل Leaflet.js (CDN)
- `<div id="map">` بارتفاع كامل
- Markers بألوان: 🟢 متاح / 🟠 في مشوار / 🔴 غير متصل
- Echo listener لتحديث المواقع لحظياً
- Popup عند الضغط على marker: اسم المندوب، عدد طلباته، آخر نشاط

#### [NEW] `app/Http/Controllers/Api/Admin/LiveMapController.php`
- `GET /api/admin/live-map/drivers`
- محمي بـ `auth:sanctum`
- يقرا مواقع المناديب من Redis keys `driver:location:*`

#### [MODIFY] [routes/api.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/routes/api.php)
- إضافة: `Route::get('/admin/live-map/drivers', [LiveMapController::class, 'index'])->middleware('auth:sanctum')`

---

### 🎟️ التيكت 6: محرك التوزيع الآلي (Auto-Dispatch Engine)

**الهدف:** بناء منطق التوزيع الآلي + Timeout handling.
**الاعتماديات:** التيكتات 1, 2, 3 + [BatchDispatcher](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Services/BatchDispatcher.php).
**التقدير الزمني:** 4 ساعات.

---

#### [NEW] `app/Services/AutoDispatcher.php`
```
المنطق:
1. فحص dispatch_mode === 'auto'
2. جلب customer location
3. جلب المناديب المتاحين (is_active, zones, مفيش Redis lock)
4. لكل مندوب: حساب Haversine distance من موقعه (Redis) لموقع العميل
5. ترتيب بالمسافة (الأقرب أولاً)
6. محاولة BatchDispatcher::assignOrderToDriver()
7. لو نجح:
   - Redis Lock: "dispatch:lock:{driver_id}" → TTL 60 ثانية
   - OrderStateMachine::transition($order, Assigned)
   - $order->save()
   - broadcast(OrderStatusChanged)
   - FcmNotificationService->sendToUser(driver->user, "طلب جديد #{id}")
   - dispatch(CheckDriverAcceptance)->delay(60 seconds)
8. لو كل المناديب فشلوا:
   - Filament Notification + FCM لكل super_admin
```

#### [NEW] `app/Jobs/CheckDriverAcceptance.php`
```
المنطق (بعد 60 ثانية):
1. جلب الطلب fresh
2. لو مش assigned → exit (المندوب قبل أو الطلب اتلغى)
3. لو مفيش batch activity update:
   - فك Redis Lock
   - OrderStateMachine::transition($order, Pending)
   - $order->driver_id = null; $order->batch_id = null; $order->version++
   - $order->saveQuietly()
   - إعادة AutoDispatcher::dispatch() (max 3 attempts)
   - بعد 3 محاولات → إشعار + FCM للمشرفين
```

#### ~~[MODIFY]~~ [app/Services/OrderStateMachine.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Services/OrderStateMachine.php)
> [!IMPORTANT]
> **✅ لا حاجة لتعديل — هذا الانتقال موجود بالفعل من Phase 5!**
> الكود الحالي يحتوي على `'pending'` في الانتقالات المسموحة من `'assigned'` لكلا النوعين:
> ```php
> 'assigned' => ['picked_up', 'cancellation_requested', 'pending'], // ✅ Standard — موجود
> 'assigned' => ['pending_pricing', 'cancellation_requested', 'pending'], // ✅ Errand — موجود
> ```
> **تطبيق هذا الـ diff مرة تانية سيسبب خطأ أو تكرار. اتخطاه.**

#### [MODIFY] [app/Filament/Resources/Orders/Pages/CreateOrder.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Filament/Resources/Orders/Pages/CreateOrder.php)
- في `afterCreate()`: لو `dispatch_mode === 'auto'` → `AutoDispatcher::dispatch($order)`

---

### 🎟️ التيكت 7: حماية التوزيع والتكامل

**الهدف:** نفس قواعد الـ Validation تنطبق على التعيين اليدوي.
**الاعتماديات:** التيكت 6.
**التقدير الزمني:** ساعتين.

---

#### [MODIFY] [app/Filament/Resources/Orders/Pages/EditOrder.php](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Filament/Resources/Orders/Pages/EditOrder.php)
- في `handleRecordUpdate()`: لو `driver_id` اتغير → استدعي `BatchDispatcher::assignOrderToDriver()` بدل التعيين المباشر
- لو الـ BatchDispatcher يرمي `ValidationException` → إظهار رسالة خطأ Filament

#### [NEW] `app/Listeners/NotifyAdminDispatchFailed.php`
- إشعار Filament + FCM لكل super_admin عند فشل التوزيع الآلي

---

### 🎟️ التيكت 8: Widgets وإحصائيات KPIs

**الهدف:** بناء 5 Widgets في الصفحة الرئيسية.
**الاعتماديات:** التيكت 1 (لقراءة Redis للمناديب النشطين).
**التقدير الزمني:** 3 ساعات.

> [!NOTE]
> الـ [AdminPanelProvider](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/app/Providers/Filament/AdminPanelProvider.php) فيه `discoverWidgets` مفعّل.
> يعني أي Widget ننشئه في `app/Filament/Widgets/` هيتسجل **أوتوماتيك** بدون تعديل `AdminPanelProvider`.

---

#### [NEW] `app/Filament/Widgets/TodayOrdersWidget.php`
- StatsOverviewWidget — 3 stats: إجمالي اليوم، Standard، Errand
- Polling: 15 ثانية

#### [NEW] `app/Filament/Widgets/TodayRevenueWidget.php`
- StatsOverviewWidget — 3 stats: إجمالي الإيرادات، رسوم التوصيل، الضرائب
- الفلوس بـ `MoneyHelper::fromCents()`

#### [NEW] `app/Filament/Widgets/ActiveDriversWidget.php`
- StatsOverviewWidget — 3 stats: نشطين (Redis check)، في مشوار، غير متصلين

#### [NEW] `app/Filament/Widgets/AverageDeliveryTimeWidget.php`
- StatsOverviewWidget — متوسط وقت التوصيل بالدقائق

#### [NEW] `app/Filament/Widgets/PendingAttentionWidget.php`
- TableWidget — الطلبات المعلقة (pending_pricing > 30 دقيقة، cancellation_requested، disputed)
- كل سطر فيه رابط لـ EditOrder

---

### 🎟️ التيكت 9: الرسوم البيانية (Charts)

**الهدف:** Charts أسبوعية وشهرية.
**الاعتماديات:** التيكت 8.
**التقدير الزمني:** ساعتين.

---

#### [NEW] `app/Filament/Widgets/WeeklyOrdersChart.php`
- ChartWidget (Line) — آخر 7 أيام، خطين: Standard/Errand

#### [NEW] `app/Filament/Widgets/MonthlyRevenueChart.php`
- ChartWidget (Bar) — آخر 30 يوم، إيرادات يومية بالريال

---

## 📄 ملخص كل الملفات النهائي

### ملفات جديدة — 25 ملف

| # | المسار | التيكت |
|---|--------|--------|
| 1 | `app/Events/OrderStatusChanged.php` | 2 |
| 2 | `app/Events/NewOrderCreated.php` | 2 |
| 3 | `app/Events/DriverLocationUpdated.php` | 2 |
| 4 | `routes/channels.php` | 2 |
| 5 | `resources/js/echo.js` | 2 |
| 6 | `resources/views/filament/hooks/echo-notifications.blade.php` | 3 |
| 7 | Migration: `create_fcm_tokens_table` | 4 |
| 8 | `app/Models/FcmToken.php` | 4 |
| 9 | `app/Services/FcmNotificationService.php` | 4 |
| 10 | `app/Http/Controllers/Api/FcmTokenController.php` | 4 |
| 11 | `public/firebase-messaging-sw.js` | 4 |
| 12 | `resources/views/filament/hooks/fcm-init.blade.php` | 4 |
| 13 | `app/Filament/Pages/LiveMapPage.php` | 5 |
| 14 | `resources/views/filament/pages/live-map.blade.php` | 5 |
| 15 | `app/Http/Controllers/Api/Admin/LiveMapController.php` | 5 |
| 16 | `app/Services/AutoDispatcher.php` | 6 |
| 17 | `app/Jobs/CheckDriverAcceptance.php` | 6 |
| 18 | `app/Listeners/NotifyAdminDispatchFailed.php` | 7 |
| 19 | `app/Filament/Widgets/TodayOrdersWidget.php` | 8 |
| 20 | `app/Filament/Widgets/TodayRevenueWidget.php` | 8 |
| 21 | `app/Filament/Widgets/ActiveDriversWidget.php` | 8 |
| 22 | `app/Filament/Widgets/AverageDeliveryTimeWidget.php` | 8 |
| 23 | `app/Filament/Widgets/PendingAttentionWidget.php` | 8 |
| 24 | `app/Filament/Widgets/WeeklyOrdersChart.php` | 9 |
| 25 | `app/Filament/Widgets/MonthlyRevenueChart.php` | 9 |

### ملفات معدّلة — 12 ملف

| # | المسار | التعديل | التيكت |
|---|--------|---------|--------|
| 1 | `.env` | CACHE, QUEUE, BROADCAST, REDIS, FCM, REVERB | 1, 2, 4 |
| 2 | `resources/js/bootstrap.js` | import echo | 2 |
| 3 | `app/Observers/OrderObserver.php` | broadcast + FCM | 3, 4 |
| 4 | `app/Filament/Resources/Orders/Pages/CreateOrder.php` | broadcast + AutoDispatcher | 3, 6 |
| 5 | `app/Http/Controllers/Api/Driver/LocationController.php` | broadcast | 3 |
| 6 | `app/Providers/Filament/AdminPanelProvider.php` | Echo + FCM hooks | 3, 4 |
| 7 | `routes/api.php` | FCM routes + LiveMap route | 4, 5 |
| 8 | `app/Models/User.php` | fcmTokens() relation | 4 |
| 9 | `config/services.php` | FCM config | 4 |
| 10 | `app/Services/OrderStateMachine.php` | assigned→pending | 6 |
| 11 | `app/Filament/Resources/Orders/Pages/EditOrder.php` | BatchDispatcher integration | 7 |
| 12 | `package.json` | laravel-echo, pusher-js | 2 |

---

## Verification Plan

### فحص كل تيكت:
| التيكت | الفحص الرئيسي |
|--------|-------------|
| 1 | `Cache::get('test')` يرجع من Redis |
| 2 | `php artisan reverb:start` شغال + `npm run build` بدون أخطاء |
| 3 | إنشاء طلب → Toast يظهر في tab تاني |
| 4 | `Notification.requestPermission()` → FCM token يتحفظ في DB |
| 5 | LiveMap تعرض markers على الخريطة |
| 6 | `dispatch_mode=auto` + إنشاء طلب → يتعين لمندوب أوتوماتيك |
| 7 | تعيين يدوي لمندوب وصل الحد → رسالة رفض |
| 8 | الصفحة الرئيسية تعرض 5 Widgets بإحصائيات حقيقية |
| 9 | Charts تعرض بيانات الأسبوع والشهر |

### الفحص النهائي الشامل:
```bash
php artisan serve    # بدون أخطاء
php artisan reverb:start  # في terminal منفصل
php artisan queue:work     # في terminal تالت
```

---

## 📝 ملفات التوثيق والتاسكات

| الملف | الصيغة | المرجع |
|-------|--------|--------|
| `task-phase6.md` | نفس هيكل [task-phase5.md](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/task-phase5.md) | 9 تيكتات بدل 7 |
| `PHASE6_CHANGELOG.md` | نفس صيغة [PHASE5_CHANGELOG.md](file:///D:/Important%20Projects/Apex_Logistics/Dashboard/PHASE5_CHANGELOG.md) | كل ملف بمحتواه + diffs + أوامر |

---

## ⏱️ الجدول الزمني النهائي

```mermaid
gantt
    title Phase 6 Timeline
    dateFormat HH:mm
    axisFormat %H:%M

    section Core Infrastructure
    T1 - Redis Docker           :t1, 00:00, 1h
    T2 - WebSocket Reverb       :t2, after t1, 3h
    T3 - Broadcasting Wiring    :t3, after t2, 2h

    section Notifications
    T4 - FCM Integration        :t4, after t1, 3h

    section Features
    T5 - Live Map               :t5, after t3, 3h
    T6 - Auto-Dispatch          :t6, after t3, 4h
    T7 - Dispatch Protection    :t7, after t6, 2h

    section Dashboard
    T8 - KPI Widgets            :t8, after t1, 3h
    T9 - Charts                 :t9, after t8, 2h
```

**الإجمالي: ~24 ساعة عمل**
**مع التوازي: ~16 ساعة فعلية** (T4 بالتوازي مع T2-T3، T8-T9 بالتوازي مع T5-T7)
