Food: single-source tracking, mode exclusivity, in-app navigation
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// food/courier/location.php — بثّ موقع السائق أثناء مهمة توصيل نشطة
|
||||
//
|
||||
// خصوصية: الموقع يُقبل فقط ما دام الطلب في courier_assigned/picked_up، ويُخزَّن
|
||||
// في Redis بعمر 90 ثانية لا في قاعدة البيانات. بمجرد التسليم يرفض الخادم أي
|
||||
// تحديث، وينتهي آخر موقع تلقائياً — فلا يتحول التتبّع إلى مراقبة للسائق بعد
|
||||
// انتهاء عمله، ولا يبقى أثر دائم لتحركاته في وحدة الطعام.
|
||||
require_once __DIR__ . '/../connect_courier.php';
|
||||
|
||||
$orderId = filterRequest('order_id', 'int');
|
||||
$lat = filterRequest('lat');
|
||||
$lng = filterRequest('lng');
|
||||
|
||||
if (!$orderId || $lat === null || $lng === null) {
|
||||
jsonError('order_id, lat and lng are required');
|
||||
}
|
||||
|
||||
$lat = (float)$lat;
|
||||
$lng = (float)$lng;
|
||||
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180 || ($lat === 0.0 && $lng === 0.0)) {
|
||||
jsonError('Invalid coordinates');
|
||||
}
|
||||
|
||||
$order = foodAssertOrderOwnership($orderId, 'courier', $food_courier_id);
|
||||
|
||||
if (!in_array($order['status'], ['courier_assigned', 'picked_up'], true)) {
|
||||
jsonError('Location sharing is only allowed while the delivery is active', 403);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'order_id' => $orderId,
|
||||
'lat' => round($lat, 6),
|
||||
'lng' => round($lng, 6),
|
||||
'heading' => filterRequest('heading') !== null ? (float)filterRequest('heading') : null,
|
||||
'ts' => time(),
|
||||
];
|
||||
|
||||
if ($redis) {
|
||||
// مصدر مسار الاحتياط: تطبيق الراكب يسحبه من order/courier_location.php
|
||||
$redis->setex("food:order:{$orderId}:courier_pos", 90, json_encode($payload));
|
||||
}
|
||||
|
||||
// المسار اللحظي — غرفة الزبون على سوكيت الطعام
|
||||
foodPushToSocket('courier_location', array_merge($payload, [
|
||||
'passenger_id' => (string)$order['passenger_id'],
|
||||
]));
|
||||
|
||||
jsonSuccess(['order_id' => $orderId, 'ts' => $payload['ts']]);
|
||||
@@ -508,7 +508,22 @@ function foodFindNearbyCouriers(float $lat, float $lng, float $radiusKm = 5, int
|
||||
$optedIn = $redis->sMembers('food:couriers:opted_in');
|
||||
if (!$optedIn) return [];
|
||||
|
||||
return array_values(array_intersect($nearby, $optedIn));
|
||||
$candidates = array_values(array_intersect($nearby, $optedIn));
|
||||
if (!$candidates) return [];
|
||||
|
||||
// مهمة واحدة في الوقت الواحد: التطبيق يُعلن السائق مشغولاً في نظام الرحلات
|
||||
// فور إسناد طلب له، فيخرج من geo:drivers:available وحده. لكن ذلك يعتمد على
|
||||
// وصول تحديث موقع، فقد يتأخر ثوانٍ. هذا الفحص هو الضمانة القاطعة: لا يُعرض
|
||||
// طلب ثانٍ على سائق يحمل طلباً نشطاً مهما تأخّر تحديث المجموعة.
|
||||
$placeholders = implode(',', array_fill(0, count($candidates), '?'));
|
||||
$busySt = Database::get('food')->prepare(
|
||||
"SELECT DISTINCT courier_id FROM food_orders
|
||||
WHERE courier_id IN ($placeholders) AND status IN ('courier_assigned','picked_up')"
|
||||
);
|
||||
$busySt->execute($candidates);
|
||||
$busy = array_column($busySt->fetchAll(), 'courier_id');
|
||||
|
||||
return array_values(array_diff($candidates, $busy));
|
||||
}
|
||||
|
||||
function foodOfferOrderToCourier(int $orderId, string $courierId): void
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// food/order/courier_location.php — آخر موقع معروف لسائق طلبي (للراكب)
|
||||
//
|
||||
// مسار سحب (polling): تطبيق الراكب لا يحمل عميل سوكيت للطعام حالياً، وهذا
|
||||
// المصدر يكفي لخريطة تتبّع سلسة مع تحديث كل بضع ثوانٍ.
|
||||
// الموقع نفسه ينتهي من Redis خلال 90 ثانية، فبعد التسليم — أو بعد توقف
|
||||
// السائق عن البثّ — تُرجع الواجهة null بلا أي أثر متبقٍ.
|
||||
require_once __DIR__ . '/../connect_app.php';
|
||||
|
||||
$orderId = filterRequest('order_id', 'int');
|
||||
if (!$orderId) jsonError('order_id is required');
|
||||
|
||||
$order = foodAssertOrderOwnership($orderId, 'customer', $food_passenger_id);
|
||||
|
||||
// لا نكشف موقع السائق إلا خلال نافذة التوصيل — لا قبل الإسناد ولا بعد التسليم
|
||||
if (!in_array($order['status'], ['courier_assigned', 'picked_up'], true)) {
|
||||
jsonSuccess(['status' => $order['status'], 'courier_position' => null]);
|
||||
}
|
||||
|
||||
$position = null;
|
||||
if ($redis) {
|
||||
$raw = $redis->get("food:order:{$orderId}:courier_pos");
|
||||
if ($raw) $position = json_decode($raw, true);
|
||||
}
|
||||
|
||||
// إحداثيات وجهتَي الخريطة — المطعم وعنوان التسليم (كلاهما معروف للزبون أصلاً)
|
||||
$st = $food_con->prepare(
|
||||
"SELECT o.delivery_lat, o.delivery_lng, o.delivery_address,
|
||||
m.name_ar AS merchant_name_ar, m.latitude AS merchant_lat, m.longitude AS merchant_lng
|
||||
FROM food_orders o JOIN food_merchants m ON m.id = o.merchant_id
|
||||
WHERE o.id = ? LIMIT 1"
|
||||
);
|
||||
$st->execute([$orderId]);
|
||||
$meta = $st->fetch() ?: [];
|
||||
|
||||
jsonSuccess([
|
||||
'status' => $order['status'],
|
||||
'courier_position' => $position,
|
||||
'merchant' => $meta ? [
|
||||
'name_ar' => $meta['merchant_name_ar'],
|
||||
'lat' => (float)$meta['merchant_lat'],
|
||||
'lng' => (float)$meta['merchant_lng'],
|
||||
] : null,
|
||||
'destination' => $meta ? [
|
||||
'address' => $meta['delivery_address'],
|
||||
'lat' => (float)$meta['delivery_lat'],
|
||||
'lng' => (float)$meta['delivery_lng'],
|
||||
] : null,
|
||||
]);
|
||||
Reference in New Issue
Block a user