264 lines
10 KiB
PHP
264 lines
10 KiB
PHP
<?php
|
|
// ============================================================
|
|
// pricing_helper.php
|
|
// يحتوي على دوال التسعير الموحدة
|
|
// ============================================================
|
|
|
|
/**
|
|
* الحصول على سعر الكيلومتر حسب نوع السيارة من جدول أسعار الدولة
|
|
*
|
|
* @param string $carType نوع السيارة
|
|
* @param array $kazanRow صف من جدول kazan
|
|
* @return float
|
|
*/
|
|
function getPerKmRate(string $carType, array $kazanRow): float {
|
|
$rateColumns = [
|
|
'Comfort' => 'comfortPrice',
|
|
'Speed' => 'speedPrice',
|
|
'Lady' => 'ladyPrice',
|
|
'Electric' => 'electricPrice',
|
|
'Van' => 'vanPrice',
|
|
'Delivery' => 'deliveryPrice',
|
|
'Mishwar Vip' => 'mishwarVipPrice',
|
|
'Fixed Price' => 'fixedPrice',
|
|
'Awfar Car' => 'awfarPrice',
|
|
];
|
|
|
|
$column = $rateColumns[$carType] ?? 'speedPrice';
|
|
|
|
// دعم التوافق مع الإصدارات القديمة (backward compatibility)
|
|
$rate = floatval($kazanRow[$column] ?? 0);
|
|
|
|
if ($rate <= 0) {
|
|
$oldColumnMap = [
|
|
'Lady' => 'familyPrice',
|
|
'Mishwar Vip' => 'freePrice',
|
|
'Electric' => 'naturePrice',
|
|
'Van' => 'heavyPrice',
|
|
];
|
|
$oldColumn = $oldColumnMap[$carType] ?? null;
|
|
if ($oldColumn && isset($kazanRow[$oldColumn])) {
|
|
$rate = floatval($kazanRow[$oldColumn]);
|
|
}
|
|
}
|
|
|
|
// Fallback أخير
|
|
if ($rate <= 0) {
|
|
$rate = floatval($kazanRow['speedPrice'] ?? 36);
|
|
}
|
|
|
|
return $rate;
|
|
}
|
|
|
|
/**
|
|
* الحصول على سعر الدقيقة حسب وقت اليوم من جدول kazan
|
|
*
|
|
* @param array $countryPricing صف من جدول kazan
|
|
* @return float
|
|
*/
|
|
function getPerMinRate(array $countryPricing): float {
|
|
$hour = (int)date('H');
|
|
|
|
// قراءة الأسعار من الأعمدة الجديدة للدقيقة
|
|
$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'; // افتراضي: ليرة سورية
|
|
}
|
|
|
|
/**
|
|
* يقدّر مقدار زيادة أجرة السائق عندنا مقارنة بأقرب منافس لنفس الرحلة.
|
|
* يعتمد على معاملات المنافس المخزّنة بـ Redis (يحدّثها cron_ai_engine.php
|
|
* كل 3 ساعات على مفتاح pricing:driver_comparison:{country_code}) — لا يوجد
|
|
* أي استعلام مباشر لقاعدة البيانات هنا لتفادي أي تأخير وقت إنشاء الرحلة.
|
|
*
|
|
* @return array|null ['extra' => float, 'currency' => string, 'competitor_name' => string]
|
|
* أو null إذا ما في بيانات كافية أو الفرق مش مجدي يُعرض
|
|
*/
|
|
function estimateDriverEarningsAdvantage(
|
|
string $countryCode,
|
|
float $distanceKm,
|
|
float $durationMin,
|
|
float $ourDriverPrice,
|
|
$redis
|
|
): ?array {
|
|
if (!$redis || $ourDriverPrice <= 0) return null;
|
|
|
|
try {
|
|
$json = $redis->get("pricing:driver_comparison:{$countryCode}");
|
|
} catch (Exception $e) {
|
|
return null;
|
|
}
|
|
if (!$json) return null;
|
|
|
|
$data = json_decode($json, true);
|
|
if (!is_array($data) || empty($data['competitor_name'])) return null;
|
|
|
|
$competitorFare = (float)($data['base_fare'] ?? 0)
|
|
+ (float)($data['km_rate'] ?? 0) * $distanceKm
|
|
+ (float)($data['min_rate'] ?? 0) * $durationMin;
|
|
|
|
if ($competitorFare <= 0) return null;
|
|
|
|
$commissionPct = (float)($data['commission_pct'] ?? 20.0);
|
|
$competitorDriverPayout = $competitorFare * (1 - $commissionPct / 100);
|
|
|
|
$extra = round($ourDriverPrice - $competitorDriverPayout, 2);
|
|
|
|
// حد أدنى نسبي (3% من أجرة سائق المنافس) حتى ما نعرض فرق تافه أو غير موثوق
|
|
$minMeaningful = $competitorDriverPayout * 0.03;
|
|
if ($extra <= $minMeaningful) return null;
|
|
|
|
return [
|
|
'extra' => $extra,
|
|
'currency' => $data['currency'] ?? 'SYP',
|
|
'competitor_name' => $data['competitor_name'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* صف التسعير الصحيح لدولة هذه الرحلة.
|
|
*
|
|
* ⚠️ لا تستخدم "SELECT * FROM kazan LIMIT 1" أبداً. جدول kazan يحمل صفاً
|
|
* لكل دولة بمقاييس متباعدة جداً:
|
|
* Syria 40 SYP/كم · 9 SYP/دقيقة
|
|
* Egypt 3.5 EGP/كم · 0.8 EGP/دقيقة
|
|
* Jordan 0.284 JOD/كم· 0.05 JOD/دقيقة
|
|
* فـ LIMIT 1 يعيد أول صف (سوريا) ويطبّق أسعارها على رحلة أردنية — أي
|
|
* مئات الأضعاف. حدث هذا فعلاً في مسارَي الإلغاء.
|
|
*
|
|
* مصدر الدولة: driver.site عبر الرحلة — نفس ما يفعله finish_ride_updates.php.
|
|
* وإن تعذّر (رحلة بلا سائق مثلاً) نقع على $fallbackCountry.
|
|
*
|
|
* @return array صف kazan، أو مصفوفة فارغة إن لم يُوجد شيء إطلاقاً.
|
|
*/
|
|
function getKazanForRide(PDO $con, $rideId, string $fallbackCountry = 'Syria', $driverId = null): array
|
|
{
|
|
$country = '';
|
|
|
|
try {
|
|
if (!empty($driverId) && (string) $driverId !== '0') {
|
|
// مسار الإلغاء يصفّر ride.driver_id قبل الوصول إلى هنا، فالانضمام
|
|
// عبر الرحلة يرجع فارغاً. المستدعي يمرّر السائق الذي يعرفه.
|
|
$stmt = $con->prepare("SELECT site FROM driver WHERE id = ? LIMIT 1");
|
|
$stmt->execute([$driverId]);
|
|
} else {
|
|
$stmt = $con->prepare("
|
|
SELECT d.site AS country_code
|
|
FROM ride r
|
|
LEFT JOIN driver d ON d.id = r.driver_id
|
|
WHERE r.id = ? LIMIT 1
|
|
");
|
|
$stmt->execute([$rideId]);
|
|
}
|
|
$country = (string) ($stmt->fetchColumn() ?: '');
|
|
} catch (PDOException $e) {
|
|
error_log("[pricing] تعذّر تحديد دولة الرحلة $rideId: " . $e->getMessage());
|
|
}
|
|
|
|
if ($country === '') {
|
|
$country = $fallbackCountry;
|
|
error_log("[pricing] الرحلة $rideId بلا دولة معروفة — السقوط على $country");
|
|
}
|
|
|
|
try {
|
|
$stmt = $con->prepare("SELECT * FROM kazan WHERE country = ? LIMIT 1");
|
|
$stmt->execute([$country]);
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$row) {
|
|
error_log("[pricing] لا صف kazan للدولة $country — السقوط على $fallbackCountry");
|
|
$stmt->execute([$fallbackCountry]);
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
return $row ?: [];
|
|
} catch (PDOException $e) {
|
|
error_log("[pricing] تعذّر جلب أسعار $country: " . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* السقف الصلب الأعلى لأي حركة مالية واحدة، بعملة الدولة.
|
|
*
|
|
* ⚠️ رقم مجرّد لا معنى له: كان الكود يستخدم 50000 في ثلاثة مواضع، وهو
|
|
* سقف بلا أثر على الليرة السورية بعد حذف صفرين منها (50000 القديمة =
|
|
* 500 اليوم)، وكارثي على الدينار الأردني (50000 دينار!).
|
|
*
|
|
* هذا ليس سعراً ولا سياسة — هو حزام أمان أخير ضد خطأ مطبعي (صفر زائد)
|
|
* أو حساب موظف مخترَق. السياسة الحقيقية تُضبط في أعمدة kazan.
|
|
*/
|
|
function getMoneyHardCap(string $currency): float
|
|
{
|
|
// سقوف حددها المالك: أقصى تعويض واحد ≈ دينار أردني واحد وما يعادله.
|
|
// رقم صغير عمداً — التعويض استرضاء لا تسوية نزاع، والحالات الكبيرة
|
|
// تُرفع للإدارة لا تُصرف من شاشة موظف.
|
|
$caps = [
|
|
'JOD' => 1.0,
|
|
'EGP' => 50.0,
|
|
'SYP' => 100.0, // بعد حذف الصفرين
|
|
'IQD' => 2000.0,
|
|
'USD' => 1.5,
|
|
'TRY' => 50.0,
|
|
'SAR' => 5.0,
|
|
'AED' => 5.0,
|
|
'LBP' => 130000.0,
|
|
];
|
|
|
|
// عملة غير معروفة: نأخذ أدنى سقف في القائمة. الرفض الخاطئ يُراجَع،
|
|
// والصرف الخاطئ لا يُسترد.
|
|
return $caps[strtoupper($currency)] ?? min($caps);
|
|
}
|