120 lines
5.3 KiB
PHP
120 lines
5.3 KiB
PHP
<?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);
|
||
}
|