Update: 2026-08-07 02:58:47
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// safety/list_incidents.php — قائمة بلاغات السلامة للوحة الإدارة
|
||||||
|
//
|
||||||
|
// قراءة خالصة. الترتيب: المفتوح أولاً ثم الأحدث — غرفة العمليات تحتاج
|
||||||
|
// ما لم يُعالَج بعد، لا أحدث ما وقع.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// تحت Admin/ عمداً: بوّابة connect.php المسارية تفرض دور admin أو
|
||||||
|
// super_admin على كل ما تحت هذا المجلد. لو بقي في safety/ لقرأه أي حامل
|
||||||
|
// JWT صالح — بما فيه راكب — وفيه معرّفات ومواقع أطراف الحادثة.
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$status = filterRequest("status"); // open | acknowledged | resolved | false_alarm | (فارغ = الكل)
|
||||||
|
$limit = (int) (filterRequest("limit", 'int') ?: 50);
|
||||||
|
$limit = max(1, min(200, $limit));
|
||||||
|
|
||||||
|
$allowed = ['open', 'acknowledged', 'resolved', 'false_alarm'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($status && in_array($status, $allowed, true)) {
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT * FROM safety_incidents
|
||||||
|
WHERE status = ?
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $limit
|
||||||
|
");
|
||||||
|
$stmt->execute([$status]);
|
||||||
|
} else {
|
||||||
|
// FIELD() يضع 'open' أولاً مهما كان تاريخه — بلاغ مفتوح من أمس
|
||||||
|
// أهم من بلاغ عولج قبل دقيقة.
|
||||||
|
$stmt = $con->query("
|
||||||
|
SELECT * FROM safety_incidents
|
||||||
|
ORDER BY FIELD(status, 'open', 'acknowledged', 'resolved', 'false_alarm'),
|
||||||
|
created_at DESC
|
||||||
|
LIMIT $limit
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// عدّاد المفتوح — الرقم الذي يظهر كشارة على اللوحة
|
||||||
|
$openCount = (int) $con->query(
|
||||||
|
"SELECT COUNT(*) FROM safety_incidents WHERE status = 'open'"
|
||||||
|
)->fetchColumn();
|
||||||
|
|
||||||
|
jsonSuccess(['open_count' => $openCount, 'incidents' => $rows], "ok");
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("[safety/list] " . $e->getMessage());
|
||||||
|
jsonError("DB Error", 500);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// Admin/safety/update_incident.php — معالجة بلاغ سلامة
|
||||||
|
//
|
||||||
|
// يحوّل البلاغ من "مفتوح" إلى حالة نهائية، ويرفع تجميد السائق عند
|
||||||
|
// الإغلاق. بدون هذه النقطة يبقى السائق مجمّداً إلى الأبد — التجميد بلا
|
||||||
|
// TTL عمداً، فرفعه قرار إنسان لا انقضاء وقت.
|
||||||
|
//
|
||||||
|
// تحت Admin/ فترث بوّابة الدور من connect.php.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$incidentId = filterRequest("incident_id", 'int');
|
||||||
|
$newStatus = filterRequest("status");
|
||||||
|
$note = filterRequest("note");
|
||||||
|
$releaseHold = filterRequest("release_hold");
|
||||||
|
|
||||||
|
$allowed = ['acknowledged', 'resolved', 'false_alarm'];
|
||||||
|
|
||||||
|
if (!$incidentId || !in_array($newStatus, $allowed, true)) {
|
||||||
|
jsonError("Missing incident_id or invalid status");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("SELECT * FROM safety_incidents WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$incidentId]);
|
||||||
|
$incident = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$incident) {
|
||||||
|
jsonError("Incident not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$con->prepare("
|
||||||
|
UPDATE safety_incidents
|
||||||
|
SET status = ?, acknowledged_by = ?, acknowledged_at = NOW(), resolution_note = ?
|
||||||
|
WHERE id = ?
|
||||||
|
")->execute([
|
||||||
|
$newStatus,
|
||||||
|
$user_id ?? 'admin',
|
||||||
|
$note ? mb_substr($note, 0, 500) : null,
|
||||||
|
$incidentId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ── رفع التجميد ─────────────────────────────────────────
|
||||||
|
// افتراضياً يُرفع عند الإغلاق (resolved / false_alarm) ولا يُرفع عند
|
||||||
|
// مجرد الاطلاع (acknowledged). ويمكن للمراجع أن يقرر صراحةً.
|
||||||
|
$shouldRelease = $releaseHold !== null
|
||||||
|
? in_array(strtolower((string) $releaseHold), ['1', 'true', 'yes'], true)
|
||||||
|
: in_array($newStatus, ['resolved', 'false_alarm'], true);
|
||||||
|
|
||||||
|
$released = false;
|
||||||
|
$driverId = $incident['driver_id'] ?? null;
|
||||||
|
|
||||||
|
if ($shouldRelease && $driverId) {
|
||||||
|
try {
|
||||||
|
// نرفع فقط إن كان التجميد من هذا البلاغ تحديداً: سائق عليه
|
||||||
|
// بلاغ ثانٍ أحدث يجب ألا يُفرَج عنه بإغلاق الأقدم.
|
||||||
|
$upd = $con->prepare("
|
||||||
|
UPDATE driver SET safetyHoldIncidentId = NULL
|
||||||
|
WHERE id = ? AND safetyHoldIncidentId = ?
|
||||||
|
");
|
||||||
|
$upd->execute([$driverId, $incidentId]);
|
||||||
|
|
||||||
|
if ($upd->rowCount() > 0) {
|
||||||
|
if (isset($redisLocation) && $redisLocation) {
|
||||||
|
$redisLocation->del("driver:$driverId:safety_hold");
|
||||||
|
}
|
||||||
|
$released = true;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("[safety/update] تعذّر رفع تجميد السائق $driverId: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[safety] بلاغ #$incidentId → $newStatus بواسطة "
|
||||||
|
. ($user_id ?? 'admin') . ($released ? " (رُفع التجميد)" : ""));
|
||||||
|
|
||||||
|
jsonSuccess([
|
||||||
|
'incident_id' => $incidentId,
|
||||||
|
'status' => $newStatus,
|
||||||
|
'hold_released' => $released,
|
||||||
|
], "Incident updated");
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("[safety/update] " . $e->getMessage());
|
||||||
|
jsonError("DB Error", 500);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
-- 2026_08_07_safety_incidents.sql
|
||||||
|
--
|
||||||
|
-- غرفة عمليات السلامة — أثر يمكن مراجعته لواقعة حقيقية.
|
||||||
|
--
|
||||||
|
-- الوضع اليوم: زر SOS في تطبيق الراكب ينتهي عند رسالة واتساب لجهة اتصال
|
||||||
|
-- شخصية خزّنها الراكب بنفسه (ui_interactions_controller.dart:145). لا نداء
|
||||||
|
-- API، لا سجل، لا تنبيه لأحد داخل الشركة. لو وقعت حادثة فعلية، الشركة لا
|
||||||
|
-- تعرف بها ولا تملك دليلاً عنها ولا تستطيع التدخل.
|
||||||
|
--
|
||||||
|
-- هذا الجدول هو الأثر المفقود. رسالة الواتساب تبقى كما هي — تُرسل بالتوازي
|
||||||
|
-- لا بدلاً، لأنها الأسرع وصولاً لمن يهمّه الأمر فعلاً.
|
||||||
|
--
|
||||||
|
-- ⚠️ يُنفَّذ على قاعدة primary (jorSiroDB) وحدها.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `safety_incidents` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
|
||||||
|
-- من أبلغ ولماذا
|
||||||
|
`reporter_type` VARCHAR(20) NOT NULL
|
||||||
|
COMMENT 'passenger | driver — من ضغط الزر',
|
||||||
|
`reporter_id` VARCHAR(100) NOT NULL,
|
||||||
|
`trigger_type` VARCHAR(30) NOT NULL DEFAULT 'sos_button'
|
||||||
|
COMMENT 'sos_button | shake | route_deviation | long_stop | overtime',
|
||||||
|
|
||||||
|
-- سياق الواقعة
|
||||||
|
`ride_id` VARCHAR(20) NULL DEFAULT NULL,
|
||||||
|
`driver_id` VARCHAR(100) NULL DEFAULT NULL,
|
||||||
|
`passenger_id` VARCHAR(100) NULL DEFAULT NULL,
|
||||||
|
`lat` DECIMAL(10,7) NULL DEFAULT NULL,
|
||||||
|
`lng` DECIMAL(10,7) NULL DEFAULT NULL,
|
||||||
|
`note` VARCHAR(500) NULL DEFAULT NULL,
|
||||||
|
|
||||||
|
-- لقطة الحالة وقت الواقعة، مجمّدة كما كانت.
|
||||||
|
-- الرحلة تتغيّر بعد الحادثة (تُلغى، تُنهى، يُصفَّر السائق) فقراءتها لاحقاً
|
||||||
|
-- لا تصف اللحظة. هذه اللقطة هي الدليل.
|
||||||
|
`snapshot` JSON NULL DEFAULT NULL,
|
||||||
|
|
||||||
|
-- دورة المعالجة
|
||||||
|
`status` VARCHAR(20) NOT NULL DEFAULT 'open'
|
||||||
|
COMMENT 'open | acknowledged | resolved | false_alarm',
|
||||||
|
`acknowledged_by` VARCHAR(100) NULL DEFAULT NULL,
|
||||||
|
`acknowledged_at` DATETIME NULL DEFAULT NULL,
|
||||||
|
`resolution_note` VARCHAR(500) NULL DEFAULT NULL,
|
||||||
|
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_status_created` (`status`, `created_at`),
|
||||||
|
KEY `idx_ride` (`ride_id`),
|
||||||
|
KEY `idx_driver` (`driver_id`, `created_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||||
|
COMMENT='بلاغات السلامة — السجل الوحيد القابل للمراجعة لواقعة';
|
||||||
|
|
||||||
|
-- تجميد إسناد الرحلات للسائق حتى المراجعة.
|
||||||
|
-- منفصل عن blockedUntil (عقوبة الإلغاءات المتكررة): ذاك مؤقت بساعات
|
||||||
|
-- وينتهي وحده، وهذا يبقى حتى يرفعه إنسان.
|
||||||
|
ALTER TABLE `driver`
|
||||||
|
ADD COLUMN `safetyHoldIncidentId` INT NULL DEFAULT NULL
|
||||||
|
COMMENT 'رقم بلاغ السلامة الذي جمّد هذا السائق. NULL = غير مجمّد';
|
||||||
|
|
||||||
|
-- للتراجع:
|
||||||
|
-- DROP TABLE `safety_incidents`;
|
||||||
|
-- ALTER TABLE `driver` DROP COLUMN `safetyHoldIncidentId`;
|
||||||
@@ -64,6 +64,15 @@ try {
|
|||||||
printFailure("Your account is temporarily suspended until $blockedUntil");
|
printFailure("Your account is temporarily suspended until $blockedUntil");
|
||||||
exit;
|
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) {
|
} catch (Throwable $eBlock) {
|
||||||
error_log("[accept_ride] تعذّر فحص الإيقاف: " . $eBlock->getMessage());
|
error_log("[accept_ride] تعذّر فحص الإيقاف: " . $eBlock->getMessage());
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// safety/incident.php — تسجيل بلاغ سلامة
|
||||||
|
//
|
||||||
|
// كان زر SOS ينتهي عند رسالة واتساب لجهة اتصال شخصية. الشركة لا تعلم،
|
||||||
|
// ولا تملك دليلاً، ولا تستطيع التدخل. هذه النقطة هي الأثر المفقود.
|
||||||
|
//
|
||||||
|
// رسالة الواتساب تبقى كما هي: التطبيق يرسلها **بالتوازي** لا بدلاً —
|
||||||
|
// هي الأسرع وصولاً لمن يهمّه أمر الراكب فعلاً، وهذه النقطة للشركة.
|
||||||
|
//
|
||||||
|
// ثلاثة أشياء تحدث هنا، بهذا الترتيب عمداً:
|
||||||
|
// ١. تسجيل البلاغ ولقطة الحالة ← الأهم، ولا يعتمد على شيء
|
||||||
|
// ٢. تجميد إسناد الرحلات للسائق ← يمنع تكرار الواقعة فوراً
|
||||||
|
// ٣. تنبيه الإدارة ← أفضل جهد، لا يُسقط ما قبله
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
$reporterType = filterRequest("reporter_type") ?: 'passenger';
|
||||||
|
$reporterId = filterRequest("reporter_id");
|
||||||
|
$rideId = filterRequest("ride_id");
|
||||||
|
$triggerType = filterRequest("trigger_type") ?: 'sos_button';
|
||||||
|
$note = filterRequest("note");
|
||||||
|
$lat = filterRequest("lat");
|
||||||
|
$lng = filterRequest("lng");
|
||||||
|
|
||||||
|
if (!$reporterId) {
|
||||||
|
jsonError("Missing reporter_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
// قائمة بيضاء: القيم تُخزَّن وتُعرض في لوحة الإدارة، ولا نريد نصاً حراً.
|
||||||
|
$allowedReporters = ['passenger', 'driver'];
|
||||||
|
$allowedTriggers = ['sos_button', 'shake', 'route_deviation', 'long_stop', 'overtime'];
|
||||||
|
if (!in_array($reporterType, $allowedReporters, true)) $reporterType = 'passenger';
|
||||||
|
if (!in_array($triggerType, $allowedTriggers, true)) $triggerType = 'sos_button';
|
||||||
|
|
||||||
|
$driverId = null;
|
||||||
|
$passengerId = null;
|
||||||
|
$snapshot = ['reported_at' => 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");
|
||||||
@@ -233,6 +233,11 @@ class AppLink {
|
|||||||
/// تستخدم نفس دالة الحساب التي يستخدمها الإلغاء الفعلي، فما يُعرض
|
/// تستخدم نفس دالة الحساب التي يستخدمها الإلغاء الفعلي، فما يُعرض
|
||||||
/// للراكب هو ما سيُخصم منه بالضبط.
|
/// للراكب هو ما سيُخصم منه بالضبط.
|
||||||
static String get previewCancelFee => "$server/ride/rides/preview_cancel_fee.php";
|
static String get previewCancelFee => "$server/ride/rides/preview_cancel_fee.php";
|
||||||
|
|
||||||
|
/// تسجيل بلاغ سلامة لدى الشركة. يُستدعى **بالتوازي** مع رسالة الواتساب
|
||||||
|
/// لجهة الاتصال الشخصية، لا بدلاً منها: الواتساب أسرع وصولاً لمن يهمّه
|
||||||
|
/// الأمر، وهذه النقطة هي الأثر الذي تملكه الشركة وتستطيع التدخل به.
|
||||||
|
static String get safetyIncident => "$server/safety/incident.php";
|
||||||
//-----------------ridessss------------------
|
//-----------------ridessss------------------
|
||||||
static String get addRides => "$rideServerSide/ride/rides/add.php";
|
static String get addRides => "$rideServerSide/ride/rides/add.php";
|
||||||
static String get getRides => "$rideServerSide/ride/rides/get.php";
|
static String get getRides => "$rideServerSide/ride/rides/get.php";
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ class UiInteractionsController extends GetxController {
|
|||||||
kolor: AppColor.redColor,
|
kolor: AppColor.redColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
|
// الشركة أولاً ثم الواتساب: تسجيل البلاغ لا ينتظر شيئاً ولا
|
||||||
|
// يؤخّر الرسالة (بلا await)، لكنه يُطلق قبلها حتى لا يضيع
|
||||||
|
// إن أغلق المستخدم التطبيق بعد فتح واتساب.
|
||||||
|
_reportSafetyIncident();
|
||||||
_shareTripDetailsSOS();
|
_shareTripDetailsSOS();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -142,6 +146,32 @@ class UiInteractionsController extends GetxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// يسجّل البلاغ لدى الشركة: يُنشئ سجل حادثة، يُجمّد إسناد الرحلات
|
||||||
|
/// للسائق حتى المراجعة، ويُنبّه غرفة العمليات.
|
||||||
|
///
|
||||||
|
/// لا ينتظر ولا يرمي: زر الطوارئ يجب ألا يتعطّل أبداً بسبب الشبكة.
|
||||||
|
/// أي فشل هنا لا يمنع رسالة الواتساب التي تليه.
|
||||||
|
void _reportSafetyIncident({String trigger = 'sos_button'}) {
|
||||||
|
try {
|
||||||
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
||||||
|
final locSearch = Get.find<LocationSearchController>();
|
||||||
|
|
||||||
|
CRUD().post(
|
||||||
|
link: AppLink.safetyIncident,
|
||||||
|
payload: {
|
||||||
|
'reporter_type': 'passenger',
|
||||||
|
'reporter_id': box.read(BoxName.passengerID).toString(),
|
||||||
|
'ride_id': rideLifecycle.rideId?.toString() ?? '',
|
||||||
|
'trigger_type': trigger,
|
||||||
|
'lat': locSearch.passengerLocation.latitude.toString(),
|
||||||
|
'lng': locSearch.passengerLocation.longitude.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("⚠️ تعذّر تسجيل بلاغ السلامة: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _shareTripDetailsSOS() {
|
void _shareTripDetailsSOS() {
|
||||||
final rideLifecycle = Get.find<RideLifecycleController>();
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
||||||
final locSearch = Get.find<LocationSearchController>();
|
final locSearch = Get.find<LocationSearchController>();
|
||||||
|
|||||||
Reference in New Issue
Block a user