Update: 2026-08-08 12:58:16

This commit is contained in:
Hamza-Ayed
2026-08-08 12:58:17 +03:00
parent 611b5e616d
commit fae0e4a38a
55 changed files with 5771 additions and 1820 deletions
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* fuel/functions.php — منطق محفظة الوقود المشترك
* ─────────────────────────────────────────────────────────────
* ‏محفظة الوقود منتج `drawdown` في محرك الالتزامات: لا دورة فوترة له،
* ‏والاستحقاق يُقيَّد لحظة الصرف من المحطة لا على جدول زمني.
*
* ‏كل ما يخصّ المال هنا يقتصر على **قيد الدَّين**. الخصم من رصيد السائق
* ‏يبقى حكراً على محرك التسوية، كما في التأمين.
*/
require_once __DIR__ . '/../obligations/functions.php';
const FUEL_PRODUCT_CODE = 'fuel_wallet';
const FUEL_VOUCHER_TTL_MIN = 45;
/**
* ‏التزام الوقود النشط للسائق، أو null.
*/
function fuelObligation(PDO $con, string $driverId): ?array
{
$st = $con->prepare("
SELECT o.*, p.currency, p.daily_cap_percent
FROM driver_obligations o
JOIN obligation_products p ON p.id = o.product_id
WHERE o.driver_id = ? AND p.code = ? AND o.status = 'active'
LIMIT 1
");
$st->execute([$driverId, FUEL_PRODUCT_CODE]);
return $st->fetch(PDO::FETCH_ASSOC) ?: null;
}
/**
* ‏يُنهي صلاحية القسائم التي مضى وقتها.
*
* ‏يُستدعى قبل أي حساب سقف لا في كرون: القسيمة المنتهية تحجز من سقف
* ‏السائق، وتأخير تحريرها ساعةً كاملةً حتى يمرّ الكرون يعني منعه من
* ‏التزوّد بسبب قسيمة ماتت. التنظيف عند القراءة يجعل الرقم صادقاً دائماً.
*/
function fuelExpireStaleVouchers(PDO $con, string $driverId): void
{
$con->prepare("
UPDATE fuel_vouchers
SET status = 'expired'
WHERE driver_id = ? AND status = 'issued' AND expires_at < NOW()
")->execute([$driverId]);
}
/**
* ‏حالة سقف السائق: المستخدَم، المحجوز، والمتاح.
*
* ‏المحجوز يُطرح كالمستخدَم تماماً: قسيمة أُصدرت ولم تُصرف بعد قد تُصرف
* ‏بعد لحظة، والسماح بإصدار قسائم بمجموع يتجاوز السقف يعني سائقاً
* ‏يتزوّد بضعف ما يستحق ثم لا نجد ما نخصم منه.
*
* @return array{limit: float, outstanding: float, held: float, available: float}
*/
function fuelCreditState(PDO $con, array $obligation): array
{
$obligationId = (int) $obligation['id'];
$st = $con->prepare("
SELECT COALESCE(SUM(amount_remaining), 0)
FROM obligation_ledger
WHERE obligation_id = ? AND status IN ('pending', 'partial')
");
$st->execute([$obligationId]);
$outstanding = (float) $st->fetchColumn();
$st = $con->prepare("
SELECT COALESCE(SUM(amount_authorized), 0)
FROM fuel_vouchers
WHERE obligation_id = ? AND status = 'issued' AND expires_at >= NOW()
");
$st->execute([$obligationId]);
$held = (float) $st->fetchColumn();
$limit = (float) $obligation['credit_limit'];
$available = round($limit - $outstanding - $held, 3);
return [
'limit' => $limit,
'outstanding' => round($outstanding, 3),
'held' => round($held, 3),
'available' => max($available, 0.0),
];
}
/**
* ‏يولّد رمز قسيمة غير متوقَّع.
*
* ‏`random_int` لا `rand`: الرمز مفتاحُ صرفٍ فعلي، ومولّد عشوائية قابل
* ‏للتنبّؤ يعني أن من يعرف رمزاً واحداً قد يخمّن التالي ويتزوّد على
* ‏حساب سائق آخر. عشرة أرقام تُبقي المجال واسعاً والرقم قابلاً للإملاء
* ‏على عامل المحطة هاتفياً.
*/
function fuelGenerateCode(PDO $con): string
{
for ($i = 0; $i < 10; $i++) {
$code = (string) random_int(1000000000, 9999999999);
$st = $con->prepare("SELECT 1 FROM fuel_vouchers WHERE code = ? LIMIT 1");
$st->execute([$code]);
if (!$st->fetchColumn()) {
return $code;
}
}
// ‏عشر محاولات فاشلة على مجال من تسعة مليارات ليست صدفة — هي عطل
// ‏(قاعدة ممتلئة أو مولّد معطوب). الاستمرار هنا يعني رمزاً مكرّراً.
throw new RuntimeException('تعذّر توليد رمز قسيمة فريد');
}