Update: 2026-08-02 17:52:28

This commit is contained in:
Hamza-Ayed
2026-08-02 17:52:28 +03:00
parent b78a6797d5
commit 4620e84d34
96 changed files with 6250 additions and 476 deletions
+26
View File
@@ -0,0 +1,26 @@
<?php
// food/courier/active.php — طلبات التوصيل الحالية للسائق (polling fallback + شاشة المهمة)
require_once __DIR__ . '/../connect_courier.php';
$st = $food_con->prepare(
"SELECT o.id, o.status, o.merchant_id, m.name_ar AS merchant_name_ar, m.latitude AS merchant_lat,
m.longitude AS merchant_lng, m.address AS merchant_address, o.delivery_fee,
o.delivery_address, o.delivery_lat, o.delivery_lng, o.created_at,
CASE WHEN o.status IN ('courier_assigned','picked_up') THEN o.delivery_address ELSE NULL END AS visible_address
FROM food_orders o
JOIN food_merchants m ON m.id = o.merchant_id
WHERE o.courier_id = ? AND o.status IN ('courier_assigned','picked_up')
ORDER BY o.courier_assigned_at ASC"
);
$st->execute([$food_courier_id]);
$orders = $st->fetchAll();
// بيانات الزبون (العنوان) تظهر فقط بعد courier_assigned وتُحجب بعد delivered — لا نُعيد أي طلب مسلَّم هنا أصلاً
foreach ($orders as &$o) {
unset($o['delivery_address']);
$o['delivery_address'] = $o['visible_address'];
unset($o['visible_address']);
}
unset($o);
jsonSuccess(['orders' => $orders]);
+34
View File
@@ -0,0 +1,34 @@
<?php
// food/courier/delivered.php — تسليم الطلب — تثبيت المال (capture) وقيد أرباح السائق
require_once __DIR__ . '/../connect_courier.php';
$orderId = filterRequest('order_id', 'int');
if (!$orderId) jsonError('order_id is required');
$order = foodAssertOrderOwnership($orderId, 'courier', $food_courier_id);
food_transition_status($orderId, 'delivered', 'courier', $food_courier_id);
if ($order['payment_method'] === 'wallet') {
// المبلغ خُصم بالكامل عند الإنشاء (انظر ملاحظة foodWalletMove) — capture هنا تسجيل محاسبي فقط
$food_con->prepare(
"INSERT INTO food_order_payments (order_id, type, amount, status) VALUES (?,'capture',?,'success')"
)->execute([$orderId, (int)$order['grand_total']]);
} else {
// نقداً: السائق حصّل grand_total كاملاً من الزبون. يحتفظ بـ delivery_fee (أجرته)
// ويبقى ديناً عليه الباقي (items_total+service_fee — حصة المطعم والمنصة) حتى يُسوّى
// إدارياً. هذا قيد محاسبي فقط هنا — لا حركة مالية آلية فعلية بعد (انظر التنبيه في
// admin/payouts.php حول عدم وجود عقد API مؤكد لتحصيل ديون السائق النقدية آلياً).
$courierOwed = (int)$order['grand_total'] - (int)$order['delivery_fee'];
$food_con->prepare(
"INSERT INTO food_order_payments (order_id, type, amount, status) VALUES (?,'cash_settlement',?,'pending')"
)->execute([$orderId, $courierOwed]);
}
// قيد أرباح السائق (أجرة التوصيل) — مُتراكم، يُسوَّى عبر دورة تسوية منفصلة
// (لا نقتطع فعلياً من/إلى محفظة السائق هنا: عقد S2S المؤكد فقط لتحويلات
// سائق↔سائق (driverWallet/transfer.php) لا لإيداع أرباح من المنصة مباشرة).
$food_con->prepare(
"INSERT INTO food_order_payments (order_id, type, amount, status) VALUES (?,'courier_payout',?,'pending')"
)->execute([$orderId, (int)$order['delivery_fee']]);
jsonSuccess(['order_id' => $orderId, 'status' => 'delivered']);
+76
View File
@@ -0,0 +1,76 @@
<?php
// food/courier/offer_respond.php — قبول/رفض عرض توصيل
// body: order_id, response ('accept'|'reject')
require_once __DIR__ . '/../connect_courier.php';
$orderId = filterRequest('order_id', 'int');
$response = filterRequest('response');
if (!$orderId || !in_array($response, ['accept', 'reject'], true)) {
jsonError('order_id and response (accept|reject) are required');
}
$assignSt = $food_con->prepare(
"SELECT id FROM food_courier_assignments WHERE order_id=? AND courier_id=? AND status='offered'
ORDER BY offered_at DESC LIMIT 1"
);
$assignSt->execute([$orderId, $food_courier_id]);
$assignment = $assignSt->fetch();
if (!$assignment) jsonError('No pending offer for this order', 404);
if ($response === 'reject') {
$food_con->prepare("UPDATE food_courier_assignments SET status='rejected', responded_at=NOW() WHERE id=?")
->execute([$assignment['id']]);
// أعد المحاولة على مرشّح آخر — يعيد استخدام نفس منطق ready.php
$orderSt = $food_con->prepare("SELECT merchant_id FROM food_orders WHERE id=? AND status='ready'");
$orderSt->execute([$orderId]);
if ($order = $orderSt->fetch()) {
$merchantSt = $food_con->prepare("SELECT latitude, longitude FROM food_merchants WHERE id=?");
$merchantSt->execute([$order['merchant_id']]);
if ($merchant = $merchantSt->fetch()) {
$candidates = foodFindNearbyCouriers((float)$merchant['latitude'], (float)$merchant['longitude']);
$priorSt = $food_con->prepare("SELECT courier_id FROM food_courier_assignments WHERE order_id=?");
$priorSt->execute([$orderId]);
$prior = array_column($priorSt->fetchAll(), 'courier_id');
foreach ($candidates as $candidateId) {
if (in_array($candidateId, $prior, true)) continue;
foodOfferOrderToCourier($orderId, $candidateId);
break;
}
}
}
jsonSuccess(['order_id' => $orderId, 'response' => 'rejected']);
}
// accept — القفل الذرّي يمنع سباق القبول لو انتهت مهلة سابقة وعُرض على اثنين معاً
if (!foodLockOrderForCourier($orderId, $food_courier_id)) {
$owner = foodOrderLockOwner($orderId);
if ($owner !== $food_courier_id) {
jsonError('Order was already claimed by another courier', 409);
}
}
// الطلب لم يُسند لأي سائق بعد عند هذه النقطة — لا فحص ملكية هنا، فقط فحص الحالة أدناه
$orderSt = $food_con->prepare("SELECT status FROM food_orders WHERE id=? LIMIT 1");
$orderSt->execute([$orderId]);
$orderRow = $orderSt->fetch();
if (!$orderRow || $orderRow['status'] !== 'ready') {
jsonError('Order is no longer available for assignment', 409);
}
$food_con->beginTransaction();
try {
$food_con->prepare("UPDATE food_orders SET courier_id=? WHERE id=?")->execute([$food_courier_id, $orderId]);
$food_con->prepare("UPDATE food_courier_assignments SET status='accepted', responded_at=NOW() WHERE id=?")
->execute([$assignment['id']]);
$food_con->commit();
} catch (Throwable $e) {
$food_con->rollBack();
appLog('[FOOD][COURIER][offer_respond] ' . $e->getMessage(), 'ERROR');
jsonError('Failed to accept offer', 500);
}
food_transition_status($orderId, 'courier_assigned', 'courier', $food_courier_id);
jsonSuccess(['order_id' => $orderId, 'response' => 'accepted']);
+19
View File
@@ -0,0 +1,19 @@
<?php
// food/courier/pending_offers.php — عروض التوصيل المعروضة على هذا السائق حالياً
// (polling fallback — food_socket يدفع 'food_delivery_offer' لحظياً، وهذا احتياطي
// لو انقطع اتصال السوكيت أو كان التطبيق لا يحمل اتصالاً حياً بالسوكيت)
require_once __DIR__ . '/../connect_courier.php';
$st = $food_con->prepare(
"SELECT a.order_id, a.offered_at, o.merchant_id, m.name_ar AS merchant_name_ar,
m.latitude AS merchant_lat, m.longitude AS merchant_lng, o.delivery_fee,
o.delivery_lat, o.delivery_lng
FROM food_courier_assignments a
JOIN food_orders o ON o.id = a.order_id
JOIN food_merchants m ON m.id = o.merchant_id
WHERE a.courier_id = ? AND a.status = 'offered' AND a.offered_at > (NOW() - INTERVAL 20 SECOND)
ORDER BY a.offered_at ASC"
);
$st->execute([$food_courier_id]);
jsonSuccess(['offers' => $st->fetchAll()]);
+11
View File
@@ -0,0 +1,11 @@
<?php
// food/courier/picked_up.php — السائق استلم الطلب من المطعم
require_once __DIR__ . '/../connect_courier.php';
$orderId = filterRequest('order_id', 'int');
if (!$orderId) jsonError('order_id is required');
foodAssertOrderOwnership($orderId, 'courier', $food_courier_id);
food_transition_status($orderId, 'picked_up', 'courier', $food_courier_id);
jsonSuccess(['order_id' => $orderId, 'status' => 'picked_up']);
@@ -0,0 +1,11 @@
<?php
// food/courier/toggle_availability.php — تفعيل/إيقاف "وضع التوصيل" للسائق
// السائق لا يظهر لعروض التوصيل إلا إذا كان أيضاً متاحاً فعلياً في geo:drivers:available
require_once __DIR__ . '/../connect_courier.php';
$enable = filterRequest('enabled', 'bool');
if ($enable === null) jsonError('enabled (true|false) is required');
foodCourierOptIn($food_courier_id, $enable);
jsonSuccess(['courier_id' => $food_courier_id, 'delivery_mode_enabled' => $enable]);