Update: 2026-08-08 14:57:58

This commit is contained in:
Hamza-Ayed
2026-08-08 14:57:59 +03:00
parent c659af67dc
commit 875ce763bd
3 changed files with 155 additions and 66 deletions
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* db_primary.php — اتصال ثانٍ بالقاعدة الأساسية من خادم الدفع
* ─────────────────────────────────────────────────────────────
* ‏خادم الدفع يتصل بقاعدته عبر `DB_PAYMENT_*` وحدها (connect.php)، لكن
* ‏نقطتين فيه تحتاجان جدولَي `ride` و`driver_orders` وهما في القاعدة
* ‏الأساسية. كانتا تشيران إليهما بلا تأهيل فسقطتا بـ
* ‏`SQLSTATE[42S02]: Table 'payment.ride' doesn't exist` — وهو ما كان
* ‏يُعطّل شاشة رصيد الكابتن بالكامل.
*
* ‏ولماذا اتصال منفصل لا إشارة مؤهَّلة (`jorSiroDB.ride`)؟
*
* ‏لأن التأهيل يشترط أن تكون القاعدتان على **نفس خادم MySQL**. وخريطة
* ‏`backend/core/Database/Database.php` تُفرد لكل قاعدة host وuser وpass
* ‏مستقلة — أي أن الفصل على خوادم مختلفة مفترضٌ في التصميم لا مستبعَد،
* ‏وقاعدة الدفع أولى ما يُفصل. إشارة مؤهَّلة تعمل اليوم بالصدفة (حاوية
* ‏mysql واحدة) وتسقط صامتةً يوم يُنقل الدفع. الاتصال المنفصل يقرأ
* ‏`DB_PRIMARY_HOST_V2` فيتبع القاعدة أينما ذهبت.
*/
if (!function_exists('primaryDbConnection')) {
/**
* ‏يعيد اتصالاً بالقاعدة الأساسية، أو null إن تعذّر — ولا يرمي أبداً.
* ‏المستدعي يقرّر ماذا يفعل عند الغياب؛ في نقاط المحفظة القرار هو
* ‏عرض الرصيد بأصفار في الإحصاء لا إسقاط الشاشة.
*/
function primaryDbConnection(): ?PDO
{
static $conn = null;
static $tried = false;
if ($tried) {
return $conn;
}
$tried = true;
$name = getenv('DB_PRIMARY_NAME_V2') ?: getenv('DB_PRIMARY_NAME');
$host = getenv('DB_PRIMARY_HOST_V2') ?: getenv('DB_PRIMARY_HOST') ?: 'mysql';
$user = getenv('DB_PRIMARY_USER_V2') ?: getenv('DB_PRIMARY_USER');
$pass = getenv('DB_PRIMARY_PASS_V2') ?: getenv('DB_PRIMARY_PASS');
if (empty($name) || empty($user)) {
error_log('[db_primary] إعداد القاعدة الأساسية ناقص — تعذّر الاتصال.');
return null;
}
try {
$conn = new PDO(
"mysql:host=$host;dbname=$name;charset=utf8mb4",
$user,
$pass,
[
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_TIMEOUT => 5,
]
);
} catch (PDOException $e) {
error_log('[db_primary] تعذّر الاتصال بالقاعدة الأساسية: ' . $e->getMessage());
$conn = null;
}
return $conn;
}
}
@@ -1,7 +1,23 @@
<?php
include "../../connect.php";
include_once "../../db_primary.php";
$driverID = filterRequest("driverID");
/**
* ‏`driver_orders` و`ride` كلاهما في القاعدة الأساسية لا في قاعدة الدفع —
* ‏نفس علّة getAllPayment.php التي كانت تُسقط الرصيد بـ
* ‏`Table 'payment.ride' doesn't exist`.
*
* ‏كل جداول هذا الاستعلام في القاعدة الأساسية، فيُنفَّذ عليها كاملاً عبر
* ‏اتصال مستقل — لا إشارة مؤهَّلة، لأن القاعدتين قد تكونان على خادمين.
*/
$primary = primaryDbConnection();
if ($primary === null) {
printFailure("Statistics unavailable");
exit;
}
$sql = "SELECT
YEAR(`driver_orders`.`created_at`) AS `year`,
MONTH(`driver_orders`.`created_at`) AS `month`,
@@ -30,9 +46,16 @@ ORDER BY
";
// كان المعرّف يُدمج في نص الاستعلام مباشرةً — حقن SQL.
$stmt = $con->prepare($sql);
$stmt->bindValue(':driverID', $driverID);
$stmt->execute();
try {
$stmt = $primary->prepare($sql);
$stmt->bindValue(':driverID', $driverID);
$stmt->execute();
} catch (PDOException $e) {
// ‏بلا هذا الالتقاط كان أي خلل في القاعدة يصير 500 يُسقط الشاشة كلها.
error_log('[driverStatistic] ' . $e->getMessage());
printFailure("Statistics unavailable");
exit;
}
if ($stmt->rowCount() > 0) {
// Fetch the record
@@ -45,4 +68,3 @@ if ($stmt->rowCount() > 0) {
// Print a failure message
printFailure($message = "No wallet record found");
}
?>
@@ -1,70 +1,71 @@
<?php
include "../../connect.php";
include_once "../../db_primary.php";
$driverID = filterRequest("driverID");
$sql = "SELECT
(
SELECT
COUNT(*)
FROM
`ride`
WHERE
LOWER(`ride`.`status`) IN ('finished','completed')
AND `ride`.`created_at` BETWEEN CURRENT_DATE() + INTERVAL 7 HOUR AND CURRENT_DATE() + INTERVAL 10 HOUR
AND `ride`.`driver_id` = :driverID
) AS morning_count,
(
SELECT
COUNT(*)
FROM
`ride`
WHERE
LOWER(`ride`.`status`) IN ('finished','completed')
AND `ride`.`created_at` BETWEEN CURRENT_DATE() + INTERVAL 15 HOUR AND CURRENT_DATE() + INTERVAL 18 HOUR
AND `ride`.`driver_id` = :driverID
) AS afternoon_count,
(
SELECT
COALESCE(SUM(amount), 0) AS total_amount
FROM
payments
WHERE
isGiven = 'waiting' AND `driverID` = :driverID
) AS total_amount,
(
SELECT
COALESCE(SUM(price), 0) AS total_amount
FROM
ride
WHERE
`driver_id` = :driverID
AND LOWER(`ride`.`status`) IN ('finished','completed')
AND `ride`.`created_at` > CURRENT_DATE() - INTERVAL 1 WEEK
) AS total_amount_last_week
FROM
dual
LIMIT 1;
";
/**
* كان معرّف السائق يُدمج في نص الاستعلام مباشرةً (حقن SQL)، وكانت الحالة
* تُطابق 'Finished' فقط بينما خط الرحلات الحالي يكتب 'completed' — فتظهر
* أرباح السائق ورحلاته أصفاراً.
* ‏كان استعلاماً واحداً يقرأ `payments` و`ride` معاً — و`ride` ليس في قاعدة
* ‏الدفع. النتيجة `SQLSTATE[42S02]: Table 'payment.ride' doesn't exist`، وبلا
* ‏try/catch صار خطأً قاتلاً يُسقط النقطة بـ 500 فتعلق شاشة رصيد الكابتن.
* ‏(وقبل إطفاء display_errors كان الخطأ يُطبع داخل الردّ فيصل التطبيق كـ
* ‏FormatException على `<br />` — نفس العلّة بوجه آخر.)
*
* ‏الفصل المعتمد: الرصيد أولاً ومستقلاً، والإحصاءات ثانياً وقابلة للفشل.
* ‏الرصيد رقم مالي من `payments` في قاعدة الدفع ولا عذر لسقوطه، أما عدّ
* ‏الرحلات فمعلومة مساعدة من قاعدة أخرى — تعذّرها يُعيد أصفاراً ولا يحجب
* ‏عن الكابتن رصيده.
*
* ‏و`ride` تُقرأ عبر اتصال مستقل لا عبر إشارة مؤهَّلة: القاعدتان قد تكونان
* ‏على خادمين مختلفين — انظر db_primary.php.
*/
$stmt = $con->prepare($sql);
$stmt->bindValue(':driverID', $driverID);
$stmt->execute();
if ($stmt->rowCount() > 0) {
// Fetch the record
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ── ١. الرصيد — من قاعدة الدفع، لا يعتمد على شيء خارجها ──
$balanceStmt = $con->prepare(
"SELECT COALESCE(SUM(amount), 0) AS total_amount
FROM payments
WHERE isGiven = 'waiting' AND `driverID` = :driverID"
);
$balanceStmt->bindValue(':driverID', $driverID);
$balanceStmt->execute();
$totalAmount = $balanceStmt->fetchColumn();
printSuccess( $row);
$result = [
'total_amount' => $totalAmount !== false ? $totalAmount : 0,
'morning_count' => 0,
'afternoon_count' => 0,
'total_amount_last_week' => 0,
];
// ── ٢. إحصاءات الرحلات — اتصال مستقل بالقاعدة الأساسية ──
$primary = primaryDbConnection();
if ($primary !== null) {
try {
$statsStmt = $primary->prepare(
"SELECT
COALESCE(SUM(`created_at` BETWEEN CURRENT_DATE() + INTERVAL 7 HOUR
AND CURRENT_DATE() + INTERVAL 10 HOUR), 0) AS morning_count,
COALESCE(SUM(`created_at` BETWEEN CURRENT_DATE() + INTERVAL 15 HOUR
AND CURRENT_DATE() + INTERVAL 18 HOUR), 0) AS afternoon_count,
COALESCE(SUM(CASE WHEN `created_at` > CURRENT_DATE() - INTERVAL 1 WEEK
THEN `price` ELSE 0 END), 0) AS total_amount_last_week
FROM `ride`
WHERE `driver_id` = :driverID
AND LOWER(`status`) IN ('finished','completed')"
);
$statsStmt->bindValue(':driverID', $driverID);
$statsStmt->execute();
if ($stats = $statsStmt->fetch(PDO::FETCH_ASSOC)) {
$result['morning_count'] = (int) $stats['morning_count'];
$result['afternoon_count'] = (int) $stats['afternoon_count'];
$result['total_amount_last_week'] = $stats['total_amount_last_week'];
}
} catch (PDOException $e) {
error_log('[getAllPayment] ride stats unavailable: ' . $e->getMessage());
}
}
else{
// Print a failure message
printFailure($message = "No wallet record found");
}
?>
// ‏الشكل كما كان: مصفوفة من صفّ واحد، والتطبيق يقرأ message[0]['total_amount'].
printSuccess([$result]);