add more controller
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Enrollment;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class StudentAssignmentService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function assignClasses(int $studentId, array $classSectionIds, bool $isEventOnly, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
$classSectionIds = $this->normalizeIds($classSectionIds);
|
||||
if ($studentId <= 0 || empty($classSectionIds) || $schoolYear === '') {
|
||||
return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
|
||||
}
|
||||
|
||||
$student = Student::query()->find($studentId);
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$sections = ClassSection::query()
|
||||
->whereIn('class_section_id', $classSectionIds)
|
||||
->orWhereIn('id', $classSectionIds)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($sections)) {
|
||||
return ['ok' => false, 'message' => 'Class/Section not found.'];
|
||||
}
|
||||
|
||||
$sectionMap = [];
|
||||
foreach ($sections as $section) {
|
||||
$sectionMap[(int) ($section['class_section_id'] ?? 0)] = $section;
|
||||
}
|
||||
|
||||
$missing = array_diff($classSectionIds, array_keys($sectionMap));
|
||||
if (!empty($missing)) {
|
||||
return ['ok' => false, 'message' => 'One or more selected classes do not exist.'];
|
||||
}
|
||||
|
||||
$existing = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->get()
|
||||
->keyBy(fn ($row) => (int) ($row->class_section_id ?? 0));
|
||||
|
||||
$primarySectionId = $classSectionIds[0];
|
||||
$primarySection = $sectionMap[$primarySectionId] ?? reset($sectionMap);
|
||||
$parentClassId = (int) ($primarySection['class_id'] ?? 0);
|
||||
if ($parentClassId <= 0) {
|
||||
$parentClassId = (int) (ClassSection::getClassId($primarySectionId) ?? 0);
|
||||
}
|
||||
|
||||
$attendanceStats = [];
|
||||
$scoreStats = [];
|
||||
|
||||
DB::transaction(function () use (
|
||||
$studentId,
|
||||
$schoolYear,
|
||||
$classSectionIds,
|
||||
$isEventOnly,
|
||||
$userId,
|
||||
$existing,
|
||||
$semester,
|
||||
$primarySectionId,
|
||||
$parentClassId,
|
||||
&$attendanceStats,
|
||||
&$scoreStats
|
||||
): void {
|
||||
foreach ($classSectionIds as $classSectionId) {
|
||||
$eventFlag = $isEventOnly ? 1 : 0;
|
||||
if ($existing->has($classSectionId)) {
|
||||
$eventFlag = (int) ($existing[$classSectionId]->is_event_only ?? 0);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'school_year' => $schoolYear,
|
||||
'is_event_only' => $eventFlag,
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($existing->has($classSectionId)) {
|
||||
$existing[$classSectionId]->fill($payload)->save();
|
||||
} else {
|
||||
StudentClass::query()->create($payload + [
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isEventOnly) {
|
||||
$enrollment = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
if ($enrollment) {
|
||||
$enrollment->update([
|
||||
'class_section_id' => $primarySectionId,
|
||||
'enrollment_status' => 'payment pending',
|
||||
'admission_status' => 'accepted',
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$attendanceStats = $this->updateAttendance(
|
||||
$studentId,
|
||||
$primarySectionId,
|
||||
$parentClassId,
|
||||
$semester,
|
||||
$schoolYear,
|
||||
$userId
|
||||
);
|
||||
|
||||
$scoreStats = $this->updateScores(
|
||||
$studentId,
|
||||
$primarySectionId,
|
||||
$semester,
|
||||
$schoolYear,
|
||||
$userId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$displayNames = [];
|
||||
foreach ($classSectionIds as $classSectionId) {
|
||||
$section = $sectionMap[$classSectionId] ?? null;
|
||||
if (!$section) {
|
||||
continue;
|
||||
}
|
||||
$name = (string) ($section['class_section_name'] ?? '');
|
||||
if ($name === '') {
|
||||
$name = 'Section #' . $classSectionId;
|
||||
}
|
||||
$displayNames[] = $name;
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $primarySectionId,
|
||||
'class_section_ids' => $classSectionIds,
|
||||
'class_section_names' => $displayNames,
|
||||
'attendance_updates' => $attendanceStats,
|
||||
'score_updates' => $scoreStats,
|
||||
];
|
||||
}
|
||||
|
||||
public function removeClass(int $studentId, int $classSectionId, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
if ($studentId <= 0 || $classSectionId <= 0 || $schoolYear === '') {
|
||||
return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
|
||||
}
|
||||
|
||||
$row = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'message' => 'Assignment not found for this student/class.'];
|
||||
}
|
||||
|
||||
$remainingIds = [];
|
||||
$remainingNames = [];
|
||||
|
||||
DB::transaction(function () use (
|
||||
$studentId,
|
||||
$classSectionId,
|
||||
$schoolYear,
|
||||
$semester,
|
||||
$userId,
|
||||
&$remainingIds,
|
||||
&$remainingNames
|
||||
): void {
|
||||
StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('school_year', $schoolYear)
|
||||
->delete();
|
||||
|
||||
$remainingIds = StudentClass::getClassSectionIdsByStudentId($studentId, $schoolYear);
|
||||
$remainingNames = StudentClass::getClassSectionsByStudentId($studentId, $schoolYear, true);
|
||||
|
||||
$enrollment = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
if ($enrollment) {
|
||||
$newClassId = !empty($remainingIds) ? $remainingIds[0] : null;
|
||||
if ((int) $enrollment->class_section_id === $classSectionId || $newClassId !== null) {
|
||||
$enrollment->update([
|
||||
'class_section_id' => $newClassId,
|
||||
'updated_at' => now(),
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'student_id' => $studentId,
|
||||
'removed_class_id' => $classSectionId,
|
||||
'remaining_ids' => $remainingIds,
|
||||
'remaining_names' => $remainingNames,
|
||||
];
|
||||
}
|
||||
|
||||
private function updateAttendance(
|
||||
int $studentId,
|
||||
int $classSectionId,
|
||||
int $classId,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
?int $modifiedBy
|
||||
): array {
|
||||
$now = now()->toDateTimeString();
|
||||
|
||||
$attendanceUpdated = DB::table('attendance_data')
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update([
|
||||
'class_section_id' => $classSectionId,
|
||||
'class_id' => $classId,
|
||||
'updated_at' => $now,
|
||||
'modified_by' => $modifiedBy,
|
||||
]);
|
||||
|
||||
$recordUpdated = DB::table('attendance_record')
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update([
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_at' => $now,
|
||||
'modified_by' => $modifiedBy,
|
||||
]);
|
||||
|
||||
return [
|
||||
'attendance_data_updated' => $attendanceUpdated,
|
||||
'attendance_record_updated' => $recordUpdated,
|
||||
];
|
||||
}
|
||||
|
||||
private function updateScores(
|
||||
int $studentId,
|
||||
int $classSectionId,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
?int $modifiedBy
|
||||
): array {
|
||||
$now = now()->toDateTimeString();
|
||||
$tables = [
|
||||
'homework',
|
||||
'quiz',
|
||||
'project',
|
||||
'participation',
|
||||
'midterm_exam',
|
||||
'final_exam',
|
||||
'final_score',
|
||||
'semester_scores',
|
||||
];
|
||||
|
||||
$results = [];
|
||||
foreach ($tables as $table) {
|
||||
$payload = [
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
if ($modifiedBy !== null && Schema::hasColumn($table, 'updated_by')) {
|
||||
$payload['updated_by'] = $modifiedBy;
|
||||
}
|
||||
|
||||
$results[$table . '_updated'] = DB::table($table)
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update($payload);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function normalizeIds(array $ids): array
|
||||
{
|
||||
$values = array_map('intval', $ids);
|
||||
$values = array_values(array_unique(array_filter($values, static fn ($v) => $v > 0)));
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\PromotionQueue;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentAutoDistributionService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function promotionTotals(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$year = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
|
||||
$bases = ClassSection::query()
|
||||
->whereRaw("class_section_name NOT LIKE '%-%'")
|
||||
->orderBy('class_id')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$wanted = [];
|
||||
foreach ($bases as $row) {
|
||||
$nameRaw = (string) ($row['class_section_name'] ?? '');
|
||||
$name = strtolower($nameRaw);
|
||||
if ($name === 'kg' || $name === 'youth') {
|
||||
$wanted[] = $row;
|
||||
continue;
|
||||
}
|
||||
if (ctype_digit($name)) {
|
||||
$num = (int) $name;
|
||||
if ($num >= 1 && $num <= 9) {
|
||||
$wanted[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($wanted as $row) {
|
||||
$classId = (int) ($row['class_id'] ?? 0);
|
||||
if ($classId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cands = DB::table('promotion_queue as pq')
|
||||
->select('pq.student_id')
|
||||
->join('enrollments as e', function ($join) use ($year) {
|
||||
$join->on('e.student_id', '=', 'pq.student_id')
|
||||
->where('e.school_year', '=', $year);
|
||||
})
|
||||
->where('pq.to_class_id', $classId)
|
||||
->where('pq.school_year_to', $year)
|
||||
->whereIn('pq.status', ['queued', 'assigned', 'applied'])
|
||||
->whereIn('e.enrollment_status', ['payment pending', 'enrolled'])
|
||||
->groupBy('pq.student_id')
|
||||
->get();
|
||||
|
||||
$rows[] = [
|
||||
'class_id' => $classId,
|
||||
'class_section_id' => (int) ($row['class_section_id'] ?? 0),
|
||||
'class_section_name' => (string) ($row['class_section_name'] ?? ''),
|
||||
'total' => $cands->count(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'year' => $year,
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
public function autoDistribute(int $classId, int $studentsPerSection, ?string $schoolYear = null, ?int $classSectionId = null, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$year = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
if ($classId <= 0 && $classSectionId > 0) {
|
||||
$classId = (int) (ClassSection::getClassId($classSectionId) ?? 0);
|
||||
}
|
||||
|
||||
if ($classId <= 0 || $studentsPerSection <= 0) {
|
||||
return ['ok' => false, 'message' => 'Invalid class_id or students_per_section.'];
|
||||
}
|
||||
|
||||
$cands = DB::table('promotion_queue as pq')
|
||||
->select('pq.*', 'students.gender')
|
||||
->join('students', 'students.id', '=', 'pq.student_id')
|
||||
->where('pq.to_class_id', $classId)
|
||||
->where('pq.school_year_to', $year)
|
||||
->whereIn('pq.status', ['queued', 'assigned'])
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
if (empty($cands)) {
|
||||
return ['ok' => false, 'message' => 'No students found in promotion queue for selected class/year.'];
|
||||
}
|
||||
|
||||
$studentIds = array_map(static fn ($r) => (int) $r['student_id'], $cands);
|
||||
$enrolledIds = DB::table('enrollments')
|
||||
->select('student_id')
|
||||
->whereIn('student_id', $studentIds)
|
||||
->where('school_year', $year)
|
||||
->whereIn('enrollment_status', ['payment pending', 'enrolled'])
|
||||
->groupBy('student_id')
|
||||
->pluck('student_id')
|
||||
->map(fn ($v) => (int) $v)
|
||||
->all();
|
||||
|
||||
$cands = array_values(array_filter($cands, static function ($row) use ($enrolledIds) {
|
||||
return in_array((int) $row['student_id'], $enrolledIds, true);
|
||||
}));
|
||||
|
||||
if (empty($cands)) {
|
||||
return ['ok' => false, 'message' => 'No eligible enrolled students found to distribute.'];
|
||||
}
|
||||
|
||||
$sectionsNeeded = (int) ceil(count($cands) / $studentsPerSection);
|
||||
$letters = ClassSection::getLetterSectionsByClassId($classId);
|
||||
|
||||
if (empty($letters)) {
|
||||
return ['ok' => false, 'message' => 'No lettered sections found for the selected class.'];
|
||||
}
|
||||
|
||||
if (count($letters) < $sectionsNeeded) {
|
||||
return ['ok' => false, 'message' => 'Not enough sections available.'];
|
||||
}
|
||||
|
||||
$letters = array_slice($letters, 0, $sectionsNeeded);
|
||||
$buckets = [];
|
||||
foreach ($letters as $idx => $section) {
|
||||
$buckets[$idx] = [
|
||||
'class_section_id' => (int) ($section['class_section_id'] ?? 0),
|
||||
'assigned' => [],
|
||||
'male' => 0,
|
||||
'female' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$males = [];
|
||||
$females = [];
|
||||
foreach ($cands as $row) {
|
||||
$gender = (string) ($row['gender'] ?? '');
|
||||
if (strcasecmp($gender, 'Female') === 0) {
|
||||
$females[] = $row;
|
||||
} else {
|
||||
$males[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$pickBucket = function (string $gender) use (&$buckets, $studentsPerSection): ?int {
|
||||
$bestIdx = null;
|
||||
$bestCnt = PHP_INT_MAX;
|
||||
foreach ($buckets as $idx => $bucket) {
|
||||
if (count($bucket['assigned']) >= $studentsPerSection) {
|
||||
continue;
|
||||
}
|
||||
$cnt = $gender === 'female' ? $bucket['female'] : $bucket['male'];
|
||||
if ($cnt < $bestCnt) {
|
||||
$bestCnt = $cnt;
|
||||
$bestIdx = $idx;
|
||||
}
|
||||
}
|
||||
return $bestIdx;
|
||||
};
|
||||
|
||||
foreach ($males as $row) {
|
||||
$idx = $pickBucket('male');
|
||||
if ($idx === null) {
|
||||
break;
|
||||
}
|
||||
$buckets[$idx]['assigned'][] = (int) $row['student_id'];
|
||||
$buckets[$idx]['male']++;
|
||||
}
|
||||
|
||||
foreach ($females as $row) {
|
||||
$idx = $pickBucket('female');
|
||||
if ($idx === null) {
|
||||
break;
|
||||
}
|
||||
$buckets[$idx]['assigned'][] = (int) $row['student_id'];
|
||||
$buckets[$idx]['female']++;
|
||||
}
|
||||
|
||||
$promoIdsByStudent = [];
|
||||
foreach ($cands as $row) {
|
||||
$promoIdsByStudent[(int) $row['student_id']] = (int) $row['id'];
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($buckets, $promoIdsByStudent, $year, $semester, $userId): void {
|
||||
foreach ($buckets as $bucket) {
|
||||
$sectionId = (int) $bucket['class_section_id'];
|
||||
foreach ($bucket['assigned'] as $studentId) {
|
||||
if (isset($promoIdsByStudent[$studentId])) {
|
||||
PromotionQueue::query()->where('id', $promoIdsByStudent[$studentId])->update([
|
||||
'to_class_section_id' => $sectionId,
|
||||
'status' => 'assigned',
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$exists = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $year)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
$payload = [
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $sectionId,
|
||||
'school_year' => $year,
|
||||
'semester' => $semester,
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($exists) {
|
||||
$exists->update($payload);
|
||||
} else {
|
||||
StudentClass::query()->create($payload + ['created_at' => now()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$nameById = [];
|
||||
foreach ($letters as $row) {
|
||||
$nameById[(int) $row['class_section_id']] = (string) ($row['class_section_name'] ?? '');
|
||||
}
|
||||
|
||||
$summary = [];
|
||||
foreach ($buckets as $bucket) {
|
||||
$sectionId = (int) $bucket['class_section_id'];
|
||||
$summary[] = [
|
||||
'class_section_id' => $sectionId,
|
||||
'class_section_name' => $nameById[$sectionId] ?? (string) $sectionId,
|
||||
'total' => count($bucket['assigned']),
|
||||
'male' => $bucket['male'],
|
||||
'female' => $bucket['female'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'Auto distribution completed.',
|
||||
'sections' => $summary,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\Configuration;
|
||||
|
||||
class StudentConfigService
|
||||
{
|
||||
public function context(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => Configuration::getConfig('school_year'),
|
||||
'semester' => Configuration::getConfig('semester'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentDirectoryService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function assignmentData(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
$yearsRows = DB::table('enrollments')
|
||||
->selectRaw('DISTINCT school_year')
|
||||
->orderByDesc('school_year')
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$schoolYears = array_values(array_filter(array_map(static function ($r) {
|
||||
return isset($r['school_year']) ? (string) $r['school_year'] : null;
|
||||
}, $yearsRows)));
|
||||
|
||||
$students = Student::query()
|
||||
->select('students.id', 'students.firstname', 'students.lastname', 'students.registration_date', 'students.is_new', 'students.age', 'students.parent_id', 'students.registration_grade')
|
||||
->join('enrollments as e', 'e.student_id', '=', 'students.id')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('e.school_year', $schoolYear))
|
||||
->groupBy('students.id')
|
||||
->orderBy('students.lastname')
|
||||
->orderBy('students.firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($students)) {
|
||||
$students = Student::query()
|
||||
->select('students.id', 'students.firstname', 'students.lastname', 'students.registration_date', 'students.is_new', 'students.age', 'students.parent_id', 'students.registration_grade')
|
||||
->orderBy('students.lastname')
|
||||
->orderBy('students.firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$studentData = [];
|
||||
foreach ($students as $student) {
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
$sectionNames = StudentClass::getClassSectionsByStudentIdWithFlags($studentId, $schoolYear, true);
|
||||
$sectionIds = StudentClass::getClassSectionIdsByStudentId($studentId, $schoolYear);
|
||||
$sectionDisplay = !empty($sectionNames) ? implode(', ', $sectionNames) : '';
|
||||
|
||||
$parentId = (int) ($student['parent_id'] ?? 0);
|
||||
$emergencyInfo = $parentId > 0
|
||||
? (EmergencyContact::getEmergencyContactByParentId($parentId) ?? [])
|
||||
: [];
|
||||
|
||||
$studentData[] = [
|
||||
'student_id' => $studentId,
|
||||
'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
|
||||
'age' => $student['age'] ?? 'N/A',
|
||||
'email' => $emergencyInfo['emergency_contact_name'] ?? '',
|
||||
'phone' => $emergencyInfo['cellphone'] ?? '',
|
||||
'registration_grade' => $student['registration_grade'] ?? 'N/A',
|
||||
'class_section_name' => $sectionDisplay !== '' ? $sectionDisplay : 'No class assigned',
|
||||
'class_section_names' => $sectionNames,
|
||||
'class_section_ids' => $sectionIds,
|
||||
'new_student' => ((int) ($student['is_new'] ?? 0) === 1) ? 'Yes' : 'No',
|
||||
'registration_date' => $student['registration_date'] ?? '',
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
];
|
||||
}
|
||||
|
||||
$classes = ClassSection::query()
|
||||
->select('id', 'class_section_id', 'class_section_name', 'school_year')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($classes)) {
|
||||
$classes = ClassSection::query()
|
||||
->select('id', 'class_section_id', 'class_section_name')
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return [
|
||||
'students' => $studentData,
|
||||
'classes' => $classes,
|
||||
'schoolYears' => $schoolYears,
|
||||
'selectedYear' => $schoolYear,
|
||||
'currentYear' => (string) ($context['school_year'] ?? ''),
|
||||
'isCurrentYear' => $schoolYear !== '' && $schoolYear === (string) ($context['school_year'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
public function removedStudents(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
|
||||
$activeStudents = Student::query()
|
||||
->select('id', 'school_id', 'firstname', 'lastname', 'gender', 'age')
|
||||
->where('is_active', 1)
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$removedStudents = Student::query()
|
||||
->select('id', 'school_id', 'firstname', 'lastname', 'gender', 'age')
|
||||
->where('is_active', 0)
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$classRows = DB::table('student_class as sc')
|
||||
->select('sc.student_id', 'cs.class_section_name')
|
||||
->join('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('sc.school_year', $schoolYear))
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$classMap = [];
|
||||
foreach ($classRows as $row) {
|
||||
$studentId = (int) ($row['student_id'] ?? 0);
|
||||
$name = trim((string) ($row['class_section_name'] ?? ''));
|
||||
if ($studentId <= 0 || $name === '') {
|
||||
continue;
|
||||
}
|
||||
$classMap[$studentId][] = $name;
|
||||
}
|
||||
|
||||
$attachClasses = static function (array $students) use ($classMap): array {
|
||||
foreach ($students as &$student) {
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
$names = $classMap[$studentId] ?? [];
|
||||
$names = array_values(array_unique(array_filter($names)));
|
||||
$student['class_sections'] = $names;
|
||||
$student['class_section_name'] = !empty($names) ? implode(', ', $names) : 'No class assigned';
|
||||
}
|
||||
unset($student);
|
||||
return $students;
|
||||
};
|
||||
|
||||
return [
|
||||
'active_students' => $attachClasses($activeStudents),
|
||||
'removed_students' => $attachClasses($removedStudents),
|
||||
'school_year' => $schoolYear,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentAllergy;
|
||||
use App\Models\StudentMedicalCondition;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentProfileService
|
||||
{
|
||||
public function updateStudent(int $studentId, array $payload): array
|
||||
{
|
||||
$student = Student::query()->find($studentId);
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$studentData = array_filter([
|
||||
'school_id' => $payload['school_id'] ?? null,
|
||||
'firstname' => $payload['firstname'] ?? null,
|
||||
'lastname' => $payload['lastname'] ?? null,
|
||||
'dob' => $payload['dob'] ?? null,
|
||||
'age' => $payload['age'] ?? null,
|
||||
'gender' => $payload['gender'] ?? null,
|
||||
'registration_grade' => $payload['registration_grade'] ?? null,
|
||||
'photo_consent' => $payload['photo_consent'] ?? null,
|
||||
'parent_id' => $payload['parent_id'] ?? null,
|
||||
'registration_date' => $payload['registration_date'] ?? null,
|
||||
'tuition_paid' => $payload['tuition_paid'] ?? null,
|
||||
'year_of_registration' => $payload['year_of_registration'] ?? null,
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'rfid_tag' => $payload['rfid_tag'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
'is_new' => $payload['is_new'] ?? null,
|
||||
'is_active' => $payload['is_active'] ?? null,
|
||||
], static fn ($value) => $value !== null);
|
||||
|
||||
$conditionsInput = (string) ($payload['medical_conditions'] ?? '');
|
||||
$allergiesInput = (string) ($payload['allergies'] ?? '');
|
||||
$conditions = $this->normalizeHealthList($conditionsInput);
|
||||
$allergies = $this->normalizeHealthList($allergiesInput);
|
||||
$shouldSyncConditions = array_key_exists('medical_conditions', $payload);
|
||||
$shouldSyncAllergies = array_key_exists('allergies', $payload);
|
||||
|
||||
DB::transaction(function () use ($student, $studentData, $conditions, $allergies): void {
|
||||
if (!empty($studentData)) {
|
||||
$student->update($studentData);
|
||||
}
|
||||
|
||||
if ($shouldSyncConditions) {
|
||||
$this->syncHealthList($student->id, StudentMedicalCondition::class, 'condition_name', $conditions);
|
||||
}
|
||||
|
||||
if ($shouldSyncAllergies) {
|
||||
$this->syncHealthList($student->id, StudentAllergy::class, 'allergy', $allergies);
|
||||
}
|
||||
});
|
||||
|
||||
return ['ok' => true, 'message' => 'Student updated successfully.'];
|
||||
}
|
||||
|
||||
private function normalizeHealthList(string $value, int $maxLen = 100): array
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parts = preg_split('/[,\n;]+/u', $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
$out = [];
|
||||
foreach ($parts as $part) {
|
||||
$item = trim(preg_replace('/\s+/u', ' ', $part));
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^(none|n\/a|na|null|nil|no)$/i', $item)) {
|
||||
continue;
|
||||
}
|
||||
$out[mb_strtolower($item, 'UTF-8')] = mb_substr($item, 0, $maxLen, 'UTF-8');
|
||||
}
|
||||
|
||||
return array_values($out);
|
||||
}
|
||||
|
||||
private function syncHealthList(int $studentId, string $modelClass, string $field, array $newValues): void
|
||||
{
|
||||
$model = new $modelClass();
|
||||
$rows = $model->newQuery()->where('student_id', $studentId)->get(['id', $field])->toArray();
|
||||
|
||||
$current = [];
|
||||
$byId = [];
|
||||
foreach ($rows as $row) {
|
||||
$val = (string) ($row[$field] ?? '');
|
||||
$key = mb_strtolower(trim($val), 'UTF-8');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$current[$key] = $val;
|
||||
$byId[$key] = (int) ($row['id'] ?? 0);
|
||||
}
|
||||
|
||||
$incoming = [];
|
||||
foreach ($newValues as $value) {
|
||||
$key = mb_strtolower(trim($value), 'UTF-8');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$incoming[$key] = $value;
|
||||
}
|
||||
|
||||
$toDeleteKeys = array_diff(array_keys($current), array_keys($incoming));
|
||||
$toInsertKeys = array_diff(array_keys($incoming), array_keys($current));
|
||||
|
||||
if (!empty($toDeleteKeys)) {
|
||||
$ids = array_map(fn ($key) => $byId[$key], $toDeleteKeys);
|
||||
if (!empty($ids)) {
|
||||
$model->newQuery()->whereIn('id', $ids)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($toInsertKeys)) {
|
||||
$batch = [];
|
||||
foreach ($toInsertKeys as $key) {
|
||||
$batch[] = [
|
||||
'student_id' => $studentId,
|
||||
$field => $incoming[$key],
|
||||
];
|
||||
}
|
||||
if (!empty($batch)) {
|
||||
$model->newQuery()->insert($batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\Student;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentScoreCardService
|
||||
{
|
||||
public function scoreCard(int $studentId): array
|
||||
{
|
||||
$student = Student::query()
|
||||
->select('id', 'firstname', 'lastname', 'school_id')
|
||||
->where('id', $studentId)
|
||||
->first();
|
||||
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$rows = DB::table('semester_scores as ss')
|
||||
->select([
|
||||
'ss.school_year',
|
||||
'ss.semester',
|
||||
'ss.homework_avg',
|
||||
'ss.project_avg',
|
||||
'ss.participation_score',
|
||||
'ss.quiz_avg',
|
||||
'ss.test_avg',
|
||||
'ss.attendance_score',
|
||||
'ss.ptap_score',
|
||||
'ss.midterm_exam_score',
|
||||
'ss.semester_score',
|
||||
'cs.class_section_name',
|
||||
])
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
|
||||
->where('ss.student_id', $studentId)
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
usort($rows, static function (array $a, array $b): int {
|
||||
$ay = (string) ($a['school_year'] ?? '');
|
||||
$by = (string) ($b['school_year'] ?? '');
|
||||
if ($ay !== $by) {
|
||||
return strcmp($by, $ay);
|
||||
}
|
||||
return strcmp((string) ($a['semester'] ?? ''), (string) ($b['semester'] ?? ''));
|
||||
});
|
||||
|
||||
$yearScoreMap = [];
|
||||
foreach ($rows as $row) {
|
||||
$year = (string) ($row['school_year'] ?? '');
|
||||
$score = $row['semester_score'] ?? null;
|
||||
if ($year === '' || !is_numeric($score)) {
|
||||
continue;
|
||||
}
|
||||
$yearScoreMap[$year][] = (float) $score;
|
||||
}
|
||||
|
||||
$yearAvg = [];
|
||||
foreach ($yearScoreMap as $year => $scores) {
|
||||
$yearAvg[$year] = round(array_sum($scores) / count($scores), 1);
|
||||
}
|
||||
|
||||
$commentRows = DB::table('score_comments')
|
||||
->select('school_year', 'semester', 'score_type', 'comment', 'comment_review')
|
||||
->where('student_id', $studentId)
|
||||
->orderByDesc('created_at')
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$commentMap = [];
|
||||
foreach ($commentRows as $row) {
|
||||
$year = (string) ($row['school_year'] ?? '');
|
||||
$semester = strtolower(trim((string) ($row['semester'] ?? '')));
|
||||
$type = strtolower(trim((string) ($row['score_type'] ?? '')));
|
||||
if ($year === '' || $type === '') {
|
||||
continue;
|
||||
}
|
||||
if ($type === 'attendance_comment') {
|
||||
$type = 'attendance';
|
||||
}
|
||||
$commentVal = trim((string) ($row['comment'] ?? ''));
|
||||
$reviewVal = trim((string) ($row['comment_review'] ?? ''));
|
||||
if (in_array($type, ['midterm', 'final', 'ptap'], true)) {
|
||||
$commentVal = $reviewVal !== '' ? $reviewVal : $commentVal;
|
||||
} elseif ($type === 'attendance' && $commentVal === '' && $reviewVal !== '') {
|
||||
$commentVal = $reviewVal;
|
||||
}
|
||||
if ($commentVal === '') {
|
||||
continue;
|
||||
}
|
||||
$key = $year . '|' . $semester;
|
||||
$commentMap[$key][$type] = $commentVal;
|
||||
}
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$year = (string) ($row['school_year'] ?? '');
|
||||
$semester = strtolower(trim((string) ($row['semester'] ?? '')));
|
||||
$row['comments'] = $commentMap[$year . '|' . $semester] ?? [];
|
||||
$row['year_score'] = $yearAvg[$year] ?? null;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'student' => $student->toArray(),
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
|
||||
class StudentStatusService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function setActive(int $studentId, bool $isActive, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
$student = Student::query()->find($studentId);
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$student->update([
|
||||
'is_active' => $isActive ? 1 : 0,
|
||||
]);
|
||||
|
||||
$message = $isActive ? 'Student restored successfully.' : 'Student removed successfully.';
|
||||
|
||||
if ($isActive) {
|
||||
$hasCurrentClass = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->first();
|
||||
|
||||
if (!$hasCurrentClass) {
|
||||
$lastClass = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->orderByDesc('updated_at')
|
||||
->orderByDesc('created_at')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
$restoreClassId = (int) ($lastClass?->class_section_id ?? 0);
|
||||
if ($restoreClassId > 0) {
|
||||
StudentClass::query()->create([
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $restoreClassId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'description' => $lastClass?->description,
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$classLabel = ClassSection::getClassSectionNameBySectionId($restoreClassId);
|
||||
if ($classLabel) {
|
||||
$message .= ' Class assignment restored to ' . $classLabel . '.';
|
||||
}
|
||||
} else {
|
||||
$message .= ' No class assignment found for the current term.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => $message,
|
||||
'is_active' => $isActive,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user