fix tests
This commit is contained in:
@@ -2,16 +2,14 @@
|
||||
|
||||
namespace App\Services\Grading;
|
||||
|
||||
use App\Support\MailHtml;
|
||||
use App\Services\EmailService;
|
||||
use App\Support\MailHtml;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BelowSixtyEmailService
|
||||
{
|
||||
public function __construct(private EmailService $emailService)
|
||||
{
|
||||
}
|
||||
public function __construct(private EmailService $emailService) {}
|
||||
|
||||
public function send(array $payload): array
|
||||
{
|
||||
@@ -25,6 +23,7 @@ class BelowSixtyEmailService
|
||||
|
||||
if ($studentId <= 0) {
|
||||
Log::warning('Below60Email: missing student_id');
|
||||
|
||||
return ['ok' => false, 'message' => 'Missing student id.'];
|
||||
}
|
||||
|
||||
@@ -42,6 +41,7 @@ class BelowSixtyEmailService
|
||||
|
||||
if (empty($rows)) {
|
||||
Log::warning('Below60Email: no guardian emails', ['student_id' => $studentId]);
|
||||
|
||||
return ['ok' => false, 'message' => 'No guardian email addresses.'];
|
||||
}
|
||||
|
||||
@@ -57,17 +57,17 @@ class BelowSixtyEmailService
|
||||
$primary = $rows[0] ?? null;
|
||||
$parentName = '';
|
||||
if ($primary) {
|
||||
$parentName = trim((string) ($primary->firstname ?? '') . ' ' . (string) ($primary->lastname ?? ''));
|
||||
$parentName = trim((string) ($primary->firstname ?? '').' '.(string) ($primary->lastname ?? ''));
|
||||
}
|
||||
|
||||
$subject = (string) ($payload['subject'] ?? '');
|
||||
if ($subject === '') {
|
||||
$subject = 'Student Performance Alert';
|
||||
if ($studentName !== '') {
|
||||
$subject .= ' — ' . $studentName;
|
||||
$subject .= ' — '.$studentName;
|
||||
}
|
||||
if ($semester !== '' || $schoolYear !== '') {
|
||||
$subject .= ' (' . trim($semester . ' ' . $schoolYear) . ')';
|
||||
$subject .= ' ('.trim($semester.' '.$schoolYear).')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Models\BelowSixtyDecision;
|
||||
use App\Models\CurrentFlag;
|
||||
use App\Models\ParentMeetingSchedule;
|
||||
use App\Models\ScoreComment;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentDecision;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
@@ -58,7 +57,7 @@ class GradingBelowSixtyService
|
||||
$studentIds = array_values(array_filter($studentIds, static fn ($id) => $id > 0));
|
||||
|
||||
$commentMap = [];
|
||||
if (!empty($studentIds)) {
|
||||
if (! empty($studentIds)) {
|
||||
$commentRows = ScoreComment::query()
|
||||
->select('student_id', 'comment', 'created_at')
|
||||
->where('score_type', 'general')
|
||||
@@ -69,14 +68,14 @@ class GradingBelowSixtyService
|
||||
->get();
|
||||
foreach ($commentRows as $row) {
|
||||
$sid = (int) $row->student_id;
|
||||
if ($sid > 0 && !isset($commentMap[$sid])) {
|
||||
if ($sid > 0 && ! isset($commentMap[$sid])) {
|
||||
$commentMap[$sid] = (string) ($row->comment ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statusMap = [];
|
||||
if (!empty($studentIds)) {
|
||||
if (! empty($studentIds)) {
|
||||
$flagRows = CurrentFlag::query()
|
||||
->select('student_id', 'flag_state')
|
||||
->where('flag', 'grade')
|
||||
@@ -110,7 +109,7 @@ class GradingBelowSixtyService
|
||||
throw new RuntimeException('Student record not found for the selected term.');
|
||||
}
|
||||
|
||||
$studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
||||
$studentName = trim((string) ($row['firstname'] ?? '').' '.(string) ($row['lastname'] ?? ''));
|
||||
$subject = $this->buildBelowSixtySubject($studentName, $semester, $schoolYear);
|
||||
$parentName = $this->fetchBelowSixtyParentName($studentId);
|
||||
|
||||
@@ -146,7 +145,7 @@ class GradingBelowSixtyService
|
||||
public function updateStatus(int $studentId, string $semester, string $schoolYear, string $status, string $note, ?int $userId): void
|
||||
{
|
||||
$status = ucfirst(strtolower($status));
|
||||
if (!in_array($status, ['Open', 'Closed'], true)) {
|
||||
if (! in_array($status, ['Open', 'Closed'], true)) {
|
||||
throw new RuntimeException('Invalid status.');
|
||||
}
|
||||
|
||||
@@ -170,21 +169,22 @@ class GradingBelowSixtyService
|
||||
$data['updated_by_open'] = $userId;
|
||||
if ($note !== '') {
|
||||
$prev = (string) ($existing->open_description ?? '');
|
||||
$data['open_description'] = trim($prev . PHP_EOL . $note);
|
||||
$data['open_description'] = trim($prev.PHP_EOL.$note);
|
||||
}
|
||||
} else {
|
||||
$data['updated_by_closed'] = $userId;
|
||||
if ($note !== '') {
|
||||
$prev = (string) ($existing->close_description ?? '');
|
||||
$data['close_description'] = trim($prev . PHP_EOL . $note);
|
||||
$data['close_description'] = trim($prev.PHP_EOL.$note);
|
||||
}
|
||||
}
|
||||
$existing->update($data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
||||
$studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
||||
$studentName = trim((string) ($row['firstname'] ?? '').' '.(string) ($row['lastname'] ?? ''));
|
||||
$grade = (string) ($row['class_section_name'] ?? '');
|
||||
$data = [
|
||||
'student_id' => $studentId,
|
||||
@@ -298,7 +298,7 @@ class GradingBelowSixtyService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($studentMap[$studentId])) {
|
||||
if (! isset($studentMap[$studentId])) {
|
||||
$studentMap[$studentId] = [
|
||||
'student_id' => $studentId,
|
||||
'school_id' => $row->school_id,
|
||||
@@ -355,7 +355,7 @@ class GradingBelowSixtyService
|
||||
$certificateMap = [];
|
||||
foreach ($certificateRows as $row) {
|
||||
$studentId = (int) ($row->student_id ?? 0);
|
||||
if ($studentId > 0 && !isset($certificateMap[$studentId])) {
|
||||
if ($studentId > 0 && ! isset($certificateMap[$studentId])) {
|
||||
$certificateMap[$studentId] = (string) ($row->certificate_number ?? '');
|
||||
}
|
||||
}
|
||||
@@ -522,8 +522,8 @@ class GradingBelowSixtyService
|
||||
}
|
||||
|
||||
$subject = 'Whole Year Academic Decision — '
|
||||
. (string) ($context['student_name'] ?? 'Student')
|
||||
. ' (' . $schoolYear . ')';
|
||||
.(string) ($context['student_name'] ?? 'Student')
|
||||
.' ('.$schoolYear.')';
|
||||
|
||||
return [
|
||||
'student_id' => $studentId,
|
||||
@@ -553,8 +553,8 @@ class GradingBelowSixtyService
|
||||
$resolvedSubject = trim((string) $subject);
|
||||
if ($resolvedSubject === '') {
|
||||
$resolvedSubject = 'Whole Year Academic Decision — '
|
||||
. (string) ($context['student_name'] ?? 'Student')
|
||||
. ' (' . $schoolYear . ')';
|
||||
.(string) ($context['student_name'] ?? 'Student')
|
||||
.' ('.$schoolYear.')';
|
||||
}
|
||||
|
||||
$payload = [
|
||||
@@ -628,7 +628,7 @@ class GradingBelowSixtyService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($studentMap[$studentId])) {
|
||||
if (! isset($studentMap[$studentId])) {
|
||||
$studentMap[$studentId] = [
|
||||
'school_id' => $row->school_id,
|
||||
'firstname' => $row->firstname,
|
||||
@@ -661,7 +661,7 @@ class GradingBelowSixtyService
|
||||
if ($studentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
|
||||
if (! isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
|
||||
$belowMap[$studentId] = $row;
|
||||
}
|
||||
}
|
||||
@@ -754,7 +754,7 @@ class GradingBelowSixtyService
|
||||
|
||||
return [
|
||||
'rows' => $rows,
|
||||
'generated' => !empty($savedMap),
|
||||
'generated' => ! empty($savedMap),
|
||||
'school_year' => $schoolYear,
|
||||
];
|
||||
}
|
||||
@@ -789,7 +789,7 @@ class GradingBelowSixtyService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($studentMap[$studentId])) {
|
||||
if (! isset($studentMap[$studentId])) {
|
||||
$studentMap[$studentId] = [
|
||||
'firstname' => $row->firstname,
|
||||
'lastname' => $row->lastname,
|
||||
@@ -818,7 +818,7 @@ class GradingBelowSixtyService
|
||||
if ($studentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
|
||||
if (! isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
|
||||
$belowMap[$studentId] = $row;
|
||||
}
|
||||
}
|
||||
@@ -912,7 +912,7 @@ class GradingBelowSixtyService
|
||||
->whereRaw('LOWER(TRIM(ss.semester)) = ?', [$semesterKey])
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
if (! $row) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -972,7 +972,7 @@ class GradingBelowSixtyService
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($semesters)) {
|
||||
if (! empty($semesters)) {
|
||||
$commentRows = DB::table('score_comments')
|
||||
->select('semester', 'score_type', 'comment', 'created_at')
|
||||
->where('student_id', $studentId)
|
||||
@@ -987,7 +987,7 @@ class GradingBelowSixtyService
|
||||
foreach ($commentRows as $row) {
|
||||
$semester = ucfirst(strtolower(trim((string) ($row->semester ?? ''))));
|
||||
$type = strtolower(trim((string) ($row->score_type ?? 'general')));
|
||||
if (isset($semesters[$semester]) && !isset($semesters[$semester]['comments'][$type])) {
|
||||
if (isset($semesters[$semester]) && ! isset($semesters[$semester]['comments'][$type])) {
|
||||
$semesters[$semester]['comments'][$type] = (string) ($row->comment ?? '');
|
||||
}
|
||||
}
|
||||
@@ -1001,23 +1001,24 @@ class GradingBelowSixtyService
|
||||
$parentName = 'Parent/Guardian';
|
||||
try {
|
||||
$rows = DB::select(
|
||||
"SELECT u.firstname, u.lastname
|
||||
'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",
|
||||
LIMIT 1',
|
||||
[$studentId]
|
||||
);
|
||||
if (!empty($rows[0])) {
|
||||
$candidate = trim((string) ($rows[0]->firstname ?? '') . ' ' . (string) ($rows[0]->lastname ?? ''));
|
||||
if (! empty($rows[0])) {
|
||||
$candidate = trim((string) ($rows[0]->firstname ?? '').' '.(string) ($rows[0]->lastname ?? ''));
|
||||
if ($candidate !== '') {
|
||||
$parentName = $candidate;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
return $parentName;
|
||||
}
|
||||
|
||||
@@ -1041,7 +1042,7 @@ class GradingBelowSixtyService
|
||||
];
|
||||
}
|
||||
|
||||
$studentName = trim((string) ($student->firstname ?? '') . ' ' . (string) ($student->lastname ?? ''));
|
||||
$studentName = trim((string) ($student->firstname ?? '').' '.(string) ($student->lastname ?? ''));
|
||||
if ($studentName === '') {
|
||||
$studentName = 'Student';
|
||||
}
|
||||
@@ -1140,22 +1141,22 @@ class GradingBelowSixtyService
|
||||
$html = '
|
||||
<p>Dear Parent/Guardian,</p>
|
||||
<p>
|
||||
This message is regarding <strong>' . e($studentName) . '</strong>
|
||||
for the <strong>' . e($schoolYear) . '</strong> school year.
|
||||
This message is regarding <strong>'.e($studentName).'</strong>
|
||||
for the <strong>'.e($schoolYear).'</strong> school year.
|
||||
</p>
|
||||
<p>
|
||||
Class: <strong>' . e($classSectionName !== '' ? $classSectionName : 'N/A') . '</strong><br>
|
||||
Fall Score: <strong>' . e($fallText) . '</strong><br>
|
||||
Spring Score: <strong>' . e($springText) . '</strong><br>
|
||||
Whole-Year Score: <strong>' . e($yearText) . '</strong><br>
|
||||
Decision: <strong>' . e($decision) . '</strong>
|
||||
Class: <strong>'.e($classSectionName !== '' ? $classSectionName : 'N/A').'</strong><br>
|
||||
Fall Score: <strong>'.e($fallText).'</strong><br>
|
||||
Spring Score: <strong>'.e($springText).'</strong><br>
|
||||
Whole-Year Score: <strong>'.e($yearText).'</strong><br>
|
||||
Decision: <strong>'.e($decision).'</strong>
|
||||
</p>';
|
||||
|
||||
if ($notes !== '') {
|
||||
$html .= '
|
||||
<p>
|
||||
<strong>Decision Notes:</strong><br>
|
||||
' . nl2br(e($notes)) . '
|
||||
'.nl2br(e($notes)).'
|
||||
</p>';
|
||||
}
|
||||
|
||||
@@ -1312,9 +1313,9 @@ class GradingBelowSixtyService
|
||||
$classSectionName = trim((string) ($semester['class_section_name'] ?? ''));
|
||||
$html .= '
|
||||
<p style="margin-top:16px;margin-bottom:6px;">
|
||||
<strong>' . e($semesterName) . ' Semester</strong>';
|
||||
<strong>'.e($semesterName).' Semester</strong>';
|
||||
if ($classSectionName !== '') {
|
||||
$html .= ' — ' . e($classSectionName);
|
||||
$html .= ' — '.e($classSectionName);
|
||||
}
|
||||
$html .= '
|
||||
</p>
|
||||
@@ -1329,7 +1330,7 @@ class GradingBelowSixtyService
|
||||
|
||||
$hasScoreRow = false;
|
||||
foreach ($scoreLabels as $key => $label) {
|
||||
if (!array_key_exists($key, $semester)) {
|
||||
if (! array_key_exists($key, $semester)) {
|
||||
continue;
|
||||
}
|
||||
$value = $semester[$key];
|
||||
@@ -1340,13 +1341,13 @@ class GradingBelowSixtyService
|
||||
$fontWeight = $key === 'semester_score' ? 'font-weight:bold;' : '';
|
||||
$html .= '
|
||||
<tr>
|
||||
<td>' . e($label) . '</td>
|
||||
<td align="right" style="' . $fontWeight . '">' . e($scoreText) . '</td>
|
||||
<td>'.e($label).'</td>
|
||||
<td align="right" style="'.$fontWeight.'">'.e($scoreText).'</td>
|
||||
</tr>';
|
||||
$hasScoreRow = true;
|
||||
}
|
||||
|
||||
if (!$hasScoreRow) {
|
||||
if (! $hasScoreRow) {
|
||||
$html .= '
|
||||
<tr>
|
||||
<td colspan="2">No scores recorded.</td>
|
||||
@@ -1358,7 +1359,7 @@ class GradingBelowSixtyService
|
||||
</table>';
|
||||
|
||||
$comments = is_array($semester['comments'] ?? null) ? $semester['comments'] : [];
|
||||
if (!empty($comments)) {
|
||||
if (! empty($comments)) {
|
||||
$deduped = [];
|
||||
$seen = [];
|
||||
foreach ($comments as $type => $text) {
|
||||
@@ -1367,7 +1368,7 @@ class GradingBelowSixtyService
|
||||
continue;
|
||||
}
|
||||
$label = $commentTypeLabels[$type] ?? (string) $type;
|
||||
$hash = $label . '|' . $text;
|
||||
$hash = $label.'|'.$text;
|
||||
if (isset($seen[$hash])) {
|
||||
continue;
|
||||
}
|
||||
@@ -1375,13 +1376,13 @@ class GradingBelowSixtyService
|
||||
$deduped[] = ['label' => $label, 'text' => $text];
|
||||
}
|
||||
|
||||
if (!empty($deduped)) {
|
||||
if (! empty($deduped)) {
|
||||
$html .= '<p style="margin:8px 0 4px;"><strong>Comments</strong></p>';
|
||||
foreach ($deduped as $comment) {
|
||||
$html .= '
|
||||
<p style="margin:4px 0 8px;padding:8px;background:#f8f9fa;border-left:4px solid #999;">
|
||||
<strong>' . e($comment['label']) . ':</strong><br>
|
||||
' . nl2br(e($comment['text'])) . '
|
||||
<strong>'.e($comment['label']).':</strong><br>
|
||||
'.nl2br(e($comment['text'])).'
|
||||
</p>';
|
||||
}
|
||||
}
|
||||
@@ -1395,11 +1396,12 @@ class GradingBelowSixtyService
|
||||
{
|
||||
$subject = 'Student Performance Alert';
|
||||
if ($studentName !== '') {
|
||||
$subject .= ' — ' . $studentName;
|
||||
$subject .= ' — '.$studentName;
|
||||
}
|
||||
if ($semester !== '' || $schoolYear !== '') {
|
||||
$subject .= ' (' . trim($semester . ' ' . $schoolYear) . ')';
|
||||
$subject .= ' ('.trim($semester.' '.$schoolYear).')';
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
@@ -1414,18 +1416,18 @@ class GradingBelowSixtyService
|
||||
$parentUserId = null;
|
||||
try {
|
||||
$pRows = DB::select(
|
||||
"SELECT u.id AS user_id, u.firstname, u.lastname
|
||||
'SELECT u.id AS user_id, 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",
|
||||
LIMIT 1',
|
||||
[$studentId]
|
||||
);
|
||||
if (!empty($pRows[0])) {
|
||||
if (! empty($pRows[0])) {
|
||||
$parentUserId = (int) ($pRows[0]->user_id ?? 0) ?: null;
|
||||
$parentName = trim((string) ($pRows[0]->firstname ?? '') . ' ' . (string) ($pRows[0]->lastname ?? ''));
|
||||
$parentName = trim((string) ($pRows[0]->firstname ?? '').' '.(string) ($pRows[0]->lastname ?? ''));
|
||||
if ($parentName === '') {
|
||||
$parentName = 'Parent/Guardian';
|
||||
}
|
||||
@@ -1436,16 +1438,16 @@ class GradingBelowSixtyService
|
||||
if ($parentUserId === null) {
|
||||
try {
|
||||
$srow = DB::selectOne(
|
||||
"SELECT s.parent_id, u.firstname, u.lastname
|
||||
'SELECT s.parent_id, u.firstname, u.lastname
|
||||
FROM students s
|
||||
LEFT JOIN users u ON u.id = s.parent_id
|
||||
WHERE s.id = ?
|
||||
LIMIT 1",
|
||||
LIMIT 1',
|
||||
[$studentId]
|
||||
);
|
||||
if (!empty($srow)) {
|
||||
if (! empty($srow)) {
|
||||
$parentUserId = (int) ($srow->parent_id ?? 0) ?: null;
|
||||
$fallbackName = trim((string) ($srow->firstname ?? '') . ' ' . (string) ($srow->lastname ?? ''));
|
||||
$fallbackName = trim((string) ($srow->firstname ?? '').' '.(string) ($srow->lastname ?? ''));
|
||||
if ($fallbackName !== '') {
|
||||
$parentName = $fallbackName;
|
||||
}
|
||||
@@ -1454,7 +1456,7 @@ class GradingBelowSixtyService
|
||||
}
|
||||
}
|
||||
|
||||
$studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
||||
$studentName = trim((string) ($row['firstname'] ?? '').' '.(string) ($row['lastname'] ?? ''));
|
||||
|
||||
return [
|
||||
'student_name' => $studentName !== '' ? $studentName : 'Student',
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Services\Grading;
|
||||
|
||||
use App\Models\GradingLock;
|
||||
use App\Models\ClassSection;
|
||||
use RuntimeException;
|
||||
use App\Models\GradingLock;
|
||||
use App\Services\Grading\Policy\GradingPolicyResolver;
|
||||
use App\Services\Grading\Validation\GradebookFinalizationValidator;
|
||||
use RuntimeException;
|
||||
|
||||
class GradingLockService
|
||||
{
|
||||
@@ -14,8 +14,8 @@ class GradingLockService
|
||||
private ?GradingPolicyResolver $policyResolver = null,
|
||||
private ?GradebookFinalizationValidator $finalizationValidator = null
|
||||
) {
|
||||
$this->policyResolver = $this->policyResolver ?? new GradingPolicyResolver();
|
||||
$this->finalizationValidator = $this->finalizationValidator ?? new GradebookFinalizationValidator();
|
||||
$this->policyResolver = $this->policyResolver ?? new GradingPolicyResolver;
|
||||
$this->finalizationValidator = $this->finalizationValidator ?? new GradebookFinalizationValidator;
|
||||
}
|
||||
|
||||
public function toggle(int $classSectionId, string $semester, string $schoolYear, ?int $userId): bool
|
||||
@@ -32,6 +32,7 @@ class GradingLockService
|
||||
'locked_by' => null,
|
||||
'locked_at' => null,
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ class GradingLockService
|
||||
foreach ($sectionIds as $sid) {
|
||||
$this->assertCanLock($sid, $semester, $schoolYear);
|
||||
|
||||
if (!empty($existingBySection[$sid])) {
|
||||
if (! empty($existingBySection[$sid])) {
|
||||
if ($existingBySection[$sid]->is_locked) {
|
||||
continue;
|
||||
}
|
||||
@@ -107,6 +108,7 @@ class GradingLockService
|
||||
'locked_at' => $now,
|
||||
]);
|
||||
$count++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ class GradingLockService
|
||||
$count++;
|
||||
}
|
||||
|
||||
if (!empty($insertRows)) {
|
||||
if (! empty($insertRows)) {
|
||||
GradingLock::query()->insert($insertRows);
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ class GradingLockService
|
||||
}
|
||||
|
||||
$result = $this->finalizationValidator->validateClassSectionBeforeLock($classSectionId, $semester, $schoolYear);
|
||||
if (!$result['valid']) {
|
||||
if (! $result['valid']) {
|
||||
$count = count($result['errors']);
|
||||
throw new RuntimeException("Cannot lock class section {$classSectionId}; strong grading validation failed with {$count} error(s).");
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class GradingOverviewService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
public function __construct() {}
|
||||
|
||||
public function overview(?int $classId, ?string $semester, ?string $schoolYear): array
|
||||
{
|
||||
@@ -38,7 +36,7 @@ class GradingOverviewService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($grades[$classIdValue])) {
|
||||
if (! isset($grades[$classIdValue])) {
|
||||
$grades[$classIdValue] = [];
|
||||
}
|
||||
$exists = false;
|
||||
@@ -48,7 +46,7 @@ class GradingOverviewService
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
if (! $exists) {
|
||||
$grades[$classIdValue][] = [
|
||||
'class_section_id' => $sectionId,
|
||||
'class_section_name' => $sectionName,
|
||||
@@ -248,6 +246,7 @@ class GradingOverviewService
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return round((float) $value, 2);
|
||||
}
|
||||
|
||||
@@ -260,6 +259,7 @@ class GradingOverviewService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $score;
|
||||
}
|
||||
|
||||
@@ -271,6 +271,7 @@ class GradingOverviewService
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
|
||||
return $requestedSemester;
|
||||
}
|
||||
if ($fallbackSemester !== null && $fallbackSemester !== '') {
|
||||
@@ -279,8 +280,10 @@ class GradingOverviewService
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
|
||||
return $fallbackSemester;
|
||||
}
|
||||
|
||||
return $semesterOptions[0] ?? '';
|
||||
}
|
||||
|
||||
@@ -306,7 +309,7 @@ class GradingOverviewService
|
||||
$semesters = ['Fall', 'Spring'];
|
||||
}
|
||||
|
||||
if ($fallbackSemester !== null && $fallbackSemester !== '' && !in_array($fallbackSemester, $semesters, true)) {
|
||||
if ($fallbackSemester !== null && $fallbackSemester !== '' && ! in_array($fallbackSemester, $semesters, true)) {
|
||||
array_unshift($semesters, $fallbackSemester);
|
||||
}
|
||||
|
||||
@@ -322,6 +325,7 @@ class GradingOverviewService
|
||||
if ($norm === 'spring') {
|
||||
return 'parent_scores_released_spring';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -330,13 +334,14 @@ class GradingOverviewService
|
||||
$key = $this->getParentReleaseKey($semester);
|
||||
$raw = $key ? Configuration::getConfig($key) : null;
|
||||
$raw = (string) ($raw ?? '');
|
||||
|
||||
return in_array(strtolower(trim($raw)), ['1', 'true', 'yes', 'y', 'on'], true);
|
||||
}
|
||||
|
||||
private function ensureParentReleaseKeyExists(string $semester): void
|
||||
{
|
||||
$key = $this->getParentReleaseKey($semester);
|
||||
if (!$key) {
|
||||
if (! $key) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class GradingPlacementService
|
||||
{
|
||||
public function placementContext(?int $classSectionId, string $schoolYear, ?string $placementTest, ?string $open): array
|
||||
{
|
||||
if (!$classSectionId) {
|
||||
if (! $classSectionId) {
|
||||
$showStudents = ($placementTest !== '' && $open === '1');
|
||||
$students = $showStudents ? $this->fetchActiveStudentsWithSection($schoolYear) : [];
|
||||
$batches = $this->fetchPlacementBatches($schoolYear);
|
||||
@@ -40,7 +40,7 @@ class GradingPlacementService
|
||||
), static fn ($id) => $id > 0));
|
||||
|
||||
$levels = [];
|
||||
if (!empty($studentIds)) {
|
||||
if (! empty($studentIds)) {
|
||||
$rows = PlacementLevel::query()
|
||||
->whereIn('student_id', $studentIds)
|
||||
->where('school_year', $schoolYear)
|
||||
@@ -67,7 +67,7 @@ class GradingPlacementService
|
||||
}
|
||||
|
||||
$level = $level === '' || $level === null ? null : (int) $level;
|
||||
if ($level !== null && !in_array($level, [1, 2, 3], true)) {
|
||||
if ($level !== null && ! in_array($level, [1, 2, 3], true)) {
|
||||
throw new RuntimeException('Invalid placement level.');
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ class GradingPlacementService
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +113,7 @@ class GradingPlacementService
|
||||
|
||||
$validSet = array_flip($validIds);
|
||||
$existingRows = [];
|
||||
if (!empty($validIds)) {
|
||||
if (! empty($validIds)) {
|
||||
$rows = PlacementLevel::query()
|
||||
->whereIn('student_id', $validIds)
|
||||
->where('school_year', $schoolYear)
|
||||
@@ -124,12 +125,12 @@ class GradingPlacementService
|
||||
|
||||
foreach ($levels as $studentIdRaw => $levelRaw) {
|
||||
$studentId = (int) $studentIdRaw;
|
||||
if (!isset($validSet[$studentId])) {
|
||||
if (! isset($validSet[$studentId])) {
|
||||
continue;
|
||||
}
|
||||
$levelRaw = trim((string) $levelRaw);
|
||||
$level = $levelRaw === '' ? null : (int) $levelRaw;
|
||||
if ($level !== null && !in_array($level, [1, 2, 3], true)) {
|
||||
if ($level !== null && ! in_array($level, [1, 2, 3], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -137,6 +138,7 @@ class GradingPlacementService
|
||||
if (isset($existingRows[$studentId])) {
|
||||
$existingRows[$studentId]->delete();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -179,7 +181,7 @@ class GradingPlacementService
|
||||
$savedCount = 0;
|
||||
foreach ($levels as $studentIdRaw => $levelRaw) {
|
||||
$studentId = (int) $studentIdRaw;
|
||||
if (!isset($validSet[$studentId])) {
|
||||
if (! isset($validSet[$studentId])) {
|
||||
continue;
|
||||
}
|
||||
$levelRaw = trim((string) $levelRaw);
|
||||
@@ -212,7 +214,7 @@ class GradingPlacementService
|
||||
public function getPlacementBatch(int $batchId): array
|
||||
{
|
||||
$batch = PlacementBatch::query()->find($batchId);
|
||||
if (!$batch) {
|
||||
if (! $batch) {
|
||||
throw new RuntimeException('Placement batch not found.');
|
||||
}
|
||||
|
||||
@@ -230,7 +232,7 @@ class GradingPlacementService
|
||||
public function updatePlacementBatch(int $batchId, string $schoolYear, array $levels, ?int $userId): void
|
||||
{
|
||||
$batch = PlacementBatch::query()->find($batchId);
|
||||
if (!$batch) {
|
||||
if (! $batch) {
|
||||
throw new RuntimeException('Placement batch not found.');
|
||||
}
|
||||
|
||||
@@ -245,7 +247,7 @@ class GradingPlacementService
|
||||
|
||||
foreach ($levels as $studentIdRaw => $scoreRaw) {
|
||||
$studentId = (int) $studentIdRaw;
|
||||
if (!isset($validSet[$studentId])) {
|
||||
if (! isset($validSet[$studentId])) {
|
||||
continue;
|
||||
}
|
||||
$scoreRaw = trim((string) $scoreRaw);
|
||||
@@ -253,6 +255,7 @@ class GradingPlacementService
|
||||
if (isset($existing[$studentId])) {
|
||||
PlacementScore::query()->whereKey($existing[$studentId]['id'])->delete();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
$score = (int) $scoreRaw;
|
||||
|
||||
@@ -10,9 +10,7 @@ use RuntimeException;
|
||||
|
||||
class GradingRefreshService
|
||||
{
|
||||
public function __construct(private SemesterScoreService $semesterScoreService)
|
||||
{
|
||||
}
|
||||
public function __construct(private SemesterScoreService $semesterScoreService) {}
|
||||
|
||||
public function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
@@ -35,7 +33,7 @@ class GradingRefreshService
|
||||
|
||||
private function refreshAttendanceComments(int $classSectionId, string $semester, string $schoolYear): void
|
||||
{
|
||||
if (!function_exists('attendance_comment_from_score')) {
|
||||
if (! function_exists('attendance_comment_from_score')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\Grading;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\FinalExam;
|
||||
use App\Models\GradingLock;
|
||||
use App\Models\Homework;
|
||||
@@ -11,23 +12,20 @@ use App\Models\Quiz;
|
||||
use App\Models\ScoreComment;
|
||||
use App\Models\SemesterScore;
|
||||
use App\Models\Student;
|
||||
use App\Models\ClassSection;
|
||||
use App\Services\Grading\Validation\ScoreValueValidator;
|
||||
use App\Services\Scores\SemesterScoreService;
|
||||
use RuntimeException;
|
||||
|
||||
class GradingScoreService
|
||||
{
|
||||
public function __construct(private SemesterScoreService $semesterScoreService)
|
||||
{
|
||||
}
|
||||
public function __construct(private SemesterScoreService $semesterScoreService) {}
|
||||
|
||||
public function show(string $type, int $classSectionId, int $studentId, string $semester, string $schoolYear): array
|
||||
{
|
||||
$model = $this->resolveModel($type);
|
||||
$student = Student::query()->find($studentId);
|
||||
|
||||
if (!$student) {
|
||||
if (! $student) {
|
||||
throw new RuntimeException('Student not found.');
|
||||
}
|
||||
|
||||
@@ -73,9 +71,9 @@ class GradingScoreService
|
||||
$scores = $payload['scores'] ?? [];
|
||||
$comments = $payload['comments'] ?? [];
|
||||
|
||||
$validator = new ScoreValueValidator();
|
||||
$validator = new ScoreValueValidator;
|
||||
foreach ($scoreIds as $i => $id) {
|
||||
$normalizedScore = $validator->normalizeNullable($scores[$i] ?? null, ScoreValueValidator::DEFAULT_MAX_POINTS, ucfirst($type) . ' score');
|
||||
$normalizedScore = $validator->normalizeNullable($scores[$i] ?? null, ScoreValueValidator::DEFAULT_MAX_POINTS, ucfirst($type).' score');
|
||||
$model->newQuery()->whereKey($id)->update([
|
||||
'score' => $normalizedScore,
|
||||
'max_points' => ScoreValueValidator::DEFAULT_MAX_POINTS,
|
||||
@@ -86,8 +84,8 @@ class GradingScoreService
|
||||
}
|
||||
} elseif (in_array($type, ['midterm', 'final', 'test'], true)) {
|
||||
$score = $payload['score'] ?? null;
|
||||
$validator = new ScoreValueValidator();
|
||||
$normalizedScore = $validator->normalizeNullable($score, ScoreValueValidator::DEFAULT_MAX_POINTS, ucfirst($type) . ' score');
|
||||
$validator = new ScoreValueValidator;
|
||||
$normalizedScore = $validator->normalizeNullable($score, ScoreValueValidator::DEFAULT_MAX_POINTS, ucfirst($type).' score');
|
||||
|
||||
$data = [
|
||||
'score' => $normalizedScore,
|
||||
@@ -135,7 +133,7 @@ class GradingScoreService
|
||||
}
|
||||
|
||||
$studentTeacherInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $updatedBy);
|
||||
if (!empty($studentTeacherInfo)) {
|
||||
if (! empty($studentTeacherInfo)) {
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentTeacherInfo, $semester, $schoolYear);
|
||||
} catch (RuntimeException $e) {
|
||||
@@ -189,13 +187,13 @@ class GradingScoreService
|
||||
private function resolveModel(string $type)
|
||||
{
|
||||
return match ($type) {
|
||||
'homework' => new Homework(),
|
||||
'quiz' => new Quiz(),
|
||||
'project' => new Project(),
|
||||
'midterm' => new MidtermExam(),
|
||||
'final' => new FinalExam(),
|
||||
'test' => new SemesterScore(),
|
||||
'comments' => new ScoreComment(),
|
||||
'homework' => new Homework,
|
||||
'quiz' => new Quiz,
|
||||
'project' => new Project,
|
||||
'midterm' => new MidtermExam,
|
||||
'final' => new FinalExam,
|
||||
'test' => new SemesterScore,
|
||||
'comments' => new ScoreComment,
|
||||
default => throw new RuntimeException("Invalid type: {$type}"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class HomeworkTrackingCalendarService
|
||||
}
|
||||
if ($startYear === null) {
|
||||
$today = new DateTime('today');
|
||||
|
||||
return [$today, $today];
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ class HomeworkTrackingCalendarService
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$today = new DateTime('today');
|
||||
|
||||
return [$today, $today];
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ class HomeworkTrackingCalendarService
|
||||
$dateToIndex = [];
|
||||
$idx = 0;
|
||||
foreach ($sundays as $ymd) {
|
||||
if (!isset($eventDays[$ymd])) {
|
||||
if (! isset($eventDays[$ymd])) {
|
||||
$idx++;
|
||||
$dateToIndex[$ymd] = $idx;
|
||||
} else {
|
||||
|
||||
@@ -9,9 +9,7 @@ class HomeworkTrackingService
|
||||
{
|
||||
private array $teacherAssignmentCache = [];
|
||||
|
||||
public function __construct(private HomeworkTrackingCalendarService $calendarService)
|
||||
{
|
||||
}
|
||||
public function __construct(private HomeworkTrackingCalendarService $calendarService) {}
|
||||
|
||||
public function report(?string $semester = null, ?string $schoolYear = null, int $page = 1): array
|
||||
{
|
||||
@@ -122,7 +120,7 @@ class HomeworkTrackingService
|
||||
$date = substr((string) ($row->hw_date ?? ''), 0, 10);
|
||||
$cnt = (int) $row->cnt;
|
||||
$firstCreated = substr((string) ($row->first_created ?? ''), 0, 10);
|
||||
if ($csid <= 0 || !$date || $cnt <= 0) {
|
||||
if ($csid <= 0 || ! $date || $cnt <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -138,7 +136,7 @@ class HomeworkTrackingService
|
||||
}
|
||||
|
||||
$j = $baseIndex;
|
||||
while ($j >= 0 && !empty($eventDays[$sundays[$j]])) {
|
||||
while ($j >= 0 && ! empty($eventDays[$sundays[$j]])) {
|
||||
$j--;
|
||||
}
|
||||
if ($j < 0) {
|
||||
@@ -177,7 +175,7 @@ class HomeworkTrackingService
|
||||
if ($sid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($bySection[$sid])) {
|
||||
if (! isset($bySection[$sid])) {
|
||||
$bySection[$sid] = [
|
||||
'class_section_id' => $sid,
|
||||
'class_id' => (int) ($row->class_id ?? 0),
|
||||
@@ -186,7 +184,7 @@ class HomeworkTrackingService
|
||||
'tas' => [],
|
||||
];
|
||||
}
|
||||
$name = trim((string) ($row->firstname ?? '') . ' ' . (string) ($row->lastname ?? ''));
|
||||
$name = trim((string) ($row->firstname ?? '').' '.(string) ($row->lastname ?? ''));
|
||||
$pos = strtolower((string) ($row->position ?? ''));
|
||||
if ($name !== '') {
|
||||
if ($pos === 'main') {
|
||||
@@ -202,15 +200,23 @@ class HomeworkTrackingService
|
||||
$ai = (int) ($a['class_id'] ?? 0);
|
||||
$bi = (int) ($b['class_id'] ?? 0);
|
||||
$order = function (int $cid): int {
|
||||
if ($cid === 13) return 0;
|
||||
if ($cid >= 1 && $cid <= 11) return $cid;
|
||||
if ($cid === 12) return 99;
|
||||
if ($cid === 13) {
|
||||
return 0;
|
||||
}
|
||||
if ($cid >= 1 && $cid <= 11) {
|
||||
return $cid;
|
||||
}
|
||||
if ($cid === 12) {
|
||||
return 99;
|
||||
}
|
||||
|
||||
return 200 + $cid;
|
||||
};
|
||||
$cmp = $order($ai) <=> $order($bi);
|
||||
if ($cmp !== 0) {
|
||||
return $cmp;
|
||||
}
|
||||
|
||||
return strnatcasecmp($a['class_section_name'] ?? '', $b['class_section_name'] ?? '');
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,17 @@ use App\Models\Configuration;
|
||||
class GradingPolicyResolver
|
||||
{
|
||||
public const LEGACY_MODE = 'legacy';
|
||||
|
||||
public const STRONG_MODE = 'strong';
|
||||
|
||||
public const LEGACY_VERSION = 'legacy_v1';
|
||||
|
||||
public const STRONG_VERSION = 'strong_v1';
|
||||
|
||||
public function calculationMode(?int $classSectionId = null, ?string $semester = null, ?string $schoolYear = null): string
|
||||
{
|
||||
$enabled = strtolower((string) (Configuration::getConfig('strong_grading_enabled') ?? ''));
|
||||
if (!in_array($enabled, ['1', 'true', 'yes', 'on'], true)) {
|
||||
if (! in_array($enabled, ['1', 'true', 'yes', 'on'], true)) {
|
||||
return self::LEGACY_MODE;
|
||||
}
|
||||
|
||||
@@ -24,6 +27,7 @@ class GradingPolicyResolver
|
||||
}
|
||||
|
||||
$ids = array_filter(array_map('trim', explode(',', $allowedSections)), fn ($v) => $v !== '');
|
||||
|
||||
return $classSectionId !== null && in_array((string) $classSectionId, $ids, true)
|
||||
? self::STRONG_MODE
|
||||
: self::LEGACY_MODE;
|
||||
|
||||
@@ -31,16 +31,19 @@ class StrongCategoryAverageCalculator
|
||||
|
||||
if ($status === 'pending' || $status === '') {
|
||||
$pending[] = $row;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($status, ['excused', 'not_assigned'], true)) {
|
||||
$excluded[] = $row;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($status === 'missing') {
|
||||
$included[] = 0.0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class GradebookFinalizationValidator
|
||||
$errors = [];
|
||||
|
||||
foreach ($this->scoreTables() as $table => $meta) {
|
||||
if (!DB::getSchemaBuilder()->hasTable($table)) {
|
||||
if (! DB::getSchemaBuilder()->hasTable($table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class ScoreValueValidator
|
||||
|
||||
$max = $this->normalizeMaxPoints($maxPoints, $label);
|
||||
|
||||
if (!is_numeric($value)) {
|
||||
if (! is_numeric($value)) {
|
||||
throw new InvalidArgumentException("{$label} must be numeric.");
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class ScoreValueValidator
|
||||
if ($max <= 0) {
|
||||
throw new InvalidArgumentException("{$label} max points must be greater than 0.");
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user