merge prod to main
This commit is contained in:
Regular → Executable
+203
-11
@@ -28,6 +28,7 @@ use App\Models\PlacementLevelModel;
|
||||
use App\Models\PlacementBatchModel;
|
||||
use App\Models\PlacementScoreModel;
|
||||
use App\Models\GradingLockModel;
|
||||
use App\Services\NavbarService;
|
||||
|
||||
//use App\Models\ScoreModel;
|
||||
|
||||
@@ -277,7 +278,7 @@ class GradingController extends Controller
|
||||
$semEsc = $this->db->escape($semester);
|
||||
$yrEsc = $this->db->escape($schoolYear);
|
||||
|
||||
$rows = $this->buildGradingRows($semEsc, $yrEsc, $schoolYear);
|
||||
$rows = $this->buildGradingRows($semEsc, $yrEsc, $schoolYear, $semester);
|
||||
|
||||
// Preload quiz/homework/project/participation/midterm score counts to distinguish true zeros from empty scores
|
||||
$quizCounts = [];
|
||||
@@ -423,7 +424,7 @@ class GradingController extends Controller
|
||||
}
|
||||
}
|
||||
// Reload rows after refresh
|
||||
$rows = $this->buildGradingRows($semEsc, $yrEsc, $schoolYear);
|
||||
$rows = $this->buildGradingRows($semEsc, $yrEsc, $schoolYear, $semester);
|
||||
}
|
||||
|
||||
// Build structures keyed by BUSINESS section id
|
||||
@@ -1079,19 +1080,22 @@ class GradingController extends Controller
|
||||
$schoolYears = $this->getSchoolYearsForScores($schoolYear);
|
||||
$rows = $this->fetchBelowSixtyRows($schoolYear, $semester);
|
||||
|
||||
$canViewGrading = $this->userHasMenuUrl('grading');
|
||||
|
||||
return view('grading/below_sixty', [
|
||||
'rows' => $rows,
|
||||
'semester' => $semester,
|
||||
'schoolYear' => $schoolYear,
|
||||
'schoolYears' => $schoolYears,
|
||||
'canViewGrading' => $canViewGrading,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBelowSixtyEmail()
|
||||
public function editBelowSixtyEmail()
|
||||
{
|
||||
$studentId = (int)$this->request->getPost('student_id');
|
||||
$semester = trim((string)$this->request->getPost('semester'));
|
||||
$schoolYear = trim((string)$this->request->getPost('school_year'));
|
||||
$studentId = (int)$this->request->getGet('student_id');
|
||||
$semester = trim((string)$this->request->getGet('semester'));
|
||||
$schoolYear = trim((string)$this->request->getGet('school_year'));
|
||||
|
||||
if ($studentId <= 0 || $semester === '' || $schoolYear === '') {
|
||||
return redirect()->back()->with('error', 'Missing student or term.');
|
||||
@@ -1103,6 +1107,65 @@ class GradingController extends Controller
|
||||
}
|
||||
|
||||
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
|
||||
$subject = $this->buildBelowSixtySubject($studentName, $semester, $schoolYear);
|
||||
$parentName = $this->fetchBelowSixtyParentName($studentId);
|
||||
|
||||
$scores = [
|
||||
'homework_avg' => $row['homework_avg'] ?? null,
|
||||
'project_avg' => $row['project_avg'] ?? null,
|
||||
'participation_score' => $row['participation_score'] ?? null,
|
||||
'test_avg' => $row['test_avg'] ?? null,
|
||||
'ptap_score' => $row['ptap_score'] ?? null,
|
||||
'attendance_score' => $row['attendance_score'] ?? null,
|
||||
'midterm_exam_score' => $row['midterm_exam_score'] ?? null,
|
||||
'semester_score' => $row['semester_score'] ?? null,
|
||||
];
|
||||
|
||||
$emailData = [
|
||||
'title' => $subject,
|
||||
'parent_name' => $parentName,
|
||||
'student_name' => $studentName !== '' ? $studentName : 'your student',
|
||||
'class_section_name' => $row['class_section_name'] ?? '',
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'scores' => $scores,
|
||||
'comment' => $row['comment'] ?? '',
|
||||
'sent_at' => utc_now(),
|
||||
];
|
||||
|
||||
$html = view('emails/below_sixty_performance', $emailData, ['saveData' => true]);
|
||||
|
||||
return view('grading/below_sixty_email_editor', [
|
||||
'studentId' => $studentId,
|
||||
'studentName' => $studentName,
|
||||
'semester' => $semester,
|
||||
'schoolYear' => $schoolYear,
|
||||
'subject' => $subject,
|
||||
'html' => $html,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBelowSixtyEmail()
|
||||
{
|
||||
$studentId = (int)$this->request->getPost('student_id');
|
||||
$semester = trim((string)$this->request->getPost('semester'));
|
||||
$schoolYear = trim((string)$this->request->getPost('school_year'));
|
||||
$subjectInput = trim((string)$this->request->getPost('subject'));
|
||||
$htmlInput = (string)($this->request->getPost('html') ?? '');
|
||||
|
||||
if ($studentId <= 0 || $semester === '' || $schoolYear === '') {
|
||||
return redirect()->back()->with('error', 'Missing student or term.');
|
||||
}
|
||||
|
||||
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
||||
if (empty($row)) {
|
||||
return redirect()->back()->with('error', 'Student record not found for the selected term.');
|
||||
}
|
||||
|
||||
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
|
||||
$subject = $subjectInput !== ''
|
||||
? $subjectInput
|
||||
: $this->buildBelowSixtySubject($studentName, $semester, $schoolYear);
|
||||
$scores = [
|
||||
'homework_avg' => $row['homework_avg'] ?? null,
|
||||
'project_avg' => $row['project_avg'] ?? null,
|
||||
@@ -1122,8 +1185,13 @@ class GradingController extends Controller
|
||||
'school_year' => $schoolYear,
|
||||
'scores' => $scores,
|
||||
'comment' => $row['comment'] ?? '',
|
||||
'subject' => $subject,
|
||||
];
|
||||
|
||||
if (trim($htmlInput) !== '') {
|
||||
$payload['html'] = $htmlInput;
|
||||
}
|
||||
|
||||
Events::trigger('below60.email', $payload);
|
||||
|
||||
return redirect()->back()->with('status', 'Email sent to parent(s).');
|
||||
@@ -1148,6 +1216,14 @@ class GradingController extends Controller
|
||||
|
||||
$flagModel = new CurrentFlagModel();
|
||||
$semKey = strtolower(trim($semester));
|
||||
$redirectUrl = base_url('grading/below-60');
|
||||
$query = http_build_query([
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
]);
|
||||
if ($query !== '') {
|
||||
$redirectUrl .= '?' . $query;
|
||||
}
|
||||
|
||||
$existing = $flagModel
|
||||
->where('student_id', $studentId)
|
||||
@@ -1158,11 +1234,14 @@ class GradingController extends Controller
|
||||
|
||||
$userId = (int)(session()->get('user_id') ?? 0) ?: null;
|
||||
$now = utc_now();
|
||||
$ok = true;
|
||||
|
||||
if ($existing) {
|
||||
$data = [
|
||||
'flag_state' => $status,
|
||||
'flag_datetime' => $now,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
if ($status === 'Open') {
|
||||
@@ -1178,7 +1257,7 @@ class GradingController extends Controller
|
||||
$data['close_description'] = trim($prev . PHP_EOL . $note);
|
||||
}
|
||||
}
|
||||
$flagModel->update((int)$existing['id'], $data);
|
||||
$ok = (bool) $flagModel->update((int)$existing['id'], $data);
|
||||
} else {
|
||||
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
||||
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
|
||||
@@ -1201,10 +1280,21 @@ class GradingController extends Controller
|
||||
$data['updated_by_closed'] = $userId;
|
||||
if ($note !== '') $data['close_description'] = $note;
|
||||
}
|
||||
$flagModel->insert($data);
|
||||
$ok = (bool) $flagModel->insert($data);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('status', 'Status updated.');
|
||||
if (!$ok) {
|
||||
log_message('error', 'updateBelowSixtyStatus failed', [
|
||||
'student_id' => $studentId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'status' => $status,
|
||||
'errors' => $flagModel->errors(),
|
||||
]);
|
||||
return redirect()->to($redirectUrl)->with('error', 'Failed to update status.');
|
||||
}
|
||||
|
||||
return redirect()->to($redirectUrl)->with('status', 'Status updated.');
|
||||
}
|
||||
|
||||
public function scheduleBelowSixty()
|
||||
@@ -1487,7 +1577,7 @@ class GradingController extends Controller
|
||||
* @param string $schoolYear Raw school year value for filtering student_class
|
||||
* @return array
|
||||
*/
|
||||
private function buildGradingRows(string $semEsc, string $yrEsc, string $schoolYear): array
|
||||
private function buildGradingRows(string $semEsc, string $yrEsc, string $schoolYear, string $semesterRaw): array
|
||||
{
|
||||
$builder = $this->db->table('student_class sc')
|
||||
->select([
|
||||
@@ -1515,6 +1605,7 @@ class GradingController extends Controller
|
||||
'ss_b.class_section_id AS matched_biz_csid',
|
||||
'ss_p.class_section_id AS matched_pk_csid'
|
||||
])
|
||||
->distinct()
|
||||
->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left')
|
||||
->join('students s', 's.id = sc.student_id', 'inner')
|
||||
->join(
|
||||
@@ -1729,9 +1820,10 @@ class GradingController extends Controller
|
||||
}
|
||||
|
||||
$statusMap = [];
|
||||
$noteMap = [];
|
||||
if (!empty($studentIds)) {
|
||||
$flagRows = $this->db->table('current_flag')
|
||||
->select('student_id, flag_state')
|
||||
->select('student_id, flag_state, open_description, close_description')
|
||||
->where('flag', 'grade')
|
||||
->where('school_year', $schoolYear)
|
||||
->where("LOWER(TRIM(semester))", $semesterKey)
|
||||
@@ -1742,6 +1834,12 @@ class GradingController extends Controller
|
||||
$sid = (int)($row['student_id'] ?? 0);
|
||||
if ($sid <= 0) continue;
|
||||
$statusMap[$sid] = (string)($row['flag_state'] ?? '');
|
||||
$openNote = trim((string)($row['open_description'] ?? ''));
|
||||
$closeNote = trim((string)($row['close_description'] ?? ''));
|
||||
$noteMap[$sid] = [
|
||||
'open' => $openNote,
|
||||
'closed' => $closeNote,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1750,6 +1848,15 @@ class GradingController extends Controller
|
||||
$row['comment'] = $commentMap[$sid] ?? '';
|
||||
$flagState = strtolower(trim((string)($statusMap[$sid] ?? '')));
|
||||
$row['status'] = ($flagState === 'closed' || $flagState === 'canceled') ? 'Closed' : 'Open';
|
||||
$noteBag = $noteMap[$sid] ?? ['open' => '', 'closed' => ''];
|
||||
$rawNote = $row['status'] === 'Closed' ? (string)$noteBag['closed'] : (string)$noteBag['open'];
|
||||
if ($rawNote !== '') {
|
||||
$lines = preg_split('/\R/', $rawNote);
|
||||
$lines = array_values(array_filter(array_map('trim', $lines), static fn($val) => $val !== ''));
|
||||
$row['note'] = $lines ? end($lines) : '';
|
||||
} else {
|
||||
$row['note'] = '';
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
@@ -1799,6 +1906,91 @@ class GradingController extends Controller
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function userHasMenuUrl(string $needle): bool
|
||||
{
|
||||
$needle = strtolower(trim($needle));
|
||||
if ($needle === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rawRole = session()->get('role');
|
||||
$roles = is_array($rawRole) ? $rawRole : [$rawRole ?? 'guest'];
|
||||
$roles = array_values(array_filter(array_map('strval', $roles)));
|
||||
if (empty($roles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = new NavbarService();
|
||||
$menu = $service->getMenuForRoles($roles);
|
||||
if (empty($menu)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalize = static function (string $url) use ($needle): string {
|
||||
$url = strtolower(trim($url));
|
||||
if ($url === '') return '';
|
||||
$url = preg_replace('#^https?://[^/]+/#i', '', $url);
|
||||
$url = ltrim($url, '/');
|
||||
return $url;
|
||||
};
|
||||
|
||||
$target = $normalize($needle);
|
||||
$stack = $menu;
|
||||
while (!empty($stack)) {
|
||||
$node = array_shift($stack);
|
||||
if (!empty($node['url'])) {
|
||||
$url = $normalize((string)$node['url']);
|
||||
if ($url !== '' && $url === $target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!empty($node['children']) && is_array($node['children'])) {
|
||||
foreach ($node['children'] as $child) {
|
||||
$stack[] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function fetchBelowSixtyParentName(int $studentId): string
|
||||
{
|
||||
$parentName = 'Parent/Guardian';
|
||||
try {
|
||||
$rows = $this->db->query(
|
||||
"SELECT u.firstname, u.lastname
|
||||
FROM family_students fs
|
||||
JOIN family_guardians fg ON fg.family_id = fs.family_id
|
||||
JOIN users u ON u.id = fg.user_id
|
||||
WHERE fs.student_id = ?
|
||||
ORDER BY fg.is_primary DESC, u.lastname, u.firstname
|
||||
LIMIT 1",
|
||||
[$studentId]
|
||||
)->getResultArray();
|
||||
if (!empty($rows[0])) {
|
||||
$candidate = trim((string)($rows[0]['firstname'] ?? '') . ' ' . (string)($rows[0]['lastname'] ?? ''));
|
||||
if ($candidate !== '') {
|
||||
$parentName = $candidate;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return $parentName;
|
||||
}
|
||||
|
||||
private function buildBelowSixtySubject(string $studentName, string $semester, string $schoolYear): string
|
||||
{
|
||||
$subject = 'Student Performance Alert';
|
||||
if ($studentName !== '') {
|
||||
$subject .= ' — ' . $studentName;
|
||||
}
|
||||
if ($semester !== '' || $schoolYear !== '') {
|
||||
$subject .= ' (' . trim($semester . ' ' . $schoolYear) . ')';
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
private function fetchBelowSixtyMeetingContext(int $studentId, string $schoolYear, string $semester): array
|
||||
{
|
||||
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
||||
|
||||
Reference in New Issue
Block a user