Update: 2026-08-08 12:58:16
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
require_once __DIR__ . '/eligibility.php';
|
||||
require_once __DIR__ . '/../obligations/functions.php';
|
||||
|
||||
$driverId = $user_id ?? '';
|
||||
if (empty($driverId) || ($role ?? '') !== 'driver') {
|
||||
@@ -22,7 +23,12 @@ if (!$policy) {
|
||||
|
||||
$reason = filterRequest('reason') ?: 'بطلب السائق';
|
||||
|
||||
// الوثيقة والالتزام يُغلقان معاً. التزام يبقى نشطاً بعد إلغاء وثيقته
|
||||
// يعني قسطاً يُقيَّد كل ليلة على سائقٍ بلا تغطية — عكس الثغرة السابقة
|
||||
// تماماً، وأسوأ منها: هناك مال يضيع على الشركة، وهنا مال يُؤخذ ظلماً.
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// الشرط على driver_id إلى جانب المعرّف: الوثيقة أتت من استعلام
|
||||
// مقيَّد بالسائق أصلاً، لكن الشرط هنا يجعل الاستعلام آمناً بذاته
|
||||
// لو أعيد استعماله يوماً في سياق آخر.
|
||||
@@ -34,24 +40,22 @@ try {
|
||||
$st->execute([mb_substr($reason, 0, 255), $policy['id'], $driverId]);
|
||||
|
||||
if ($st->rowCount() === 0) {
|
||||
$con->rollBack();
|
||||
jsonError('تعذّر إلغاء الوثيقة');
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
|
||||
obligationClose($con, $driverId, $policy['code'], $reason);
|
||||
|
||||
$con->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($con->inTransaction()) $con->rollBack();
|
||||
error_log('[assurance/cancel] ' . $e->getMessage());
|
||||
jsonError('Server error');
|
||||
}
|
||||
|
||||
$pending = 0.0;
|
||||
try {
|
||||
$st = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0) FROM insurance_premium_ledger
|
||||
WHERE policy_id = ? AND status = 'pending'
|
||||
");
|
||||
$st->execute([$policy['id']]);
|
||||
$pending = (float) $st->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log('[assurance/cancel] تعذّرت قراءة المستحق: ' . $e->getMessage());
|
||||
}
|
||||
// المستحق يُقرأ من الدفتر الموحّد لا من دفتر التأمين القديم: القديم
|
||||
// مجمَّد بعد الترحيل، وقراءته تعني رقماً لا يتغيّر مهما سدّد السائق.
|
||||
$pending = obligationPendingTotal($con, $driverId, $policy['code']);
|
||||
|
||||
error_log("[assurance] أُلغيت وثيقة #{$policy['id']} للسائق $driverId");
|
||||
|
||||
|
||||
@@ -32,28 +32,35 @@ $summary = ['pending_total' => 0.0, 'settled_total' => 0.0,
|
||||
'currency' => $policy['currency']];
|
||||
$entries = [];
|
||||
|
||||
// المصدر هو الدفتر الموحّد لا دفتر التأمين القديم (المجمَّد بعد ترحيل
|
||||
// 2026_08_08). المفتاح صار (السائق + رمز المنتج) بدل رقم الوثيقة:
|
||||
// الدفتر الموحّد لا يعرف الوثائق، يعرف التزامات.
|
||||
try {
|
||||
// المستحق من `amount_remaining` والمحصَّل من `amount_collected`:
|
||||
// قيدٌ حُصِّل نصفه يظهر في الرقمين معاً بحصّته الصحيحة، بينما جمع
|
||||
// `amount` حسب الحالة كان سيعدّه كاملاً في أحدهما ويكذب في كليهما.
|
||||
$st = $con->prepare("
|
||||
SELECT status, SUM(amount) AS total
|
||||
FROM insurance_premium_ledger
|
||||
WHERE policy_id = ?
|
||||
GROUP BY status
|
||||
SELECT COALESCE(SUM(amount_remaining), 0) AS pending_total,
|
||||
COALESCE(SUM(amount_collected), 0) AS settled_total
|
||||
FROM obligation_ledger
|
||||
WHERE driver_id = ? AND product_code = ?
|
||||
");
|
||||
$st->execute([$policy['id']]);
|
||||
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
if ($row['status'] === 'pending') $summary['pending_total'] = (float) $row['total'];
|
||||
if ($row['status'] === 'settled') $summary['settled_total'] = (float) $row['total'];
|
||||
$st->execute([$driverId, $policy['code']]);
|
||||
if ($row = $st->fetch(PDO::FETCH_ASSOC)) {
|
||||
$summary['pending_total'] = (float) $row['pending_total'];
|
||||
$summary['settled_total'] = (float) $row['settled_total'];
|
||||
}
|
||||
|
||||
// آخر ثلاثين قيداً: كافية لشهر يومي، وتمنع رداً ينمو بلا حد.
|
||||
$st = $con->prepare("
|
||||
SELECT charge_date, amount, currency, status, note
|
||||
FROM insurance_premium_ledger
|
||||
WHERE policy_id = ?
|
||||
SELECT charge_date, amount, amount_collected, amount_remaining,
|
||||
currency, status, note
|
||||
FROM obligation_ledger
|
||||
WHERE driver_id = ? AND product_code = ?
|
||||
ORDER BY charge_date DESC
|
||||
LIMIT 30
|
||||
");
|
||||
$st->execute([$policy['id']]);
|
||||
$st->execute([$driverId, $policy['code']]);
|
||||
$entries = $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log('[assurance/get] تعذّرت قراءة الدفتر: ' . $e->getMessage());
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
require_once __DIR__ . '/eligibility.php';
|
||||
require_once __DIR__ . '/../obligations/functions.php';
|
||||
|
||||
// الهوية من الـJWT لا من الطلب — الأهلية معلومة عن السائق نفسه،
|
||||
// وقراءتها بمعرّف مُرسَل تكشف أرقام غيره.
|
||||
@@ -37,10 +38,29 @@ try {
|
||||
|
||||
$current = assuranceActivePolicy($con, $driverId);
|
||||
|
||||
// نداء واحد للمحفظة لكل الشاشة: الملخّص عن السائق لا عن الخطة.
|
||||
// بدونه كانت الشاشة تعرض «مؤهَّل» ثم يُرفض الطلب عند الضغط — وهو أسوأ
|
||||
// من الرفض المبكر: يعد السائق بشيء ثم يسحبه.
|
||||
$incomeSummary = obligationWalletIncomeSummary($driverId);
|
||||
|
||||
$out = [];
|
||||
foreach ($plans as $plan) {
|
||||
$check = assuranceCheckEligibility($con, $driverId, $plan);
|
||||
|
||||
// بوابة الائتمان تُقاس على منتج الالتزام المقابل للخطة. غيابه يعني
|
||||
// خطة لم تُرحَّل بعد — لا نحجبها ولا ندّعي أهليةً لها، بل نتركها
|
||||
// لحكم شروط الخطة وحدها ونسجّل الحالة.
|
||||
$product = obligationProduct($con, $plan['code']);
|
||||
if ($product) {
|
||||
$gate = obligationCheckWalletIncome($product, $driverId, $incomeSummary);
|
||||
if (!$gate['eligible']) {
|
||||
$check['eligible'] = false;
|
||||
$check['reasons'] = array_merge($check['reasons'], $gate['reasons']);
|
||||
}
|
||||
} else {
|
||||
error_log("[assurance/plans] لا منتج التزام للخطة {$plan['code']}");
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'id' => (int) $plan['id'],
|
||||
'code' => $plan['code'],
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
require_once __DIR__ . '/eligibility.php';
|
||||
require_once __DIR__ . '/../obligations/functions.php';
|
||||
|
||||
$driverId = $user_id ?? '';
|
||||
if (empty($driverId) || ($role ?? '') !== 'driver') {
|
||||
@@ -49,7 +50,30 @@ if (!$check['eligible']) {
|
||||
jsonError('لم تستوفِ شروط هذه الخطة بعد: ' . implode('، ', $check['reasons']));
|
||||
}
|
||||
|
||||
// بوابة الائتمان تُفحص هنا صراحةً رغم أن `obligationOpen` تفحصها أيضاً:
|
||||
// الفحص هناك حارسٌ أخير يرمي استثناءً، ورسالته تصل للسائق كخطأ عام.
|
||||
// السائق الذي يُرفض طلبه يستحق أن يعرف السبب وما يفعله ليستوفيه.
|
||||
$product = obligationProduct($con, $plan['code']);
|
||||
if (!$product) {
|
||||
error_log("[assurance/subscribe] منتج الالتزام مفقود للخطة {$plan['code']}");
|
||||
jsonError('تعذّر تفعيل الوثيقة حالياً');
|
||||
}
|
||||
|
||||
$walletGate = obligationCheckWalletIncome($product, $driverId);
|
||||
if (!$walletGate['eligible']) {
|
||||
jsonError(
|
||||
'التأمين يُخصم قسطه من أرباحك في المحفظة، ولذلك يشترط أن تمرّ أرباحك بها: '
|
||||
. implode('، ', $walletGate['reasons'])
|
||||
);
|
||||
}
|
||||
|
||||
// كتابتان في معاملة واحدة: الوثيقة (السجل التأميني الذي يقرأه التطبيق)
|
||||
// والالتزام (ما يراه محرك القيد والتسوية). وثيقة بلا التزام تعني تغطية
|
||||
// تُمنح ولا يُحصَّل قسطها أبداً — وهي بالضبط الثغرة التي جاء هذا المحرك
|
||||
// ليغلقها، فلا يجوز أن نعيد فتحها من باب الاشتراكات الجديدة.
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
$con->prepare("
|
||||
INSERT INTO driver_insurance_policies
|
||||
(driver_id, plan_id, status, started_at, rides_at_signup, rating_at_signup)
|
||||
@@ -57,7 +81,16 @@ try {
|
||||
")->execute([$driverId, $planId, $check['rides'], $check['rating']]);
|
||||
|
||||
$policyId = (int) $con->lastInsertId();
|
||||
} catch (PDOException $e) {
|
||||
|
||||
obligationOpen($con, $driverId, $plan['code'], [
|
||||
'cycle_amount' => (float) $plan['premium'],
|
||||
'rides' => $check['rides'],
|
||||
'rating' => $check['rating'],
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($con->inTransaction()) $con->rollBack();
|
||||
error_log('[assurance/subscribe] تعذّر إنشاء الوثيقة: ' . $e->getMessage());
|
||||
jsonError('تعذّر تفعيل الوثيقة حالياً');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user