Update: 2026-06-21 18:58:05
This commit is contained in:
101
backend/ride/location/save_driver_destination.php
Normal file
101
backend/ride/location/save_driver_destination.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// ride/location/save_driver_destination.php
|
||||
// API Endpoint for Captains to set their destination (max 2 times daily)
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 1. Authorize Driver
|
||||
if ($role !== 'driver') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized. Driver role required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Filter Inputs
|
||||
$action = filterRequest('action') ?? 'set';
|
||||
$destLat = filterRequest('destination_lat') ?? filterRequest('target_latitude');
|
||||
$destLng = filterRequest('destination_lng') ?? filterRequest('target_longitude');
|
||||
$destName = filterRequest('destination_name') ?? 'Destination';
|
||||
|
||||
try {
|
||||
if ($action === 'get') {
|
||||
$stmtGet = $con->prepare("
|
||||
SELECT target_latitude, target_longitude, destination_name, created_at
|
||||
FROM driver_destinations
|
||||
WHERE driver_id = :did
|
||||
AND is_active = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtGet->execute([':did' => $user_id]);
|
||||
$activeDest = $stmtGet->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($activeDest) {
|
||||
jsonSuccess($activeDest, "Active destination retrieved.");
|
||||
} else {
|
||||
jsonSuccess(null, "No active destination set.");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'clear') {
|
||||
$stmtDeactivate = $con->prepare("
|
||||
UPDATE driver_destinations
|
||||
SET is_active = 0
|
||||
WHERE driver_id = :did
|
||||
AND is_active = 1
|
||||
");
|
||||
$stmtDeactivate->execute([':did' => $user_id]);
|
||||
jsonSuccess(null, "تم إلغاء تفعيل الوجهة الشخصية بنجاح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Default action: set
|
||||
if (empty($destLat) || empty($destLng)) {
|
||||
jsonError("Missing required parameters: destination_lat and destination_lng are required.");
|
||||
}
|
||||
|
||||
// 3. Enforce Limit: Max 2 times daily
|
||||
$stmtCount = $con->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM driver_destinations
|
||||
WHERE driver_id = :did
|
||||
AND usage_date = CURDATE()
|
||||
AND is_active = 1
|
||||
");
|
||||
$stmtCount->execute([':did' => $user_id]);
|
||||
$dailyCount = intval($stmtCount->fetchColumn());
|
||||
|
||||
if ($dailyCount >= 2) {
|
||||
jsonError("حسناً كابتن، لقد وصلت للحد الأقصى المسموح به لتحديد الوجهة اليوم (مرتان في اليوم).");
|
||||
}
|
||||
|
||||
// 4. Deactivate previous active destinations for this driver
|
||||
$stmtDeactivate = $con->prepare("
|
||||
UPDATE driver_destinations
|
||||
SET is_active = 0
|
||||
WHERE driver_id = :did
|
||||
AND is_active = 1
|
||||
");
|
||||
$stmtDeactivate->execute([':did' => $user_id]);
|
||||
|
||||
// 5. Insert new destination
|
||||
$stmtInsert = $con->prepare("
|
||||
INSERT INTO driver_destinations
|
||||
(driver_id, target_latitude, target_longitude, destination_name, is_active, usage_date)
|
||||
VALUES (:did, :lat, :lng, :name, 1, CURDATE())
|
||||
");
|
||||
$stmtInsert->execute([
|
||||
':did' => $user_id,
|
||||
':lat' => (float)$destLat,
|
||||
':lng' => (float)$destLng,
|
||||
':name' => $destName
|
||||
]);
|
||||
|
||||
jsonSuccess(null, "تم حفظ وجهتك كابتن بنجاح! سيتم توجيه الطلبات المطابقة لوجهتك.");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[save_driver_destination.php] Error: " . $e->getMessage());
|
||||
jsonError("Failed to save driver destination: " . $e->getMessage());
|
||||
}
|
||||
@@ -109,12 +109,13 @@ function getPerKmRate($carType, $kazanRow) {
|
||||
}
|
||||
|
||||
function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType = 'Speed') {
|
||||
global $redis, $redisLocation;
|
||||
global $redis, $redisLocation, $con;
|
||||
|
||||
$surgeMultiplier = 1.0;
|
||||
if (isset($redis) && $redis !== null) {
|
||||
try {
|
||||
$grid_size = 0.0135;
|
||||
$refLat = 33.5;
|
||||
$grid_size = 0.0135 * (cos(deg2rad($refLat)) / max(cos(deg2rad((float)$passengerLat)), 0.01));
|
||||
$grid_lat = round((float)$passengerLat / $grid_size) * $grid_size;
|
||||
$grid_lng = round((float)$passengerLng / $grid_size) * $grid_size;
|
||||
$grid_id = $grid_lat . "_" . $grid_lng;
|
||||
@@ -242,6 +243,105 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
||||
|
||||
$price = max($fare, $minFare);
|
||||
|
||||
// Apply competitor-linked regional pricing overrides (undercut by 8%)
|
||||
$competitorTarget = null;
|
||||
if (isset($con) && $con !== null) {
|
||||
try {
|
||||
$countryCodeMap = [
|
||||
'Syria' => 'SY',
|
||||
'Jordan' => 'JO',
|
||||
'Egypt' => 'EG',
|
||||
'Iraq' => 'IQ'
|
||||
];
|
||||
$cc = $countryCodeMap[$country] ?? 'SY';
|
||||
|
||||
$latDelta = 0.02;
|
||||
$lngDelta = 0.02;
|
||||
|
||||
$minFlat = $passengerLat - $latDelta;
|
||||
$maxFlat = $passengerLat + $latDelta;
|
||||
$minFlng = $passengerLng - $lngDelta;
|
||||
$maxFlng = $passengerLng + $lngDelta;
|
||||
|
||||
$minTlat = $destLat - $latDelta;
|
||||
$maxTlat = $destLat + $latDelta;
|
||||
$minTlng = $destLng - $lngDelta;
|
||||
$maxTlng = $destLng + $lngDelta;
|
||||
|
||||
// Layer 1: Start and End match within bounding box
|
||||
$sqlComp = "SELECT total_price, distance_km
|
||||
FROM competitor_prices
|
||||
WHERE country_code = :country_code
|
||||
AND (from_latitude + 0.0) BETWEEN :min_flat AND :max_flat
|
||||
AND (from_longitude + 0.0) BETWEEN :min_flng AND :max_flng
|
||||
AND (to_latitude + 0.0) BETWEEN :min_tlat AND :max_tlat
|
||||
AND (to_longitude + 0.0) BETWEEN :min_tlng AND :max_tlng
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
ORDER BY created_at DESC LIMIT 5";
|
||||
$stmtComp = $con->prepare($sqlComp);
|
||||
$stmtComp->execute([
|
||||
':country_code' => $cc,
|
||||
':min_flat' => $minFlat,
|
||||
':max_flat' => $maxFlat,
|
||||
':min_flng' => $minFlng,
|
||||
':max_flng' => $maxFlng,
|
||||
':min_tlat' => $minTlat,
|
||||
':max_tlat' => $maxTlat,
|
||||
':min_tlng' => $minTlng,
|
||||
':max_tlng' => $maxTlng
|
||||
]);
|
||||
$matches = $stmtComp->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($matches)) {
|
||||
// Layer 2 Fallback: Start match only within bounding box
|
||||
$sqlFallback = "SELECT total_price, distance_km
|
||||
FROM competitor_prices
|
||||
WHERE country_code = :country_code
|
||||
AND (from_latitude + 0.0) BETWEEN :min_flat AND :max_flat
|
||||
AND (from_longitude + 0.0) BETWEEN :min_flng AND :max_flng
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
ORDER BY created_at DESC LIMIT 10";
|
||||
$stmtFallback = $con->prepare($sqlFallback);
|
||||
$stmtFallback->execute([
|
||||
':country_code' => $cc,
|
||||
':min_flat' => $minFlat,
|
||||
':max_flat' => $maxFlat,
|
||||
':min_flng' => $minFlng,
|
||||
':max_flng' => $maxFlng
|
||||
]);
|
||||
$matches = $stmtFallback->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if (!empty($matches)) {
|
||||
$normalizedPrices = [];
|
||||
foreach ($matches as $row) {
|
||||
$compDist = (float)$row['distance_km'];
|
||||
$compPrice = (float)$row['total_price'];
|
||||
if ($compDist > 0) {
|
||||
$normalizedPrices[] = ($compPrice / $compDist) * $distance;
|
||||
}
|
||||
}
|
||||
if (!empty($normalizedPrices)) {
|
||||
$competitorTarget = array_sum($normalizedPrices) / count($normalizedPrices);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[calculateDynamicPrice] Competitor pricing query failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($competitorTarget !== null) {
|
||||
$undercutPrice = $competitorTarget * 0.92;
|
||||
$speedBaseRate = getPerKmRate('Speed', $kazanRow);
|
||||
$currentBaseRate = getPerKmRate($carType, $kazanRow);
|
||||
$categoryMultiplier = $speedBaseRate > 0 ? ($currentBaseRate / $speedBaseRate) : 1.0;
|
||||
|
||||
$targetAdjustedPrice = $undercutPrice * $categoryMultiplier;
|
||||
if ($price > $targetAdjustedPrice) {
|
||||
$price = $targetAdjustedPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply kazan (e.g. 11%)
|
||||
$withCommission = ceil($price * (1 + $kazanPercent / 100));
|
||||
$kazan = $withCommission - $price;
|
||||
@@ -341,7 +441,7 @@ if (isset($encryptionHelper)) {
|
||||
// ✅ FIX R6: تضمين distance و duration في الـ token لمنع التلاعب
|
||||
'distance' => $distance,
|
||||
'duration' => $duration,
|
||||
'expires' => time() + 180, // Valid for 3 minutes
|
||||
'expires' => time() + 420, // Valid for 7 minutes
|
||||
'prices' => $pricesRaw
|
||||
];
|
||||
$priceToken = $encryptionHelper->encryptData(json_encode($tokenPayload));
|
||||
|
||||
@@ -281,7 +281,7 @@ try {
|
||||
];
|
||||
|
||||
// Direct dispatch للسائقين القريبين
|
||||
$driversData = findBestDrivers($con, $startLat, $startLng, $carType);
|
||||
$driversData = findBestDrivers($con, $startLat, $startLng, $carType, $endLat, $endLng);
|
||||
if (!empty($driversData)) {
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper);
|
||||
error_log("[add_ride] Dispatched RideID=$insertedId to " . count($driversData) . " drivers.");
|
||||
|
||||
@@ -82,7 +82,7 @@ try {
|
||||
if ($driverId > 0) {
|
||||
|
||||
// أ) Socket (إشعار السائق في التطبيق فوراً)
|
||||
$socketUrl = 'http://188.68.36.205:2021';
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://location.intaleq.xyz:2021';
|
||||
$internalKeyPath = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '';
|
||||
$internalKey = ($internalKeyPath && file_exists($internalKeyPath)) ? trim(file_get_contents($internalKeyPath)) : (getenv('INTERNAL_SOCKET_KEY') ?: '');
|
||||
|
||||
|
||||
@@ -93,11 +93,11 @@ try {
|
||||
$latVal = doubleval($startLat);
|
||||
$lngVal = doubleval($startLng);
|
||||
|
||||
$driversData = findBestDrivers($con, $con_tracking, $latVal, $lngVal, $carType);
|
||||
$driversData = findBestDrivers($con, $latVal, $lngVal, $carType, $endLat, $endLng);
|
||||
|
||||
if (!empty($driversData)) {
|
||||
// استدعاء دالة الإرسال الموحدة (الموجودة في functions.php)
|
||||
dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startName);
|
||||
dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startName, $encryptionHelper);
|
||||
}
|
||||
|
||||
jsonSuccess(null, "Ride reset and resent to drivers");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
// test_socket_dispatch.php
|
||||
|
||||
$socketUrl = "http://188.68.36.205:2021";
|
||||
$socketUrl = getenv('LOCATION_SERVER_URL') ?: 'http://location.intaleq.xyz:2021';
|
||||
$INTERNAL_KEY = getenv('INTERNAL_SOCKET_KEY');
|
||||
if (empty($INTERNAL_KEY)) {
|
||||
$keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
|
||||
|
||||
Reference in New Issue
Block a user