date('c'), 'trigger' => $triggerType]; try { // ── ١. لقطة الحالة ────────────────────────────────────── // ‏تُلتقط الآن لأن الرحلة تتغيّر بعد الحادثة (تُلغى، يُصفَّر السائق)، // ‏فقراءتها لاحقاً لا تصف اللحظة التي وقع فيها البلاغ. if ($rideId) { $stmt = $con->prepare("SELECT * FROM ride WHERE id = ? LIMIT 1"); $stmt->execute([$rideId]); $ride = $stmt->fetch(PDO::FETCH_ASSOC); if ($ride) { $driverId = ($ride['driver_id'] ?? '0') !== '0' ? $ride['driver_id'] : null; $passengerId = $ride['passenger_id'] ?? null; $snapshot['ride'] = [ 'status' => $ride['status'] ?? null, 'start_location' => $ride['start_location'] ?? null, 'end_location' => $ride['end_location'] ?? null, 'carType' => $ride['carType'] ?? $ride['car_type'] ?? null, 'price' => $ride['price'] ?? null, 'rideTimeStart' => $ride['rideTimeStart'] ?? null, ]; } } // ‏آخر موقع معروف للسائق من Redis — أدق من أي شيء في MySQL، ويجيب // ‏على السؤال الأول الذي تسأله غرفة العمليات: أين هم الآن؟ if ($driverId && isset($redisLocation) && $redisLocation) { try { $loc = $redisLocation->hGetAll("driver:public:$driverId"); if (!empty($loc)) { $snapshot['driver_location'] = [ 'lat' => $loc['lat'] ?? null, 'lng' => $loc['lng'] ?? null, 'heading' => $loc['heading'] ?? null, 'speed' => $loc['speed'] ?? null, 'updated_at' => $loc['updated_at'] ?? null, ]; } } catch (Throwable $e) { error_log("[safety] تعذّرت قراءة موقع السائق: " . $e->getMessage()); } } // ‏موقع المبلِّغ كما أرسله التطبيق. نخزّنه كما هو ولا نبني عليه قراراً — // ‏قيمة قادمة من العميل، لكنها في بلاغ سلامة أفضل من لا شيء. if (is_numeric($lat) && is_numeric($lng)) { $snapshot['reporter_location'] = ['lat' => (float) $lat, 'lng' => (float) $lng]; } // ── ٢. تسجيل البلاغ ───────────────────────────────────── $ins = $con->prepare(" INSERT INTO safety_incidents (reporter_type, reporter_id, trigger_type, ride_id, driver_id, passenger_id, lat, lng, note, snapshot) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $ins->execute([ $reporterType, $reporterId, $triggerType, $rideId ?: null, $driverId, $passengerId, is_numeric($lat) ? $lat : null, is_numeric($lng) ? $lng : null, $note ? mb_substr($note, 0, 500) : null, json_encode($snapshot, JSON_UNESCAPED_UNICODE), ]); $incidentId = (int) $con->lastInsertId(); error_log("[safety] بلاغ #$incidentId — $triggerType من $reporterType" . " $reporterId (رحلة=" . ($rideId ?: '-') . " سائق=" . ($driverId ?: '-') . ")"); } catch (PDOException $e) { error_log("[safety] فشل تسجيل البلاغ: " . $e->getMessage()); jsonError("Failed to record incident", 500); } // ── ٣. تجميد إسناد الرحلات للسائق ─────────────────────────── // ‏يوقف تكرار الواقعة فوراً. منفصل عن blockedUntil (عقوبة الإلغاءات) الذي // ‏ينتهي بعد ساعات وحده — هذا يبقى حتى يرفعه إنسان. // ‏لا يُطبَّق على بلاغ السائق عن نفسه: قد يكون هو المعتدى عليه. if ($driverId && $reporterType === 'passenger') { try { $con->prepare("UPDATE driver SET safetyHoldIncidentId = ? WHERE id = ?") ->execute([$incidentId, $driverId]); // ‏نسخة Redis هي ما يفحصه acceptRide.php — بلا TTL: التجميد لا ينتهي بالوقت. if (isset($redisLocation) && $redisLocation) { $redisLocation->set("driver:$driverId:safety_hold", (string) $incidentId); } } catch (Throwable $e) { error_log("[safety] تعذّر تجميد السائق $driverId: " . $e->getMessage()); } } // ── ٤. تنبيه الإدارة ──────────────────────────────────────── // ‏أفضل جهد: البلاغ مسجَّل بالفعل، وفشل التنبيه لا يُسقطه. try { if (isset($redis) && $redis) { // ‏قائمة يقرأها بانل الإدارة — أسرع من استعلام على الجدول عند كل تحديث $redis->lPush('safety:open_incidents', (string) $incidentId); $redis->lTrim('safety:open_incidents', 0, 199); } if (function_exists('sendFCM_Internal')) { $topic = getenv('SAFETY_ALERT_TOPIC') ?: 'siro_safety_ops'; sendFCM_Internal( $topic, "🚨 بلاغ سلامة #$incidentId", "بلاغ $triggerType من $reporterType" . ($rideId ? " — رحلة $rideId" : ""), ['category' => 'safety_incident', 'incident_id' => (string) $incidentId], "safety_incident", true // topic ); } } catch (Throwable $e) { error_log("[safety] تعذّر تنبيه الإدارة للبلاغ #$incidentId: " . $e->getMessage()); } jsonSuccess([ 'incident_id' => $incidentId, 'driver_frozen' => (bool) ($driverId && $reporterType === 'passenger'), ], "Incident recorded");