Update Saqel Platform: 2026-09-08 13:43:36
This commit is contained in:
@@ -8,6 +8,76 @@ use App\Core\Database;
|
||||
|
||||
class GuardianController
|
||||
{
|
||||
public function requestChildLink(Request $request, Response $response): void
|
||||
{
|
||||
$body = $request->getBody();
|
||||
$nationalId = trim((string)($body['national_id'] ?? ''));
|
||||
$relationship = (string)($body['relationship_type'] ?? 'guardian');
|
||||
if (!preg_match('/^[0-9]{10}$/', $nationalId)) {
|
||||
$response->status(422)->json(['status' => 'error', 'message' => 'الرقم الوطني يجب أن يتكون من 10 أرقام']);
|
||||
return;
|
||||
}
|
||||
if (!in_array($relationship, ['father', 'mother', 'brother', 'guardian'], true)) {
|
||||
$relationship = 'guardian';
|
||||
}
|
||||
$student = Database::selectOne("SELECT id FROM students WHERE national_id_hash = ? LIMIT 1", [\App\Core\Security::blindIndex($nationalId)]);
|
||||
if (!$student) {
|
||||
// Do not reveal whether a national identity exists.
|
||||
$response->status(202)->json(['status' => 'pending', 'message' => 'تم استلام طلب الربط للمراجعة']);
|
||||
return;
|
||||
}
|
||||
$existing = Database::selectOne("SELECT id FROM guardian_students WHERE guardian_id = ? AND student_id = ? LIMIT 1", [$request->user_id, $student['id']]);
|
||||
if ($existing) {
|
||||
$response->json(['status' => 'success', 'message' => 'الطالب مرتبط بهذا الحساب مسبقاً']);
|
||||
return;
|
||||
}
|
||||
$uuid = $this->uuid();
|
||||
Database::query(
|
||||
"INSERT INTO guardian_link_requests (uuid, guardian_id, student_id, relationship_type, status)
|
||||
VALUES (?, ?, ?, ?, 'pending') ON DUPLICATE KEY UPDATE relationship_type = VALUES(relationship_type)",
|
||||
[$uuid, $request->user_id, $student['id'], $relationship]
|
||||
);
|
||||
$response->status(202)->json(['status' => 'pending', 'message' => 'أُرسل طلب الربط إلى الطالب للموافقة']);
|
||||
}
|
||||
|
||||
public function pendingLinkRequests(Request $request, Response $response): void
|
||||
{
|
||||
$requests = Database::select(
|
||||
"SELECT glr.uuid, glr.relationship_type, glr.created_at, g.full_name AS guardian_name
|
||||
FROM guardian_link_requests glr JOIN guardians g ON g.id = glr.guardian_id
|
||||
WHERE glr.student_id = ? AND glr.status = 'pending' ORDER BY glr.created_at DESC",
|
||||
[$request->user_id]
|
||||
);
|
||||
$response->json(['status' => 'success', 'data' => $requests]);
|
||||
}
|
||||
|
||||
public function reviewLinkRequest(Request $request, Response $response): void
|
||||
{
|
||||
$uuid = trim((string)($request->getBody()['request_uuid'] ?? ''));
|
||||
$decision = (string)($request->getBody()['decision'] ?? '');
|
||||
if (!in_array($decision, ['approved', 'rejected'], true)) {
|
||||
$response->status(422)->json(['status' => 'error', 'message' => 'قرار الربط غير صالح']);
|
||||
return;
|
||||
}
|
||||
$link = Database::selectOne(
|
||||
"SELECT * FROM guardian_link_requests WHERE uuid = ? AND student_id = ? AND status = 'pending' LIMIT 1",
|
||||
[$uuid, $request->user_id]
|
||||
);
|
||||
if (!$link) {
|
||||
$response->status(404)->json(['status' => 'error', 'message' => 'طلب الربط غير موجود']);
|
||||
return;
|
||||
}
|
||||
Database::query("UPDATE guardian_link_requests SET status = ?, reviewed_at = NOW() WHERE id = ?", [$decision, $link['id']]);
|
||||
if ($decision === 'approved') {
|
||||
Database::query(
|
||||
"INSERT INTO guardian_students (guardian_id, student_id, relationship_type) VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE relationship_type = VALUES(relationship_type), can_view_analytics = 1",
|
||||
[$link['guardian_id'], $request->user_id, $link['relationship_type']]
|
||||
);
|
||||
}
|
||||
$response->json(['status' => 'success', 'message' => $decision === 'approved' ? 'تم اعتماد ربط ولي الأمر' : 'تم رفض طلب الربط']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dashboard Data for Guardian
|
||||
* GET /api/guardian/dashboard
|
||||
@@ -25,49 +95,6 @@ class GuardianController
|
||||
[$guardianId]
|
||||
);
|
||||
|
||||
// Auto-discover and link student if link does not exist yet
|
||||
if (empty($children)) {
|
||||
$guardianUser = Database::selectOne("SELECT identity_id FROM guardians WHERE id = ?", [$guardianId]);
|
||||
if ($guardianUser && !empty($guardianUser['identity_id'])) {
|
||||
$matchedStudents = Database::select("SELECT id FROM students WHERE identity_id = ?", [$guardianUser['identity_id']]);
|
||||
foreach ($matchedStudents as $ms) {
|
||||
Database::insert("INSERT IGNORE INTO guardian_students (guardian_id, student_id) VALUES (?, ?)", [$guardianId, $ms['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
// If still empty, link to first available student in DB
|
||||
$existingCount = Database::selectOne("SELECT COUNT(*) as cnt FROM guardian_students WHERE guardian_id = ?", [$guardianId])['cnt'] ?? 0;
|
||||
if ($existingCount == 0) {
|
||||
$defaultStudent = Database::selectOne("SELECT id FROM students ORDER BY id ASC LIMIT 1");
|
||||
if ($defaultStudent) {
|
||||
Database::insert("INSERT IGNORE INTO guardian_students (guardian_id, student_id) VALUES (?, ?)", [$guardianId, $defaultStudent['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-fetch after auto-linking
|
||||
$children = Database::select(
|
||||
"SELECT s.id, s.uuid, s.full_name, s.national_id, s.grade_level, s.stream
|
||||
FROM students s
|
||||
JOIN guardian_students gs ON gs.student_id = s.id
|
||||
WHERE gs.guardian_id = ?",
|
||||
[$guardianId]
|
||||
);
|
||||
}
|
||||
|
||||
// Resilient fallback if database has no student records yet (Real Student Profile)
|
||||
if (empty($children)) {
|
||||
$children = [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => 'std-10-sama-01',
|
||||
'full_name' => 'سما حمزة',
|
||||
'national_id' => '2009102450',
|
||||
'grade_level' => 'الصف العاشر الأساسي',
|
||||
'stream' => 'علمي'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$dashboardData = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
@@ -123,16 +150,16 @@ class GuardianController
|
||||
'student' => [
|
||||
'id' => $child['id'],
|
||||
'uuid' => $child['uuid'],
|
||||
'name' => $child['full_name'] ?: 'طالب جديد',
|
||||
'national_id' => $child['national_id'] ?: 'غير محدد',
|
||||
'name' => $child['full_name'],
|
||||
'national_id' => $this->maskNationalId((string)$child['national_id']),
|
||||
'grade_stream' => ($child['grade_level'] ?? 'غير محدد') . ' (' . ($child['stream'] ?? 'عام') . ')'
|
||||
],
|
||||
'metrics' => [
|
||||
'readiness_score' => ($mastery && !empty($mastery['tawjihi_readiness_score'])) ? (float)$mastery['tawjihi_readiness_score'] : 88.5,
|
||||
'checkpoints_passed' => $checkpointsCount > 0 ? $checkpointsCount : 18,
|
||||
'remediations_flagged' => $remediationCount > 0 ? $remediationCount : 1,
|
||||
'exams_passed_count' => ($mastery && !empty($mastery['exams_passed_count'])) ? (int)$mastery['exams_passed_count'] : 18,
|
||||
'exams_total_count' => ($mastery && !empty($mastery['exams_total_count'])) ? (int)$mastery['exams_total_count'] : 20
|
||||
'readiness_score' => $mastery ? (float)$mastery['tawjihi_readiness_score'] : 0.0,
|
||||
'checkpoints_passed' => (int)$checkpointsCount,
|
||||
'remediations_flagged' => (int)$remediationCount,
|
||||
'exams_passed_count' => $mastery ? (int)$mastery['exams_passed_count'] : 0,
|
||||
'exams_total_count' => $mastery ? (int)$mastery['exams_total_count'] : 0
|
||||
],
|
||||
'diagnostics' => $diagnosticLogs
|
||||
];
|
||||
@@ -145,4 +172,30 @@ class GuardianController
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
private function maskNationalId(string $storedValue): string
|
||||
{
|
||||
if ($storedValue === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$decrypted = \App\Core\Security::decrypt($storedValue);
|
||||
$nationalId = $decrypted !== '' ? $decrypted : $storedValue;
|
||||
} catch (\Throwable $e) {
|
||||
$nationalId = $storedValue;
|
||||
}
|
||||
|
||||
return strlen($nationalId) >= 4
|
||||
? str_repeat('*', max(0, strlen($nationalId) - 4)) . substr($nationalId, -4)
|
||||
: '****';
|
||||
}
|
||||
|
||||
private function uuid(): string
|
||||
{
|
||||
$data = random_bytes(16);
|
||||
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
|
||||
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user