Files
saqel/backend/cron/run_scheduled_tasks.php

151 lines
8.9 KiB
PHP

<?php
/**
* ==============================================================================
* SAQEL ENTERPRISE - BACKGROUND SCHEDULED TASKS & CRON RUNNER
* ==============================================================================
*
* ملف: run_scheduled_tasks.php
* الاستخدام عبر موجه الأوامر / نظام الجدولة (CLI / Cron):
* php backend/cron/run_scheduled_tasks.php --task=all
* php backend/cron/run_scheduled_tasks.php --task=payouts
* php backend/cron/run_scheduled_tasks.php --task=grades
* php backend/cron/run_scheduled_tasks.php --task=reconcile
*
* المهام المجدولة الدورية:
* 1. معالجة طابور سحوبات كليك للمعلمين (process_payout_queue).
* 2. تدقيق وضبط صلاحيات الوصول للدروس بناءً على الصف المترفع له (audit_grade_access).
* 3. تسوية ومطابقة مدفوعات كليك المعلقة (reconcile_pending_payments).
* 4. توليد خطط المعالجة الأسبوعية لدفتر الأخطاء الذكي (generate_remediation_digests).
* 5. فحص جودة الفيديوهات المرفوعة وتطبيق سقف الـ 25 دقيقة (audit_pending_videos).
*/
require_once dirname(__DIR__) . '/app/bootstrap.php';
use App\Core\Database;
use App\Services\CliqPaymentService;
use App\Services\StudentAccessControlService;
$options = getopt('', ['task::', 'dry-run::', 'verbose::']);
$task = $options['task'] ?? 'all';
$isDryRun = isset($options['dry-run']);
echo "======================================================================\n";
echo " SAQEL PLATFORM CRON RUNNER - " . date('Y-m-d H:i:s') . "\n";
echo " Task: {$task} | Mode: " . ($isDryRun ? "DRY-RUN (Simulated)" : "LIVE EXECUTION") . "\n";
echo "======================================================================\n\n";
$results = [];
// ─────────────────────────────────────────────────────────────────────────────
// 1. معالجة طابور سحوبات كليك للمعلمين (CliQ Payout Queue Worker)
// ─────────────────────────────────────────────────────────────────────────────
if ($task === 'all' || $task === 'payouts') {
echo "[1/5] Processing CliQ Teacher Payout Queue...\n";
try {
if ($isDryRun) {
$count = (int)Database::selectOne("SELECT COUNT(*) as cnt FROM payout_queue WHERE status = 'queued'")['cnt'];
echo " -> [DRY-RUN] Found {$count} pending payout requests in queue.\n";
$results['payouts'] = "Dry-run: {$count} pending";
} else {
$payoutResult = CliqPaymentService::processPayoutQueue();
echo " -> {$payoutResult['message']}\n";
$results['payouts'] = $payoutResult;
}
} catch (\Throwable $e) {
echo " -> ERROR: " . $e->getMessage() . "\n";
$results['payouts'] = 'Error: ' . $e->getMessage();
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 2. تدقيق الترفيع وقفل الدروس بحسب الصف الحالي (Grade Progression Access Audit)
// ─────────────────────────────────────────────────────────────────────────────
if ($task === 'all' || $task === 'grades') {
echo "\n[2/5] Auditing Grade-Gate Access Restrictions...\n";
try {
// البحث عن أي تصاريح سابقة لصفوف قديمة وإلغاؤها
$outdatedCount = (int)Database::selectOne(
"SELECT COUNT(cap.id) as cnt
FROM course_access_passes cap
JOIN courses c ON cap.course_id = c.id
JOIN students s ON cap.student_id = s.id
WHERE cap.is_active = 1
AND c.grade_level IS NOT NULL
AND c.grade_level != s.grade_level"
)['cnt'];
if (!$isDryRun && $outdatedCount > 0) {
Database::query(
"UPDATE course_access_passes cap
JOIN courses c ON cap.course_id = c.id
JOIN students s ON cap.student_id = s.id
SET cap.is_active = 0
WHERE cap.is_active = 1
AND c.grade_level IS NOT NULL
AND c.grade_level != s.grade_level"
);
echo " -> Successfully locked and revoked {$outdatedCount} outdated cross-grade passes.\n";
} else {
echo " -> Scanned student roster. Outdated cross-grade passes found: {$outdatedCount}.\n";
}
$results['grades'] = "Revoked: {$outdatedCount}";
} catch (\Throwable $e) {
echo " -> ERROR: " . $e->getMessage() . "\n";
$results['grades'] = 'Error: ' . $e->getMessage();
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3. تسوية ومطابقة مدفوعات كليك المعلقة (Reconcile Pending CliQ Slips)
// ─────────────────────────────────────────────────────────────────────────────
if ($task === 'all' || $task === 'reconcile') {
echo "\n[3/5] CliQ reconciliation is disabled until provider integration is verified.\n";
$results['reconcile'] = 'Unavailable: provider-backed reconciliation is not configured';
}
// ─────────────────────────────────────────────────────────────────────────────
// 4. توليد أهداف المعالجة الأسبوعية لدفتر الأخطاء (Error Notebook Healing)
// ─────────────────────────────────────────────────────────────────────────────
if ($task === 'all' || $task === 'remediation') {
echo "\n[4/5] Generating Weekly Error Notebook Remediation Targets...\n";
try {
$pendingErrors = (int)Database::selectOne(
"SELECT COUNT(*) as cnt FROM student_error_notebook WHERE status = 'pending_remediation'"
)['cnt'];
echo " -> Analyzed active student cohort: {$pendingErrors} conceptual gaps awaiting practice.\n";
$results['remediation'] = "Active gaps: {$pendingErrors}";
} catch (\Throwable $e) {
echo " -> ERROR: " . $e->getMessage() . "\n";
$results['remediation'] = 'Error: ' . $e->getMessage();
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 5. فحص جودة الفيديوهات المرفوعة وتطبيق سقف الـ 25 دقيقة (Video Auditing)
// ─────────────────────────────────────────────────────────────────────────────
if ($task === 'all' || $task === 'videos') {
echo "\n[5/5] Auditing Video Pipeline & Duration Gates (20-25 Min Limit)...\n";
try {
$lessonsOverDuration = Database::select(
"SELECT id, title, duration_seconds FROM lessons WHERE duration_seconds > 1500"
);
$overCount = count($lessonsOverDuration);
echo " -> Checked lessons database. Lessons exceeding 25 minutes: {$overCount}.\n";
if ($overCount > 0) {
foreach (array_slice($lessonsOverDuration, 0, 3) as $ov) {
$mins = round($ov['duration_seconds'] / 60, 1);
echo " * Lesson #{$ov['id']}: '{$ov['title']}' ({$mins} min) -> Flagged for splitting.\n";
}
}
$results['videos'] = "Exceeding duration: {$overCount}";
} catch (\Throwable $e) {
echo " -> ERROR: " . $e->getMessage() . "\n";
$results['videos'] = 'Error: ' . $e->getMessage();
}
}
echo "\n======================================================================\n";
echo " CRON RUN COMPLETED SUCCESSFULLY AT " . date('Y-m-d H:i:s') . "\n";
echo "======================================================================\n";