prepare($sql); $st->execute([$driverId]); $row = $st->fetch(PDO::FETCH_ASSOC) ?: []; $net = round((float) ($row['net'] ?? 0), 2); $gross = round((float) ($row['gross'] ?? 0), 2); return [ 'trips' => (int) ($row['trips'] ?? 0), 'net' => $net, 'gross' => $gross, // ‏العمولة تُعرض صراحةً لا تُطرح بصمت. السائق الذي يرى «كسبتَ ٢٠، // ‏عمولة المنصة ٣، لك ١٧» يفهم؛ والذي يرى ١٧ وحدها يخمّن ويشكّ. 'commission' => round(max($gross - $net, 0), 2), 'distance' => round((float) ($row['distance'] ?? 0), 1), 'minutes' => (int) ($row['minutes'] ?? 0), ]; } try { $today = earningsPeriod($con, $driverId, 'DATE(r.created_at) = CURDATE()'); $week = earningsPeriod($con, $driverId, 'r.created_at >= DATE(NOW()) - INTERVAL 6 DAY'); $month = earningsPeriod($con, $driverId, 'r.created_at >= DATE_FORMAT(NOW(), "%Y-%m-01")'); // ‏الأسبوع السابق للمقارنة. المقارنة بالذات لا بالسائقين الآخرين: // ‏«أعلى من أسبوعك الماضي» تحفّز الجميع، بينما ترتيب السائقين يُحبط // ‏أكثرهم — ومن يحتاج التحفيز أكثر هو من سيقع في أسفل الترتيب. $prevWeek = earningsPeriod( $con, $driverId, 'r.created_at >= DATE(NOW()) - INTERVAL 13 DAY AND r.created_at < DATE(NOW()) - INTERVAL 6 DAY' ); $trend = null; if ($prevWeek['net'] > 0) { $trend = round((($week['net'] - $prevWeek['net']) / $prevWeek['net']) * 100, 1); } // ── السلسلة اليومية للرسم البياني ── $st = $con->prepare(" SELECT DATE(r.created_at) AS day, COUNT(r.id) AS trips, COALESCE(SUM(r.price_for_driver), 0) AS net, COALESCE(SUM( GREATEST(TIMESTAMPDIFF(MINUTE, r.rideTimeStart, r.rideTimeFinish), 0) ), 0) AS minutes FROM ride r WHERE r.driver_id = ? AND " . EARNINGS_FINISHED . " AND r.created_at >= DATE(NOW()) - INTERVAL 6 DAY GROUP BY DATE(r.created_at) ORDER BY DATE(r.created_at) ASC "); $st->execute([$driverId]); $rows = $st->fetchAll(PDO::FETCH_ASSOC); // ‏الأيام الفارغة تُملأ بأصفار هنا لا في التطبيق: رسم بياني ينقصه يوم // ‏يُزيح باقي الأعمدة فيقرأ السائق الثلاثاء مكان الأربعاء. $byDay = []; foreach ($rows as $r) { $byDay[$r['day']] = $r; } $daily = []; for ($i = 6; $i >= 0; $i--) { $day = date('Y-m-d', strtotime("-$i day")); $r = $byDay[$day] ?? null; $daily[] = [ 'day' => $day, 'trips' => (int) ($r['trips'] ?? 0), 'net' => round((float) ($r['net'] ?? 0), 2), 'minutes' => (int) ($r['minutes'] ?? 0), ]; } $best = null; foreach ($daily as $d) { if ($best === null || $d['net'] > $best['net']) { $best = $d; } } } catch (PDOException $e) { error_log('[earnings/summary] ' . $e->getMessage()); jsonError('تعذّر حساب الأرباح حالياً', 500); } // ── الاقتطاعات ── // ‏تُعرض في شاشة الأرباح لا في شاشة المنتج وحدها. بعد محرك الالتزامات // ‏صار يُخصم من السائق قسط تأمين ودَين وقود، واقتطاعٌ لا يظهر بجانب // ‏الأرباح التي اقتُطع منها يُقرأ سرقةً مهما كان مشروعاً. $deductions = ['today' => 0.0, 'week' => 0.0, 'month' => 0.0, 'outstanding' => 0.0, 'items' => []]; try { $st = $con->prepare(" SELECT COALESCE(SUM(CASE WHEN DATE(updated_at) = CURDATE() THEN amount_collected ELSE 0 END), 0) AS today, COALESCE(SUM(CASE WHEN updated_at >= DATE(NOW()) - INTERVAL 6 DAY THEN amount_collected ELSE 0 END), 0) AS week, COALESCE(SUM(CASE WHEN updated_at >= DATE_FORMAT(NOW(), '%Y-%m-01') THEN amount_collected ELSE 0 END), 0) AS month, COALESCE(SUM(amount_remaining), 0) AS outstanding FROM obligation_ledger WHERE driver_id = ? "); $st->execute([$driverId]); $d = $st->fetch(PDO::FETCH_ASSOC) ?: []; $deductions['today'] = round((float) ($d['today'] ?? 0), 2); $deductions['week'] = round((float) ($d['week'] ?? 0), 2); $deductions['month'] = round((float) ($d['month'] ?? 0), 2); $deductions['outstanding'] = round((float) ($d['outstanding'] ?? 0), 2); // ‏تفصيل حسب المنتج: «اقتُطع ٣» سؤال، و«٣ لمحفظة الوقود» جواب. $st = $con->prepare(" SELECT l.product_code, p.name_ar, COALESCE(SUM(l.amount_remaining), 0) AS outstanding FROM obligation_ledger l JOIN driver_obligations o ON o.id = l.obligation_id JOIN obligation_products p ON p.id = o.product_id WHERE l.driver_id = ? AND l.amount_remaining > 0 GROUP BY l.product_code, p.name_ar "); $st->execute([$driverId]); $deductions['items'] = array_map(static fn(array $r): array => [ 'product' => $r['product_code'], 'name' => $r['name_ar'], 'outstanding' => round((float) $r['outstanding'], 2), ], $st->fetchAll(PDO::FETCH_ASSOC)); } catch (PDOException $e) { // ‏غياب جداول الالتزامات (قبل تنفيذ الترحيل) ليس خطأً يُفشل الشاشة: // ‏الأرباح تُعرض والاقتطاعات أصفار. إسقاط الشاشة كلها لأن ميزةً // ‏جانبية غير مهيّأة عقوبةٌ في غير محلها. error_log('[earnings/summary] تعذّرت قراءة الاقتطاعات: ' . $e->getMessage()); } jsonSuccess([ 'today' => $today, 'week' => $week, 'month' => $month, 'prev_week' => $prevWeek, 'trend_percent' => $trend, 'daily' => $daily, 'best_day' => $best, 'deductions' => $deductions, // ‏ختم زمني واحد لكل الأرقام. التطبيق يعرضه كـ«آخر تحديث»، فيعرف // ‏السائق متى قِيست هذه الأرقام بدل أن يظنّ القديم حاضراً. 'as_of' => date('c'), ], 'success');