src/Controller/ClassRoomController.php line 1615

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\Routing\Annotation\Route;
  7. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  8. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  9. use Symfony\Component\HttpFoundation\StreamedResponse;
  10. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
  11. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  12. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  13. use Knp\Snappy\Pdf;
  14. use Knp\Bundle\SnappyBundle\Snappy\Response\PdfResponse;
  15. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  16. use App\Repository\AttributionRepository;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use App\Repository\ClassRoomRepository;
  19. use App\Repository\SchoolYearRepository;
  20. use App\Repository\QuaterRepository;
  21. use App\Repository\SequenceRepository;
  22. use App\Repository\EvaluationRepository;
  23. use App\Repository\StudentRepository;
  24. use App\Repository\MainTeacherRepository;
  25. use App\Repository\MarkRepository;
  26. use App\Entity\ClassRoom;
  27. use App\Entity\Course;
  28. use App\Repository\LevelRepository;
  29. use App\Entity\SchoolYear;
  30. use App\Form\ClassRoomType;
  31. use App\Entity\Sequence;
  32. use App\Entity\Quater;
  33. use App\Repository\SubscriptionRepository;
  34. use App\Repository\InstallmentRepository;
  35. use App\Service\SchoolYearService;
  36. /**
  37. * ClassRoom controller.
  38. *
  39. * @Route("prof/rooms")
  40. */
  41. class ClassRoomController extends AbstractController
  42. {
  43. private $em;
  44. private $repo;
  45. private $scRepo;
  46. private $stdRepo;
  47. private $subRepo;
  48. private $seqRepo;
  49. private $evalRepo;
  50. private $qtRepo;
  51. private $markRepo;
  52. private $snappy;
  53. private $session;
  54. private $quaterData;
  55. private $annualMark;
  56. private $annualAbs;
  57. private $annualRanks;
  58. private $imagesExists;
  59. private Pdf $pdf;
  60. private SchoolYearService $schoolYearService;
  61. private MainTeacherRepository $mainTeacherRepo;
  62. private AttributionRepository $attRepo;
  63. private InstallmentRepository $instRepo;
  64. private $annualAvgArray = [];
  65. private $sumAvg = 0;
  66. public function __construct(
  67. Pdf $pdf,
  68. InstallmentRepository $instRepo,
  69. AttributionRepository $attRepo,
  70. MainTeacherRepository $mainTeacherRepo,
  71. SchoolYearService $schoolYearService,
  72. MarkRepository $markRepo,
  73. QuaterRepository $qtRepo,
  74. StudentRepository $stdRepo,
  75. EvaluationRepository $evalRepo,
  76. SchoolYearRepository $scRepo,
  77. SequenceRepository $seqRepo,
  78. ClassRoomRepository $repo,
  79. SubscriptionRepository $subRepo,
  80. EntityManagerInterface $em,
  81. Pdf $snappy,
  82. SessionInterface $session
  83. ) {
  84. $this->annualMark = [];
  85. $this->annualAbs = [];
  86. $this->annualRanks = [];
  87. $this->quaterData = [];
  88. $this->em = $em;
  89. $this->pdf = $pdf;
  90. $this->repo = $repo;
  91. $this->scRepo = $scRepo;
  92. $this->attRepo = $attRepo;
  93. $this->seqRepo = $seqRepo;
  94. $this->evalRepo = $evalRepo;
  95. $this->mainTeacherRepo = $mainTeacherRepo;
  96. $this->stdRepo = $stdRepo;
  97. $this->instRepo = $instRepo;
  98. $this->qtRepo = $qtRepo;
  99. $this->subRepo = $subRepo;
  100. $this->markRepo = $markRepo;
  101. $this->snappy = $snappy;
  102. $this->session = $session;
  103. $this->schoolYearService = $schoolYearService;
  104. }
  105. /**
  106. * Lists all ClassRoom entities.
  107. *
  108. * @Route("/", name="admin_classrooms")
  109. * @Method("GET")
  110. * @Template()
  111. */
  112. public function indexAction(LevelRepository $levelRepository)
  113. {
  114. $classrooms = $this->repo->findAll();
  115. $year = $this->schoolYearService->sessionYearById();
  116. $seq = $this->seqRepo->findOneBy(['activated' => true]);
  117. $mainTeachers = $this->mainTeacherRepo->findBy(['schoolYear' => $year]);
  118. $mainTeachersMap = [];
  119. foreach ($mainTeachers as $mt) {
  120. $mainTeachersMap[$mt->getClassRoom()->getId()] = $mt->getTeacher();
  121. }
  122. $levels = $levelRepository->findAll();
  123. return $this->render('classroom/index.html.twig', [
  124. 'mainTeachers' => $mainTeachersMap,
  125. 'classrooms' => $classrooms,
  126. 'levels' => $levels,
  127. 'year' => $year,
  128. 'seq' => $seq ? $seq->getId() : 0,
  129. ]);
  130. }
  131. /**
  132. * Existence de fichiers image des élèves
  133. */
  134. private function fileExists(ClassRoom $classroom, SchoolYear $year): array
  135. {
  136. $imageExists = [];
  137. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  138. $baseDir = $this->getParameter('kernel.project_dir') . '/public/assets/images/student/';
  139. foreach ($studentEnrolled as $std) {
  140. $matricule = $std->getMatricule();
  141. $found = false;
  142. foreach (['jpg', 'jpeg', 'PNG'] as $ext) {
  143. if (file_exists($baseDir . $matricule . '.' . $ext)) {
  144. $found = true;
  145. break;
  146. }
  147. }
  148. $imageExists[$std->getId()] = $found;
  149. }
  150. return $imageExists;
  151. }
  152. /**
  153. * Calcule le facteur de zoom CSS à appliquer sur chaque bulletin.
  154. */
  155. private function computeScale(ClassRoom $room): float
  156. {
  157. $nbCourses = 0;
  158. foreach ($room->getModules() as $module) {
  159. $nbCourses += count($module->getCourses());
  160. }
  161. return match (true) {
  162. $nbCourses <= 12 => 0.95,
  163. $nbCourses <= 16 => 0.90,
  164. $nbCourses <= 20 => 0.85,
  165. default => 0.80,
  166. };
  167. }
  168. /**
  169. * Finds and displays a ClassRoom entity.
  170. *
  171. * @Route("/{id}/show", name="admin_classrooms_show", requirements={"id"="\d+"})
  172. * @Method("GET")
  173. * @Template()
  174. */
  175. public function showAction(ClassRoom $classroom, StudentRepository $stdRepo): Response
  176. {
  177. $year = $this->schoolYearService->sessionYearById();
  178. $attributions = $this->attRepo->findByYearAndByRoom($year, $classroom);
  179. $attributionsMapCourses = [];
  180. foreach ($attributions as $att) {
  181. $attributionsMapCourses[$att->getCourse()->getId()] = $att;
  182. }
  183. $officialExamResults = $this->subRepo->countByMention($year, $classroom);
  184. $mentionCategories = [];
  185. $mentionCountCategories = [];
  186. $mentionMap = [
  187. '0' => 'ECHEC', '1p' => 'PASSABLE', '1a' => 'ASSEZ-BIEN',
  188. '1b' => 'BIEN', '1t' => 'TRES-BIEN', '1e' => 'EXCELLENT',
  189. 'A' => '5 POINTS', 'B' => '4 POINTS', 'C' => '3 POINTS',
  190. 'D' => '2 POINTS', 'E' => '1 POINT',
  191. ];
  192. foreach ($officialExamResults as $exam) {
  193. $mentionCategories[] = $mentionMap[$exam['officialExamResult']] ?? $exam['officialExamResult'];
  194. $mentionCountCategories[] = $exam['count'];
  195. }
  196. $seqs = $this->seqRepo->findSequenceThisYear($year);
  197. $evalSeqs = [];
  198. foreach ($seqs as $seq) {
  199. $evalSeqs[$seq->getId()] = $this->evalRepo->findBy([
  200. 'classRoom' => $classroom,
  201. 'sequence' => $seq,
  202. ]);
  203. }
  204. $allCoursesMap = [];
  205. foreach ($evalSeqs as $seqEvals) {
  206. foreach ($seqEvals as $eval) {
  207. $cid = $eval->getCourse()->getId();
  208. if (!isset($allCoursesMap[$cid])) {
  209. $allCoursesMap[$cid] = $eval->getCourse()->getWording();
  210. }
  211. }
  212. }
  213. asort($allCoursesMap);
  214. $allCourseIds = array_keys($allCoursesMap);
  215. $allCourseLabels = array_values($allCoursesMap);
  216. $matrix = [];
  217. foreach ($allCourseIds as $cid) {
  218. foreach ($seqs as $seq) {
  219. $matrix[$cid][$seq->getId()] = null;
  220. }
  221. }
  222. foreach ($evalSeqs as $seqId => $seqEvals) {
  223. foreach ($seqEvals as $eval) {
  224. $cid = $eval->getCourse()->getId();
  225. $matrix[$cid][$seqId] = [
  226. 'avg' => $eval->getMoyenne() > 0 ? round($eval->getMoyenne(), 2) : null,
  227. 'success' => $eval->getSuccessH() + $eval->getSuccessF(),
  228. 'failures' => $eval->getFailluresH() + $eval->getFailluresF(),
  229. 'absents' => $eval->getAbscent(),
  230. ];
  231. }
  232. }
  233. $heatmapData = [];
  234. foreach ($allCourseIds as $cid) {
  235. $row = ['course' => $allCoursesMap[$cid]];
  236. foreach ($seqs as $seq) {
  237. $sid = $seq->getId();
  238. $row['s'.$sid] = $matrix[$cid][$sid] ? $matrix[$cid][$sid]['avg'] : null;
  239. }
  240. $heatmapData[] = $row;
  241. }
  242. $seqLabels = array_map(fn($s) => $s->getWording(), $seqs);
  243. $seqIds = array_map(fn($s) => $s->getId(), $seqs);
  244. $generalAvgBySeq = [];
  245. foreach ($seqs as $seq) {
  246. $sumWeighted = 0.0;
  247. $sumCoef = 0;
  248. $hasData = false;
  249. foreach ($evalSeqs[$seq->getId()] as $eval) {
  250. if ($eval->getMoyenne() > 0) {
  251. $coef = $eval->getCourse()->getCoefficient();
  252. $sumWeighted += $eval->getMoyenne() * $coef;
  253. $sumCoef += $coef;
  254. $hasData = true;
  255. }
  256. }
  257. $generalAvgBySeq[] = $hasData && $sumCoef > 0
  258. ? round($sumWeighted / $sumCoef, 2)
  259. : null;
  260. }
  261. $activeSeq = $this->seqRepo->findOneBy(['activated' => true]);
  262. $successFailData = ['labels' => [], 'success' => [], 'failures' => [], 'absents' => []];
  263. if ($activeSeq && isset($evalSeqs[$activeSeq->getId()])) {
  264. foreach ($evalSeqs[$activeSeq->getId()] as $eval) {
  265. if ($eval->getMoyenne() > 0) {
  266. $successFailData['labels'][] = $eval->getCourse()->getWording();
  267. $successFailData['success'][] = $eval->getSuccessH() + $eval->getSuccessF();
  268. $successFailData['failures'][] = $eval->getFailluresH() + $eval->getFailluresF();
  269. $successFailData['absents'][] = $eval->getAbscent();
  270. }
  271. }
  272. }
  273. $quaterProfiles = [];
  274. $trimNames = [];
  275. $seqsByQuater = [];
  276. foreach ($seqs as $seq) {
  277. $qid = $seq->getQuater()->getId();
  278. $seqsByQuater[$qid][] = $seq;
  279. $trimNames[$qid] = $seq->getQuater()->getWording();
  280. }
  281. foreach ($seqsByQuater as $qid => $qSeqs) {
  282. $profile = [];
  283. foreach ($allCourseIds as $cid) {
  284. $sum = 0.0; $n = 0;
  285. foreach ($qSeqs as $seq) {
  286. $cell = $matrix[$cid][$seq->getId()] ?? null;
  287. if ($cell && $cell['avg'] !== null) {
  288. $sum += $cell['avg'];
  289. $n++;
  290. }
  291. }
  292. $profile[] = $n > 0 ? round($sum / $n, 2) : null;
  293. }
  294. $quaterProfiles[] = [
  295. 'label' => $trimNames[$qid],
  296. 'data' => $profile,
  297. ];
  298. }
  299. return $this->render('classroom/show.html.twig', [
  300. 'mainteacher' => $this->_getMainTeacher($classroom, $year),
  301. 'classroom' => $classroom,
  302. 'attributions' => $attributionsMapCourses,
  303. 'modules' => $classroom->getModules(),
  304. 'studentEnrolled' => $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year),
  305. 'fileExists' => $this->fileExists($classroom, $year),
  306. 'mentionCategories' => json_encode($mentionCategories),
  307. 'mentionCountCategories' => json_encode($mentionCountCategories),
  308. 'courseLabels' => json_encode($allCourseLabels),
  309. 'seqLabels' => json_encode($seqLabels),
  310. 'seqIds' => json_encode($seqIds),
  311. 'heatmapData' => json_encode($heatmapData),
  312. 'generalAvgBySeq' => json_encode($generalAvgBySeq),
  313. 'successFailData' => json_encode($successFailData),
  314. 'quaterProfiles' => json_encode($quaterProfiles),
  315. 'activeSeqLabel' => $activeSeq ? $activeSeq->getWording() : '',
  316. ]);
  317. }
  318. private function _getMainTeacher(ClassRoom $classroom, $year): ?object
  319. {
  320. foreach ($classroom->getMainTeachers() as $mainT) {
  321. if ($mainT->getSchoolYear()->getId() === $year->getId()) {
  322. return $mainT->getTeacher();
  323. }
  324. }
  325. return null;
  326. }
  327. /**
  328. * @Route("/{id}/stat", name="admin_classrooms_stat", requirements={"id"="\d+"})
  329. * @Method("GET")
  330. * @Template()
  331. */
  332. public function statAction(ClassRoom $classroom)
  333. {
  334. return $this->render('classroom/show.html.twig', []);
  335. }
  336. /**
  337. * @Route("/{id}/reportCardsYear", name="admin_classrooms_reportcards_year", requirements={"id"="\d+"})
  338. * @Method("GET")
  339. * @Template()
  340. */
  341. public function reportCardsYearAction(ClassRoom $classroom)
  342. {
  343. set_time_limit(600);
  344. $connection = $this->em->getConnection();
  345. $year = $this->schoolYearService->sessionYearById();
  346. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  347. // Création des vues séquentielles via prepare/bindValue/execute (API legacy DBAL 3.x compatible)
  348. $seqDefs = [
  349. 1 => "eval.sequence_id = 1",
  350. 2 => "eval.sequence_id = 2",
  351. 3 => "eval.sequence_id = 3",
  352. 4 => "eval.sequence_id = 4",
  353. 5 => "eval.sequence_id = 5",
  354. 6 => "eval.sequence_id = 6",
  355. ];
  356. // SEQ1 : vue complète avec toutes les infos élève
  357. $stmt = $connection->prepare(
  358. "CREATE OR REPLACE VIEW V_STUDENT_MARK_DATA_SEQ1 AS
  359. SELECT DISTINCT eval.id as eval, crs.id as crs, room.id as room, year.id as year,
  360. std.matricule as matricule, std.image_name as profileImagePath,
  361. std.lastname as lastname, std.firstname as firstname, std.birthday as birthday,
  362. std.gender as gender, std.birthplace as birthplace,
  363. teach.full_name as teacher, modu.name as module, crs.wording as wording,
  364. crs.coefficient as coefficient, m.value as valeur, m.weight as weight,
  365. m.appreciation as appreciation
  366. FROM mark m
  367. JOIN student std ON m.student_id = std.id
  368. JOIN evaluation eval ON m.evaluation_id = eval.id
  369. JOIN class_room room ON eval.class_room_id = room.id
  370. JOIN course crs ON eval.course_id = crs.id
  371. JOIN attribution att ON att.course_id = crs.id
  372. JOIN user teach ON att.teacher_id = teach.id
  373. JOIN module modu ON modu.id = crs.module_id
  374. JOIN sequence seq ON seq.id = eval.sequence_id
  375. JOIN quater quat ON seq.quater_id = quat.id
  376. JOIN school_year year ON quat.school_year_id = year.id
  377. WHERE room.id = ? AND eval.sequence_id = 1"
  378. );
  379. $stmt->bindValue(1, $classroom->getId());
  380. $stmt->execute();
  381. // SEQ2-6 : vues allégées
  382. foreach ([2, 3, 4, 5, 6] as $i) {
  383. $stmt = $connection->prepare(
  384. "CREATE OR REPLACE VIEW V_STUDENT_MARK_DATA_SEQ{$i} AS
  385. SELECT DISTINCT crs.id as crs, eval.id as eval, std.matricule as matricule,
  386. m.value as valeur, m.weight as weight, m.appreciation as appreciation
  387. FROM mark m
  388. JOIN student std ON m.student_id = std.id
  389. JOIN evaluation eval ON m.evaluation_id = eval.id
  390. JOIN course crs ON eval.course_id = crs.id
  391. WHERE eval.class_room_id = ? AND eval.sequence_id = {$i}
  392. ORDER BY matricule, eval"
  393. );
  394. $stmt->bindValue(1, $classroom->getId());
  395. $stmt->execute();
  396. }
  397. // BUG FIX : fetchAll() déprécié → fetchAllAssociative()
  398. $dataYear = $connection->executeQuery(
  399. "SELECT * FROM V_STUDENT_MARK_DATA_SEQ1
  400. INNER JOIN V_STUDENT_MARK_DATA_SEQ2 ON V_STUDENT_MARK_DATA_SEQ1.matricule = V_STUDENT_MARK_DATA_SEQ2.matricule
  401. INNER JOIN V_STUDENT_MARK_DATA_SEQ3 ON V_STUDENT_MARK_DATA_SEQ2.matricule = V_STUDENT_MARK_DATA_SEQ3.matricule
  402. INNER JOIN V_STUDENT_MARK_DATA_SEQ4 ON V_STUDENT_MARK_DATA_SEQ3.matricule = V_STUDENT_MARK_DATA_SEQ4.matricule
  403. INNER JOIN V_STUDENT_MARK_DATA_SEQ5 ON V_STUDENT_MARK_DATA_SEQ4.matricule = V_STUDENT_MARK_DATA_SEQ5.matricule
  404. INNER JOIN V_STUDENT_MARK_DATA_SEQ6 ON V_STUDENT_MARK_DATA_SEQ5.matricule = V_STUDENT_MARK_DATA_SEQ6.matricule"
  405. )->fetchAllAssociative();
  406. $this->snappy->setTimeout(600);
  407. $html = $this->renderView('classroom/reportcard/annual.html.twig', [
  408. 'year' => $year,
  409. 'data' => $dataYear,
  410. 'room' => $classroom,
  411. 'students' => $studentEnrolled,
  412. ]);
  413. return new Response($this->snappy->getOutputFromHtml($html), 200, [
  414. 'Content-Type' => 'application/pdf',
  415. 'Content-Disposition' => 'attachment; filename="BUL_ANN_' . $classroom->getName() . '.pdf"',
  416. ]);
  417. }
  418. /**
  419. * BUG FIX : $classroom et $seq n'étaient pas des paramètres de la méthode.
  420. * Renommée en viewSeqForRoom pour éviter la confusion avec getViewSeqData.
  421. */
  422. public function viewSeqForRoom(ClassRoom $classroom, Sequence $seq, int $i): void
  423. {
  424. $year = $this->schoolYearService->sessionYearById();
  425. $connection = $this->em->getConnection();
  426. $stmt = $connection->prepare(
  427. "CREATE OR REPLACE VIEW V_STUDENT_MARK_SEQ{$i} AS
  428. SELECT DISTINCT eval.id as eval, crs.id as crs, room.id as room, year.id as year,
  429. std.id as std, teach.full_name as teacher, modu.id as module,
  430. m.value as value, m.weight as weight
  431. FROM mark m
  432. JOIN student std ON m.student_id = std.id
  433. JOIN evaluation eval ON m.evaluation_id = eval.id
  434. JOIN class_room room ON eval.class_room_id = room.id
  435. JOIN course crs ON eval.course_id = crs.id
  436. JOIN attribution att ON att.course_id = crs.id
  437. JOIN user teach ON att.teacher_id = teach.id
  438. JOIN module modu ON modu.id = crs.module_id
  439. JOIN sequence seq ON seq.id = eval.sequence_id
  440. JOIN quater quat ON seq.quater_id = quat.id
  441. JOIN school_year year ON quat.school_year_id = year.id
  442. WHERE att.year_id = ? AND room.id = ? AND eval.sequence_id = ?
  443. ORDER BY room.id, modu.id, std"
  444. );
  445. $stmt->bindValue(1, $year->getId());
  446. $stmt->bindValue(2, $classroom->getId());
  447. $stmt->bindValue(3, $seq->getId());
  448. $stmt->execute();
  449. }
  450. /**
  451. * @Route("/{id}/reportCardsApcYearapc", name="admin_class_reportcards_apc_year", requirements={"id"="\d+"})
  452. * @Method("GET")
  453. * @Template()
  454. */
  455. public function reportCards2YearAction(ClassRoom $classroom)
  456. {
  457. set_time_limit(600);
  458. $connection = $this->em->getConnection();
  459. $year = $this->schoolYearService->sessionYearById();
  460. $sequences = $this->seqRepo->findSequenceThisYear($year);
  461. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  462. foreach ($sequences as $seq) {
  463. $this->getViewSeqData($classroom, $seq);
  464. }
  465. $stmt = $connection->prepare(
  466. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_1 AS
  467. SELECT DISTINCT seq1.std as std, seq1.crs as crs,
  468. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  469. greatest(seq1.weight, seq2.weight) as weight,
  470. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  471. FROM V_STUDENT_MARK_SEQ1 seq1
  472. LEFT JOIN V_STUDENT_MARK_SEQ2 seq2 ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  473. ORDER BY seq1.std"
  474. );
  475. $stmt->execute();
  476. $stmt = $connection->prepare(
  477. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_2 AS
  478. SELECT DISTINCT seq1.std as std, seq1.crs as crs,
  479. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  480. greatest(seq1.weight, seq2.weight) as weight,
  481. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  482. FROM V_STUDENT_MARK_SEQ3 seq1
  483. LEFT JOIN V_STUDENT_MARK_SEQ4 seq2 ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  484. ORDER BY seq1.std"
  485. );
  486. $stmt->execute();
  487. $stmt = $connection->prepare(
  488. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_3 AS
  489. SELECT DISTINCT seq1.std as std, seq1.crs as crs,
  490. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  491. greatest(seq1.weight, seq2.weight) as weight,
  492. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  493. FROM V_STUDENT_MARK_SEQ5 seq1
  494. LEFT JOIN V_STUDENT_MARK_SEQ6 seq2 ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  495. ORDER BY seq1.std"
  496. );
  497. $stmt->execute();
  498. // BUG FIX : "JOIN LEFT" → "LEFT JOIN"
  499. $stmt = $connection->prepare(
  500. "CREATE OR REPLACE VIEW ANNUAL_DATA AS
  501. SELECT DISTINCT student.id as idStd, student.matricule as matricule,
  502. student.image_name as profileImagePath,
  503. student.lastname as lastname, student.firstname as firstname,
  504. student.birthday as birthday, student.gender as gender,
  505. student.birthplace as birthplace,
  506. class_room.name as room_name,
  507. course.wording as course, course.coefficient as coef,
  508. module.name as module, user.full_name as teacher,
  509. quat1.std, quat1.modu,
  510. quat1.value as value1, quat1.weight as weight1,
  511. quat2.value as value2, quat2.weight as weight2,
  512. quat3.value as value3, quat3.weight as weight3,
  513. greatest(quat1.weight, quat2.weight, quat3.weight) as weight,
  514. (quat1.value*quat1.weight + quat2.value*quat2.weight + quat3.value*quat3.weight)
  515. / (quat1.weight+quat2.weight+quat3.weight) as value
  516. FROM student
  517. JOIN V_STUDENT_MARK_QUATER_1 quat1 ON student.id = quat1.std
  518. JOIN class_room ON class_room.id = quat1.room
  519. JOIN course ON course.id = quat1.crs
  520. JOIN module ON course.module_id = quat1.modu
  521. JOIN user ON user.full_name = quat1.teacher
  522. LEFT JOIN V_STUDENT_MARK_QUATER_2 quat2 ON quat1.std = quat2.std AND quat1.crs = quat2.crs
  523. LEFT JOIN V_STUDENT_MARK_QUATER_3 quat3 ON quat1.std = quat3.std AND quat1.crs = quat3.crs
  524. ORDER BY quat1.std, quat1.modu"
  525. );
  526. $stmt->execute();
  527. // BUG FIX : fetchAll() → fetchAllAssociative()
  528. $dataYear = $connection->executeQuery("SELECT * FROM ANNUAL_DATA")->fetchAllAssociative();
  529. $stmt = $connection->prepare(
  530. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  531. SELECT DISTINCT idStd,
  532. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  533. SUM(weight*coef) as totalCoef
  534. FROM ANNUAL_DATA
  535. GROUP BY idStd
  536. ORDER BY SUM(value*weight*coef) DESC"
  537. );
  538. $stmt->execute();
  539. $annualAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  540. $rank = 0;
  541. $rankArray = [];
  542. foreach ($annualAvg as $avg) {
  543. $this->annualAvgArray[$avg['idStd']] = $avg['moyenne'];
  544. $rankArray[$avg['idStd']] = ++$rank;
  545. $this->sumAvg += $avg['moyenne'];
  546. }
  547. $this->snappy->setTimeout(600);
  548. $html = $this->renderView('classroom/reportcardYear.html.twig', [
  549. 'year' => $year,
  550. 'data' => $dataYear,
  551. 'room' => $classroom,
  552. 'students' => $studentEnrolled,
  553. 'ranks' => $rankArray,
  554. 'means' => $this->annualAvgArray,
  555. 'genMean' => $this->sumAvg / sizeof($this->annualAvgArray),
  556. ]);
  557. return new Response($this->snappy->getOutputFromHtml($html), 200, [
  558. 'Content-Type' => 'application/pdf',
  559. 'Content-Disposition' => 'attachment; filename="BUL_ANN_' . $classroom->getName() . '.pdf"',
  560. ]);
  561. }
  562. /**
  563. * @Route("/{room}/{seq}/pdf", name="admin_classrooms_recapitulatif", requirements={"room"="\d+","seq"="\d+"})
  564. * @Method("GET")
  565. * @Template()
  566. */
  567. public function recapitulatifAction(ClassRoom $room, Sequence $seq)
  568. {
  569. $year = $this->schoolYearService->sessionYearById();
  570. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  571. $html = $this->renderView('classroom/templating/recapitulatifseqvierge.html.twig', [
  572. 'room' => $room,
  573. 'seq' => $seq,
  574. 'students' => $studentEnrolled,
  575. 'year' => $year,
  576. ]);
  577. return new Response(
  578. $this->pdf->getOutputFromHtml($html, ['orientation' => 'Landscape', 'page-size' => 'A4']),
  579. 200,
  580. [
  581. 'Content-Type' => 'application/pdf',
  582. 'Content-Disposition' => 'inline; filename="fiche_recep_' . $room->getName() . '.pdf"',
  583. ]
  584. );
  585. }
  586. /**
  587. * @Route("/{id}/recapitulatifseq", name="admin_classrooms_recapitulatif_seq", requirements={"id"="\d+"})
  588. * @Method("GET")
  589. * @Template()
  590. */
  591. public function recapSeqAction(ClassRoom $room, Request $request)
  592. {
  593. $checkedValues = $request->request->get('selected_courses');
  594. $year = $this->schoolYearService->sessionYearById();
  595. $seq = $this->seqRepo->findOneBy(['activated' => true]);
  596. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  597. $datas = $this->markRepo->findMarksBySequenceAndClassOrderByStd($seq, $room);
  598. $html = $this->renderView('classroom/recapitulatifseq.html.twig', [
  599. 'room' => $room,
  600. 'datas' => $datas,
  601. 'year' => $year,
  602. 'seq' => $seq,
  603. 'students' => $studentEnrolled,
  604. 'checkedValues' => $checkedValues,
  605. ]);
  606. return new Response($html);
  607. }
  608. /**
  609. * @Route("/create", name="admin_classrooms_new", methods={"GET","POST"})
  610. */
  611. public function create(Request $request): Response
  612. {
  613. if (!$this->getUser()) {
  614. $this->addFlash('warning', 'You need login first!');
  615. return $this->redirectToRoute('app_login');
  616. }
  617. $schoolyear = new ClassRoom();
  618. $form = $this->createForm(ClassRoomType::class, $schoolyear);
  619. $form->handleRequest($request);
  620. if ($form->isSubmitted() && $form->isValid()) {
  621. $this->em->persist($schoolyear);
  622. $this->em->flush();
  623. $this->addFlash('success', 'ClassRoom successfully created');
  624. return $this->redirectToRoute('admin_classrooms');
  625. }
  626. return $this->render('classroom/new.html.twig', ['form' => $form->createView()]);
  627. }
  628. /**
  629. * @Route("/{id}/edit", name="admin_classrooms_edit", requirements={"id"="\d+"}, methods={"GET","PUT"})
  630. * @Template()
  631. */
  632. public function edit(Request $request, ClassRoom $room): Response
  633. {
  634. $form = $this->createForm(ClassRoomType::class, $room, ['method' => 'PUT']);
  635. $form->handleRequest($request);
  636. if ($form->isSubmitted() && $form->isValid()) {
  637. $this->em->flush();
  638. $this->addFlash('success', 'ClassRoom successfully updated');
  639. return $this->redirectToRoute('admin_classrooms');
  640. }
  641. return $this->render('classroom/edit.html.twig', [
  642. 'room' => $room,
  643. 'form' => $form->createView(),
  644. ]);
  645. }
  646. /**
  647. * @Route("/{id}/delete", name="admin_classrooms_delete", requirements={"id"="\d+"}, methods={"DELETE"})
  648. */
  649. public function delete(ClassRoom $q, Request $request): Response
  650. {
  651. $this->em->remove($q);
  652. $this->em->flush();
  653. $this->addFlash('info', 'ClassRoom successfully deleted');
  654. return $this->redirectToRoute('admin_classrooms');
  655. }
  656. /**
  657. * @Route("/{id}/fichesimple", name="admin_classrooms_fichesimple", requirements={"id"="\d+"})
  658. * @Method("GET")
  659. * @Template()
  660. */
  661. public function fichesiplmeAction(ClassRoom $classroom)
  662. {
  663. $year = $this->schoolYearService->sessionYearById();
  664. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  665. $html = $this->renderView('classroom/templating/fiche_repport_notes.html.twig', [
  666. 'year' => $year,
  667. 'room' => $classroom,
  668. 'students' => $studentEnrolled,
  669. ]);
  670. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  671. 'Content-Type' => 'application/pdf',
  672. 'Content-Disposition' => 'inline; filename="fiche_pv_' . $classroom->getName() . '.pdf"',
  673. ]);
  674. }
  675. /**
  676. * @Route("/{id}/disciplinary_record", name="admin_classrooms_disciplinary_record", requirements={"id"="\d+"})
  677. * @Method("GET")
  678. * @Template()
  679. */
  680. public function ficheDisciplineAction(ClassRoom $classroom)
  681. {
  682. $year = $this->schoolYearService->sessionYearById();
  683. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  684. $html = $this->renderView('classroom/templating/fiche_repport_disc.html.twig', [
  685. 'year' => $year,
  686. 'room' => $classroom,
  687. 'students' => $studentEnrolled,
  688. ]);
  689. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  690. 'Content-Type' => 'application/pdf',
  691. 'Content-Disposition' => 'inline; filename="fich_disc_' . $classroom->getName() . '.pdf"',
  692. ]);
  693. }
  694. /**
  695. * @Route("/{id}/presentation", name="admin_classrooms_presentation", requirements={"id"="\d+"})
  696. * @Method("GET")
  697. * @Template()
  698. */
  699. public function presentationAction(ClassRoom $classroom)
  700. {
  701. $year = $this->schoolYearService->sessionYearById();
  702. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  703. $html = $this->renderView('classroom/templating/student_list.html.twig', [
  704. 'year' => $year,
  705. 'room' => $classroom,
  706. 'students' => $studentEnrolled,
  707. ]);
  708. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  709. 'Content-Type' => 'application/pdf',
  710. 'Content-Disposition' => 'inline; filename="std_list_' . $classroom->getName() . '.pdf"',
  711. ]);
  712. }
  713. /**
  714. * @Route("/{id_room}/{id_seq}/sqavg", name="admin_classrooms_avg_seq", requirements={"id_room"="\d+", "id_seq"="\d+"})
  715. * @ParamConverter("room", options={"mapping": {"id_room" : "id"}})
  716. * @ParamConverter("seq", options={"mapping": {"id_seq" : "id"}})
  717. * @Method("GET")
  718. * @Template()
  719. */
  720. public function generalSeqAverage(ClassRoom $room, Sequence $seq)
  721. {
  722. $dql = "SELECT SUM(evaluation.moyenne * course.coefficient)/SUM(course.coefficient)
  723. FROM App\Entity\Evaluation evaluation, App\Entity\Course course
  724. WHERE evaluation.course = course.id
  725. AND evaluation.sequence = ?2
  726. AND evaluation.classRoom = ?1";
  727. $avg = $this->em->createQuery($dql)
  728. ->setParameter(1, $room->getId())
  729. ->setParameter(2, $seq->getId())
  730. ->getSingleScalarResult();
  731. return round($avg, 2);
  732. }
  733. /**
  734. * @Route("/{id_room}/{id_quat}/qtavg", name="admin_classrooms_avg_quat", requirements={"id_room"="\d+", "id_quat"="\d+"})
  735. * @ParamConverter("room", options={"mapping": {"id_room" : "id"}})
  736. * @ParamConverter("quater", options={"mapping": {"id_quat" : "id"}})
  737. * @Method("GET")
  738. * @Template()
  739. */
  740. public function generalQuatAverage(ClassRoom $room, Quater $quater)
  741. {
  742. $dql = "SELECT SUM(evaluation.moyenne * course.coefficient)/SUM(course.coefficient)
  743. FROM App\Entity\Evaluation evaluation, App\Entity\Course course
  744. WHERE evaluation.course = course.id
  745. AND evaluation.sequence = ?2
  746. AND evaluation.classRoom = ?1";
  747. $avg_seq = 0;
  748. foreach ($quater->getSequences() as $seq) {
  749. $avg_seq += $this->em->createQuery($dql)
  750. ->setParameter(1, $room->getId())
  751. ->setParameter(2, $seq->getId())
  752. ->getSingleScalarResult();
  753. }
  754. return round($avg_seq / 2, 2);
  755. }
  756. /**
  757. * @Route("/{room}/pdf", name="admin_classrooms_blanc_ann", requirements={"room"="\d+"})
  758. * @Method("GET")
  759. * @Template()
  760. */
  761. public function annualSummaryAction(ClassRoom $room)
  762. {
  763. $year = $this->schoolYearService->sessionYearById();
  764. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  765. $html = $this->renderView('classroom/templating/blankAnnualForm.html.twig', [
  766. 'room' => $room,
  767. 'students' => $studentEnrolled,
  768. 'year' => $year,
  769. ]);
  770. return new Response(
  771. $this->pdf->getOutputFromHtml($html, ['default-header' => false]),
  772. 200,
  773. [
  774. 'Content-Type' => 'application/pdf',
  775. 'Content-Disposition' => 'attachment; filename="recap_empty_' . $room->getName() . '.pdf"',
  776. ]
  777. );
  778. }
  779. /**
  780. * @Route("/{id}/reportCardSeq", name="admin_classrooms_reportcards_seq", requirements={"id"="\d+"})
  781. * @Method("GET")
  782. * @Template()
  783. */
  784. public function reportCardSeqAction(ClassRoom $classroom)
  785. {
  786. set_time_limit(600);
  787. $totalNtCoef = 0;
  788. $totalCoef = 0;
  789. $year = $this->schoolYearService->sessionYearById();
  790. $sequence = $this->seqRepo->findOneBy(['activated' => true]);
  791. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  792. $evaluations = $this->evalRepo->findSequantialExamsOfRoom($classroom->getId(), $sequence->getId());
  793. foreach ($evaluations as $ev) {
  794. $totalNtCoef += $ev->getMoyenne() * $ev->getCourse()->getCoefficient();
  795. $totalCoef += $ev->getCourse()->getCoefficient();
  796. }
  797. $this->getViewSeqData($classroom, $sequence);
  798. $connection = $this->em->getConnection();
  799. // BUG FIX : fetchAll() → fetchAllAssociative()
  800. $datas = $connection->executeQuery("SELECT * FROM V_STUDENT_MARK_SEQ0")->fetchAllAssociative();
  801. $stmt = $connection->prepare(
  802. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  803. SELECT DISTINCT std,
  804. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  805. SUM(weight*coef) as totalCoef
  806. FROM V_STUDENT_MARK_SEQ0
  807. GROUP BY std
  808. ORDER BY SUM(value*weight*coef) DESC"
  809. );
  810. $stmt->execute();
  811. $seqAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  812. $seqAvgArray = [];
  813. $sumAvg = 0;
  814. $rank = 0;
  815. $rankArray = [];
  816. foreach ($seqAvg as $avg) {
  817. $seqAvgArray[$avg['std']] = $avg['moyenne'];
  818. $rankArray[$avg['std']] = ++$rank;
  819. $sumAvg += $avg['moyenne'];
  820. }
  821. $stmt = $connection->prepare(
  822. "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_SEQ AS
  823. SELECT DISTINCT seq.std as std, seq.total_hours as abscences
  824. FROM V_STUDENT_ABSCENCE_SEQ0 seq
  825. ORDER BY std"
  826. );
  827. $stmt->execute();
  828. $absences = $connection->executeQuery("SELECT * FROM V_STUDENT_ABSCENCE_SEQ")->fetchAllAssociative();
  829. $absencesArray = [];
  830. foreach ($absences as $abs) {
  831. $absencesArray[$abs['std']] = $abs['abscences'];
  832. }
  833. $html = $this->renderView('classroom/reportcard/sequential.html.twig', [
  834. 'year' => $year,
  835. 'datas' => $datas,
  836. 'students' => $studentEnrolled,
  837. 'sequence' => $sequence,
  838. 'quater' => $sequence->getQuater(),
  839. 'room' => $classroom,
  840. 'means' => $seqAvgArray,
  841. 'abscences' => $absencesArray,
  842. 'genMean' => $sumAvg / sizeof($seqAvgArray),
  843. 'ranks' => $rankArray,
  844. 'fileExists' => $this->fileExists($classroom, $year),
  845. ]);
  846. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  847. 'Content-Type' => 'application/pdf',
  848. 'Content-Disposition' => 'inline; filename="bull_seq_' . $classroom->getName() . '.pdf"',
  849. ]);
  850. }
  851. public function buildAbsViewSeq(ClassRoom $room, Sequence $seq): void
  852. {
  853. $connection = $this->em->getConnection();
  854. $stmt = $connection->prepare(
  855. "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $seq->getId() . " AS
  856. SELECT DISTINCT abs.student_id as std, SUM(abs.weight) as total_hours
  857. FROM class_room room
  858. LEFT JOIN abscence_sheet sheet ON sheet.class_room_id = room.id AND sheet.sequence_id = ?
  859. LEFT JOIN abscence abs ON sheet.id = abs.abscence_sheet_id
  860. WHERE room.id = ?
  861. GROUP BY std
  862. ORDER BY std"
  863. );
  864. $stmt->bindValue(1, $seq->getId());
  865. $stmt->bindValue(2, $room->getId());
  866. $stmt->execute();
  867. }
  868. public function getAbsSeqFromView(ClassRoom $room, Sequence $seq): array
  869. {
  870. $connection = $this->em->getConnection();
  871. return $connection->executeQuery(
  872. "SELECT * FROM V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $seq->getId()
  873. )->fetchAllAssociative();
  874. }
  875. public function buildAbsQuaterView(ClassRoom $room, Quater $quater): void
  876. {
  877. $sequences = $this->seqRepo->findBy(['quater' => $quater]);
  878. foreach ($sequences as $seq) {
  879. $this->buildAbsViewSeq($room, $seq);
  880. }
  881. $query = "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_QUATER" . $room->getId() . "_" . $quater->getId() . " AS
  882. SELECT DISTINCT seq1.std AS std,
  883. seq1.total_hours + IFNULL(seq2.total_hours, 0) AS total_hours
  884. FROM V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1
  885. LEFT JOIN V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2 ON seq1.std = seq2.std
  886. UNION
  887. SELECT DISTINCT seq2.std AS std,
  888. IFNULL(seq1.total_hours, 0) + seq2.total_hours AS total_hours
  889. FROM V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2
  890. LEFT JOIN V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1 ON seq1.std = seq2.std
  891. ORDER BY std";
  892. $this->em->getConnection()->executeQuery($query);
  893. }
  894. public function getAbsQuaterFromView(ClassRoom $room, Quater $quater): array
  895. {
  896. $this->buildAbsQuaterView($room, $quater);
  897. $query = "SELECT * FROM V_STUDENT_ABSCENCE_QUATER" . $room->getId() . "_" . $quater->getId();
  898. $connection = $this->em->getConnection();
  899. $absences = $connection->executeQuery($query)->fetchAllAssociative();
  900. $absencesArray = [];
  901. foreach ($absences as $abs) {
  902. $absencesArray[$abs['std']] = $abs['total_hours'];
  903. }
  904. return $absencesArray;
  905. }
  906. public function getViewSeqData(ClassRoom $room, Sequence $seq): void
  907. {
  908. $connection = $this->em->getConnection();
  909. $year = $this->schoolYearService->sessionYearById();
  910. $stmt = $connection->prepare(
  911. "CREATE OR REPLACE VIEW V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $seq->getId() . " AS
  912. SELECT DISTINCT eval.id as eval, crs.id as crs, crs.coefficient as coef,
  913. room.id as room, std.id as std, teach.full_name as teacher,
  914. modu.id as module, m.value as value, m.weight as weight
  915. FROM mark m
  916. JOIN student std ON m.student_id = std.id
  917. JOIN evaluation eval ON m.evaluation_id = eval.id
  918. JOIN class_room room ON eval.class_room_id = room.id
  919. JOIN course crs ON eval.course_id = crs.id
  920. JOIN attribution att ON att.course_id = crs.id AND att.year_id = ?
  921. JOIN user teach ON att.teacher_id = teach.id
  922. JOIN module modu ON modu.id = crs.module_id
  923. JOIN sequence seq ON seq.id = eval.sequence_id
  924. LEFT JOIN abscence_sheet sheet ON seq.id = sheet.sequence_id AND sheet.class_room_id = room.id
  925. LEFT JOIN abscence abs ON sheet.id = abs.abscence_sheet_id
  926. JOIN quater quat ON seq.quater_id = quat.id
  927. WHERE room.id = ? AND eval.sequence_id = ?
  928. ORDER BY room.id, modu.id, std"
  929. );
  930. $stmt->bindValue(1, $year->getId());
  931. $stmt->bindValue(2, $room->getId());
  932. $stmt->bindValue(3, $seq->getId());
  933. $stmt->execute();
  934. $stmt = $connection->prepare(
  935. "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $seq->getId() . " AS
  936. SELECT DISTINCT room.id as room, abs.student_id as std, SUM(abs.weight) as total_hours
  937. FROM class_room room
  938. LEFT JOIN abscence_sheet sheet ON sheet.class_room_id = room.id AND sheet.sequence_id = ?
  939. LEFT JOIN abscence abs ON sheet.id = abs.abscence_sheet_id
  940. WHERE room.id = ?
  941. GROUP BY std
  942. ORDER BY room.id, std"
  943. );
  944. $stmt->bindValue(1, $seq->getId());
  945. $stmt->bindValue(2, $room->getId());
  946. $stmt->execute();
  947. }
  948. public function getViewSeqMark2024(ClassRoom $room, Sequence $seq): void
  949. {
  950. $connection = $this->em->getConnection();
  951. $stmt = $connection->prepare(
  952. "CREATE OR REPLACE VIEW V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $seq->getId() . " AS
  953. SELECT DISTINCT crs.id as crs, crs.coefficient as coef, std.id as std,
  954. m.value as value, m.weight as weight, eval.mini as mini, eval.maxi as maxi
  955. FROM mark m
  956. JOIN student std ON m.student_id = std.id
  957. JOIN evaluation eval ON m.evaluation_id = eval.id
  958. JOIN class_room room ON eval.class_room_id = room.id
  959. JOIN course crs ON eval.course_id = crs.id
  960. JOIN sequence seq ON seq.id = eval.sequence_id
  961. WHERE room.id = ? AND eval.sequence_id = ?
  962. ORDER BY std, crs"
  963. );
  964. $stmt->bindValue(1, $room->getId());
  965. $stmt->bindValue(2, $seq->getId());
  966. $stmt->execute();
  967. }
  968. /**
  969. * BUG FIX : $classroom → $room, $fileExists → $this->fileExists($room, $year)
  970. *
  971. * @Route("/{id}/reportCardsTrim", name="admin_classrooms_reportcards_trim", requirements={"id"="\d+"})
  972. * @Method("GET")
  973. * @Template()
  974. */
  975. public function reportCardsTrimAction(ClassRoom $room, Request $request)
  976. {
  977. $connection = $this->em->getConnection();
  978. $year = $this->schoolYearService->sessionYearById();
  979. $quater = $this->qtRepo->findOneBy(['activated' => true]);
  980. $sequences = $this->seqRepo->findBy(['quater' => $quater]);
  981. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  982. // BUG FIX : $classroom → $room
  983. $fileExists = $this->fileExists($room, $year);
  984. foreach ($sequences as $seq) {
  985. $this->getViewSeqData($room, $seq);
  986. }
  987. $stmt = $connection->prepare(
  988. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER AS
  989. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  990. seq1.value as value1, seq1.weight as weight1,
  991. seq2.value as value2, seq2.weight as weight2,
  992. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  993. greatest(seq1.weight, seq2.weight) as weight,
  994. seq1.teacher as teacher, seq1.module as module, seq1.room as room
  995. FROM V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1
  996. JOIN V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2
  997. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  998. ORDER BY std, module"
  999. );
  1000. $stmt->execute();
  1001. $stmt = $connection->prepare(
  1002. "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_QUATER AS
  1003. SELECT DISTINCT seq1.std as std, seq1.total_hours + seq2.total_hours as abscences
  1004. FROM V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1
  1005. LEFT JOIN V_STUDENT_ABSCENCE_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2
  1006. ON (seq1.std = seq2.std)
  1007. ORDER BY std"
  1008. );
  1009. $stmt->execute();
  1010. // BUG FIX : fetchAll() → fetchAllAssociative()
  1011. $dataQuater = $connection->executeQuery("SELECT * FROM V_STUDENT_MARK_QUATER")->fetchAllAssociative();
  1012. $stmt = $connection->prepare(
  1013. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  1014. SELECT DISTINCT std,
  1015. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  1016. SUM(weight*coef) as totalCoef
  1017. FROM V_STUDENT_MARK_QUATER
  1018. GROUP BY std
  1019. ORDER BY SUM(value*weight*coef) DESC"
  1020. );
  1021. $stmt->execute();
  1022. $quaterAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  1023. $quaterAvgArray = [];
  1024. $sumAvg = 0;
  1025. $rank = 0;
  1026. $rankArray = [];
  1027. foreach ($quaterAvg as $avg) {
  1028. $quaterAvgArray[$avg['std']] = $avg['moyenne'];
  1029. $rankArray[$avg['std']] = ++$rank;
  1030. $sumAvg += $avg['moyenne'];
  1031. }
  1032. $absences = $connection->executeQuery("SELECT * FROM V_STUDENT_ABSCENCE_QUATER")->fetchAllAssociative();
  1033. $absencesArray = [];
  1034. foreach ($absences as $abs) {
  1035. $absencesArray[$abs['std']] = $abs['abscences'];
  1036. }
  1037. $this->pdf->setTimeout(600);
  1038. $html = $this->renderView('classroom/reportcard/quaterly_2024.html.twig', [
  1039. 'year' => $year,
  1040. 'data' => $dataQuater,
  1041. 'ranks' => $rankArray,
  1042. 'means' => $quaterAvgArray,
  1043. 'abscences' => $absencesArray,
  1044. 'genMean' => $sumAvg / sizeof($quaterAvgArray),
  1045. 'room' => $room,
  1046. 'quater' => $quater,
  1047. 'sequences' => $sequences,
  1048. 'students' => $studentEnrolled,
  1049. // BUG FIX : variable correctement définie
  1050. 'fileExists' => $fileExists,
  1051. ]);
  1052. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  1053. 'Content-Type' => 'application/pdf',
  1054. 'Content-Disposition' => 'inline; filename="' . $room->getName() . '.pdf"',
  1055. ]);
  1056. }
  1057. /**
  1058. * @Route("/{id}/reportCardsTrim2024", name="admin_classrooms_reportcards_trim_2024", requirements={"id"="\d+"})
  1059. * @Method("GET")
  1060. * @Template()
  1061. */
  1062. public function reportCardsTrim2024Action(ClassRoom $room, Request $request)
  1063. {
  1064. if (!$this->getUser()) {
  1065. $this->addFlash('warning', 'You need login first!');
  1066. return $this->redirectToRoute('app_login');
  1067. }
  1068. if (!$this->getUser()->isVerified()) {
  1069. $this->addFlash('warning', 'You need to have a verified account!');
  1070. return $this->redirectToRoute('app_login');
  1071. }
  1072. $copyright = $request->request->get('copyright') == "on";
  1073. $reverse = $request->request->get('reverse') == "on";
  1074. $scale = $request->request->get('scale')
  1075. ? max(0.70, min(1.00, (float) $request->request->get('scale')))
  1076. : 1.0;
  1077. $connection = $this->em->getConnection();
  1078. $year = $this->schoolYearService->sessionYearById();
  1079. $quater = $this->qtRepo->findOneBy(['activated' => true]);
  1080. $students = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  1081. $mainTeacher = $this->mainTeacherRepo->findOneBy(['classRoom' => $room, 'schoolYear' => $year])->getTeacher();
  1082. $query = "SELECT DISTINCT student.id as student_id, student.firstname as student_firstname,
  1083. student.lastname as student_last_name, student.birthday as student_birthday,
  1084. student.matricule as matricule, sequence.id as sequence, course.id as course_id,
  1085. course.wording, course.coefficient, mark.value, mark.weight, mark.rank2,
  1086. evaluation.mini as mini, evaluation.maxi as maxi, evaluation.competence,
  1087. attribution.teacher_id, school_year.id, user.full_name
  1088. FROM sequence
  1089. JOIN evaluation ON evaluation.sequence_id = sequence.id AND evaluation.class_room_id = :room_id
  1090. JOIN course ON evaluation.course_id = course.id
  1091. JOIN attribution ON attribution.course_id = course.id
  1092. JOIN user ON user.id = attribution.teacher_id
  1093. JOIN mark ON evaluation.id = mark.evaluation_id
  1094. JOIN student ON mark.student_id = student.id
  1095. JOIN quater ON sequence.quater_id = quater.id
  1096. JOIN school_year ON quater.school_year_id = school_year.id AND school_year.id = attribution.year_id
  1097. WHERE quater.id = :quater_id
  1098. ORDER BY student_id, course.id, sequence.id";
  1099. $dataMarks = $connection->executeQuery($query, [
  1100. 'quater_id' => $quater->getId(),
  1101. 'room_id' => $room->getId(),
  1102. ])->fetchAllAssociative();
  1103. $sequences = $this->seqRepo->findBy(['quater' => $quater]);
  1104. foreach ($sequences as $seq) {
  1105. $this->getViewSeqMark2024($room, $seq);
  1106. }
  1107. $connection->executeQuery(
  1108. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER AS
  1109. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1110. seq1.value as value1, seq1.weight as weight1,
  1111. seq2.value as value2, seq2.weight as weight2,
  1112. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  1113. greatest(seq1.weight, seq2.weight) as weight,
  1114. (seq1.mini + seq2.mini)/2 as mini, (seq1.maxi + seq2.maxi)/2 as maxi
  1115. FROM V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1
  1116. LEFT JOIN V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2
  1117. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1118. ORDER BY std"
  1119. );
  1120. $stmt = $connection->prepare(
  1121. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  1122. SELECT DISTINCT std,
  1123. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  1124. SUM(weight*coef) as totalCoef
  1125. FROM V_STUDENT_MARK_QUATER
  1126. GROUP BY std
  1127. ORDER BY SUM(value*weight*coef) DESC"
  1128. );
  1129. $stmt->execute();
  1130. $quaterAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  1131. $quaterAvgArray = [];
  1132. $sumAvg = 0;
  1133. $rank = 0;
  1134. $minAvg = 20;
  1135. $maxAvg = 0;
  1136. $rankArray = [];
  1137. foreach ($quaterAvg as $avg) {
  1138. $quaterAvgArray[$avg['std']] = $avg['moyenne'];
  1139. $rankArray[$avg['std']] = ++$rank;
  1140. $sumAvg += $avg['moyenne'];
  1141. if ($minAvg > $avg['moyenne']) $minAvg = $avg['moyenne'];
  1142. if ($maxAvg < $avg['moyenne']) $maxAvg = $avg['moyenne'];
  1143. }
  1144. $html = $this->renderView('classroom/reportcard/quaterly_2024.html.twig', [
  1145. 'genMean' => $sumAvg / sizeof($quaterAvgArray),
  1146. 'ranks' => $rankArray,
  1147. 'year' => $year,
  1148. 'minAvg' => $minAvg,
  1149. 'maxAvg' => $maxAvg,
  1150. 'quater' => $quater,
  1151. 'mainTeacher' => $mainTeacher,
  1152. 'dataMarks' => $dataMarks,
  1153. 'dataAbs' => $this->getAbsQuaterFromView($room, $quater),
  1154. 'students' => $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year),
  1155. 'room' => $room,
  1156. 'fileExists' => $this->fileExists($room, $year),
  1157. 'copyright' => $copyright,
  1158. 'reverse' => $reverse,
  1159. 'scale' => $scale,
  1160. ]);
  1161. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  1162. 'Content-Type' => 'application/pdf',
  1163. 'Content-Disposition' => 'inline; filename="bull_' . $quater->getId() . '.pdf"',
  1164. ]);
  1165. }
  1166. /**
  1167. * BUG FIX : $room → $classroom, $i non initialisé → $i = 0, $sumAvg local
  1168. *
  1169. * @Route("/{id}/annualavglist", name="admin_avg_list", requirements={"id"="\d+"})
  1170. * @Method("GET")
  1171. * @Template()
  1172. */
  1173. public function annualAvgList(ClassRoom $classroom, Request $request)
  1174. {
  1175. $connection = $this->em->getConnection();
  1176. $year = $this->schoolYearService->sessionYearById();
  1177. $sequences = $this->seqRepo->findSequenceThisYear($year);
  1178. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  1179. // BUG FIX : $i initialisé
  1180. $i = 0;
  1181. foreach ($sequences as $seq) {
  1182. $this->getViewSeqData($classroom, $seq);
  1183. $i++;
  1184. }
  1185. $stmt = $connection->prepare(
  1186. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_1 AS
  1187. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1188. seq1.value as value1, seq1.weight as weight1,
  1189. seq2.value as value2, seq2.weight as weight2,
  1190. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  1191. greatest(seq1.weight, seq2.weight) as weight,
  1192. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  1193. FROM V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[0]->getId() . " seq1
  1194. LEFT JOIN V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[1]->getId() . " seq2
  1195. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1196. ORDER BY std, modu"
  1197. );
  1198. $stmt->execute();
  1199. $stmt = $connection->prepare(
  1200. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_2 AS
  1201. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1202. seq1.value as value1, seq1.weight as weight1,
  1203. seq2.value as value2, seq2.weight as weight2,
  1204. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  1205. greatest(seq1.weight, seq2.weight) as weight,
  1206. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  1207. FROM V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[2]->getId() . " seq1
  1208. JOIN V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[3]->getId() . " seq2
  1209. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1210. ORDER BY std, modu"
  1211. );
  1212. $stmt->execute();
  1213. $stmt = $connection->prepare(
  1214. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER_3 AS
  1215. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1216. seq1.value as value1, seq1.weight as weight1,
  1217. seq2.value as value2, seq2.weight as weight2,
  1218. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  1219. greatest(seq1.weight, seq2.weight) as weight,
  1220. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  1221. FROM V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[4]->getId() . " seq1
  1222. JOIN V_STUDENT_MARK_SEQ" . $classroom->getId() . "_" . $sequences[5]->getId() . " seq2
  1223. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1224. ORDER BY std, modu"
  1225. );
  1226. $stmt->execute();
  1227. // BUG FIX : virgule manquante dans le SQL original entre student.matricule et student.lastname
  1228. $stmt = $connection->prepare(
  1229. "CREATE OR REPLACE VIEW ANNUAL_DATA AS
  1230. SELECT DISTINCT student.id as idStd, student.matricule as matricule,
  1231. student.lastname as lastname, student.firstname as firstname,
  1232. course.wording as course, course.coefficient as coef,
  1233. module.name as module, user.full_name as teacher,
  1234. quat1.std, quat1.modu,
  1235. quat1.value as value1, quat1.weight as weight1,
  1236. quat2.value as value2, quat2.weight as weight2,
  1237. quat3.value as value3, quat3.weight as weight3,
  1238. greatest(quat1.weight, quat2.weight, quat3.weight) as weight,
  1239. (quat1.value*quat1.weight + quat2.value*quat2.weight + quat3.value*quat3.weight)
  1240. / (quat1.weight+quat2.weight+quat3.weight) as value
  1241. FROM student
  1242. LEFT JOIN V_STUDENT_MARK_QUATER_1 quat1 ON student.id = quat1.std
  1243. LEFT JOIN V_STUDENT_MARK_QUATER_2 quat2 ON student.id = quat2.std AND quat1.crs = quat2.crs
  1244. LEFT JOIN V_STUDENT_MARK_QUATER_3 quat3 ON student.id = quat3.std AND quat2.crs = quat3.crs
  1245. JOIN class_room ON class_room.id = quat1.room
  1246. JOIN course ON course.id = quat1.crs
  1247. JOIN module ON course.module_id = quat1.modu
  1248. JOIN user ON user.full_name = quat1.teacher
  1249. ORDER BY quat1.std, quat1.modu"
  1250. );
  1251. $stmt->execute();
  1252. $dataYear = $connection->executeQuery("SELECT * FROM ANNUAL_DATA")->fetchAllAssociative();
  1253. $stmt = $connection->prepare(
  1254. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  1255. SELECT DISTINCT idStd,
  1256. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  1257. SUM(weight*coef) as totalCoef
  1258. FROM ANNUAL_DATA
  1259. GROUP BY idStd
  1260. ORDER BY SUM(value*weight*coef) DESC"
  1261. );
  1262. $stmt->execute();
  1263. $annualAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  1264. $rank = 0;
  1265. $rankArray = [];
  1266. // BUG FIX : $sumAvg local
  1267. $sumAvg = 0;
  1268. foreach ($annualAvg as $avg) {
  1269. $this->annualAvgArray[$avg['idStd']] = $avg['moyenne'];
  1270. $rankArray[$avg['idStd']] = ++$rank;
  1271. $sumAvg += $avg['moyenne'];
  1272. }
  1273. $html = $this->renderView('classroom/avglist.html.twig', [
  1274. 'year' => $year,
  1275. 'room' => $classroom,
  1276. 'students' => $studentEnrolled,
  1277. 'ranks' => $rankArray,
  1278. 'means' => $this->annualAvgArray,
  1279. 'genMean' => $sumAvg / sizeof($this->annualAvgArray),
  1280. ]);
  1281. return new Response(
  1282. $this->snappy->getOutputFromHtml($html, ['page-size' => 'A4']),
  1283. 200,
  1284. [
  1285. 'Content-Type' => 'application/pdf',
  1286. 'Content-Disposition' => 'attachment; filename="BUL_ANN_' . $classroom->getName() . '.pdf"',
  1287. ]
  1288. );
  1289. }
  1290. /**
  1291. * @Route("/{id}/reportCards3ApcYearApc", name="admin_class_reportcards_3_apc_year", requirements={"id"="\d+"})
  1292. * @Method("GET")
  1293. * @Template()
  1294. */
  1295. public function reportCards3YearAction(ClassRoom $classroom, Request $request)
  1296. {
  1297. $headerFontSize = $request->request->get('header_font_size');
  1298. $lineHeight = $request->request->get('line_height');
  1299. $copyright = $request->request->get('copyright') == "on";
  1300. $reverse = $request->request->get('reverse') == "on";
  1301. $connection = $this->em->getConnection();
  1302. $year = $this->schoolYearService->sessionYearById();
  1303. $sequences = $this->seqRepo->findSequenceThisYear($year);
  1304. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($classroom, $year);
  1305. foreach ($sequences as $seq) {
  1306. $this->getViewSeqData($classroom, $seq);
  1307. }
  1308. $trimDefs = [
  1309. ['V_STUDENT_MARK_QUATER_1', 0, 1, 'V_STUDENT_ABSCENCE_QUATER_1'],
  1310. ['V_STUDENT_MARK_QUATER_2', 2, 3, 'V_STUDENT_ABSCENCE_QUATER_2'],
  1311. ['V_STUDENT_MARK_QUATER_3', 4, 5, 'V_STUDENT_ABSCENCE_QUATER_3'],
  1312. ];
  1313. foreach ($trimDefs as [$viewName, $idx1, $idx2, $absViewName]) {
  1314. $s1 = $sequences[$idx1];
  1315. $s2 = $sequences[$idx2];
  1316. $rid = $classroom->getId();
  1317. $stmt = $connection->prepare(
  1318. "CREATE OR REPLACE VIEW {$viewName} AS
  1319. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1320. seq1.value as value1, seq1.weight as weight1,
  1321. seq2.value as value2, seq2.weight as weight2,
  1322. (seq1.value*seq1.weight + seq2.value*seq2.weight)/(seq1.weight+seq2.weight) as value,
  1323. greatest(seq1.weight, seq2.weight) as weight,
  1324. seq1.teacher as teacher, seq1.module as modu, seq1.room as room
  1325. FROM V_STUDENT_MARK_SEQ{$rid}_{$s1->getId()} seq1
  1326. LEFT JOIN V_STUDENT_MARK_SEQ{$rid}_{$s2->getId()} seq2
  1327. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1328. ORDER BY std, modu"
  1329. );
  1330. $stmt->execute();
  1331. $stmt = $connection->prepare(
  1332. "CREATE OR REPLACE VIEW {$absViewName} AS
  1333. SELECT DISTINCT seq1.std as std, seq1.total_hours + seq2.total_hours as abscences
  1334. FROM V_STUDENT_ABSCENCE_SEQ{$rid}_{$s1->getId()} seq1
  1335. LEFT JOIN V_STUDENT_ABSCENCE_SEQ{$rid}_{$s2->getId()} seq2 ON (seq1.std = seq2.std)
  1336. ORDER BY std"
  1337. );
  1338. $stmt->execute();
  1339. }
  1340. set_time_limit(600);
  1341. $stmt = $connection->prepare(
  1342. "CREATE OR REPLACE VIEW ANNUAL_DATA AS
  1343. SELECT DISTINCT student.id as idStd, student.matricule as matricule,
  1344. student.image_name as profileImagePath,
  1345. student.lastname as lastname, student.firstname as firstname,
  1346. student.birthday as birthday, student.gender as gender,
  1347. student.birthplace as birthplace, class_room.name as room_name,
  1348. course.wording as course, course.coefficient as coef,
  1349. module.name as module, user.full_name as teacher,
  1350. quat1.std, quat1.modu,
  1351. quat1.value as value1, quat1.weight as weight1,
  1352. quat2.value as value2, quat2.weight as weight2,
  1353. quat3.value as value3, quat3.weight as weight3,
  1354. greatest(quat1.weight, quat2.weight, quat3.weight) as weight,
  1355. (quat1.value*quat1.weight + quat2.value*quat2.weight + quat3.value*quat3.weight)
  1356. / (quat1.weight+quat2.weight+quat3.weight) as value
  1357. FROM student
  1358. LEFT JOIN V_STUDENT_MARK_QUATER_1 quat1 ON student.id = quat1.std
  1359. LEFT JOIN V_STUDENT_MARK_QUATER_2 quat2 ON student.id = quat2.std AND quat1.crs = quat2.crs
  1360. LEFT JOIN V_STUDENT_MARK_QUATER_3 quat3 ON student.id = quat3.std AND quat2.crs = quat3.crs
  1361. LEFT JOIN class_room ON class_room.id = quat1.room
  1362. JOIN course ON course.id = quat1.crs
  1363. JOIN module ON course.module_id = quat1.modu
  1364. JOIN user ON user.full_name = quat1.teacher
  1365. ORDER BY quat1.std, quat1.modu"
  1366. );
  1367. $stmt->execute();
  1368. $dataYear = $connection->executeQuery("SELECT * FROM ANNUAL_DATA")->fetchAllAssociative();
  1369. $stmt = $connection->prepare(
  1370. "CREATE OR REPLACE VIEW V_STUDENT_RANKS AS
  1371. SELECT DISTINCT idStd,
  1372. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  1373. SUM(weight*coef) as totalCoef
  1374. FROM ANNUAL_DATA
  1375. GROUP BY idStd
  1376. ORDER BY SUM(value*weight*coef) DESC"
  1377. );
  1378. $stmt->execute();
  1379. $annualAvg = $connection->executeQuery("SELECT * FROM V_STUDENT_RANKS")->fetchAllAssociative();
  1380. $rank = 0;
  1381. $rankArray = [];
  1382. foreach ($annualAvg as $avg) {
  1383. $this->annualAvgArray[$avg['idStd']] = $avg['moyenne'];
  1384. $rankArray[$avg['idStd']] = ++$rank;
  1385. $this->sumAvg += $avg['moyenne'];
  1386. }
  1387. $stmt = $connection->prepare(
  1388. "CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_ANNUAL AS
  1389. SELECT DISTINCT q1.std as std, q1.abscences + q2.abscences + q3.abscences as abscences
  1390. FROM V_STUDENT_ABSCENCE_QUATER_1 q1
  1391. LEFT JOIN V_STUDENT_ABSCENCE_QUATER_2 q2 ON (q1.std = q2.std)
  1392. LEFT JOIN V_STUDENT_ABSCENCE_QUATER_3 q3 ON (q1.std = q3.std)
  1393. ORDER BY std"
  1394. );
  1395. $stmt->execute();
  1396. $absences = $connection->executeQuery("SELECT * FROM V_STUDENT_ABSCENCE_ANNUAL")->fetchAllAssociative();
  1397. $absencesArray = [];
  1398. foreach ($absences as $abs) {
  1399. $absencesArray[$abs['std']] = $abs['abscences'];
  1400. }
  1401. $html = $this->renderView('classroom/reportcard/annual.html.twig', [
  1402. 'headerFontSize' => $headerFontSize,
  1403. 'lineHeight' => $lineHeight,
  1404. 'copyright' => $copyright,
  1405. 'reverse' => $reverse,
  1406. 'year' => $year,
  1407. 'data' => $dataYear,
  1408. 'room' => $classroom,
  1409. 'students' => $studentEnrolled,
  1410. 'abscences' => $absencesArray,
  1411. 'ranks' => $rankArray,
  1412. 'means' => $this->annualAvgArray,
  1413. 'genMean' => $this->sumAvg / sizeof($this->annualAvgArray),
  1414. // BUG FIX : $this->imagesExist → $this->fileExists()
  1415. 'fileExists' => $this->fileExists($classroom, $year),
  1416. ]);
  1417. return new Response(
  1418. $this->snappy->getOutputFromHtml($html, ['page-size' => 'A4']),
  1419. 200,
  1420. [
  1421. 'Content-Type' => 'application/pdf',
  1422. 'Content-Disposition' => 'attachment; filename="BUL_ANN_' . $classroom->getName() . '.pdf"',
  1423. ]
  1424. );
  1425. }
  1426. public function ranking(ClassRoom $room, SchoolYear $year): array
  1427. {
  1428. $connection = $this->em->getConnection();
  1429. $stmt = $connection->prepare(
  1430. "CREATE OR REPLACE VIEW V_STUDENT_RANKS" . $room->getId() . "_" . $year->getId() . " AS
  1431. SELECT DISTINCT std,
  1432. CAST(SUM(value*weight*coef) / SUM(weight*coef) AS decimal(4,2)) as moyenne,
  1433. SUM(weight*coef) as totalCoef
  1434. FROM V_STUDENT_MARK_YEAR" . $room->getId() . "_" . $year->getId() . "
  1435. GROUP BY std
  1436. ORDER BY SUM(value*weight*coef) DESC"
  1437. );
  1438. $stmt->execute();
  1439. $annualAvg = $connection->executeQuery(
  1440. "SELECT * FROM V_STUDENT_RANKS" . $room->getId() . "_" . $year->getId() . " WHERE totalCoef > 0"
  1441. )->fetchAllAssociative();
  1442. $this->sumAvg = 0;
  1443. $rank = 0;
  1444. foreach ($annualAvg as $avg) {
  1445. $this->annualAvgArray[$avg['std']] = $avg['moyenne'];
  1446. $this->annualRanks[$avg['std']] = ++$rank;
  1447. $this->sumAvg += $avg['moyenne'];
  1448. }
  1449. return array_filter($this->annualRanks, fn($v, $k) => $k !== '', ARRAY_FILTER_USE_BOTH);
  1450. }
  1451. /**
  1452. * @Route("/{id}/reportCards4ApcYearApc", name="admin_class_reportcards_year_2024", requirements={"id"="\d+"})
  1453. * @Method("GET")
  1454. * @Template()
  1455. */
  1456. public function reportCards2024YearAction(ClassRoom $room, Request $request)
  1457. {
  1458. // Parametres de presentation du bulletin
  1459. $headerFontSize = $request->request->get('header_font_size');
  1460. $bodyFontSize = $request->request->get('body_font_size');
  1461. $lineHeight = $request->request->get('line_height');
  1462. $copyright = $request->request->get('copyright')=="on";
  1463. $reverse = $request->request->get('reverse')=="on";
  1464. $connection = $this->em->getConnection();
  1465. $year = $this->schoolYearService->sessionYearById();
  1466. $quaters = $year->getQuaters();
  1467. $sequences = $this->seqRepo->findSequenceThisYear($year);
  1468. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYearInClass($room, $year);
  1469. $mainTeacher = $this->mainTeacherRepo->findOneBy(['classRoom' => $room, 'schoolYear' => $year])->getTeacher();
  1470. $this->getViewYearMark2024($room, $year);
  1471. $absencesArray = [];
  1472. $this->annualAbs = $this->getAbsYearFromView($room, $year);
  1473. foreach ($this->annualAbs as $abs) {
  1474. $absencesArray[$abs['std']] = $abs['total_hours'];
  1475. }
  1476. set_time_limit(600);
  1477. $this->getViewYearMark2024($room, $year);
  1478. // For calculating ranks
  1479. $html = $this->renderView('classroom/reportcard/annual_2024.html.twig', array(
  1480. "headerFontSize" => $headerFontSize,
  1481. "bodyFontSize" => $bodyFontSize,
  1482. "lineHeight" => $lineHeight,
  1483. "copyright" => $copyright,
  1484. "reverse" => $reverse,
  1485. 'mainTeacher'=>$mainTeacher,
  1486. 'year' => $year,
  1487. 'data' => $this->annualMark,
  1488. 'room' => $room,
  1489. 'students' => $studentEnrolled,
  1490. 'abscences' => $absencesArray,
  1491. 'ranks' => $this->ranking($room, $year),
  1492. 'means' => $this->annualAvgArray,
  1493. 'genMean' => $this->sumAvg / sizeof($studentEnrolled),
  1494. 'fileExists'=> $this->fileExists($room, $year)
  1495. ));
  1496. return new Response(
  1497. $this->snappy->getOutputFromHtml($html, ['page-size' => 'A4']),
  1498. 200,
  1499. [
  1500. 'Content-Type' => 'application/pdf',
  1501. 'Content-Disposition' => 'attachment; filename="BUL_ANN_' . $room->getName() . '_' . $year->getId() . '.pdf"',
  1502. ]
  1503. );
  1504. }
  1505. public function getStudentQuaterMark(array $std, Course $crs): ?array
  1506. {
  1507. foreach ($this->quaterData as $data) {
  1508. if ($std['id'] == $data['std'] && $crs->getId() == $data['crs']) {
  1509. return [
  1510. 'seq1' => $data['value1'],
  1511. 'weight1' => $data['weight1'],
  1512. 'seq2' => $data['value2'],
  1513. 'weight2' => $data['weight2'],
  1514. 'coef' => $data['coef'],
  1515. ];
  1516. }
  1517. }
  1518. return null;
  1519. }
  1520. public function getStudentAnnualMark(array $std, Course $crs): ?array
  1521. {
  1522. foreach ($this->annualMark as $data) {
  1523. if ($std['id'] == $data['std'] && $crs->getId() == $data['crs']) {
  1524. return [
  1525. 'value' => $data['value'],
  1526. 'weight' => $data['weight'],
  1527. 'coef' => $data['coef'],
  1528. ];
  1529. }
  1530. }
  1531. return null;
  1532. }
  1533. public function getViewQuaterMark2024(ClassRoom $room, Quater $quater): void
  1534. {
  1535. $connection = $this->em->getConnection();
  1536. $sequences = $this->seqRepo->findBy(['quater' => $quater]);
  1537. foreach ($sequences as $seq) {
  1538. $this->getViewSeqMark2024($room, $seq);
  1539. }
  1540. $connection->executeQuery(
  1541. "CREATE OR REPLACE VIEW V_STUDENT_MARK_QUATER" . $room->getId() . "_" . $quater->getId() . " AS
  1542. SELECT DISTINCT seq1.std as std, seq1.crs as crs, seq1.coef as coef,
  1543. seq1.value as value1, seq1.weight as weight1,
  1544. seq2.value as value2, seq2.weight as weight2,
  1545. (COALESCE(seq1.value*seq1.weight,0) + COALESCE(seq2.value*seq2.weight,0))/(seq1.weight+seq2.weight) as value,
  1546. greatest(seq1.weight, seq2.weight) as weight,
  1547. (seq1.mini + seq2.mini)/2 as mini, (seq1.maxi + seq2.maxi)/2 as maxi
  1548. FROM V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[0]->getId() . " seq1
  1549. LEFT JOIN V_STUDENT_MARK_SEQ" . $room->getId() . "_" . $sequences[1]->getId() . " seq2
  1550. ON (seq1.std = seq2.std AND seq1.crs = seq2.crs)
  1551. ORDER BY std"
  1552. );
  1553. $this->quaterData = $connection->fetchAllAssociative(
  1554. "SELECT * FROM V_STUDENT_MARK_QUATER" . $room->getId() . "_" . $quater->getId()
  1555. );
  1556. }
  1557. public function getViewYearMark2024(ClassRoom $room, SchoolYear $year): void
  1558. {
  1559. $connection = $this->em->getConnection();
  1560. $quaters = $year->getQuaters();
  1561. foreach ($quaters as $quater) {
  1562. $this->getViewQuaterMark2024($room, $quater);
  1563. }
  1564. $connection->executeQuery("
  1565. CREATE OR REPLACE VIEW V_STUDENT_MARK_YEAR" . $room->getId() . "_" . $year->getId() . " AS
  1566. SELECT DISTINCT
  1567. `user`.full_name, course.wording,
  1568. qt1.std AS std, qt1.crs AS crs, qt1.coef AS coef,
  1569. qt1.value AS value1, qt1.weight AS weight1,
  1570. qt2.value AS value2, qt2.weight AS weight2,
  1571. qt3.value AS value3, qt3.weight AS weight3,
  1572. (
  1573. COALESCE(qt1.value,0)*COALESCE(qt1.weight,0) +
  1574. COALESCE(qt2.value,0)*COALESCE(qt2.weight,0) +
  1575. COALESCE(qt3.value,0)*COALESCE(qt3.weight,0)
  1576. ) / NULLIF(COALESCE(qt1.weight,0)+COALESCE(qt2.weight,0)+COALESCE(qt3.weight,0), 0) AS `value`,
  1577. GREATEST(COALESCE(qt1.weight,0), COALESCE(qt2.weight,0), COALESCE(qt3.weight,0)) AS weight,
  1578. (COALESCE(qt1.mini,0)+COALESCE(qt2.mini,0)+COALESCE(qt3.mini,0))/3 AS mini,
  1579. (COALESCE(qt1.maxi,0)+COALESCE(qt2.maxi,0)+COALESCE(qt3.maxi,0))/3 AS maxi,
  1580. RANK() OVER (
  1581. PARTITION BY course.id
  1582. ORDER BY (
  1583. COALESCE(qt1.value,0)*COALESCE(qt1.weight,0) +
  1584. COALESCE(qt2.value,0)*COALESCE(qt2.weight,0) +
  1585. COALESCE(qt3.value,0)*COALESCE(qt3.weight,0)
  1586. ) / NULLIF(COALESCE(qt1.weight,0)+COALESCE(qt2.weight,0)+COALESCE(qt3.weight,0), 0) DESC
  1587. ) AS `rank`
  1588. FROM course
  1589. JOIN attribution att ON att.course_id = course.id AND att.year_id = " . $year->getId() . "
  1590. JOIN `user` ON `user`.id = att.teacher_id
  1591. JOIN V_STUDENT_MARK_QUATER" . $room->getId() . "_" . $quaters[0]->getId() . " qt1 ON course.id = qt1.crs
  1592. LEFT JOIN V_STUDENT_MARK_QUATER" . $room->getId() . "_" . $quaters[1]->getId() . " qt2 ON qt1.std = qt2.std AND qt1.crs = qt2.crs
  1593. LEFT JOIN V_STUDENT_MARK_QUATER" . $room->getId() . "_" . $quaters[2]->getId() . " qt3 ON qt1.std = qt3.std AND qt1.crs = qt3.crs
  1594. ORDER BY qt1.std
  1595. ");
  1596. $this->annualMark = $connection->fetchAllAssociative(
  1597. "SELECT * FROM V_STUDENT_MARK_YEAR" . $room->getId() . "_" . $year->getId()
  1598. );
  1599. }
  1600. public function buildAbsYearView(ClassRoom $room, SchoolYear $year): void
  1601. {
  1602. $connection = $this->em->getConnection();
  1603. $quaters = $year->getQuaters();
  1604. foreach ($quaters as $quater) {
  1605. $this->buildAbsQuaterView($room, $quater);
  1606. }
  1607. $rid = $room->getId();
  1608. $yid = $year->getId();
  1609. $q0 = $quaters[0]->getId();
  1610. $q1 = $quaters[1]->getId();
  1611. $q2 = $quaters[2]->getId();
  1612. $connection->executeQuery("
  1613. CREATE OR REPLACE VIEW V_STUDENT_ABSCENCE_YEAR{$rid}_{$yid} AS
  1614. SELECT DISTINCT qt1.std AS std,
  1615. COALESCE(qt1.total_hours,0)+COALESCE(qt2.total_hours,0)+COALESCE(qt3.total_hours,0) AS total_hours
  1616. FROM V_STUDENT_ABSCENCE_QUATER{$rid}_{$q0} qt1
  1617. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q1} qt2 ON qt1.std = qt2.std
  1618. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q2} qt3 ON qt1.std = qt3.std
  1619. UNION
  1620. SELECT DISTINCT qt2.std AS std,
  1621. COALESCE(qt1.total_hours,0)+COALESCE(qt2.total_hours,0)+COALESCE(qt3.total_hours,0) AS total_hours
  1622. FROM V_STUDENT_ABSCENCE_QUATER{$rid}_{$q1} qt2
  1623. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q0} qt1 ON qt1.std = qt2.std
  1624. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q2} qt3 ON qt2.std = qt3.std
  1625. UNION
  1626. SELECT DISTINCT qt3.std AS std,
  1627. COALESCE(qt1.total_hours,0)+COALESCE(qt2.total_hours,0)+COALESCE(qt3.total_hours,0) AS total_hours
  1628. FROM V_STUDENT_ABSCENCE_QUATER{$rid}_{$q2} qt3
  1629. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q0} qt1 ON qt1.std = qt3.std
  1630. LEFT JOIN V_STUDENT_ABSCENCE_QUATER{$rid}_{$q1} qt2 ON qt2.std = qt3.std
  1631. ORDER BY std
  1632. ");
  1633. }
  1634. public function getAbsYearFromView(ClassRoom $room, SchoolYear $year): array
  1635. {
  1636. $this->buildAbsYearView($room, $year);
  1637. return $this->em->getConnection()->fetchAllAssociative(
  1638. "SELECT * FROM V_STUDENT_ABSCENCE_YEAR" . $room->getId() . "_" . $year->getId()
  1639. );
  1640. }
  1641. /**
  1642. * @Route("/{id}/recapitulatiftrim", name="admin_classrooms_recapitulatif_trim", requirements={"id"="\d+"})
  1643. * @Method("GET")
  1644. * @Template()
  1645. */
  1646. public function recapTrimAction(ClassRoom $room, Request $request)
  1647. {
  1648. $checkedValues = $request->request->get('selected_courses');
  1649. set_time_limit(600);
  1650. $year = $this->schoolYearService->sessionYearById();
  1651. $quater = $this->qtRepo->findOneBy(['activated' => true]);
  1652. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYear($room, $year->getId());
  1653. $this->getViewQuaterMark2024($room, $quater);
  1654. $result = [];
  1655. foreach ($studentEnrolled as $std) {
  1656. $result[$std['id']] = [];
  1657. foreach ($room->getModules() as $module) {
  1658. foreach ($module->getCourses() as $crs) {
  1659. if (in_array($crs->getId(), $checkedValues)) {
  1660. $result[$std['id']][$crs->getId()] = $this->getStudentQuaterMark($std, $crs);
  1661. }
  1662. }
  1663. }
  1664. }
  1665. $mainTeacher = $this->mainTeacherRepo->findOneBy(['classRoom' => $room, 'schoolYear' => $year])->getTeacher();
  1666. $html = $this->renderView('classroom/recapitulatiftrimWithMoy.html.twig', [
  1667. 'year' => $year,
  1668. 'datas' => $result,
  1669. 'room' => $room,
  1670. 'quater' => $quater,
  1671. 'checkedValues' => $checkedValues,
  1672. 'students' => $studentEnrolled,
  1673. 'mainTeacher' => $mainTeacher,
  1674. ]);
  1675. return new Response(
  1676. $this->pdf->getOutputFromHtml($html, [
  1677. 'orientation' => 'Landscape',
  1678. 'page-size' => 'A4',
  1679. 'margin-top' => '10mm',
  1680. 'margin-bottom'=> '10mm',
  1681. 'margin-left' => '10mm',
  1682. 'margin-right' => '10mm',
  1683. ]),
  1684. 200,
  1685. [
  1686. 'Content-Type' => 'application/pdf',
  1687. 'Content-Disposition' => 'inline; filename="recap_trim' . $quater->getId() . '_' . $room->getName() . '.pdf"',
  1688. ]
  1689. );
  1690. }
  1691. /**
  1692. * @Route("/{id}/recapitulatifannexcel", name="admin_classrooms_recapitulatif_ann_excel", requirements={"id"="\d+"})
  1693. * @Method("GET")
  1694. * @Template()
  1695. */
  1696. public function recapAnnExcelAction(ClassRoom $room, Request $request)
  1697. {
  1698. $checkedValues = $request->request->get('selected_courses');
  1699. $year = $this->schoolYearService->sessionYearById();
  1700. $this->getViewYearMark2024($room, $year);
  1701. $studentEnrolled = $this->stdRepo->findEnrolledStudentsThisYear($room, $year->getId());
  1702. $result = [];
  1703. $courses = [];
  1704. foreach ($studentEnrolled as $std) {
  1705. $result[$std['id']]['student'] = $std['lastname'] . ' ' . $std['firstname'];
  1706. $result[$std['id']]['gender'] = $std['gender'];
  1707. $result[$std['id']]['birthday'] = $std['birthday'];
  1708. foreach ($room->getModules() as $module) {
  1709. foreach ($module->getCourses() as $crs) {
  1710. if (in_array($crs->getId(), $checkedValues)) {
  1711. $courses[$crs->getId()] = $crs->getWording();
  1712. $result[$std['id']][$crs->getId()] = $this->getStudentAnnualMark($std, $crs);
  1713. }
  1714. }
  1715. }
  1716. }
  1717. $spreadsheet = new Spreadsheet();
  1718. $sheet = $spreadsheet->getActiveSheet();
  1719. $sheet->setCellValue('A1', 'Élève');
  1720. $sheet->setCellValue('B1', 'Sexe');
  1721. $sheet->setCellValue('C1', 'Age');
  1722. $colIndex = 4;
  1723. foreach ($courses as $courseId => $courseName) {
  1724. $sheet->setCellValueByColumnAndRow($colIndex, 1, $courseName);
  1725. $colIndex++;
  1726. }
  1727. $sheet->setCellValueByColumnAndRow($colIndex, 1, 'MOYENNE');
  1728. $rowIndex = 2;
  1729. foreach ($result as $studentData) {
  1730. $sheet->setCellValueByColumnAndRow(1, $rowIndex, $studentData['student']);
  1731. $genderLabel = ((int) $studentData['gender'] === 1) ? 'Femme' : 'Homme';
  1732. $sheet->setCellValueByColumnAndRow(2, $rowIndex, $genderLabel);
  1733. $sheet->setCellValueByColumnAndRow(3, $rowIndex, $studentData['birthday']);
  1734. $colIndex = 4;
  1735. $totalMarkCoef = 0;
  1736. $totalCoef = 0;
  1737. foreach ($courses as $courseId => $courseName) {
  1738. $data = $studentData[$courseId] ?? ['value' => 0, 'weight' => 0, 'coef' => 0];
  1739. $mark = $data['value'] * $data['weight'];
  1740. $coef = $data['coef'];
  1741. $totalMarkCoef += $mark * $coef;
  1742. $totalCoef += $coef;
  1743. $sheet->setCellValueByColumnAndRow($colIndex, $rowIndex, $mark);
  1744. $colIndex++;
  1745. }
  1746. $moy = $totalCoef > 0 ? $totalMarkCoef / $totalCoef : '#';
  1747. $sheet->setCellValueByColumnAndRow($colIndex, $rowIndex, $moy);
  1748. $rowIndex++;
  1749. }
  1750. $writer = new Xlsx($spreadsheet);
  1751. $filename = 'recapitulatif_' . $room->getName() . '_' . date('Ymd_His') . '.xlsx';
  1752. $response = new StreamedResponse(function () use ($writer) {
  1753. $writer->save('php://output');
  1754. });
  1755. $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  1756. $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
  1757. $response->headers->set('Cache-Control', 'max-age=0');
  1758. return $response;
  1759. }
  1760. /**
  1761. * @Route("/classroom/insolvents", name="admin_classroom_insolvents")
  1762. */
  1763. public function listInsolventStudents(): Response
  1764. {
  1765. $year = $this->schoolYearService->sessionYearById();
  1766. $subscriptions = $this->subRepo->findBy(['schoolYear' => $year], ['classRoom' => 'ASC']);
  1767. $insolventSub = [];
  1768. foreach ($subscriptions as $sub) {
  1769. if ($year->paymentThresholdAmount($sub->getClassRoom()) > $sub->getStudent()->getPaymentsSum($year)) {
  1770. $insolventSub[] = $sub;
  1771. }
  1772. }
  1773. $html = $this->render('school_year/templating/insolvent_students_list.html.twig', [
  1774. 'students' => $insolventSub,
  1775. 'year' => $year,
  1776. ]);
  1777. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  1778. 'Content-Type' => 'application/pdf',
  1779. 'Content-Disposition' => 'inline; filename="insolvent_student_' . $year->getCode() . '.pdf"',
  1780. ]);
  1781. }
  1782. /**
  1783. * BUG FIX : $id non défini → utiliser $room directement
  1784. *
  1785. * @Route("/classroom/{id}", name="class_room_stats")
  1786. */
  1787. public function showClassRoomStats(ClassRoomRepository $classRoomRepository, ClassRoom $room): Response
  1788. {
  1789. $successfulCount = $classRoomRepository->countSuccessfulStudentsForClass($room);
  1790. $unsuccessfulCount = $classRoomRepository->countUnsuccessfulStudentsForClass($room);
  1791. $mentionStatistics = $classRoomRepository->getMentionStatisticsForClass($room);
  1792. return $this->render('class_room/stats.html.twig', [
  1793. 'classRoom' => $room,
  1794. 'successfulCount' => $successfulCount,
  1795. 'unsuccessfulCount' => $unsuccessfulCount,
  1796. 'mentionStatistics' => $mentionStatistics,
  1797. ]);
  1798. }
  1799. /**
  1800. * @Route("/classroom/{id}/insolvent", name="admin_classroom_insolvent")
  1801. */
  1802. public function listInsolventStudentsByRoom(ClassRoom $room): Response
  1803. {
  1804. $year = $this->schoolYearService->sessionYearById();
  1805. $subscriptions = $this->subRepo->findBy(['schoolYear' => $year, 'classRoom' => $room]);
  1806. $students = [];
  1807. $dueAmounts = [];
  1808. foreach ($subscriptions as $sub) {
  1809. if ($year->paymentThresholdAmount($room) > $sub->getStudent()->getPaymentsSum($year)) {
  1810. $students[] = $sub->getStudent();
  1811. $dueAmounts[$sub->getStudent()->getId()] = $year->paymentThresholdAmount($room) - $sub->getStudent()->getPaymentsSum($year);
  1812. }
  1813. }
  1814. $html = $this->render('classroom/templating/insolvent_student_list.html.twig', [
  1815. 'room' => $room,
  1816. 'students' => $students,
  1817. 'year' => $year,
  1818. 'amounts' => $dueAmounts,
  1819. ]);
  1820. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  1821. 'Content-Type' => 'application/pdf',
  1822. 'Content-Disposition' => 'inline; filename="insolvent_student_' . $room->getName() . '.pdf"',
  1823. ]);
  1824. }
  1825. /**
  1826. * @Route("/insolventspercentage", name="admin_classroom_insolvents_percentage")
  1827. */
  1828. public function insolventStudentsRate(): Response
  1829. {
  1830. $year = $this->schoolYearService->sessionYearById();
  1831. $paymentPlan = $year->getPaymentPlan();
  1832. $rooms = $this->repo->findAll();
  1833. $rates = [];
  1834. foreach ($rooms as $room) {
  1835. $subscriptions = $this->subRepo->findBy(['schoolYear' => $year, 'classRoom' => $room]);
  1836. $installments = $this->instRepo->findBy(['classRoom' => $room, 'paymentPlan' => $paymentPlan]);
  1837. $sum = 0;
  1838. foreach ($installments as $installment) {
  1839. $sum += $installment->getAmount();
  1840. }
  1841. $ratesByRoom = [];
  1842. foreach ($subscriptions as $sub) {
  1843. $ratesByRoom[] = 100 * $sub->getStudent()->getPaymentsSum($year) / $sum;
  1844. }
  1845. $rates[$room->getName()] = count($ratesByRoom) > 0 ? array_sum($ratesByRoom) / count($ratesByRoom) : 0;
  1846. }
  1847. $html = $this->render('school_year/templating/recovery_rates_by_room.html.twig', [
  1848. 'rates' => $rates,
  1849. 'year' => $year,
  1850. ]);
  1851. return new Response($this->pdf->getOutputFromHtml($html), 200, [
  1852. 'Content-Type' => 'application/pdf',
  1853. 'Content-Disposition' => 'inline; filename="insolvent_student_' . $year->getCode() . '.pdf"',
  1854. ]);
  1855. }
  1856. }