From f43e1b975125ec18c65646401e069151ad2f8d38 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 16 Apr 2026 08:13:52 +0000 Subject: [PATCH] updating cycle system --- approved_school.php | 246 +++++- assessments.php | 370 +++++++++ assets/css/custom.css | 10 + attendance.php | 372 +++++++++ db/migrations/20260416_school_cycles.sql | 18 + includes/app.php | 453 ++++++++++- includes/cycles.php | 910 +++++++++++++++++++++++ students.php | 115 ++- teachers.php | 126 +++- 9 files changed, 2565 insertions(+), 55 deletions(-) create mode 100644 assessments.php create mode 100644 attendance.php create mode 100644 db/migrations/20260416_school_cycles.sql create mode 100644 includes/cycles.php diff --git a/approved_school.php b/approved_school.php index 0744080..04308ad 100644 --- a/approved_school.php +++ b/approved_school.php @@ -4,7 +4,11 @@ require_once __DIR__ . '/includes/app.php'; $flash = consume_flash(); $applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0; +$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0; $application = $applicationId > 0 ? get_application($applicationId) : null; +$cycleValues = school_cycle_defaults($application ?: null); +$cycleErrors = []; +$cycleRollover = school_cycle_rollover_defaults(); if (!$application) { http_response_code(404); @@ -26,6 +30,91 @@ if (!$application) { } $isApproved = (string) $application['status'] === 'approved'; +$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false]; +$selectedCycle = null; +$selectedCycleId = 0; +$isCycleReadOnly = false; +$cycleLabel = 'لا توجد دورة بعد'; + +if ($isApproved) { + $cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId); + $selectedCycle = $cycleContext['selected']; + $selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0; + $isCycleReadOnly = (bool) $cycleContext['read_only']; + $cycleLabel = $selectedCycle ? (string) $selectedCycle['cycle_name'] : $cycleLabel; + $cycleRollover = school_cycle_rollover_defaults($selectedCycle); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $isApproved) { + $cycleAction = clean_text((string) ($_POST['cycle_action'] ?? ''), 30); + if ($cycleAction === 'create_cycle') { + [$cycleValues, $cycleErrors] = validate_school_cycle_input($_POST, $application); + $cycleRollover = school_cycle_rollover_input((int) $application['id'], $_POST, $selectedCycle); + $wantsRollover = !empty($cycleRollover['copy_teachers']) || !empty($cycleRollover['copy_assessments']) || !empty($cycleRollover['copy_students']); + if ($wantsRollover && (int) $cycleRollover['source_cycle_id'] <= 0) { + $cycleErrors['form'] = 'اختر دورة مصدر إذا كنت تريد نسخ الفريق أو التقييمات أو الطلاب إلى الدورة الجديدة.'; + } + if ($cycleErrors === []) { + try { + $cycleCreation = create_school_cycle((int) $application['id'], $cycleValues, $cycleRollover); + $newCycleId = (int) ($cycleCreation['cycle_id'] ?? 0); + $rolloverSummary = (array) ($cycleCreation['rollover'] ?? []); + $flashMessage = 'تم إنشاء الدورة الموسمية الجديدة بنجاح. يمكنك الآن العمل داخل ' . format_school_cycle_name($cycleValues['season'], (int) $cycleValues['year']) . '.'; + $rolloverParts = []; + if (!empty($rolloverSummary['teachers'])) { + $rolloverParts[] = 'ترحيل ' . (int) $rolloverSummary['teachers'] . ' من أعضاء الفريق'; + } + if (!empty($rolloverSummary['assessments'])) { + $rolloverParts[] = 'نسخ ' . (int) $rolloverSummary['assessments'] . ' من بنود التقييم'; + } + if (!empty($rolloverSummary['students'])) { + $rolloverParts[] = 'نقل ' . (int) $rolloverSummary['students'] . ' من الطلاب المستمرين'; + } + if ($rolloverParts !== []) { + $sourceCycleName = (string) ($cycleCreation['source_cycle_name'] ?? 'الدورة السابقة'); + $flashMessage .= ' تم أيضاً ' . implode('، ', $rolloverParts) . ' من ' . $sourceCycleName . '.'; + } + set_flash('success', $flashMessage); + header('Location: ' . school_page_url('approved_school.php', (int) $application['id'], $newCycleId)); + exit; + } catch (PDOException $exception) { + $duplicateCode = isset($exception->errorInfo[1]) && (int) $exception->errorInfo[1] === 1062; + if ($duplicateCode) { + $cycleErrors['year'] = 'هذه الدورة موجودة مسبقاً لهذا المركز. اختر موسماً أو سنة مختلفة.'; + } else { + $cycleErrors['form'] = 'تعذر إنشاء الدورة حالياً. يرجى المحاولة مرة أخرى.'; + } + } catch (InvalidArgumentException $exception) { + $cycleErrors['form'] = 'تعذر ترحيل البيانات من الدورة المحددة. اختر دورة صحيحة ثم حاول مرة أخرى.'; + } catch (Throwable $exception) { + $cycleErrors['form'] = 'تعذر إنشاء الدورة حالياً. يرجى المحاولة مرة أخرى.'; + } + } + } elseif ($cycleAction === 'archive_cycle') { + $postedCycleId = (int) ($_POST['cycle_id'] ?? 0); + $cycleToArchive = $postedCycleId > 0 ? get_school_cycle((int) $application['id'], $postedCycleId) : null; + if (!$cycleToArchive) { + $cycleErrors['form'] = 'تعذر العثور على الدورة المطلوب أرشفتها.'; + } elseif ((string) $cycleToArchive['status'] === 'archived') { + $cycleErrors['form'] = 'هذه الدورة مؤرشفة بالفعل.'; + } else { + archive_school_cycle((int) $application['id'], $postedCycleId); + set_flash('success', 'تمت أرشفة الدورة ' . (string) $cycleToArchive['cycle_name'] . ' بنجاح. يمكنك فتح دورة جديدة متى شئت.'); + header('Location: ' . school_page_url('approved_school.php', (int) $application['id'])); + exit; + } + } + + $cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId); + $selectedCycle = $cycleContext['selected']; + $selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0; + $isCycleReadOnly = (bool) $cycleContext['read_only']; + $cycleLabel = $selectedCycle ? (string) $selectedCycle['cycle_name'] : $cycleLabel; + if ($cycleAction !== 'create_cycle') { + $cycleRollover = school_cycle_rollover_defaults($selectedCycle); + } +} + $scoreValue = $application['evaluation_score'] !== null ? max(0, min(100, (int) $application['evaluation_score'])) : null; $durationDays = 0; $startTs = strtotime((string) $application['start_date']); @@ -34,9 +123,19 @@ if ($startTs !== false && $endTs !== false && $endTs >= $startTs) { $durationDays = (int) floor(($endTs - $startTs) / 86400) + 1; } -$pageTitle = $isApproved ? 'صفحة المركز المعتمد: ' . (string) $application['center_name'] : 'صفحة المركز بعد الاعتماد'; +$studentsUrl = school_page_url('students.php', (int) $application['id'], $selectedCycleId); +$teachersUrl = school_page_url('teachers.php', (int) $application['id'], $selectedCycleId); +$assessmentsUrl = school_page_url('assessments.php', (int) $application['id'], $selectedCycleId); +$attendanceUrl = school_page_url('attendance.php', (int) $application['id'], $selectedCycleId); +$approvedSchoolUrl = school_page_url('approved_school.php', (int) $application['id'], $selectedCycleId); +$studentCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_student_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0]; +$teacherCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_teacher_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0]; +$assessmentCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_assessment_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0, 'active_weight' => 0]; +$attendanceCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_attendance_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'today_count' => 0, 'latest_date' => '']; + +$pageTitle = $isApproved ? 'صفحة المركز المعتمد: ' . (string) $application['center_name'] . ($selectedCycle ? ' — ' . $cycleLabel : '') : 'صفحة المركز بعد الاعتماد'; $pageDescription = $isApproved - ? 'صفحة هبوط تشغيلية للمركز المعتمد تعرض الجاهزية، بيانات التواصل، والخطوات التالية بعد الموافقة.' + ? 'صفحة تشغيلية للمركز المعتمد تعرض الجاهزية، الدورات الموسمية، والخطوات التالية بعد الموافقة.' : 'هذه الصفحة تصبح متاحة بعد اعتماد طلب المركز من المشرف العام.'; render_page_start($pageTitle, 'approved', $pageDescription); @@ -76,8 +175,10 @@ render_flash($flash);
فتح ملف الاعتماد - تسجيل الطلاب - فريق المعلمين + تسجيل الطلاب + فريق المعلمين + التقييمات والأوزان + غياب الطلاب كل المراكز المعتمدة لوحة القيادة
@@ -106,6 +207,129 @@ render_flash($flash);
مرجع التشغيل
#
استخدم هذا الرقم في أي متابعة إدارية لاحقة.
+ +
+
+
+
+
+
الدورات الموسمية والأرشفة
+
كل الطلاب والمعلمين والتقييمات والغياب أصبحت الآن مرتبطة بدورة مستقلة مثل Summer 2026 أو Winter 2026. عند نهاية الموسم قم بأرشفة الدورة الحالية ثم ابدأ دورة جديدة للحفاظ على التاريخ بدون خلط السجلات.
+
+ +
+
+
الدورة المحددة
+
الفترة
+
حالة الدورة
+
الطلاب
+
الفريق
+
التقييمات النشطة
+
سجلات الغياب
+
+ + +
هذه الدورة مؤرشفة حالياً، لذلك كل الصفحات المرتبطة بها أصبحت للقراءة فقط. يمكنك فتح دورة جديدة من النموذج المجاور.
+ +
+
+
+ + +
+
+ +
@@ -148,8 +372,12 @@ render_flash($flash);

صفحة الفريق أصبحت جاهزة الآن لإضافة المعلمين والمشرفين وربط أدوارهم التشغيلية مباشرة بالمركز المعتمد.

- 3) متابعة التشغيل -

بعد الانطلاق يمكن توسيع هذه الصفحة لاحقاً بمؤشرات حضور يومية، تقييمات، وتنبيهات تشغيلية.

+ 3) ضبط التقييمات +

صفحة التقييمات أصبحت جاهزة الآن لتحديد نوع التقييم، المقياس، والوزن قبل بدء الرصد والمتابعة.

+
+
+ 4) متابعة الغياب +

صفحة غياب الطلاب أصبحت جاهزة الآن لتسجيل الغياب اليومي، الأعذار، وحالات التأخر لكل طالب داخل المركز.

@@ -174,8 +402,10 @@ render_flash($flash); @@ -96,9 +119,11 @@ render_flash($flash); المقاعد المتبقية
- العودة لصفحة المركز - فريق المعلمين - ملف الاعتماد + العودة لصفحة المركز + فريق المعلمين + التقييمات والأوزان + غياب الطلاب + ملف الاعتماد
@@ -116,6 +141,56 @@ render_flash($flash);
+ + +
+
+
+
+
+
الدورة الموسمية الحالية
+
كل بيانات هذه الصفحة مرتبطة الآن بالدورة . عند انتهاء الموسم يمكنك أرشفتها من صفحة المركز والبدء بدورة جديدة بدون فقدان السجلات القديمة.
+
+ +
+
+
اسم الدورة
+
الفترة
+
عدد الدورات دورة للمركز
+
+ + +
هذه الدورة مؤرشفة حالياً، لذلك تبقى السجلات قابلة للمراجعة فقط بدون إضافة طلاب جدد.
+ +
+
+
+ +
+
+ +
@@ -96,9 +118,11 @@ render_flash($flash); أدوار إشرافية
- العودة لصفحة المركز - تسجيل الطلاب - ملف الاعتماد + العودة لصفحة المركز + تسجيل الطلاب + التقييمات والأوزان + غياب الطلاب + ملف الاعتماد
@@ -115,6 +139,56 @@ render_flash($flash);
+ + +
+
+
+
+
+
الدورة الموسمية الحالية
+
أعضاء الفريق في هذه الصفحة مرتبطون بالدورة . عند أرشفة الموسم ستبقى القائمة محفوظة للرجوع إليها لاحقاً.
+
+ +
+
+
اسم الدورة
+
الفترة
+
عدد الدورات دورة للمركز
+
+ + +
هذه الدورة مؤرشفة، لذلك تبقى صفحة الفريق للقراءة فقط حالياً.
+ +
+
+
+ +
+
+ +
إجمالي الفريق
عدد أعضاء الكادر المسجلين لهذا المركز.
مفعلون
جاهزون للبدء الفعلي ضمن الخطة التشغيلية.
@@ -136,6 +210,9 @@ render_flash($flash);
+ +
هذه الدورة مؤرشفة. يمكنك مراجعة الفريق فقط، أو فتح دورة جديدة من صفحة المركز.
+
@@ -191,13 +268,14 @@ render_flash($flash);
+
@@ -268,8 +346,10 @@ render_flash($flash);
البريد الرسمي
- العودة إلى الطلاب - صفحة المركز + العودة إلى الطلاب + فتح التقييمات + فتح الغياب + صفحة المركز