610 lines
26 KiB
Markdown
610 lines
26 KiB
Markdown
<div dir="rtl" lang="ar">
|
||
|
||
# تحليل نظام متابعة الرحلات في لوحة تحكم المشرف (Siro Admin)
|
||
|
||
## دراسة شاملة للبحث، التتبع، التعديل، والتعامل مع الرحلات المعلقة
|
||
|
||
---
|
||
|
||
## 📋 فهرس المحتويات
|
||
|
||
1. [النظام الحالي لمتابعة الرحلات](#النظام-الحالي)
|
||
2. [تحليل شاشات وأدوات متابعة الرحلات](#تحليل-الشاشات)
|
||
3. [حالات الرحلة الفعلية من قاعدة البيانات](#حالات-الرحلة)
|
||
4. [مشكلة رقم الهاتف والدولة](#مشكلة-رقم-الهاتف-والدولة)
|
||
5. [الـ Endpoints المستخدمة واستجاباتها](#الـ-endpoints)
|
||
6. [ما هو موجود وما هو مفقود](#ما-هو-موجود-وما-هو-مفقود)
|
||
7. [التوصيات والتحسينات المطلوبة مع الإصلاحات](#التوصيات-والإصلاحات)
|
||
|
||
---
|
||
|
||
## النظام الحالي
|
||
|
||
### الملفات المشاركة في متابعة الرحلات
|
||
|
||
| الملف | الدور |
|
||
|-------|--------|
|
||
| `siro_admin/lib/views/admin/rides/ride_lookup_page.dart` | لوحة إدارة الرحلات (Dashboard) - الأحدث |
|
||
| `siro_admin/lib/views/admin/rides/rides.dart` | إحصائيات الرحلات الشهرية (قديم) |
|
||
| `siro_admin/lib/views/admin/drivers/monitor_ride.dart` | شاشة مراقبة رحلة محددة بالخريطة |
|
||
| `siro_admin/lib/controller/rides/ride_lookup_controller.dart` | تحكم البحث عن رحلة وتحديث حالتها |
|
||
| `siro_admin/lib/controller/admin/ride_admin_controller.dart` | تحكم إحصائيات الرحلات |
|
||
|
||
### ملفات الباك إند
|
||
|
||
| الملف | الوظيفة |
|
||
|-------|---------|
|
||
| `backend/Admin/rides/get_rides_by_status.php` | جلب الرحلات حسب الحالة (Begin, New, Finished, Canceled) |
|
||
| `backend/Admin/rides/admin_get_rides_by_phone.php` | البحث عن رحلة برقم هاتف الراكب (مشفر) |
|
||
| `backend/Admin/rides/admin_update_ride_status.php` | تحديث حالة الرحلة (Pending→Completed...) |
|
||
| `backend/Admin/rides/monitorRide.php` | مراقبة رحلة نشطة برقم الهاتف (يبحث في السائق والراكب) |
|
||
| `backend/Admin/rides/get_driver_live_pos.php` | جلب الموقع اللحظي للسائق من `car_locations` |
|
||
|
||
---
|
||
|
||
## حالات الرحلة الفعلية في قاعدة البيانات
|
||
|
||
### 📌 هيكل الجدول
|
||
|
||
```sql
|
||
CREATE TABLE `ride` (
|
||
`id` int NOT NULL AUTO_INCREMENT,
|
||
`status` varchar(200) NOT NULL DEFAULT 'nothing', -- ❌ NOT ENUM! نص عادي
|
||
...
|
||
) ENGINE=InnoDB;
|
||
|
||
CREATE TABLE `waitingRides` (
|
||
`id` varchar(100) NOT NULL,
|
||
`status` varchar(200) NOT NULL DEFAULT 'nothing', -- ❌ NOT ENUM! نص عادي
|
||
...
|
||
) ENGINE=InnoDB;
|
||
```
|
||
|
||
### 🗺️ جميع حالات الرحلة المستخدمة فعلياً في الكود
|
||
|
||
| الحالة في DB | المعنى | أين تستخدم |
|
||
|-------------|--------|------------|
|
||
| `nothing` | القيمة الافتراضية | عند إنشاء الرحلة في `add.php` و `addWaitingRide.php` |
|
||
| `New` | رحلة جديدة (لم يتم تعيين سائق بعد) | في `Admin/rides/get_rides_by_status.php` للـ Admin Dashboard |
|
||
| `waiting` | الرحلة قيد البحث عن سائق | في `retry_search_drivers.php` عند إعادة البحث |
|
||
| `wait` | في انتظار (مستخدم في بعض السيرفرات) | في `getRideStatusFromStartApp.php` و `cancelRideByPassenger.php` |
|
||
| `Apply` | سائق قبل الرحلة (قيد التوجه) | في `acceptRide.php`, `overLay/get.php`, `driver_statistic.php` |
|
||
| `Applied` | تم قبول التطبيق | في `get_rides_by_status.php` (مع Begin) |
|
||
| `arrived` | السائق وصل إلى موقع الراكب (lowercase) | في `arrive_ride.php` |
|
||
| `Arrived` | السائق وصل (uppercase) | في `get_rides_by_status.php` (مستخدم أيضاً) |
|
||
| `Begin` | **الرحلة قيد التشغيل** 🚗 | في `start_ride.php`, `monitorRide.php`, `finish_ride_updates.php` |
|
||
| `started` | بدأت الرحلة (مرادف لـ Begin) | في `start_ride.php` (رد API) |
|
||
| `Finished` | **الرحلة اكتملت** ✅ | في معظم التقارير والإحصائيات |
|
||
| `Cancel` | ملغاة (عام) | في `cancelRideFromDriver.php`, `cancel_ride_by_passenger.php` |
|
||
| `CancelFromDriver` | ألغى السائق | في `get_rides_by_status.php` |
|
||
| `CancelFromDriverAfterApply` | ألغى السائق بعد القبول | في `get_rides_by_status.php` |
|
||
| `CancelFromPassenger` | ألغى الراكب | في `get_rides_by_status.php` |
|
||
| `TimeOut` | انتهت المهلة الزمنية | في `get_rides_by_status.php` |
|
||
|
||
### 🔄 تسلسل حالة الرحلة الطبيعي:
|
||
|
||
```
|
||
nothing / New
|
||
│
|
||
├──→ waiting ──→ Apply ──→ Applied ──→ Arrived ──→ Begin ──→ Finished
|
||
│ │ │ │ │ │
|
||
│ │ │ │ │ └── ✅ مكتملة
|
||
│ │ │ │ │
|
||
│ │ │ │ └── CancelFromDriverAfterApply
|
||
│ │ │ │
|
||
│ │ │ └── CancelFromPassenger
|
||
│ │ │
|
||
│ │ └── TimeOut (انتهاء المهلة)
|
||
│ │
|
||
│ └── CancelFromDriver
|
||
│
|
||
└── Cancel (إلغاء مباشر)
|
||
```
|
||
|
||
### ❌ المشكلة في Admin Dashboard الحالي:
|
||
|
||
في `get_rides_by_status.php`، التصنيف خاطئ:
|
||
|
||
```php
|
||
case 'Begin':
|
||
$whereClause = "WHERE r.status IN ('Begin','Apply','Applied')"; // ✅ Begin + Apply + Applied
|
||
break;
|
||
case 'New':
|
||
$whereClause = "WHERE r.status = 'New'"; // ✅ جديد
|
||
break;
|
||
case 'Completed':
|
||
$whereClause = "WHERE r.status = 'Finished'"; // ✅ مكتملة
|
||
break;
|
||
case 'Canceled':
|
||
$whereClause = "WHERE r.status IN ('Cancel', 'CancelFromDriverAfterApply', 'TimeOut')"; // ❌ ناقص!
|
||
break;
|
||
```
|
||
|
||
**🔴 ملاحظات على التصنيف:**
|
||
1. `CancelFromDriver` و `CancelFromPassenger` و `Cancel` و `arrived` (lowercase) **غير مشمولة** في أي تصنيف
|
||
2. `Apply` و `Applied` مصنفين مع `Begin` (جارية) — هذا صحيح جزئياً لأن السائق في طريقه
|
||
3. `TimeOut` مصنف مع `Canceled` — صحيح
|
||
4. الرحلة التي حالتها `wait` أو `waiting` **غير معروضة إطلاقاً** في أي تبويب
|
||
|
||
---
|
||
|
||
## تحليل الشاشات
|
||
|
||
### 1️⃣ شاشة إدارة الرحلات (RidesDashboardScreen)
|
||
|
||
**الملف**: `ride_lookup_page.dart`
|
||
|
||
#### الوظائف الحالية:
|
||
- ✅ عرض الرحلات مصنفة حسب الحالة (جارية، جديدة، مكتملة، ملغاة)
|
||
- ✅ إحصائيات فورية (عدد الرحلات لكل حالة + الإيرادات + المسافة)
|
||
- ✅ شريط بحث ديناميكي يصفّي النتائج مباشرة
|
||
- ✅ الضغط على رحلة → يفتح خريطة التتبع (RideMapMonitorScreen)
|
||
- ✅ إظهار معلومات السائق والراكب مع أيقونة اتصال (للمشرف العام)
|
||
|
||
#### منطق البحث الحالي:
|
||
```dart
|
||
void filterRides(String query) {
|
||
displayedRides.value = allRidesList.where((ride) {
|
||
return ride.driverPhone.contains(query) ||
|
||
ride.passengerPhone.contains(query) ||
|
||
ride.driverName.toLowerCase().contains(query.toLowerCase()) ||
|
||
ride.passengerName.toLowerCase().contains(query.toLowerCase()) ||
|
||
ride.rideId.contains(query);
|
||
}).toList();
|
||
}
|
||
```
|
||
|
||
🔴 **مشكلة**: البحث يتم **محلياً** (في الذاكرة) بعد جلب كل الرحلات من API. إذا كانت الرحلات كثيرة (100+ رحلة)، البحث لا يشمل الرحلات الغير محملة.
|
||
|
||
🔴 **مشكلة**: الـ API يجلب فقط آخر 100 رحلة (LIMIT 100) ولا يوجد Pagination.
|
||
|
||
#### الـ API المستخدم:
|
||
```
|
||
POST $server/Admin/rides/get_rides_by_status.php
|
||
payload: { "status": "Begin" }
|
||
← { "status": "success", "message": [ {...}, {...} ] }
|
||
```
|
||
|
||
#### خريطة التتبع (RideMapMonitorScreen):
|
||
- ✅ يعرض مسار الرحلة (نقطة بداية → نقطة نهاية)
|
||
- ✅ يتابع موقع السائق اللحظي (Polling كل 10 ثوانٍ)
|
||
- ✅ زر اتصال بالسائق أو الراكب
|
||
- 🔴 **لا يوجد تحديث لحالة الرحلة** من هذه الشاشة
|
||
|
||
---
|
||
|
||
### 2️⃣ شاشة مراقبة رحلة (RideMonitorScreen)
|
||
|
||
**الملف**: `monitor_ride.dart`
|
||
|
||
#### الوظائف الحالية:
|
||
- ✅ إدخال رقم هاتف للبحث عن رحلة نشطة
|
||
- ✅ عرض خريطة كاملة مع مسار الرحلة وموقع السائق
|
||
- ✅ تحديث تلقائي (Polling كل 10 ثوانٍ)
|
||
- ✅ إظهار اسم السائق، حالة الرحلة، السرعة، آخر تحديث
|
||
|
||
#### الـ API المستخدم:
|
||
```
|
||
POST $server/Admin/rides/monitorRide.php
|
||
payload: { "phone": "963$phone" }
|
||
← { "status": "success", "message": {
|
||
"ride_details": { ... },
|
||
"driver_details": { ... },
|
||
"driver_location": { "latitude": ..., "longitude": ..., "speed": ..., "heading": ... }
|
||
}}
|
||
```
|
||
|
||
#### منطق البحث الحالي:
|
||
```dart
|
||
final response = await CRUD().post(
|
||
link: apiUrl,
|
||
payload: {"phone": "963$phone"}, // ⚠️ Hardcoded Syria prefix!
|
||
);
|
||
```
|
||
|
||
🔴 **مشكلة حرجة**: `963$phone` ثابت (سوريا فقط). الأردن يحتاج `962` ومصر تحتاج `20`.
|
||
|
||
#### منطق الباك إند (`monitorRide.php`):
|
||
```php
|
||
$encPhone = $encryptionHelper->encryptData($phone);
|
||
// يبحث أولاً في جدول driver
|
||
$driverQuery = $con->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||
// ثم في جدول passengers
|
||
$customerQuery = $con->prepare("SELECT id FROM passengers WHERE phone = :phone LIMIT 1");
|
||
// ثم يجلب آخر رحلة status='Begin'
|
||
```
|
||
|
||
🔴 **ملاحظة**: `monitorRide.php` يبحث فقط عن الرحلات التي حالتها `Begin`. إذا كانت الرحلة بحالة `Apply` أو `Arrived` لن يجدها.
|
||
|
||
---
|
||
|
||
### 3️⃣ شاشة البحث عن رحلة وتحديث حالتها (RideLookupController)
|
||
|
||
**الملف**: `ride_lookup_controller.dart`
|
||
|
||
#### الوظائف الحالية:
|
||
- ✅ البحث برقم هاتف الراكب (مشفر) عبر `admin_get_rides_by_phone.php`
|
||
- ✅ عرض بيانات الراكب + آخر رحلة له
|
||
- ✅ تحديث حالة الرحلة عبر `admin_update_ride_status.php`
|
||
- ✅ قائمة منسدلة للحالات المسموحة: `Pending, Accepted, EnRoute, Arrived, Started, Completed, Canceled`
|
||
|
||
**🔴 هذه القائمة لا تطابق حالات الرحلة الفعلية في النظام!**
|
||
|
||
| القيمة في التطبيق | هل تستخدم في DB؟ | الحالة الصحيحة |
|
||
|------------------|------------------|----------------|
|
||
| `Pending` | ❌ لا | `waiting` أو `New` أو `nothing` |
|
||
| `Accepted` | ❌ لا | `Apply` أو `Applied` |
|
||
| `EnRoute` | ❌ لا | `Apply` أو `Applied` (السائق في الطريق) |
|
||
| `Arrived` | ✅ نعم | `arrived` أو `Arrived` |
|
||
| `Started` | ❌ لا | `Begin` |
|
||
| `Completed` | ✅ نعم | `Finished` |
|
||
| `Canceled` | ✅ نعم | `Cancel` أو `CancelFrom*` |
|
||
|
||
#### الـ API المستخدم للبحث:
|
||
```
|
||
POST $server/Admin/rides/admin_get_rides_by_phone.php
|
||
payload: { "phone": "...", "status": "..." }
|
||
← { "status": "success", "message": {
|
||
"passenger": { "id", "first_name", "last_name", "phone" },
|
||
"ride": { "id", "status", "start_location", ... }
|
||
}}
|
||
```
|
||
|
||
🔴 **مشكلة**: هذا الـ API يبحث فقط في جدول `passengers`. إذا كان الرقم تابعاً لسائق، لن يعثر على شيء.
|
||
|
||
---
|
||
|
||
## مشكلة رقم الهاتف والدولة
|
||
|
||
### كيف يتم تخزين أرقام الهواتف؟
|
||
|
||
في قاعدة البيانات، جميع الأرقام مشفرة باستخدام `encryptData()`:
|
||
|
||
```php
|
||
$encryptedPhone = $encryptionHelper->encryptData($raw);
|
||
```
|
||
|
||
**الأرقام تخزن مع كود الدولة:**
|
||
- الأردن: `9627XXXXXXXX`
|
||
- مصر: `2010XXXXXXXX`
|
||
- سوريا: `9639XXXXXXXX`
|
||
|
||
### كيف يتم البحث في تطبيق المشرف حالياً؟
|
||
|
||
#### 1️⃣ في `monitor_ride.dart`:
|
||
```dart
|
||
payload: {"phone": "963$phone"}
|
||
// المستخدم يدخل: 0992952235
|
||
// التطبيق يرسل: 9630992952235 ← غلط! الصحيح: 963992952235
|
||
```
|
||
|
||
#### 2️⃣ في `admin_get_rides_by_phone.php`:
|
||
```php
|
||
$phone = filterRequest('phone');
|
||
$enc_raw = $encryptionHelper->encryptData($raw);
|
||
// يبحث فقط في passengers
|
||
```
|
||
|
||
### جدول مقارنة تنسيق الأرقام:
|
||
|
||
| الدولة | كود الدولة | تنسيق الإدخال | التخزين في DB | ما يرسله `monitor_ride.dart` | هل يتطابق؟ |
|
||
|--------|-----------|--------------|---------------|------------------------------|-----------|
|
||
| 🇯🇴 الأردن | `962` | `079XXXXXXX` | `96279XXXXXXX` | `963079XXXXXXX` ❌ | لا |
|
||
| 🇪🇬 مصر | `20` | `010XXXXXXXX` | `2010XXXXXXXX` | `963010XXXXXXXX` ❌ | لا |
|
||
| 🇸🇾 سوريا | `963` | `0992952235` | `963992952235` | `9630992952235` ❌ | لا (0 زائد) |
|
||
|
||
### تحليل المشكلة:
|
||
|
||
1. **المشرف يدخل**: `0992952235` (سوريا) أو `079XXXXXXX` (أردن)
|
||
2. **التطبيق يضيف**: `963` ثابت (سوريا فقط)
|
||
3. **الباك إند يبحث**: عن الرقم `9630992952235` أو `963079XXXXXXX`
|
||
4. **الرقم المخزن**: `963992952235` (بدون 0 بعد كود الدولة) أو `96279XXXXXXX`
|
||
5. **النتيجة**: فشل في البحث عن أي رقم غير سوري، وحتى السوري يفشل إذا كان المدخل بـ `0`
|
||
|
||
---
|
||
|
||
## الـ Endpoints
|
||
|
||
### الحالية في `backend/Admin/rides/`:
|
||
|
||
| الإند بوينت | الطريقة | المدخلات | الاستجابة | المشاكل |
|
||
|------------|---------|----------|-----------|---------|
|
||
| `get_rides_by_status.php` | POST | `status` (Begin/New/Completed/Canceled) | قائمة رحلات (100) مع تفاصيل السائق والراكب | لا يوجد Pagination + بعض الحالات غير مشمولة (`wait`, `CancelFromDriver`, `arrived`) |
|
||
| `admin_get_rides_by_phone.php` | POST | `phone` | بيانات الراكب + آخر رحلة | يبحث فقط في passengers |
|
||
| `admin_update_ride_status.php` | POST | `id, status, reason (optional)` | الرحلة المحدّثة | Whitelist لا يطابق حالات DB الفعلية |
|
||
| `monitorRide.php` | POST | `phone` | تفاصيل الرحلة + السائق + الموقع | يبحث فقط عن `Begin` + كود الدولة ثابت 963 |
|
||
| `get_driver_live_pos.php` | POST | `driver_id` | آخر موقع للسائق | يعمل بشكل صحيح |
|
||
|
||
---
|
||
|
||
## التوصيات والإصلاحات
|
||
|
||
### 🔴 الإصلاح 1: تصحيح حالات الرحلة في Admin Dashboard
|
||
|
||
**المشكلة**: `get_rides_by_status.php` يستخدم حالات غير دقيقة.
|
||
|
||
**الحل**: تعديل mapping الحالات ليطابق الواقع:
|
||
|
||
```php
|
||
// backend/Admin/rides/get_rides_by_status.php - تصحيح
|
||
switch ($statusFilter) {
|
||
case 'Begin':
|
||
// الرحلات الجارية: من Apply إلى Begin
|
||
$whereClause = "WHERE r.status IN ('Apply','Applied','Arrived','arrived','Begin')";
|
||
break;
|
||
|
||
case 'New':
|
||
// الرحلات الجديدة: بانتظار سائق
|
||
$whereClause = "WHERE r.status IN ('New','nothing','waiting','wait')";
|
||
break;
|
||
|
||
case 'Completed':
|
||
$whereClause = "WHERE r.status = 'Finished'";
|
||
break;
|
||
|
||
case 'Canceled':
|
||
// جميع أنواع الإلغاء
|
||
$whereClause = "WHERE r.status IN ('Cancel','CancelFromDriver','CancelFromDriverAfterApply','CancelFromPassenger','TimeOut')";
|
||
break;
|
||
|
||
default:
|
||
$whereClause = "WHERE r.status = ?";
|
||
$params[] = $statusFilter;
|
||
break;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 🔴 الإصلاح 2: تصحيح قائمة الحالات المسموحة في RideLookupController
|
||
|
||
**المشكلة**: `statusOptions` في `ride_lookup_controller.dart` لا تطابق حالات DB.
|
||
|
||
**الحل**: تعديل القائمة:
|
||
|
||
```dart
|
||
// في ride_lookup_controller.dart
|
||
final List<String> statusOptions = const [
|
||
'New', // جديد (بدلاً من Pending)
|
||
'waiting', // في انتظار سائق
|
||
'Apply', // سائق قبل (بدلاً من Accepted)
|
||
'Arrived', // وصل السائق
|
||
'Begin', // الرحلة بدأت (بدلاً من Started)
|
||
'Finished', // مكتملة (بدلاً من Completed)
|
||
'Cancel', // إلغاء (بدلاً من Canceled)
|
||
];
|
||
```
|
||
|
||
**وتحديث `admin_update_ride_status.php`**:
|
||
```php
|
||
// backend/Admin/rides/admin_update_ride_status.php
|
||
$allowed = [
|
||
'New', 'waiting', 'wait', 'Apply', 'Applied',
|
||
'Arrived', 'arrived', 'Begin', 'Finished',
|
||
'Cancel', 'CancelFromDriver', 'CancelFromPassenger', 'TimeOut'
|
||
];
|
||
```
|
||
|
||
---
|
||
|
||
### 🔴 الإصلاح 3: معالجة أرقام الهواتف حسب الدولة
|
||
|
||
**المشكلة**: `monitor_ride.dart` يستخدم `963` ثابت.
|
||
|
||
**الحل**: إضافة دالة لتوحيد تنسيق الرقم:
|
||
|
||
```dart
|
||
// في monitor_ride.dart
|
||
String normalizePhone(String input) {
|
||
final clean = input.replaceAll(RegExp(r'\D+'), '');
|
||
|
||
// Syria: 099XXXXXXX or 9639XXXXXXX
|
||
if (clean.length == 10 && clean.startsWith('09'))
|
||
return '963${clean.substring(1)}';
|
||
if (clean.length == 12 && clean.startsWith('963'))
|
||
return clean;
|
||
if (clean.length == 9 && clean.startsWith('9'))
|
||
return '963$clean';
|
||
|
||
// Jordan: 079XXXXXXX or 9627XXXXXXX
|
||
if (clean.length == 10 && clean.startsWith('07'))
|
||
return '962${clean.substring(1)}';
|
||
if (clean.length == 12 && clean.startsWith('962'))
|
||
return clean;
|
||
if (clean.length == 9 && clean.startsWith('7'))
|
||
return '962$clean';
|
||
|
||
// Egypt: 010XXXXXXXX or 2010XXXXXXXX
|
||
if (clean.length == 11 && clean.startsWith('01'))
|
||
return '20${clean.substring(1)}';
|
||
if (clean.length == 13 && clean.startsWith('20'))
|
||
return clean;
|
||
|
||
return clean;
|
||
}
|
||
|
||
// عند البحث:
|
||
final normalizedPhone = normalizePhone(phoneInputController.text.trim());
|
||
final response = await CRUD().post(
|
||
link: apiUrl,
|
||
payload: {"phone": normalizedPhone},
|
||
);
|
||
```
|
||
|
||
**وفي الباك إند (`monitorRide.php` و `admin_get_rides_by_phone.php`)**:
|
||
```php
|
||
function normalizePhone($phone) {
|
||
$clean = preg_replace('/\D+/', '', $phone);
|
||
// Syria: remove leading 0 after country code
|
||
if (strlen($clean) === 10 && strpos($clean, '09') === 0) return '963' . substr($clean, 1);
|
||
if (strlen($clean) === 12 && strpos($clean, '963') === 0) return $clean;
|
||
if (strlen($clean) === 9 && strpos($clean, '9') === 0) return '963' . $clean;
|
||
// Jordan
|
||
if (strlen($clean) === 10 && strpos($clean, '07') === 0) return '962' . substr($clean, 1);
|
||
if (strlen($clean) === 12 && strpos($clean, '962') === 0) return $clean;
|
||
if (strlen($clean) === 9 && strpos($clean, '7') === 0) return '962' . $clean;
|
||
// Egypt
|
||
if (strlen($clean) === 11 && strpos($clean, '01') === 0) return '20' . substr($clean, 1);
|
||
if (strlen($clean) === 13 && strpos($clean, '20') === 0) return $clean;
|
||
return $clean;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 🔴 الإصلاح 4: البحث في السائق والراكب معاً (مع جميع حالات الرحلة)
|
||
|
||
**المشكلة**: `admin_get_rides_by_phone.php` يبحث فقط في `passengers` وحالة `Begin` فقط.
|
||
|
||
**الحل**: تعديل `admin_get_rides_by_phone.php` و `monitorRide.php` ليشمل driver + passenger + جميع الحالات النشطة:
|
||
|
||
```php
|
||
// backend/Admin/rides/admin_get_rides_by_phone.php - معدل
|
||
$phone = filterRequest('phone');
|
||
if (!$phone) { jsonError("Phone is required"); exit; }
|
||
|
||
$normalizedPhone = normalizePhone($phone);
|
||
$encPhone = $encryptionHelper->encryptData($normalizedPhone);
|
||
|
||
// 1) ابحث في driver
|
||
$driverStmt = $con->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||
$driverStmt->execute([':phone' => $encPhone]);
|
||
$driver = $driverStmt->fetch();
|
||
|
||
// 2) ابحث في passengers
|
||
$passengerStmt = $con->prepare("SELECT id FROM passengers WHERE phone = :phone LIMIT 1");
|
||
$passengerStmt->execute([':phone' => $encPhone]);
|
||
$passenger = $passengerStmt->fetch();
|
||
|
||
if (!$driver && !$passenger) {
|
||
jsonError('Phone number not found in system');
|
||
exit;
|
||
}
|
||
|
||
// 3) اجلب الرحلة حسب النوع
|
||
$userId = $driver ? $driver['id'] : $passenger['id'];
|
||
$userField = $driver ? 'r.driver_id' : 'r.passenger_id';
|
||
$filterStatus = filterRequest('status') ?: 'all';
|
||
|
||
$whereExtra = '';
|
||
if ($filterStatus !== 'all') {
|
||
$whereExtra = "AND r.status = :filter_status";
|
||
}
|
||
|
||
$rideStmt = $con->prepare("
|
||
SELECT r.*, d.first_name as d_fname, d.last_name as d_lname, d.phone as d_phone,
|
||
p.first_name as p_fname, p.last_name as p_lname, p.phone as p_phone
|
||
FROM ride r
|
||
LEFT JOIN driver d ON r.driver_id = d.id
|
||
LEFT JOIN passengers p ON r.passenger_id = p.id
|
||
WHERE $userField = :uid $whereExtra
|
||
ORDER BY r.id DESC
|
||
LIMIT 20
|
||
");
|
||
|
||
$params = [':uid' => $userId];
|
||
if ($filterStatus !== 'all') $params[':filter_status'] = $filterStatus;
|
||
$rideStmt->execute($params);
|
||
$rides = $rideStmt->fetchAll(PDO::FETCH_ASSOC);
|
||
```
|
||
|
||
---
|
||
|
||
### 🟡 الإصلاح 5: إضافة شاشة للرحلات المعلقة (Pending/Waiting)
|
||
|
||
**المشكلة**: لا توجد واجهة لعرض الرحلات العالقة (`waiting`, `New`, `nothing`).
|
||
|
||
**الحل المقترح**:
|
||
- إضافة تبويب خامس باسم **"معلقة"** في RidesDashboardScreen
|
||
- يعرض الرحلات ذات الحالة: `New`, `waiting`, `wait`, `nothing`
|
||
- إضافة زر "إلغاء الرحلة" مع إدخال سبب
|
||
- تحديث `admin_update_ride_status.php` ليدعم الإلغاء مع إشعار
|
||
|
||
```dart
|
||
// إضافة في ride_lookup_page.dart (RidesListController)
|
||
void changeTab(String status) {
|
||
currentStatus = status;
|
||
searchController.clear();
|
||
fetchRides();
|
||
}
|
||
|
||
// إضافة في RidesDashboardScreen
|
||
Tab(text: 'معلقة', icon: Icon(Icons.hourglass_empty_rounded)),
|
||
// عند اختيار هذا التبويب، يرسل status='Pending'
|
||
// الـ API يتعامل معها كـ: IN ('New','waiting','wait','nothing')
|
||
```
|
||
|
||
---
|
||
|
||
### 🟢 الإصلاح 6: إضافة البحث برقم الرحلة (Ride ID) + Pagination
|
||
|
||
**لماذا Ride ID هو الأفضل؟**
|
||
- المعرف الوحيد الفريد (Primary Key)
|
||
- لا توجد مشاكل تشفير (الأرقام المعرفية غير مشفرة)
|
||
- لا توجد مشاكل تنسيق دولة
|
||
- دقيق 100%
|
||
|
||
**مثال الإند بوينت الجديد**:
|
||
```php
|
||
// backend/Admin/rides/admin_get_ride_by_id.php
|
||
$rideId = filterRequest('id');
|
||
$stmt = $con->prepare("
|
||
SELECT r.*,
|
||
d.first_name as d_fname, d.last_name as d_lname, d.phone as d_phone,
|
||
p.first_name as p_fname, p.last_name as p_lname, p.phone as p_phone
|
||
FROM ride r
|
||
LEFT JOIN driver d ON r.driver_id = d.id
|
||
LEFT JOIN passengers p ON r.passenger_id = p.id
|
||
WHERE r.id = :id
|
||
LIMIT 1
|
||
");
|
||
$stmt->execute([':id' => $rideId]);
|
||
jsonSuccess($stmt->fetch());
|
||
```
|
||
|
||
**Pagination في `get_rides_by_status.php`**:
|
||
```php
|
||
$page = (int)(filterRequest('page') ?? 1);
|
||
$limit = 50;
|
||
$offset = ($page - 1) * $limit;
|
||
$sql .= " ORDER BY r.id DESC LIMIT $limit OFFSET $offset";
|
||
```
|
||
|
||
---
|
||
|
||
## الملخص النهائي
|
||
|
||
### جدول حالات الرحلة - ما هو صحيح وما هو خاطئ:
|
||
|
||
| ما في النظام (DB) | ما في التطبيق (Admin) | التصحيح |
|
||
|-------------------|----------------------|---------|
|
||
| `nothing` / `New` | `New` ✅ | ✅ صحيح (جديد) |
|
||
| `waiting` / `wait` | غير موجود ❌ | 🟡 يجب إضافته (معلق) |
|
||
| `Apply` / `Applied` | `Begin` (مصنف مع الجارية) ✅ | ✅ صحيح (جاري التوصيل) |
|
||
| `arrived` / `Arrived` | غير موجود ❌ | 🟡 يجب إضافته مع الجارية |
|
||
| `Begin` | `Begin` ✅ | ✅ صحيح (قيد التشغيل) |
|
||
| `Finished` | `Completed` ✅ | ✅ صحيح (مكتملة) |
|
||
| `Cancel` / `CancelFrom*` / `TimeOut` | `Canceled` (ناقص) ❌ | 🔴 يجب إضافة `CancelFromDriver` و `CancelFromPassenger` |
|
||
|
||
### قائمة الإصلاحات:
|
||
|
||
| # | الإصلاح | الملف | الأولوية |
|
||
|---|---------|-------|----------|
|
||
| 1 | تصحيح حالات الرحلة في `get_rides_by_status.php` | `backend/Admin/rides/get_rides_by_status.php` | 🔴 عاجل |
|
||
| 2 | تصحيح `statusOptions` في `ride_lookup_controller.dart` | `siro_admin/lib/controller/rides/ride_lookup_controller.dart` | 🔴 عاجل |
|
||
| 3 | تصحيح whitelist في `admin_update_ride_status.php` | `backend/Admin/rides/admin_update_ride_status.php` | 🔴 عاجل |
|
||
| 4 | إضافة `normalizePhone()` في `monitor_ride.dart` | `siro_admin/lib/views/admin/drivers/monitor_ride.dart` | 🔴 عاجل |
|
||
| 5 | إضافة `normalizePhone()` في `admin_get_rides_by_phone.php` | `backend/Admin/rides/admin_get_rides_by_phone.php` | 🔴 عاجل |
|
||
| 6 | إضافة `normalizePhone()` في `monitorRide.php` | `backend/Admin/rides/monitorRide.php` | 🔴 عاجل |
|
||
| 7 | إضافة البحث في driver + passenger في `admin_get_rides_by_phone.php` | `backend/Admin/rides/admin_get_rides_by_phone.php` | 🟡 ضروري |
|
||
| 8 | إضافة شاشة للرحلات المعلقة (Pending/Waiting) | `siro_admin/lib/views/admin/rides/ride_lookup_page.dart` | 🟡 ضروري |
|
||
| 9 | إضافة البحث برقم الرحلة (Ride ID) | Endpoint + Flutter جديد | 🟡 ضروري |
|
||
| 10 | إضافة Pagination | `backend/Admin/rides/get_rides_by_status.php` | 🟢 مفيد |
|
||
|
||
### أفضل طرق البحث (مرتبة حسب الأولوية):
|
||
|
||
1. **رقم الرحلة (Ride ID)** — الأدق، بدون مشاكل تشفير أو دولة
|
||
2. **رقم هاتف الراكب/السائق** — مع معالجة الدولة (normalizePhone)
|
||
3. **بحث بنطاق تاريخي** — لاستعراض الرحلات حسب الفترة الزمنية
|
||
|
||
</div> |