Update codebase
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// ride/scheduled/add.php — إنشاء حجز مسبق
|
||||
//
|
||||
// لا يُنشئ رحلة. يسجّل نيّة سفر في وقت محدد، ويتولّى الكرون
|
||||
// (bot/cron_scheduled_rides.php) تحويلها لرحلة فعلية قبل الموعد بهامش.
|
||||
//
|
||||
// الفصل مقصود: رحلة تُنشأ الآن لموعد بعد ست ساعات ستدور في السوق ست
|
||||
// ساعات، وتُربك الإسناد والتسعير والخريطة الحرارية.
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// الهوية من الـJWT لا من الطلب.
|
||||
$passengerId = $user_id ?? '';
|
||||
if (empty($passengerId)) {
|
||||
jsonError('Unauthorized', 401);
|
||||
}
|
||||
|
||||
$startLocation = filterRequest('start_location');
|
||||
$endLocation = filterRequest('end_location');
|
||||
$startName = filterRequest('start_name');
|
||||
$endName = filterRequest('end_name');
|
||||
$carType = filterRequest('car_type') ?: 'Speed';
|
||||
$scheduledAt = filterRequest('scheduled_at'); // 'YYYY-MM-DD HH:MM:SS'
|
||||
$distance = (float) (filterRequest('distance', 'float') ?: 0);
|
||||
$duration = (int) (filterRequest('duration', 'int') ?: 0);
|
||||
$estimated = (float) (filterRequest('estimated_price', 'float') ?: 0);
|
||||
$note = filterRequest('note');
|
||||
|
||||
if (!$startLocation || !$endLocation || !$scheduledAt) {
|
||||
jsonError('start_location, end_location and scheduled_at are required');
|
||||
}
|
||||
|
||||
// قائمة بيضاء لنوع السيارة — نفس منطق findBestDrivers.
|
||||
$allowedCarTypes = ['Comfort', 'Mishwar Vip', 'Scooter', 'Pink Bike', 'Electric',
|
||||
'Lady', 'Van', 'Awfar Car', 'Fixed Price', 'Speed', 'Rayeh Gai'];
|
||||
if (!in_array($carType, $allowedCarTypes, true)) {
|
||||
$carType = 'Speed';
|
||||
}
|
||||
|
||||
$ts = strtotime($scheduledAt);
|
||||
if ($ts === false) {
|
||||
jsonError('Invalid scheduled_at format');
|
||||
}
|
||||
|
||||
// ── حدود زمنية ──────────────────────────────────────────────
|
||||
// الحد الأدنى: حجز لبعد عشر دقائق لا معنى له — اطلب رحلة عادية.
|
||||
// الحد الأعلى: يومان. قرار المالك — حجز لبعد أسبوعين كلام فاضٍ: الراكب
|
||||
// ينساه، وخطته تتغيّر، ويحتل مكاناً في التوقّع بلا قيمة. اليوم والغد
|
||||
// وبعده هي المدى الذي يلتزم به الناس فعلاً.
|
||||
const SCHEDULE_MIN_LEAD_MINUTES = 30;
|
||||
const SCHEDULE_MAX_DAYS_AHEAD = 2;
|
||||
|
||||
$minutesAhead = ($ts - time()) / 60;
|
||||
|
||||
if ($minutesAhead < SCHEDULE_MIN_LEAD_MINUTES) {
|
||||
jsonError('Scheduled time must be at least ' . SCHEDULE_MIN_LEAD_MINUTES . ' minutes from now');
|
||||
}
|
||||
if ($minutesAhead > SCHEDULE_MAX_DAYS_AHEAD * 24 * 60) {
|
||||
jsonError('يمكنك الحجز حتى ' . SCHEDULE_MAX_DAYS_AHEAD . ' يومين مقدماً فقط');
|
||||
}
|
||||
|
||||
// ── هامش البحث عن سائق ──────────────────────────────────────
|
||||
// نشتقّه من المسافة بدل أن نسأل الراكب: رحلة مطار بعيدة تحتاج وقتاً
|
||||
// أطول لإيجاد سائق وللوصول إليه من رحلة داخل الحي.
|
||||
$leadMinutes = 15;
|
||||
if ($distance > 25) {
|
||||
$leadMinutes = 40;
|
||||
} elseif ($distance > 10) {
|
||||
$leadMinutes = 25;
|
||||
}
|
||||
|
||||
try {
|
||||
// ── منع الحجز المزدوج ───────────────────────────────────
|
||||
// راكب له حجزان في نفس النصف ساعة غالباً ضغط مرتين. الحجز المكرر
|
||||
// ينتج رحلتين حقيقيتين ويُحمّله رسمَي إلغاء.
|
||||
$dup = $con->prepare("
|
||||
SELECT id FROM scheduled_rides
|
||||
WHERE passenger_id = ? AND status = 'scheduled'
|
||||
AND ABS(TIMESTAMPDIFF(MINUTE, scheduled_at, ?)) < 30
|
||||
LIMIT 1
|
||||
");
|
||||
$dup->execute([$passengerId, date('Y-m-d H:i:s', $ts)]);
|
||||
if ($dup->fetchColumn()) {
|
||||
jsonError('You already have a booking around this time', 409);
|
||||
}
|
||||
|
||||
$ins = $con->prepare("
|
||||
INSERT INTO scheduled_rides
|
||||
(passenger_id, start_location, end_location, start_name, end_name,
|
||||
car_type, distance, duration, estimated_price,
|
||||
scheduled_at, lead_minutes, note)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
");
|
||||
$ins->execute([
|
||||
$passengerId, $startLocation, $endLocation, $startName, $endName,
|
||||
$carType, $distance, $duration, $estimated,
|
||||
date('Y-m-d H:i:s', $ts), $leadMinutes,
|
||||
$note ? mb_substr($note, 0, 255) : null,
|
||||
]);
|
||||
|
||||
$id = (int) $con->lastInsertId();
|
||||
|
||||
error_log("[scheduled] حجز #$id للراكب $passengerId في "
|
||||
. date('Y-m-d H:i', $ts) . " (هامش {$leadMinutes}د)");
|
||||
|
||||
jsonSuccess([
|
||||
'id' => $id,
|
||||
'scheduled_at' => date('Y-m-d H:i:s', $ts),
|
||||
'lead_minutes' => $leadMinutes,
|
||||
// نصرّح بأن السعر تقديري: الراكب يجب أن يعرف أن الرقم قد يتغيّر.
|
||||
'price_is_estimate' => true,
|
||||
], 'Ride scheduled');
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('[scheduled/add] ' . $e->getMessage());
|
||||
jsonError('DB Error', 500);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// ride/scheduled/cancel.php — إلغاء حجز مسبق
|
||||
//
|
||||
// بلا رسم: لا سائق قُبِل ولا أحد تحرّك. رسم الإلغاء يبدأ من لحظة قبول
|
||||
// السائق، والحجز لم يصل تلك المرحلة بعد.
|
||||
//
|
||||
// أما إن كان الكرون قد حوّله لرحلة فعلية (status=dispatched) فالإلغاء
|
||||
// يتبع مسار الرحلات العادي — cancel_ride_by_passenger.php — بقواعده
|
||||
// وإعفاءاته ورسومه.
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$passengerId = $user_id ?? '';
|
||||
$bookingId = filterRequest('id', 'int');
|
||||
$reason = filterRequest('reason');
|
||||
|
||||
if (empty($passengerId)) jsonError('Unauthorized', 401);
|
||||
if (!$bookingId) jsonError('Missing id');
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare("SELECT * FROM scheduled_rides WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$bookingId]);
|
||||
$booking = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$booking) {
|
||||
jsonError('Booking not found', 404);
|
||||
}
|
||||
|
||||
// الملكية: الحجز يحمل نقاط انطلاق ووجهة الراكب — لا يُلغيه غيره.
|
||||
if ((string) $booking['passenger_id'] !== (string) $passengerId) {
|
||||
error_log("[scheduled/cancel] SECURITY: محاولة إلغاء حجز غير مملوك"
|
||||
. " (booking=$bookingId caller=$passengerId)");
|
||||
jsonError('Forbidden', 403);
|
||||
}
|
||||
|
||||
if ($booking['status'] === 'dispatched') {
|
||||
jsonError('Ride already created — cancel it from the active ride screen', 409);
|
||||
}
|
||||
|
||||
if ($booking['status'] !== 'scheduled') {
|
||||
jsonSuccess(['id' => $bookingId, 'status' => $booking['status']],
|
||||
'Booking is not active');
|
||||
}
|
||||
|
||||
$con->prepare("
|
||||
UPDATE scheduled_rides
|
||||
SET status = 'cancelled', cancelled_reason = ?
|
||||
WHERE id = ? AND status = 'scheduled'
|
||||
")->execute([$reason ? mb_substr($reason, 0, 255) : null, $bookingId]);
|
||||
|
||||
jsonSuccess(['id' => $bookingId, 'status' => 'cancelled'], 'Booking cancelled');
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('[scheduled/cancel] ' . $e->getMessage());
|
||||
jsonError('DB Error', 500);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// ride/scheduled/list.php — حجوزات الراكب
|
||||
//
|
||||
// القادمة أولاً ثم الأحدث تاريخاً: الراكب يفتح الشاشة ليرى ما ينتظره،
|
||||
// لا ليتصفّح أرشيفه.
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$passengerId = $user_id ?? '';
|
||||
if (empty($passengerId)) {
|
||||
jsonError('Unauthorized', 401);
|
||||
}
|
||||
|
||||
$scope = filterRequest('scope') ?: 'upcoming';
|
||||
|
||||
try {
|
||||
if ($scope === 'upcoming_driver') {
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// لا عمود `driver_id` في scheduled_rides — ولا يصحّ أن يوجد:
|
||||
// الحجز يُنشئه الراكب، والسائق لا يُسند إلا لحظة توليد الرحلة
|
||||
// الفعلية عبر add_ride.php. الاستعلام السابق كان يسأل عن عمود
|
||||
// غير موجود فيسقط بـ 500 على كل فتح للشاشة.
|
||||
//
|
||||
// الربط الصحيح عبر الرحلة المولّدة: حجوزات أُسندت لهذا السائق
|
||||
// ولم يحن موعدها بعد.
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
$sql = "SELECT sr.*
|
||||
FROM scheduled_rides sr
|
||||
JOIN ride r ON r.id = sr.ride_id
|
||||
WHERE r.driver_id = ?
|
||||
AND sr.scheduled_at >= NOW()
|
||||
AND sr.status NOT IN ('cancelled', 'expired')
|
||||
ORDER BY sr.scheduled_at ASC
|
||||
LIMIT 50";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([$passengerId]); // $passengerId here is actually the user_id (driver's ID)
|
||||
} else {
|
||||
$sql = "SELECT * FROM scheduled_rides WHERE passenger_id = ?";
|
||||
if ($scope === 'upcoming') {
|
||||
$sql .= " AND status = 'scheduled' AND scheduled_at >= NOW()";
|
||||
}
|
||||
$sql .= " ORDER BY scheduled_at ASC LIMIT 50";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([$passengerId]);
|
||||
}
|
||||
|
||||
jsonSuccess(['bookings' => $stmt->fetchAll(PDO::FETCH_ASSOC)], 'ok');
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('[scheduled/list] ' . $e->getMessage());
|
||||
jsonError('DB Error', 500);
|
||||
}
|
||||
Reference in New Issue
Block a user