Fix admin status handling for the current ride pipeline; extend console

The ride table holds two generations of status values: the legacy CamelCase
set ('Finished', 'CancelFromPassenger') and the lowercase set written by
backend/ride/rides/* today ('completed', 'cancelled_by_passenger'). Admin
queries only matched the legacy set, so on live data:

- get_rides_by_status.php returned nothing meaningful for every filter, and
  the "in progress" default masked it.
- dashbord.php reported total_driver_earnings as NULL, completed_rides as a
  fraction of the real count, and cancelled_rides as 0.
- driver_avg_duration averaged in negative durations, yielding "-00h 22m".

All three now match on LOWER(status) across both families.

Staff/pending.php ran with no authentication at all, exposing pending
admins' names and phone numbers to any caller; it now goes through
connect.php with a role check. It also returned HTTP 400 for everything when
the `users` table was absent — each source is queried independently and
reports its own availability.

Console:
- Render rides from either schema generation (price/date/time and
  start_location coordinates, or the older address/created_at columns).
- Null aggregates render as "—" rather than a measured 0.00.
- Add tariff/promo, WhatsApp send and encryption modules, all super-admin
  gated; pricing remains read-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-25 01:47:55 +03:00
co-authored by Claude Opus 5
parent 915a148ebf
commit 852c6ece5c
6 changed files with 204 additions and 59 deletions
+39 -19
View File
@@ -3,40 +3,60 @@
* Admin/Staff/pending.php
* جلب الحسابات المعلقة للإداريين والخدمة
*/
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
// connect.php يفرض JWT — بدونه كانت هذه النقطة تكشف أسماء وأرقام
// المشرفين المعلقين لأي زائر بلا أي مصادقة.
require_once __DIR__ . '/../../connect.php';
if ($role !== 'admin' && $role !== 'super_admin') {
http_response_code(403);
echo json_encode(['error' => 'Unauthorized: Admin access required']);
exit;
}
$allPending = [];
$sources = [];
// كل مصدر يُجلب على حدة: غياب جدول users في بعض عمليات النشر كان يُفشل
// الطلب بالكامل ويخفي طلبات المشرفين المعلقة أيضاً.
try {
$con = Database::get('main');
// جلب الإداريين المعلقين
$stmt1 = $con->query("SELECT id, name, phone, role, created_at, 'admin' as type FROM adminUser WHERE status = 'pending'");
$admins = $stmt1->fetchAll(PDO::FETCH_ASSOC);
// فك التشفير للأسماء والأرقام للإداريين
foreach ($admins as &$admin) {
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
$admin['phone'] = $encryptionHelper->decryptData($admin['phone']) ?: $admin['phone'];
}
unset($admin);
// جلب موظفي الخدمة المعلقين
$allPending = array_merge($allPending, $admins);
$sources['admins'] = 'ok';
} catch (Throwable $e) {
error_log("[Staff Pending] adminUser query failed: " . $e->getMessage());
$sources['admins'] = 'unavailable';
}
try {
$stmt2 = $con->query("SELECT id, first_name, last_name, phone, user_type as role, created_at, 'service' as type FROM users WHERE status = 'pending' AND user_type = 'service'");
$services = $stmt2->fetchAll(PDO::FETCH_ASSOC);
// فك التشفير لموظفي الخدمة
foreach ($services as &$service) {
$service['name'] = trim(($encryptionHelper->decryptData($service['first_name']) ?: $service['first_name']) . ' ' . ($encryptionHelper->decryptData($service['last_name']) ?: $service['last_name']));
$service['name'] = trim(
($encryptionHelper->decryptData($service['first_name']) ?: $service['first_name']) . ' ' .
($encryptionHelper->decryptData($service['last_name']) ?: $service['last_name'])
);
$service['phone'] = $encryptionHelper->decryptData($service['phone']) ?: $service['phone'];
}
unset($service);
$allPending = array_merge($admins, $services);
printSuccess([
"data" => $allPending
]);
} catch (Exception $e) {
error_log("[Staff Pending Error] " . $e->getMessage());
jsonError("An internal error occurred. Please try again later.");
$allPending = array_merge($allPending, $services);
$sources['service_staff'] = 'ok';
} catch (Throwable $e) {
error_log("[Staff Pending] users query failed: " . $e->getMessage());
$sources['service_staff'] = 'unavailable';
}
printSuccess([
"data" => $allPending,
"sources" => $sources,
]);
exit();
+10 -6
View File
@@ -29,14 +29,18 @@ SELECT
-- المحافظ والتحويلات
-- إحصائيات وقت ومسافة الرحلات
(SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS driver_avg_duration,
-- تُستثنى الفروق السالبة (رحلات سجّلت وقت نهاية أقدم من البداية) لأنها
-- كانت تُنتج متوسطاً سالباً مثل "-00h 22m".
(SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL AND TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish) > 0) AS driver_avg_duration,
(SELECT MAX(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))) FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS longest_duration,
(SELECT ROUND(SUM(distance),2) FROM ride) AS total_distance,
(SELECT ROUND(AVG(distance),2) FROM ride) AS average_distance,
(SELECT ROUND(MAX(distance),2) FROM ride) AS longest_distance,
-- أرباح السائق والشركة
(SELECT SUM(price_for_driver) FROM ride WHERE status = 'Finished') AS total_driver_earnings,
-- ملاحظة: خط الرحلات الحالي يكتب 'completed' بينما القديم يكتب 'Finished'،
-- والاكتفاء بالقديم كان يُرجع NULL للأرباح وصفراً للرحلات المكتملة/الملغاة.
(SELECT SUM(price_for_driver) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS total_driver_earnings,
(SELECT ROUND(AVG(price_for_passenger),2) FROM ride) AS avg_passenger_price,
-- توزيع الرحلات حسب الوقت
@@ -49,10 +53,10 @@ SELECT
(SELECT COUNT(*) FROM ride WHERE carType = 'Speed') AS speed,
(SELECT COUNT(*) FROM ride WHERE carType = 'Lady') AS lady,
-- حالة الرحلات
(SELECT COUNT(*) FROM ride WHERE status = 'wait') AS ongoing_rides,
(SELECT COUNT(*) FROM ride WHERE status = 'Finished') AS completed_rides,
(SELECT COUNT(*) FROM ride WHERE status = 'cancel') AS cancelled_rides,
-- حالة الرحلات (تغطي عائلتي الحالات: القديمة CamelCase والجديدة lowercase)
(SELECT COUNT(*) FROM ride WHERE LOWER(status) IN ('wait','waiting','new','nothing','pending','searching')) AS ongoing_rides,
(SELECT COUNT(*) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS completed_rides,
(SELECT COUNT(*) FROM ride WHERE LOWER(status) LIKE 'cancel%' OR LOWER(status) IN ('timeout','refused')) AS cancelled_rides,
-- عدد السائقين الفريدين
(SELECT COUNT(*) FROM (SELECT driver_id FROM ride GROUP BY driver_id) AS sub) AS num_Driver,
+13 -5
View File
@@ -18,29 +18,37 @@ try {
$whereClause = ""; // لا يوجد شرط، اجلب الكل
break;
// ملاحظة: قاعدة البيانات تحتوي عائلتين من الحالات — القديمة بصيغة
// CamelCase ('Finished','Begin','CancelFromPassenger') والجديدة التي
// يكتبها خط الرحلات الحالي بأحرف صغيرة ('completed','accepted',
// 'cancelled_by_passenger'). المقارنة تتم بـ LOWER() لتغطية الاثنتين.
case 'Pending':
// الرحلات المعلقة/الجديدة: بانتظار سائق
$whereClause = "WHERE r.status IN ('New','nothing','waiting','wait')";
$whereClause = "WHERE LOWER(r.status) IN ('new','nothing','waiting','wait','pending','searching')";
break;
case 'Begin':
// الرحلات الجارية: من قبول السائق إلى بدء التشغيل
$whereClause = "WHERE r.status IN ('Apply','Applied','Arrived','arrived','Begin')";
$whereClause = "WHERE LOWER(r.status) IN ('apply','applied','arrived','begin','accepted','started','claimed')";
break;
case 'Completed':
// الرحلات المكتملة
$whereClause = "WHERE r.status = 'Finished'";
$whereClause = "WHERE LOWER(r.status) IN ('finished','completed')";
break;
case 'Canceled':
// جميع أنواع الإلغاء
$whereClause = "WHERE r.status IN ('Cancel','CancelFromDriver','CancelFromDriverAfterApply','CancelFromPassenger','TimeOut')";
$whereClause = "WHERE LOWER(r.status) IN (
'cancel','cancelfromdriver','cancelfromdriverafterapply','cancelfrompassenger',
'timeout','refused','cancelled_by_passenger','cancelled_by_driver',
'cancelled_no_driver_found'
)";
break;
default:
// في حال تم إرسال حالة محددة غير المذكورين
$whereClause = "WHERE r.status = ?";
$whereClause = "WHERE LOWER(r.status) = LOWER(?)";
$params[] = $statusFilter;
break;
}