= e((string) $item['tagline']) ?>
+= e((string) $item['center_name']) ?>
+تُمنح هذه الشهادة إلى
+= e((string) ($honor['completion_line_ar'] ?? '')) ?>
+diff --git a/assets/css/custom.css b/assets/css/custom.css
index dfb57b7..49ea438 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -21,7 +21,7 @@
--radius-sm: 10px;
--radius-md: 14px;
--radius-lg: 18px;
- --font-ui: "IBM Plex Sans Arabic", "Segoe UI", Tahoma, Arial, sans-serif;
+ --font-ui: "Cairo", "Segoe UI", Tahoma, Arial, sans-serif;
}
* {
@@ -1662,8 +1662,11 @@ textarea.form-control {
html,
body {
- width: 297mm;
- min-height: 210mm;
+ margin: 0 !important;
+ padding: 0 !important;
+ width: auto !important;
+ min-height: auto !important;
+ height: auto !important;
background: #fff !important;
}
@@ -1672,6 +1675,12 @@ textarea.form-control {
print-color-adjust: exact;
}
+ .app-shell,
+ main {
+ margin: 0 !important;
+ padding: 0 !important;
+ }
+
.site-header,
.site-footer,
.certificate-toolbar,
@@ -1704,9 +1713,9 @@ textarea.form-control {
.completion-certificate-card {
border: 2px solid rgba(180, 146, 42, 0.62);
border-radius: 1rem;
- height: 190mm;
- min-height: 190mm;
- max-height: 190mm;
+ height: 188mm;
+ min-height: 188mm;
+ max-height: 188mm;
overflow: hidden;
page-break-inside: avoid;
break-inside: avoid-page;
@@ -1898,3 +1907,41 @@ textarea.form-control {
}
}
+.completion-batch-page .certificate-toolbar {
+ position: sticky;
+ top: 1rem;
+ z-index: 5;
+}
+
+.batch-certificate-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.completion-batch-item {
+ page-break-inside: avoid;
+ break-inside: avoid-page;
+}
+
+@media print {
+ .completion-batch-page {
+ padding: 0 !important;
+ }
+
+ .batch-certificate-stack {
+ gap: 0 !important;
+ }
+
+ .completion-batch-item {
+ margin: 0 !important;
+ page-break-after: always;
+ break-after: page;
+ }
+
+ .completion-batch-item:last-child {
+ page-break-after: auto;
+ break-after: auto;
+ }
+}
+
diff --git a/includes/app.php b/includes/app.php
index 5a2e44c..b2fab68 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -1413,7 +1413,7 @@ function render_page_start(string $pageTitle, string $active = 'home', string $p
-
+
diff --git a/includes/cycles.php b/includes/cycles.php
index d8f5247..a839e82 100644
--- a/includes/cycles.php
+++ b/includes/cycles.php
@@ -1984,10 +1984,8 @@ function school_student_certificate_summary(int $centerApplicationId, int $cycle
]);
$rows = $stmt->fetchAll();
- $weightedScore = 0.0;
- $weightedTotal = 0.0;
- $plainPercentageSum = 0.0;
- $plainPercentageCount = 0;
+ $subjectBuckets = [];
+ $completedAssessments = 0;
$scoreTotal = 0.0;
$maxScoreTotal = 0.0;
$latestAssessedOn = '';
@@ -2011,15 +2009,21 @@ function school_student_certificate_summary(int $centerApplicationId, int $cycle
$percentage = round(($score / $maxScore) * 100, 2);
$weight = max(0.0, (float) ($row['weight_percentage'] ?? 0));
- if ($weight > 0) {
- $weightedScore += $percentage * $weight;
- $weightedTotal += $weight;
- }
- $plainPercentageSum += $percentage;
- $plainPercentageCount++;
+ $completedAssessments++;
$scoreTotal += $score;
$maxScoreTotal += $maxScore;
+ $subjectId = (int) ($row['subject_id'] ?? 0);
+ $subjectKey = $subjectId > 0 ? 'subject_' . $subjectId : 'assessment_' . (int) ($row['assessment_type_id'] ?? 0);
+ if (!isset($subjectBuckets[$subjectKey])) {
+ $subjectBuckets[$subjectKey] = [
+ 'score_total' => 0.0,
+ 'max_score_total' => 0.0,
+ ];
+ }
+ $subjectBuckets[$subjectKey]['score_total'] += $score;
+ $subjectBuckets[$subjectKey]['max_score_total'] += $maxScore;
+
$assessedOn = (string) ($row['assessed_on'] ?? '');
if ($assessedOn !== '' && ($latestAssessedOn === '' || strtotime($assessedOn) > strtotime($latestAssessedOn))) {
$latestAssessedOn = $assessedOn;
@@ -2038,16 +2042,26 @@ function school_student_certificate_summary(int $centerApplicationId, int $cycle
];
}
- $overallPercentage = 0.0;
- if ($weightedTotal > 0) {
- $overallPercentage = round($weightedScore / $weightedTotal, 2);
- } elseif ($plainPercentageCount > 0) {
- $overallPercentage = round($plainPercentageSum / $plainPercentageCount, 2);
+ $subjectPercentageSum = 0.0;
+ $subjectCount = 0;
+ foreach ($subjectBuckets as $bucket) {
+ $subjectMaxScore = (float) ($bucket['max_score_total'] ?? 0);
+ if ($subjectMaxScore <= 0) {
+ continue;
+ }
+
+ $subjectPercentage = ((float) ($bucket['score_total'] ?? 0) / $subjectMaxScore) * 100;
+ $subjectPercentageSum += max(0.0, min(100.0, $subjectPercentage));
+ $subjectCount++;
}
- $summary['completed_assessments'] = $plainPercentageCount;
+ $overallPercentage = $subjectCount > 0
+ ? round($subjectPercentageSum / $subjectCount, 2)
+ : 0.0;
+
+ $summary['completed_assessments'] = $completedAssessments;
$summary['missing_assessments'] = max(0, $summary['active_assessments'] - $summary['completed_assessments'] - $summary['absent_assessments'] - $summary['excused_assessments']);
- $summary['has_results'] = $plainPercentageCount > 0;
+ $summary['has_results'] = $completedAssessments > 0;
$summary['overall_percentage'] = $overallPercentage;
$summary['score_total'] = round($scoreTotal, 2);
$summary['max_score_total'] = round($maxScoreTotal, 2);
diff --git a/student_completion_certificates.php b/student_completion_certificates.php
new file mode 100644
index 0000000..1b38061
--- /dev/null
+++ b/student_completion_certificates.php
@@ -0,0 +1,299 @@
+ 'completion',
+ 'title' => 'Successful Completion',
+ 'title_ar' => 'إتمام ناجح',
+ 'completion_line_ar' => 'أتمّ/أتمّت الدورة بنجاح واستفاد/استفادت من محتواها العلمي.',
+ ];
+
+ $template = (string) ($settings['completion_certificate_template'] ?? 'modern');
+ if (!in_array($template, ['modern', 'classic'], true)) {
+ $template = 'modern';
+ }
+
+ $centerName = trim((string) ($application['center_name'] ?? ''));
+ if ($centerName === '') {
+ $centerName = (string) ($settings['app_name'] ?? project_name());
+ }
+
+ $centerLogo = trim((string) ($application['logo'] ?? ''));
+ if ($centerLogo === '') {
+ $centerLogo = trim((string) ($settings['app_logo'] ?? ''));
+ }
+
+ $tagline = trim((string) ($settings['completion_certificate_tagline'] ?? ''));
+ if ($tagline === '') {
+ $tagline = 'شهادة إتمام وتكريم';
+ }
+
+ $overallPercentage = (float) ($certificate['overall_percentage'] ?? 0);
+ $percentageLabel = $hasResults ? completion_certificate_decimal_value($overallPercentage) . '%' : '—';
+ $fallbackMessage = sprintf(
+ 'تشهد إدارة %s بأن الطالب/الطالبة %s قد أتمّ/أتمّت متطلبات %s %s.',
+ $centerName,
+ (string) ($student['full_name'] ?? 'الطالب/الطالبة'),
+ $cycleLabel,
+ $honor['title_ar']
+ );
+
+ $bodyMessage = completion_certificate_message_text(
+ (string) ($settings['completion_certificate_message'] ?? ''),
+ [
+ '{student}' => (string) ($student['full_name'] ?? ''),
+ '{center}' => $centerName,
+ '{cycle}' => $cycleLabel,
+ '{performance}' => (string) ($performance['label_ar'] ?? 'مميز'),
+ '{honor}' => (string) ($honor['title_ar'] ?? 'بنجاح'),
+ '{percentage}' => $hasResults ? completion_certificate_decimal_value($overallPercentage) : '0',
+ ],
+ $fallbackMessage
+ );
+
+ return [
+ 'certificate' => $certificate,
+ 'student' => $student,
+ 'performance' => $performance,
+ 'has_results' => $hasResults,
+ 'honor' => $honor,
+ 'template' => $template,
+ 'center_name' => $centerName,
+ 'center_logo' => $centerLogo,
+ 'tagline' => $tagline,
+ 'percentage_label' => $percentageLabel,
+ 'body_message' => $bodyMessage,
+ 'issue_date_label' => completion_certificate_date_label((string) ($certificate['latest_assessed_on'] ?? '')),
+ ];
+}
+
+$flash = consume_flash();
+$settings = get_app_settings();
+$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
+$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0;
+$autoprint = filter_input(INPUT_GET, 'autoprint', FILTER_VALIDATE_INT) ?: 0;
+$search = clean_text($_GET['search'] ?? '', 255);
+$filters = [
+ 'gender' => clean_text($_GET['gender'] ?? '', 50),
+ 'grade_level' => clean_text($_GET['grade_level'] ?? '', 50),
+ 'enrollment_status' => clean_text($_GET['enrollment_status'] ?? '', 50),
+];
+
+$application = $applicationId > 0 ? get_application($applicationId) : null;
+$isApprovedSchool = $application && (string) $application['status'] === 'approved';
+$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
+$selectedCycle = null;
+$selectedCycleId = 0;
+$cycleLabel = 'الدورة الحالية';
+$certificateItems = [];
+
+if ($application && $isApprovedSchool) {
+ $cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
+ $selectedCycle = $cycleContext['selected'];
+ $selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
+ $cycleLabel = $selectedCycle ? (string) ($selectedCycle['cycle_name'] ?? 'الدورة الحالية') : $cycleLabel;
+
+ if ($selectedCycleId > 0) {
+ $students = list_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search, 0, 0, $filters);
+ foreach ($students as $studentRow) {
+ $viewModel = completion_certificate_view_model($application, $settings, $selectedCycleId, $cycleLabel, (int) ($studentRow['id'] ?? 0));
+ if ($viewModel !== null) {
+ $certificateItems[] = $viewModel;
+ }
+ }
+ }
+}
+
+if (!$application || !$isApprovedSchool || $selectedCycleId <= 0) {
+ http_response_code(404);
+}
+
+$studentsUrl = $application ? school_page_url('students.php', (int) $application['id'], $selectedCycleId) : 'students.php';
+$pageTitle = $application ? 'تنزيل شهادات الإتمام: ' . (string) ($application['center_name'] ?? '') : 'تنزيل شهادات الإتمام';
+$pageDescription = 'صفحة مجمعة لطباعة أو حفظ جميع شهادات الإتمام والتكريم كملف PDF واحد للمركز والدورة المختارة.';
+
+render_page_start($pageTitle, 'approved', $pageDescription);
+render_flash($flash);
+?>
+ يمكن إصدار شهادات الإتمام للمراكز المعتمدة فقط. اختر دورة صحيحة من صفحة الطلاب ثم أعد فتح تنزيل الشهادات. قد تكون الفلاتر الحالية لا تُظهر أي طالب، أو لا توجد سجلات في هذه الدورة بعد. = e((string) $item['center_name']) ?> تُمنح هذه الشهادة إلى = e((string) ($honor['completion_line_ar'] ?? '')) ?>
+
+
+
+
= e((string) $item['tagline']) ?>
+