Files
saqel/backend/app/Services/SchoolRosterService.php
T

99 lines
4.4 KiB
PHP

<?php
namespace App\Services;
use App\Core\Database;
use App\Core\Security;
/**
* ==============================================================================
* SAQEL ENTERPRISE (EDTECH 2.0) - SCHOOL ROSTER & NATIONAL ID ENCRYPTION SERVICE
* ==============================================================================
*
* ملف: SchoolRosterService.php
* الهدف المعماري:
* استيراد كشوفات المدارس وتشفير الأرقام الوطنية للطلبة والمعلمين (AES-256-GCM):
* 1. التحقق من صحة الرقم الوطني الأردني (10 خانات رقمية).
* 2. تشفير الرقم الوطني تشفيراً سيادياً وتوليد المؤشر الأعمى (Blind Index) لمنع تداخل الأسماء.
* 3. استيراد كشف المدرسة الجماعي وربط الطلبة بمدارسهم ومديريتهم.
*/
class SchoolRosterService
{
/**
* استيراد وتشفير كشف الطلبة للمدرسة
*/
public static function importStudentRoster(int $schoolId, array $records): array
{
$importedCount = 0;
$failedCount = 0;
$errors = [];
foreach ($records as $index => $row) {
$nationalId = trim((string)($row['national_id'] ?? ''));
$fullName = trim((string)($row['full_name'] ?? ''));
$gradeLevel = trim((string)($row['grade_level'] ?? 'grade_10'));
$stream = self::normalizeStream((string)($row['stream'] ?? 'general'));
// 1. Validate 10-digit Jordanian National ID
if (!preg_match('/^[0-9]{10}$/', $nationalId)) {
$failedCount++;
$errors[] = "السطر " . ($index + 1) . ": الرقم الوطني ($nationalId) غير صالح (يجب أن يتكون من 10 أرقام).";
continue;
}
if (empty($fullName)) {
$failedCount++;
$errors[] = "السطر " . ($index + 1) . ": اسم الطالب مطلوب.";
continue;
}
// 2. Encrypt National ID and generate HMAC Blind Index
$encryptedNationalId = Security::encrypt($nationalId);
$blindIndex = Security::blindIndex($nationalId);
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
// Insert or update in DB
try {
// If students table is active
Database::query(
"INSERT INTO students (uuid, national_id, national_id_hash, full_name, grade_level, stream, school_id, is_school_sponsored, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE national_id = VALUES(national_id), full_name = VALUES(full_name), grade_level = VALUES(grade_level), stream = VALUES(stream), school_id = VALUES(school_id)",
[$uuid, $encryptedNationalId, $blindIndex, $fullName, $gradeLevel, $stream, $schoolId]
);
$importedCount++;
} catch (\Throwable $e) {
$failedCount++;
$errors[] = 'السطر ' . ($index + 1) . ': تعذر حفظ السجل في قاعدة البيانات.';
error_log('[SchoolRosterService] import failure: ' . $e->getMessage());
}
}
return [
'status' => 'success',
'school_id' => $schoolId,
'total_received' => count($records),
'imported_count' => $importedCount,
'failed_count' => $failedCount,
'errors' => $errors,
'encryption_info'=> 'تم تشفير جميع الأرقام الوطنية بنجاح عبر خوارزمية AES-256-GCM السيادية ومؤشر HMAC الأعمى.',
];
}
private static function normalizeStream(string $stream): string
{
return match (trim(mb_strtolower($stream))) {
'علمي', 'scientific' => 'scientific',
'أدبي', 'literary' => 'literary',
'مهني', 'vocational' => 'vocational',
default => 'general',
};
}
}