Update codebase
This commit is contained in:
@@ -46,6 +46,64 @@ if (!in_array($status, $allowedStatuses, true)) {
|
||||
error_log("[accept_ride] DriverID=$driverId attempting RideID=$rideId");
|
||||
|
||||
try {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP A0 — تطبيق الإيقاف على الخادم
|
||||
// كان الإيقاف بعد ٣ إلغاءات يُكتب في تخزين تطبيق السائق وحده، فيُفكّ
|
||||
// بمسح بيانات التطبيق أو إعادة تثبيته. هذه هي البوابة الحقيقية: سائق
|
||||
// موقوف لا يستطيع أن يفوز برحلة مهما فعل بتطبيقه.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// الفحص من Redis لا MySQL: هذا المسار يُستدعى على كل محاولة قبول من كل
|
||||
// سائق، والإيقاف حالة نادرة قصيرة العمر. مفتاح بـ TTL يجعل انتهاء
|
||||
// الإيقاف تلقائياً بلا كرون ولا استعلام تنظيف.
|
||||
// MySQL يبقى السجل الدائم للمراجعة الإدارية، ولا يُقرأ هنا.
|
||||
try {
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
$blockedUntil = $redisLocation->get("driver:$driverId:blocked");
|
||||
if ($blockedUntil) {
|
||||
error_log("[accept_ride] DriverID=$driverId موقوف حتى $blockedUntil — رُفض القبول");
|
||||
printFailure("Your account is temporarily suspended until $blockedUntil");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تجميد سلامة — بلا TTL: لا ينتهي بالوقت بل برفع إنسان له.
|
||||
// سائق عليه بلاغ سلامة مفتوح لا يُسند له راكب جديد.
|
||||
$safetyHold = $redisLocation->get("driver:$driverId:safety_hold");
|
||||
if ($safetyHold) {
|
||||
error_log("[accept_ride] DriverID=$driverId مجمّد ببلاغ سلامة #$safetyHold — رُفض القبول");
|
||||
printFailure("Your account is under review. Please contact support.");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $eBlock) {
|
||||
error_log("[accept_ride] تعذّر فحص الإيقاف: " . $eBlock->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP A0b — حارس الحصرية في الإسناد بالدفعات
|
||||
//
|
||||
// بدون هذا الفحص تبقى الحصرية شكلية: سائق تلقّى العرض سابقاً (أو صاغ
|
||||
// الطلب يدوياً) يستطيع القبول خارج دوره فيعود السباق كما كان.
|
||||
//
|
||||
// وجود المفتاح هو ما يحدد الوضع: البثّ الحر لا يكتبه أصلاً، فالفحص
|
||||
// يمرّ بلا أثر — لا حاجة لقراءة DISPATCH_MODE هنا.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
$currentOffer = $redisLocation->get("ride:$rideId:current_offer");
|
||||
if ($currentOffer !== false && $currentOffer !== null
|
||||
&& (string) $currentOffer !== (string) $driverId) {
|
||||
error_log("[accept_ride] DriverID=$driverId حاول قبول RideID=$rideId"
|
||||
. " والعرض الحالي لـ $currentOffer — رُفض");
|
||||
printFailure("Ride not available");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $eOffer) {
|
||||
// فشل الفحص لا يمنع القبول — القفل التفاؤلي في STEP A يبقى الضامن
|
||||
// ضد الازدواج، وأسوأ نتيجة هنا هي عودة سلوك البثّ الحر لهذه الرحلة.
|
||||
error_log("[accept_ride] تعذّر فحص حصرية العرض: " . $eOffer->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP A — القفل على ride DB (المرجع الأساسي)
|
||||
// Optimistic lock: نغير فقط إذا status لا يزال 'waiting' أو 'wait'
|
||||
@@ -70,6 +128,17 @@ try {
|
||||
|
||||
error_log("[accept_ride] ride DB locked. RideID=$rideId → DriverID=$driverId");
|
||||
|
||||
// عدّاد القبول — بسط معدّل القبول الذي يرتّب به محرك الإسناد.
|
||||
// يُعدّ هنا بعد نجاح القفل حصراً: العدّ قبله كان سيحسب للخاسرين في
|
||||
// السباق قبولاً لم يحدث.
|
||||
try {
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
$redisLocation->hIncrBy("driver:score:$driverId", 'accepts', 1);
|
||||
}
|
||||
} catch (Throwable $eScore) {
|
||||
error_log("[accept_ride] تعذّر تحديث عدّاد القبول: " . $eScore->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP B — تزامن primary DB (بعد نجاح القفل)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -87,6 +156,53 @@ try {
|
||||
error_log("[accept_ride] primary DB sync WARNING: " . $eSync->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP B1 — تثبيت مسافة الاقتراب لحظة القبول (في Redis لا MySQL)
|
||||
//
|
||||
// هذه المسافة أساس تعويض عدم الحضور لاحقاً، ويجب أن تُقاس الآن حصراً:
|
||||
// عند الإلغاء يكون السائق واقفاً عند نقطة الالتقاط فتصير صفراً.
|
||||
//
|
||||
// لكنها قيمة عابرة تُقرأ مرة واحدة عند نهاية الرحلة — فلا تستحق كتابتين
|
||||
// في MySQL على أسخن مسار في النظام. تُحفظ في نفس الهاش الذي يستخدمه
|
||||
// السوكِت لحالة الرحلة (ride:$id:state)، وتُثبَّت في MySQL كتابةً واحدة
|
||||
// مع تحديث الحالة النهائي عند الإلغاء.
|
||||
//
|
||||
// hSet لا يمسح حقول السوكِت الأخرى في نفس الهاش، وTTL الـ24 ساعة الذي
|
||||
// يضبطه السوكِت يغطي هذه الحقول تلقائياً.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
$approachKm = 0.0;
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
$stmtPickup = $con->prepare("SELECT start_location FROM ride WHERE id = ?");
|
||||
$stmtPickup->execute([$rideId]);
|
||||
[$pickupLat, $pickupLng] = array_pad(
|
||||
array_map('trim', explode(',', (string) $stmtPickup->fetchColumn(), 2)), 2, null
|
||||
);
|
||||
|
||||
if (is_numeric($pickupLat) && is_numeric($pickupLng)) {
|
||||
$loc = $redisLocation->hGetAll("driver:public:$driverId");
|
||||
if (!empty($loc['lat']) && !empty($loc['lng'])) {
|
||||
$approachKm = round(getDistanceBetweenPoints(
|
||||
(float) $loc['lat'], (float) $loc['lng'],
|
||||
(float) $pickupLat, (float) $pickupLng
|
||||
), 3);
|
||||
}
|
||||
}
|
||||
|
||||
if ($approachKm > 0) {
|
||||
$redisLocation->hSet("ride:$rideId:state", 'pickup_approach_km', $approachKm);
|
||||
}
|
||||
}
|
||||
|
||||
if ($approachKm <= 0) {
|
||||
error_log("[accept_ride] تعذّر قياس مسافة الاقتراب RideID=$rideId"
|
||||
. " — التعويض سيحتسب زمن الانتظار وحده");
|
||||
}
|
||||
} catch (Throwable $eApproach) {
|
||||
// قياس التعويض لا يُسقط قبول الرحلة أبداً.
|
||||
error_log("[accept_ride] pickupApproachKm WARNING: " . $eApproach->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP C — driver_orders (INSERT أو UPDATE بسطر واحد آمن)
|
||||
// ON DUPLICATE KEY يمنع race condition ثانية على هذا الجدول
|
||||
@@ -275,6 +391,42 @@ try {
|
||||
'passenger_id' => $passengerIdValue ?? '',
|
||||
]);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// الراكب الذي طلب برسالة نصية لا يملك إنترنت: السوكيت لن يصله،
|
||||
// والإشعار الصامت أعلاه يذهب إلى العدم. الرسالة النصية هي قناته
|
||||
// الوحيدة لمعرفة أن سائقاً قَبِل، ومن هو، وكيف يعرف سيارته.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
$stmtSms = $con->prepare("
|
||||
SELECT sender_norm FROM sms_ride_requests
|
||||
WHERE ride_id = ? AND status = 'dispatched' LIMIT 1
|
||||
");
|
||||
$stmtSms->execute([$rideId]);
|
||||
$smsSender = $stmtSms->fetchColumn();
|
||||
|
||||
if ($smsSender) {
|
||||
require_once __DIR__ . '/../../sms/helpers.php';
|
||||
|
||||
$plate = trim((string) ($driverInfo['car_plate'] ?? ''));
|
||||
$car = trim(($driverInfo['make'] ?? '') . ' ' . ($driverInfo['model'] ?? ''));
|
||||
$color = trim((string) ($driverInfo['color'] ?? ''));
|
||||
|
||||
$lines = ['سيرو: وجدنا لك سائقاً.'];
|
||||
$lines[] = 'السائق: ' . ($driverInfo['driverName'] ?? '—');
|
||||
if (!empty($driverInfo['phone'])) $lines[] = 'هاتفه: ' . $driverInfo['phone'];
|
||||
if ($car !== '' || $color !== '') $lines[] = 'السيارة: ' . trim("$car $color");
|
||||
if ($plate !== '') $lines[] = 'اللوحة: ' . $plate;
|
||||
$lines[] = 'سيتصل بك عند وصوله.';
|
||||
|
||||
smsEnqueue($con, (string) $smsSender, implode("\n", $lines),
|
||||
'driver_found', (string) $rideId);
|
||||
}
|
||||
} catch (Throwable $eSms) {
|
||||
// لا نُسقط قبول الرحلة من أجل رسالة: السائق قَبِل فعلاً،
|
||||
// والرسالة الفائتة أهون من رحلة تُلغى بخطأ.
|
||||
error_log('[accept_ride] تعذّر تجهيز رسالة SMS للراكب: ' . $eSms->getMessage());
|
||||
}
|
||||
|
||||
error_log("[accept_ride] SUCCESS. RideID=$rideId accepted by DriverID=$driverId");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user