Update: 2026-06-14 22:10:07
This commit is contained in:
@@ -1,6 +1,25 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
/**
|
||||
* تطبيع رقم الهاتف ليتوافق مع التخزين في قاعدة البيانات
|
||||
*/
|
||||
function normalizePhone($phone) {
|
||||
$clean = preg_replace('/\D+/', '', $phone);
|
||||
// Syria: 099XXXXXXX or 9639XXXXXXX
|
||||
if (strlen($clean) === 10 && strpos($clean, '09') === 0) return '963' . substr($clean, 1);
|
||||
if (strlen($clean) === 12 && strpos($clean, '963') === 0) return $clean;
|
||||
if (strlen($clean) === 9 && strpos($clean, '9') === 0) return '963' . $clean;
|
||||
// Jordan: 079XXXXXXX or 9627XXXXXXX
|
||||
if (strlen($clean) === 10 && strpos($clean, '07') === 0) return '962' . substr($clean, 1);
|
||||
if (strlen($clean) === 12 && strpos($clean, '962') === 0) return $clean;
|
||||
if (strlen($clean) === 9 && strpos($clean, '7') === 0) return '962' . $clean;
|
||||
// Egypt: 010XXXXXXXX or 2010XXXXXXXX
|
||||
if (strlen($clean) === 11 && strpos($clean, '01') === 0) return '20' . substr($clean, 1);
|
||||
if (strlen($clean) === 13 && strpos($clean, '20') === 0) return $clean;
|
||||
return $clean;
|
||||
}
|
||||
|
||||
$phone = filterRequest('phone');
|
||||
if (!$phone) {
|
||||
error_log("[get_last_ride] Missing phone parameter");
|
||||
@@ -8,33 +27,69 @@ if (!$phone) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$raw = $phone;
|
||||
// تطبيع الرقم أولاً
|
||||
$raw = normalizePhone($phone);
|
||||
|
||||
// شَفِّر قبل الاستعلام
|
||||
$enc_raw = $encryptionHelper->encryptData($raw);
|
||||
|
||||
try {
|
||||
error_log("[get_last_ride] Searching passenger with phone=$raw");
|
||||
error_log("[get_last_ride] Searching phone normalized=$raw");
|
||||
|
||||
// 1) ابحث عن الراكب بالهاتف المشفّر
|
||||
$selP = $con->prepare("
|
||||
SELECT id, first_name, last_name, phone
|
||||
FROM passengers
|
||||
WHERE phone =:enc_raw
|
||||
WHERE phone = :enc_raw
|
||||
LIMIT 1
|
||||
");
|
||||
$selP->execute(['enc_raw' => $enc_raw]);
|
||||
$passenger = $selP->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$passenger) {
|
||||
error_log("[get_last_ride] Passenger not found (phone=$raw)");
|
||||
jsonError('Passenger not found for provided phone');
|
||||
// 2) ابحث عن السائق بالهاتف المشفّر
|
||||
$selD = $con->prepare("
|
||||
SELECT id AS driverID, first_name, last_name, phone
|
||||
FROM driver
|
||||
WHERE phone = :enc_raw
|
||||
LIMIT 1
|
||||
");
|
||||
$selD->execute(['enc_raw' => $enc_raw]);
|
||||
$driver = $selD->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$userId = null;
|
||||
$userType = null;
|
||||
|
||||
if ($passenger) {
|
||||
$userId = $passenger['id'];
|
||||
$userType = 'passenger';
|
||||
error_log("[get_last_ride] Passenger found id=" . $userId);
|
||||
}
|
||||
if ($driver) {
|
||||
$userId = $driver['driverID'];
|
||||
$userType = 'driver';
|
||||
error_log("[get_last_ride] Driver found id=" . $userId);
|
||||
}
|
||||
|
||||
if (!$userId) {
|
||||
error_log("[get_last_ride] User not found (phone=$raw)");
|
||||
jsonError('Phone number not found in system');
|
||||
exit;
|
||||
}
|
||||
|
||||
error_log("[get_last_ride] Passenger found id=" . $passenger['id']);
|
||||
// 3) تحديد حقل البحث في الرحلة
|
||||
$userField = ($userType === 'driver') ? 'r.driver_id' : 'r.passenger_id';
|
||||
|
||||
// 2) آخر رحلة لهذا الراكب
|
||||
// فلترة حسب الحالة إذا أُرسلت
|
||||
$filterStatus = filterRequest('status');
|
||||
$whereExtra = '';
|
||||
$params = [':uid' => $userId];
|
||||
|
||||
if (!empty($filterStatus) && $filterStatus !== 'all') {
|
||||
$whereExtra = "AND r.status = :filter_status";
|
||||
$params[':filter_status'] = $filterStatus;
|
||||
}
|
||||
|
||||
// 4) آخر 20 رحلة لهذا المستخدم
|
||||
$rideStmt = $con->prepare("
|
||||
SELECT
|
||||
r.id,
|
||||
@@ -58,43 +113,63 @@ try {
|
||||
r.rideTimeStart,
|
||||
r.rideTimeFinish,
|
||||
d.first_name AS driver_first_name,
|
||||
d.last_name AS driver_last_name
|
||||
d.last_name AS driver_last_name,
|
||||
d.phone AS d_phone,
|
||||
p.first_name AS p_fname,
|
||||
p.last_name AS p_lname,
|
||||
p.phone AS p_phone
|
||||
FROM ride r
|
||||
LEFT JOIN driver d ON d.id = r.driver_id
|
||||
WHERE r.passenger_id = :pid
|
||||
LEFT JOIN passengers p ON p.id = r.passenger_id
|
||||
WHERE $userField = :uid $whereExtra
|
||||
ORDER BY r.created_at DESC, r.id DESC
|
||||
LIMIT 1
|
||||
LIMIT 20
|
||||
");
|
||||
$rideStmt->execute(['pid' => $passenger['id']]);
|
||||
$ride = $rideStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$rideStmt->execute($params);
|
||||
$rides = $rideStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ride) {
|
||||
error_log("[get_last_ride] No rides found for passenger_id=" . $passenger['id']);
|
||||
jsonError('No rides found for this passenger');
|
||||
exit;
|
||||
// 5) فك تشفير الأسماء
|
||||
if ($passenger) {
|
||||
$passenger['first_name'] = $encryptionHelper->decryptData($passenger['first_name']);
|
||||
$passenger['last_name'] = $encryptionHelper->decryptData($passenger['last_name']);
|
||||
$passenger['phone'] = $encryptionHelper->decryptData($passenger['phone']);
|
||||
}
|
||||
if ($driver) {
|
||||
$driver['first_name'] = $encryptionHelper->decryptData($driver['first_name']);
|
||||
$driver['last_name'] = $encryptionHelper->decryptData($driver['last_name']);
|
||||
$driver['phone'] = $encryptionHelper->decryptData($driver['phone']);
|
||||
}
|
||||
|
||||
error_log("[get_last_ride] Found ride id=" . $ride['id'] . " for passenger_id=" . $passenger['id']);
|
||||
foreach ($rides as &$ride) {
|
||||
if (!empty($ride['driver_first_name'])) {
|
||||
$ride['driver_first_name'] = $encryptionHelper->decryptData($ride['driver_first_name']);
|
||||
}
|
||||
if (!empty($ride['driver_last_name'])) {
|
||||
$ride['driver_last_name'] = $encryptionHelper->decryptData($ride['driver_last_name']);
|
||||
}
|
||||
if (!empty($ride['d_phone'])) {
|
||||
$ride['d_phone'] = $encryptionHelper->decryptData($ride['d_phone']);
|
||||
}
|
||||
if (!empty($ride['p_fname'])) {
|
||||
$ride['p_fname'] = $encryptionHelper->decryptData($ride['p_fname']);
|
||||
}
|
||||
if (!empty($ride['p_lname'])) {
|
||||
$ride['p_lname'] = $encryptionHelper->decryptData($ride['p_lname']);
|
||||
}
|
||||
if (!empty($ride['p_phone'])) {
|
||||
$ride['p_phone'] = $encryptionHelper->decryptData($ride['p_phone']);
|
||||
}
|
||||
}
|
||||
unset($ride);
|
||||
|
||||
// فك التشفير
|
||||
$passenger['first_name'] = $encryptionHelper->decryptData($passenger['first_name']);
|
||||
$passenger['last_name'] = $encryptionHelper->decryptData($passenger['last_name']);
|
||||
$passenger['phone'] = $encryptionHelper->decryptData($passenger['phone']);
|
||||
$ride['driver_first_name'] = $encryptionHelper->decryptData($ride['driver_first_name']);
|
||||
$ride['driver_last_name'] = $encryptionHelper->decryptData($ride['driver_last_name']);
|
||||
|
||||
// 3) اطبع النتيجة
|
||||
// 6) الرد
|
||||
$response = [
|
||||
'passenger' => [
|
||||
'id' => $passenger['id'],
|
||||
'first_name' => $passenger['first_name'],
|
||||
'last_name' => $passenger['last_name'],
|
||||
'phone' => $passenger['phone'],
|
||||
],
|
||||
'ride' => $ride
|
||||
'user_type' => $userType,
|
||||
'user' => $userType === 'driver' ? $driver : $passenger,
|
||||
'rides' => $rides
|
||||
];
|
||||
|
||||
error_log("[get_last_ride] Success response for passenger_id=" . $passenger['id']);
|
||||
error_log("[get_last_ride] Success response for " . $userType . " id=" . $userId);
|
||||
jsonSuccess($response);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -12,10 +12,11 @@ if (empty($rideId) || empty($status)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/* whitelist للحالات المسموحة – عدّل حسب نظامك */
|
||||
/* whitelist للحالات المسموحة – تطابق حالات DB الفعلية */
|
||||
$allowed = [
|
||||
'Pending', 'Accepted', 'EnRoute', 'Arrived',
|
||||
'Started', 'Completed', 'Canceled'
|
||||
'New', 'waiting', 'wait', 'Apply', 'Applied',
|
||||
'Arrived', 'arrived', 'Begin', 'Finished',
|
||||
'Cancel', 'CancelFromDriver', 'CancelFromPassenger', 'TimeOut'
|
||||
];
|
||||
|
||||
if (!in_array($status, $allowed, true)) {
|
||||
|
||||
@@ -12,31 +12,32 @@ try {
|
||||
$params = [];
|
||||
$whereClause = "";
|
||||
|
||||
// --- منطق ترجمة الحالات (Mapping Logic) ---
|
||||
// --- منطق ترجمة الحالات (Mapping Logic) - مصحح ليطابق حالات DB الفعلية ---
|
||||
switch ($statusFilter) {
|
||||
case 'All':
|
||||
$whereClause = ""; // لا يوجد شرط، اجلب الكل
|
||||
break;
|
||||
|
||||
case 'Begin':
|
||||
// قد تكون الرحلة بدأت أو وصل السائق
|
||||
$whereClause = "WHERE r.status IN ('Begin','Apply','Applied')";
|
||||
|
||||
case 'Pending':
|
||||
// الرحلات المعلقة/الجديدة: بانتظار سائق
|
||||
$whereClause = "WHERE r.status IN ('New','nothing','waiting','wait')";
|
||||
break;
|
||||
|
||||
case 'New':
|
||||
$whereClause = "WHERE r.status = 'New'";
|
||||
case 'Begin':
|
||||
// الرحلات الجارية: من قبول السائق إلى بدء التشغيل
|
||||
$whereClause = "WHERE r.status IN ('Apply','Applied','Arrived','arrived','Begin')";
|
||||
break;
|
||||
|
||||
case 'Completed':
|
||||
// في قاعدة البيانات الحالة اسمها Finished
|
||||
// الرحلات المكتملة
|
||||
$whereClause = "WHERE r.status = 'Finished'";
|
||||
break;
|
||||
|
||||
case 'Canceled':
|
||||
// نجمع كل حالات الإلغاء الممكنة
|
||||
$whereClause = "WHERE r.status IN ('Cancel', 'CancelFromDriverAfterApply', 'TimeOut')";
|
||||
// جميع أنواع الإلغاء
|
||||
$whereClause = "WHERE r.status IN ('Cancel','CancelFromDriver','CancelFromDriverAfterApply','CancelFromPassenger','TimeOut')";
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// في حال تم إرسال حالة محددة غير المذكورين
|
||||
$whereClause = "WHERE r.status = ?";
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 1. Log the start of the request
|
||||
/**
|
||||
* تطبيع رقم الهاتف ليتوافق مع التخزين في قاعدة البيانات
|
||||
*/
|
||||
function normalizePhone($phone) {
|
||||
$clean = preg_replace('/\D+/', '', $phone);
|
||||
// Syria: 099XXXXXXX or 9639XXXXXXX
|
||||
if (strlen($clean) === 10 && strpos($clean, '09') === 0) return '963' . substr($clean, 1);
|
||||
if (strlen($clean) === 12 && strpos($clean, '963') === 0) return $clean;
|
||||
if (strlen($clean) === 9 && strpos($clean, '9') === 0) return '963' . $clean;
|
||||
// Jordan: 079XXXXXXX or 9627XXXXXXX
|
||||
if (strlen($clean) === 10 && strpos($clean, '07') === 0) return '962' . substr($clean, 1);
|
||||
if (strlen($clean) === 12 && strpos($clean, '962') === 0) return $clean;
|
||||
if (strlen($clean) === 9 && strpos($clean, '7') === 0) return '962' . $clean;
|
||||
// Egypt: 010XXXXXXXX or 2010XXXXXXXX
|
||||
if (strlen($clean) === 11 && strpos($clean, '01') === 0) return '20' . substr($clean, 1);
|
||||
if (strlen($clean) === 13 && strpos($clean, '20') === 0) return $clean;
|
||||
return $clean;
|
||||
}
|
||||
|
||||
// 1. تسجيل بداية الطلب
|
||||
$phone = filterRequest("phone");
|
||||
error_log("[MONITOR_RIDE] ---------------- START REQUEST ----------------");
|
||||
error_log("[MONITOR_RIDE] 1. Received Phone: " . $phone);
|
||||
|
||||
// تطبيع الرقم
|
||||
$phone = normalizePhone($phone);
|
||||
error_log("[MONITOR_RIDE] 1.5 Normalized Phone: " . $phone);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// 1) البحث عن الهاتف أولاً في جدول السائق ثم جدول الراكب
|
||||
//------------------------------------------------------------------------
|
||||
@@ -23,7 +46,6 @@ $customerQuery = $con->prepare("SELECT id AS customerID FROM passengers WHERE ph
|
||||
$customerQuery->execute([':phone' => $encPhone]);
|
||||
$customer = $customerQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
// حدد نوع المستخدم
|
||||
$userType = '';
|
||||
$driverID = null;
|
||||
@@ -44,14 +66,16 @@ if ($driver) {
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// 2) جلب آخر رحلة حالتها "بدأت" بناءً على نوع المستخدم
|
||||
// 2) جلب آخر رحلة حالتها نشطة (Apply, Applied, Arrived, Begin)
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
$activeStatuses = "'Apply','Applied','Arrived','arrived','Begin'";
|
||||
|
||||
if ($userType == 'driver') {
|
||||
error_log("[MONITOR_RIDE] 4. Searching for active ride for Driver ID: " . $driverID);
|
||||
$rideQuery = $con->prepare("
|
||||
SELECT * FROM ride
|
||||
WHERE driver_id = :driverID AND status = 'Begin'
|
||||
WHERE driver_id = :driverID AND status IN ($activeStatuses)
|
||||
ORDER BY id DESC LIMIT 1
|
||||
");
|
||||
$rideQuery->execute([':driverID' => $driverID]);
|
||||
@@ -59,7 +83,7 @@ if ($userType == 'driver') {
|
||||
error_log("[MONITOR_RIDE] 4. Searching for active ride for Customer ID: " . $customerID);
|
||||
$rideQuery = $con->prepare("
|
||||
SELECT * FROM ride
|
||||
WHERE passenger_id = :customerID AND status = 'Begin'
|
||||
WHERE passenger_id = :customerID AND status IN ($activeStatuses)
|
||||
ORDER BY id DESC LIMIT 1
|
||||
");
|
||||
$rideQuery->execute([':customerID' => $customerID]);
|
||||
@@ -68,23 +92,20 @@ if ($userType == 'driver') {
|
||||
$ride = $rideQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ride) {
|
||||
error_log("[MONITOR_RIDE] 4. FAILURE: No ride with status 'Begin' found.");
|
||||
jsonError("لا توجد رحلة بدأت لهذا المستخدم.");
|
||||
error_log("[MONITOR_RIDE] 4. FAILURE: No active ride found.");
|
||||
jsonError("لا توجد رحلة نشطة لهذا المستخدم.");
|
||||
exit;
|
||||
} else {
|
||||
error_log("[MONITOR_RIDE] 4. SUCCESS: Active Ride Found. Ride ID: " . $ride['id']);
|
||||
error_log("[MONITOR_RIDE] 4. SUCCESS: Active Ride Found. Ride ID: " . $ride['id'] . " Status: " . $ride['status']);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// 3) جلب معلومات السائق من الرحلة
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
// FIX 1: Safe assignment of driver ID (checking driverID vs driver_id)
|
||||
$rideDriverID = $ride['driverID'] ?? $ride['driver_id'];
|
||||
|
||||
error_log("[MONITOR_RIDE] 5. Fetching info for Driver ID from Ride: " . $rideDriverID);
|
||||
|
||||
// FIX 2: Select first_name and last_name instead of fullname
|
||||
$driverInfoQuery = $con->prepare("
|
||||
SELECT id, first_name, last_name, phone
|
||||
FROM driver
|
||||
@@ -96,29 +117,22 @@ $driverInfoQuery->execute([':driverID' => $rideDriverID]);
|
||||
$driverInfo = $driverInfoQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($driverInfo) {
|
||||
// فك التشفير للهاتف
|
||||
$driverInfo['phone'] = $encryptionHelper->decryptData($driverInfo['phone']);
|
||||
|
||||
// FIX 4: Decrypt First Name and Last Name
|
||||
$driverInfo['first_name'] = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||
$driverInfo['last_name'] = $encryptionHelper->decryptData($driverInfo['last_name']);
|
||||
|
||||
// Construct fullname for the response
|
||||
$fullName = $driverInfo['first_name'] . " " . $driverInfo['last_name'];
|
||||
$driverInfo['fullname'] = $fullName;
|
||||
|
||||
error_log("[MONITOR_RIDE] 5. Driver Info Found: " . $fullName);
|
||||
} else {
|
||||
error_log("[MONITOR_RIDE] 5. WARNING: Driver info not found for ID " . $rideDriverID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// 4) جلب آخر موقع للسائق من جدول driver_location بشرط الحالة ON
|
||||
// 4) جلب آخر موقع للسائق من جدول car_locations بشرط الحالة ON
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
error_log("[MONITOR_RIDE] 6. Querying Tracking DB for Driver ID: " . $rideDriverID);
|
||||
|
||||
// FIX 3: Changed ORDER BY id DESC to ORDER BY updated_at DESC
|
||||
$locationQuery = $con_tracking->prepare("
|
||||
SELECT latitude, longitude, speed, heading, updated_at
|
||||
FROM car_locations
|
||||
@@ -129,9 +143,9 @@ $locationQuery->execute([':driverID' => $rideDriverID]);
|
||||
$location = $locationQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($location) {
|
||||
error_log("[MONITOR_RIDE] 6. Location Found: Lat=" . $location['latitude'] . " Lng=" . $location['longitude'] . " Updated=" . $location['updated_at']);
|
||||
error_log("[MONITOR_RIDE] 6. Location Found: Lat=" . $location['latitude'] . " Lng=" . $location['longitude']);
|
||||
} else {
|
||||
error_log("[MONITOR_RIDE] 6. WARNING: No live location found (status=ON) or list empty.");
|
||||
error_log("[MONITOR_RIDE] 6. WARNING: No live location found.");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@@ -145,6 +159,4 @@ $response = [
|
||||
];
|
||||
|
||||
error_log("[MONITOR_RIDE] 7. Sending Success Response.");
|
||||
jsonSuccess($response);
|
||||
|
||||
?>
|
||||
jsonSuccess($response);
|
||||
@@ -18,8 +18,8 @@ if (!$rideId) {
|
||||
}
|
||||
|
||||
try {
|
||||
// جلب بيانات الرحلة للتحقق
|
||||
$stmt = $con->prepare("SELECT driver_id, status FROM ride WHERE id = ?");
|
||||
// جلب بيانات الرحلة للتحقق (مع passenger_id للإدراج في canecl)
|
||||
$stmt = $con->prepare("SELECT driver_id, passenger_id, status FROM ride WHERE id = ?");
|
||||
$stmt->execute([$rideId]);
|
||||
$ride = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -55,6 +55,15 @@ try {
|
||||
$updateOrder->execute([$reason, $rideId, $driverId]);
|
||||
}
|
||||
|
||||
// إدراج سبب الإلغاء في جدول canecl المخصص
|
||||
if ($driverId > 0 && !empty($reason)) {
|
||||
$passengerId = $ride['passenger_id'] ?? '0';
|
||||
$insertCanecl = $con->prepare(
|
||||
"INSERT INTO canecl (driverID, passengerID, rideID, note) VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
$insertCanecl->execute([$driverId, $passengerId, $rideId, $reason]);
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
|
||||
// تحديث السيرفر البعيد (Remote DB)
|
||||
|
||||
@@ -15,14 +15,15 @@ try {
|
||||
// and atomically updates all databases within a transaction.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Receive raw params from driver app
|
||||
// 2. Calculate price server-side (from DB + actual distance)
|
||||
// 3. BEGIN TRANSACTION (local DB)
|
||||
// 4. Update ride on local DB + remote DB (con_ride)
|
||||
// 5. Update driver_orders
|
||||
// 6. S2S cURL → Wallet Payment Server (process_ride_payments.php)
|
||||
// 7. If payment OK → COMMIT, notify passenger (Socket + FCM)
|
||||
// 8. If payment FAIL → ROLLBACK, ride stays 'Begin', safe retry
|
||||
// 1. Receive raw params from driver app (including country_code)
|
||||
// 2. Load country pricing from `kazan` table
|
||||
// 3. Calculate price server-side (from DB + actual distance)
|
||||
// 4. BEGIN TRANSACTION (local DB)
|
||||
// 5. Update ride on local DB + remote DB (con_ride)
|
||||
// 6. Update driver_orders
|
||||
// 7. S2S cURL → Wallet Payment Server (process_ride_payments.php)
|
||||
// 8. If payment OK → COMMIT, notify passenger (Socket + FCM)
|
||||
// 9. If payment FAIL → ROLLBACK, ride stays 'Begin', safe retry
|
||||
// ============================================================
|
||||
|
||||
// --- Secure S2S Configuration ---
|
||||
@@ -42,6 +43,7 @@ $passengerToken = filterRequest("passengerToken");
|
||||
$driver_token = filterRequest("driver_token");
|
||||
$walletChecked = filterRequest("walletChecked");
|
||||
$passengerWalletBurc = filterRequest("passengerWalletBurc");
|
||||
$countryCode = filterRequest("country_code"); // 🆕 الدولة: Syria, Egypt, ...
|
||||
|
||||
if (empty($rideId) || empty($newStatus) || empty($driver_id) || empty($passengerId)) {
|
||||
jsonError("Missing required parameters: rideId, driver_id, passengerId, status");
|
||||
@@ -53,8 +55,44 @@ if ($newStatus !== 'Finished') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// 🆕 إذا لم يتم إرسال country_code، نأخذه من قاعدة بيانات الرحلة
|
||||
if (empty($countryCode)) {
|
||||
try {
|
||||
$stmtCountry = $con->prepare("SELECT r.id, d.site AS country_code
|
||||
FROM ride r
|
||||
LEFT JOIN driver d ON r.driver_id = d.id
|
||||
WHERE r.id = ? LIMIT 1");
|
||||
$stmtCountry->execute([$rideId]);
|
||||
$rowCountry = $stmtCountry->fetch(PDO::FETCH_ASSOC);
|
||||
$countryCode = $rowCountry['country_code'] ?? 'Syria';
|
||||
} catch (Exception $e) {
|
||||
$countryCode = 'Syria'; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. Server-Side Price Calculation (Secure — NOT from client)
|
||||
// 2. Load Country Pricing from `kazan` Table
|
||||
// ============================================================
|
||||
try {
|
||||
$stmtKazan = $con->prepare("SELECT * FROM kazan WHERE country = ? LIMIT 1");
|
||||
$stmtKazan->execute([$countryCode]);
|
||||
$countryPricing = $stmtKazan->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$countryPricing) {
|
||||
// Fallback: إذا لم نجد سعر للدولة، نستخدم Syria كافتراضي
|
||||
error_log("[finish_ride_updates] No pricing found for country: $countryCode. Falling back to Syria.");
|
||||
$stmtKazan->execute(['Syria']);
|
||||
$countryPricing = $stmtKazan->fetch(PDO::FETCH_ASSOC);
|
||||
$countryCode = 'Syria';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("[finish_ride_updates] Failed to load country pricing: " . $e->getMessage());
|
||||
jsonError("Failed to load pricing configuration.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. Server-Side Price Calculation (Secure — NOT from client)
|
||||
// ============================================================
|
||||
try {
|
||||
// Fetch ride data from remote/local DB for server-side calculation
|
||||
@@ -73,10 +111,10 @@ try {
|
||||
}
|
||||
|
||||
$quotedPrice = floatval($rideData['quoted_price'] ?? 0);
|
||||
$kazanPercent = 10;
|
||||
$kazanPercent = floatval($countryPricing['kazanPercent'] ?? $countryPricing['kazan'] ?? 10); // 🆕 من جدول kazan (kazanPercent هو الاسم الجديد)
|
||||
$carType = $rideData['car_type'] ?? 'Fixed Price';
|
||||
|
||||
// Fixed-price types: use quoted price as-is
|
||||
// Fixed-price types, Speed & Awfar: use quoted price as-is
|
||||
$fixedPriceTypes = ['Speed', 'Fixed Price', 'Awfar Car'];
|
||||
if (in_array($carType, $fixedPriceTypes)) {
|
||||
$finalPrice = $quotedPrice;
|
||||
@@ -88,8 +126,9 @@ try {
|
||||
if ($distanceKm <= 0) {
|
||||
$finalPrice = $quotedPrice; // fallback
|
||||
} else {
|
||||
$perKmRate = getPerKmRate($carType);
|
||||
$perMinRate = getPerMinRate();
|
||||
// 🆕 استخدام الأسعار من جدول kazan حسب الدولة (كل نوع سيارة له عمود سعره الخاص)
|
||||
$perKmRate = getPerKmRate($carType, $countryPricing);
|
||||
$perMinRate = getPerMinRate($countryPricing);
|
||||
$durationMin = intval(preg_replace('/[^0-9]/', '', $actualDuration));
|
||||
|
||||
$calculated = ($distanceKm * $perKmRate) + ($durationMin * $perMinRate);
|
||||
@@ -98,18 +137,20 @@ try {
|
||||
$finalPrice = max($quotedPrice, round($calculated, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 تحديد رمز العملة حسب الدولة
|
||||
$currency = getCurrencyByCountry($countryCode);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Error calculating price: " . $e->getMessage());
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. Atomic Transaction: Update DBs + Process Payment
|
||||
// 4. Atomic Transaction: Update DBs + Process Payment
|
||||
// ============================================================
|
||||
try {
|
||||
// --- Update Remote DB (con_ride) FIRST ---
|
||||
// (Not in transaction — remote DB doesn't support cross-DB rollback,
|
||||
// but we keep it minimal as a "best-effort" update)
|
||||
if (isset($con_ride)) {
|
||||
$stmtRemote = $con_ride->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
@@ -120,7 +161,7 @@ try {
|
||||
// --- BEGIN Local DB Transaction ---
|
||||
$con->beginTransaction();
|
||||
|
||||
// 3a. Update ride (local DB)
|
||||
// 4a. Update ride (local DB)
|
||||
$stmtLocal = $con->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
);
|
||||
@@ -130,7 +171,7 @@ try {
|
||||
throw new Exception("Ride already finished or not found in local DB.");
|
||||
}
|
||||
|
||||
// 3b. Update driver_orders
|
||||
// 4b. Update driver_orders
|
||||
$checkStmt = $con->prepare("SELECT order_id FROM driver_orders WHERE order_id = ?");
|
||||
$checkStmt->execute([$rideId]);
|
||||
|
||||
@@ -143,7 +184,7 @@ try {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3c. Server-to-Server Payment Processing (S2S)
|
||||
// 4c. Server-to-Server Payment Processing (S2S)
|
||||
// ============================================================
|
||||
$paymentPayload = [
|
||||
'rideId' => $rideId,
|
||||
@@ -154,6 +195,8 @@ try {
|
||||
'walletChecked' => $walletChecked,
|
||||
'passengerWalletBurc' => $passengerWalletBurc,
|
||||
'authToken' => $driver_token,
|
||||
'currency' => $currency, // 🆕 إرسال العملة لمخدم الدفع
|
||||
'country_code' => $countryCode, // 🆕 إرسال الدولة لمخدم الدفع
|
||||
];
|
||||
|
||||
$ch = curl_init(WALLET_PAYMENT_URL);
|
||||
@@ -202,7 +245,7 @@ try {
|
||||
$con->commit();
|
||||
|
||||
// ============================================================
|
||||
// 4. Notifications (After successful commit)
|
||||
// 5. Notifications (After successful commit)
|
||||
// ============================================================
|
||||
$passenger_id = $passengerId; // alias for legacy code
|
||||
|
||||
@@ -220,6 +263,7 @@ try {
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'finished',
|
||||
'price' => $finalPrice,
|
||||
'currency' => $currency, // 🆕
|
||||
'DriverList' => $legacyList
|
||||
];
|
||||
|
||||
@@ -232,13 +276,14 @@ try {
|
||||
$fcmData = [
|
||||
'ride_id' => (string)$rideId,
|
||||
'price' => (string)$finalPrice,
|
||||
'currency' => $currency, // 🆕
|
||||
'DriverList' => $legacyList
|
||||
];
|
||||
|
||||
sendFCM_Internal(
|
||||
$passengerToken,
|
||||
"تم إنهاء الرحلة 🏁",
|
||||
"المبلغ المطلوب: " . $finalPrice . " ل.س",
|
||||
"المبلغ المطلوب: " . $finalPrice . " " . $currency,
|
||||
$fcmData,
|
||||
'Driver Finish Trip',
|
||||
false
|
||||
@@ -247,11 +292,12 @@ try {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. Return Success with server-calculated price
|
||||
// 6. Return Success with server-calculated price + currency
|
||||
// ============================================================
|
||||
jsonSuccess([
|
||||
'price' => $finalPrice,
|
||||
'rideId' => $rideId
|
||||
'price' => $finalPrice,
|
||||
'currency' => $currency, // 🆕 إرجاع العملة للتطبيق
|
||||
'rideId' => $rideId
|
||||
], "Ride finished and payment processed successfully.");
|
||||
|
||||
} catch (Exception $e) {
|
||||
@@ -263,28 +309,131 @@ try {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper Functions
|
||||
// Helper Functions — الآن تقرأ الأسعار من جدول kazan حسب الدولة
|
||||
// ============================================================
|
||||
|
||||
function getPerKmRate(string $carType): float {
|
||||
$rates = [
|
||||
'Comfort' => 44,
|
||||
'Lady' => 44,
|
||||
'Mishwar Vip' => 50,
|
||||
'Electric' => 45,
|
||||
'Van' => 63,
|
||||
'Delivery' => 25,
|
||||
'Speed' => 36,
|
||||
'Fixed Price' => 36,
|
||||
'Awfar Car' => 36,
|
||||
/**
|
||||
* الحصول على سعر الكيلومتر حسب نوع السيارة من جدول أسعار الدولة
|
||||
*
|
||||
* 🆕 كل نوع سيارة له عمود مستقل في جدول kazan:
|
||||
* Speed → speedPrice | Comfort → comfortPrice | Lady → ladyPrice
|
||||
* Electric → electricPrice | Van → vanPrice | Delivery → deliveryPrice
|
||||
* Mishwar Vip → mishwarVipPrice | Fixed Price → fixedPrice | Awfar → awfarPrice
|
||||
*
|
||||
* @param string $carType نوع السيارة
|
||||
* @param array $countryPricing صف من جدول kazan
|
||||
* @return float
|
||||
*/
|
||||
function getPerKmRate(string $carType, array $countryPricing): float {
|
||||
// 🆕 الخريطة المباشرة: كل نوع سيارة يقابله عمود بنفس الاسم + "Price"
|
||||
$rateColumns = [
|
||||
'Comfort' => 'comfortPrice',
|
||||
'Speed' => 'speedPrice',
|
||||
'Lady' => 'ladyPrice',
|
||||
'Electric' => 'electricPrice',
|
||||
'Van' => 'vanPrice',
|
||||
'Delivery' => 'deliveryPrice',
|
||||
'Mishwar Vip' => 'mishwarVipPrice',
|
||||
'Fixed Price' => 'fixedPrice',
|
||||
'Awfar Car' => 'awfarPrice',
|
||||
];
|
||||
return $rates[$carType] ?? 36;
|
||||
|
||||
$column = $rateColumns[$carType] ?? 'speedPrice';
|
||||
|
||||
// دعم التوافق مع الإصدارات القديمة (backward compatibility)
|
||||
$rate = floatval($countryPricing[$column] ?? 0);
|
||||
|
||||
// إذا كان السعر صفر أو غير موجود، نبحث في الأسماء القديمة
|
||||
if ($rate <= 0) {
|
||||
$oldColumnMap = [
|
||||
'Lady' => 'familyPrice',
|
||||
'Mishwar Vip' => 'freePrice',
|
||||
'Electric' => 'naturePrice',
|
||||
'Van' => 'heavyPrice',
|
||||
];
|
||||
$oldColumn = $oldColumnMap[$carType] ?? null;
|
||||
if ($oldColumn && isset($countryPricing[$oldColumn])) {
|
||||
$rate = floatval($countryPricing[$oldColumn]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback أخير
|
||||
if ($rate <= 0) {
|
||||
$rate = floatval($countryPricing['speedPrice'] ?? 36);
|
||||
}
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
function getPerMinRate(): float {
|
||||
/**
|
||||
* الحصول على سعر الدقيقة حسب وقت اليوم من جدول kazan
|
||||
*
|
||||
* 🆕 الآن يقرأ من أعمدة الدقيقة المنفصلة:
|
||||
* normalMinPrice → Normal (9 ص - 2 م / 6 م - 9 م)
|
||||
* peakMinPrice → Peak (2 م - 5 م)
|
||||
* lateMinPrice → Late (9 م - 1 ص)
|
||||
*
|
||||
* @param array $countryPricing صف من جدول kazan
|
||||
* @return float
|
||||
*/
|
||||
function getPerMinRate(array $countryPricing): float {
|
||||
$hour = (int)date('H');
|
||||
if ($hour >= 21 || $hour < 1) return 11; // Late
|
||||
if ($hour >= 14 && $hour <= 17) return 10; // Peak
|
||||
return 9; // Normal
|
||||
|
||||
// 🆕 قراءة الأسعار من الأعمدة الجديدة للدقيقة
|
||||
$normalMinPrice = floatval($countryPricing['normalMinPrice'] ?? 0);
|
||||
$peakMinPrice = floatval($countryPricing['peakMinPrice'] ?? 0);
|
||||
$lateMinPrice = floatval($countryPricing['lateMinPrice'] ?? 0);
|
||||
|
||||
// 🆕 دعم التوافق مع الإصدارات القديمة (latePrice, naturePrice)
|
||||
if ($lateMinPrice <= 0) $lateMinPrice = floatval($countryPricing['latePrice'] ?? 0);
|
||||
if ($normalMinPrice <= 0) $normalMinPrice = floatval($countryPricing['naturePrice'] ?? 0);
|
||||
|
||||
// Fallback: حساب سعر الدقيقة من speedPrice إذا كانت الأعمدة الجديدة فارغة
|
||||
if ($normalMinPrice <= 0) {
|
||||
$speedPrice = floatval($countryPricing['speedPrice'] ?? 36);
|
||||
$normalMinPrice = $speedPrice / 4;
|
||||
}
|
||||
if ($peakMinPrice <= 0) $peakMinPrice = $normalMinPrice * 1.15; // 15% زيادة
|
||||
if ($lateMinPrice <= 0) $lateMinPrice = $normalMinPrice * 1.25; // 25% زيادة
|
||||
|
||||
if ($hour >= 21 || $hour < 1) {
|
||||
return round($lateMinPrice, 2); // Late Night
|
||||
}
|
||||
if ($hour >= 14 && $hour <= 17) {
|
||||
return round($peakMinPrice, 2); // Peak
|
||||
}
|
||||
return round($normalMinPrice, 2); // Normal
|
||||
}
|
||||
?>
|
||||
|
||||
/**
|
||||
* تحديد رمز العملة حسب الدولة
|
||||
*
|
||||
* @param string $countryCode رمز الدولة (Syria, Egypt, Jordan, ...)
|
||||
* @return string رمز العملة (SYP, EGP, JOD, ...)
|
||||
*/
|
||||
function getCurrencyByCountry(string $countryCode): string {
|
||||
$currencies = [
|
||||
'Syria' => 'SYP',
|
||||
'Egypt' => 'EGP',
|
||||
'Jordan' => 'JOD',
|
||||
'Iraq' => 'IQD',
|
||||
'UAE' => 'AED',
|
||||
'Saudi Arabia' => 'SAR',
|
||||
'Qatar' => 'QAR',
|
||||
'Kuwait' => 'KWD',
|
||||
'Bahrain' => 'BHD',
|
||||
'Oman' => 'OMR',
|
||||
'Turkey' => 'TRY',
|
||||
'Lebanon' => 'LBP',
|
||||
'Palestine' => 'ILS',
|
||||
'Yemen' => 'YER',
|
||||
'Libya' => 'LYD',
|
||||
'Tunisia' => 'TND',
|
||||
'Algeria' => 'DZD',
|
||||
'Morocco' => 'MAD',
|
||||
'Sudan' => 'SDG',
|
||||
];
|
||||
|
||||
return $currencies[$countryCode] ?? 'SYP'; // افتراضي: ليرة سورية
|
||||
}
|
||||
?>
|
||||
@@ -802,29 +802,64 @@ CREATE TABLE `invoicesAdmin` (
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `kazan`
|
||||
-- ============================================================
|
||||
-- 🆕 جدول الأسعار لكل دولة (kazan) - النسخة المطورة 🆕
|
||||
-- ============================================================
|
||||
-- قائمة أنواع السيارات المعتمدة (كل نوع له عمود سعر مستقل):
|
||||
-- 1. Speed - السيارات الاقتصادية السريعة
|
||||
-- 2. Comfort - السيارات المكيفة والمريحة
|
||||
-- 3. Lady - سائقات للسيدات
|
||||
-- 4. Electric - السيارات الكهربائية
|
||||
-- 5. Van - فانات نقل الركاب
|
||||
-- 6. Delivery - توصيل الطلبات
|
||||
-- 7. Mishwar Vip - مشوار VIP
|
||||
-- 8. Fixed Price - سعر ثابت
|
||||
-- 9. Awfar Car - أوفر كار (السيارات الأقدم)
|
||||
-- ============================================================
|
||||
-- أعمدة أسعار الدقيقة (منفصلة عن أسعار الكيلومتر):
|
||||
-- - normalMinPrice: سعر الدقيقة في الوقت العادي (9 ص - 2 م / 6 م - 9 م)
|
||||
-- - peakMinPrice: سعر الدقيقة في وقت الذروة (2 م - 5 م)
|
||||
-- - lateMinPrice: سعر الدقيقة في الليل المتأخر (9 م - 1 ص)
|
||||
-- ============================================================
|
||||
-- عند إضافة دولة جديدة، فقط أضف صفاً جديداً في هذا الجدول.
|
||||
-- عند إضافة نوع سيارة جديد، أضف عموداً جديداً هنا وفي finish_ride_updates.php.
|
||||
-- ============================================================
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `kazan`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `kazan` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`country` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`kazan` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`comfortPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`speedPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`familyPrice` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`deliveryPrice` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`freePrice` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`latePrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`heavyPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`adminId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`country` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'اسم الدولة: Syria, Egypt, Jordan, Iraq, ...',
|
||||
|
||||
-- ========== ⚙️ الإعدادات العامة ==========
|
||||
`kazanPercent` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '10' COMMENT 'نسبة الكازان (عمولة المنصة) %',
|
||||
`fuelPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الوقود المرجعي',
|
||||
`currency` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'SYP' COMMENT 'رمز العملة: SYP, EGP, JOD, IQD, ...',
|
||||
|
||||
-- ========== 🚗 أسعار الكيلومتر لكل نوع سيارة ==========
|
||||
`speedPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Speed',
|
||||
`comfortPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Comfort',
|
||||
`ladyPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Lady (سائقة للسيدات)',
|
||||
`electricPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Electric',
|
||||
`vanPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Van',
|
||||
`deliveryPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Delivery',
|
||||
`mishwarVipPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Mishwar Vip',
|
||||
`fixedPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Fixed Price',
|
||||
`awfarPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الكيلومتر - Awfar Car',
|
||||
|
||||
-- ========== ⏱️ أسعار الدقيقة (زمن الرحلة) ==========
|
||||
`normalMinPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الدقيقة - الوقت العادي (9ص-2م / 6م-9م)',
|
||||
`peakMinPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الدقيقة - وقت الذروة (2م-5م)',
|
||||
`lateMinPrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'سعر الدقيقة - الليل المتأخر (9م-1ص)',
|
||||
|
||||
-- ========== إداري ==========
|
||||
`adminId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`naturePrice` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`fuelPrice` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_country` (`country`) COMMENT 'ضمان عدم وجود صفين لنفس الدولة'
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='أسعار الرحلات حسب الدولة - كل نوع سيارة له عمود مستقل';
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user