177 lines
6.4 KiB
PHP
177 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Dashboard\Services;
|
|
|
|
use App\Modules\Auth\Entities\Role;
|
|
|
|
/**
|
|
* Dashboard Realtime Service
|
|
*
|
|
* Fetches latest attendance records for SSE stream (by id > afterId).
|
|
* Role-aware filtering: ADMIN (all), WALI_KELAS (by assigned class), GURU_MAPEL (by teacher), ORANG_TUA (by own children).
|
|
*/
|
|
class DashboardRealtimeService
|
|
{
|
|
/** Scope type: no filter */
|
|
public const SCOPE_ADMIN = 'admin';
|
|
|
|
/** Scope type: filter by class_id(s) where user is wali */
|
|
public const SCOPE_WALI_KELAS = 'wali_kelas';
|
|
|
|
/** Scope type: filter by schedules taught by this teacher (teacher_user_id) */
|
|
public const SCOPE_GURU_MAPEL = 'guru_mapel';
|
|
|
|
/** Scope type: filter by student_ids linked to parent */
|
|
public const SCOPE_ORANG_TUA = 'orang_tua';
|
|
|
|
/**
|
|
* Resolve user scope for realtime filtering from current user (with roles).
|
|
*
|
|
* @param array $user { id, name, email, roles: [ { role_code, role_name } ] }
|
|
* @return array{ type: string, class_ids?: int[], teacher_user_id?: int, student_ids?: int[] }
|
|
*/
|
|
public function resolveUserScope(array $user): array
|
|
{
|
|
$roles = $user['roles'] ?? [];
|
|
$roleCodes = array_column($roles, 'role_code');
|
|
|
|
if (in_array(Role::CODE_ADMIN, $roleCodes, true)) {
|
|
return ['type' => self::SCOPE_ADMIN];
|
|
}
|
|
|
|
if (in_array(Role::CODE_WALI_KELAS, $roleCodes, true)) {
|
|
$classIds = $this->getClassIdsForWali($user['id']);
|
|
if ($classIds !== []) {
|
|
return ['type' => self::SCOPE_WALI_KELAS, 'class_ids' => $classIds];
|
|
}
|
|
}
|
|
|
|
if (in_array(Role::CODE_GURU_MAPEL, $roleCodes, true)) {
|
|
return ['type' => self::SCOPE_GURU_MAPEL, 'teacher_user_id' => (int) ($user['id'] ?? 0)];
|
|
}
|
|
|
|
if (in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) {
|
|
$studentIds = $this->getStudentIdsForParent($user['id']);
|
|
if ($studentIds !== []) {
|
|
return ['type' => self::SCOPE_ORANG_TUA, 'student_ids' => $studentIds];
|
|
}
|
|
}
|
|
|
|
return ['type' => self::SCOPE_ADMIN];
|
|
}
|
|
|
|
/**
|
|
* Get class IDs where user is wali (classes.wali_user_id = userId).
|
|
*/
|
|
protected function getClassIdsForWali(int $userId): array
|
|
{
|
|
$db = \Config\Database::connect();
|
|
$rows = $db->table('classes')->select('id')->where('wali_user_id', $userId)->get()->getResultArray();
|
|
return array_map('intval', array_column($rows, 'id'));
|
|
}
|
|
|
|
/**
|
|
* Get student IDs linked to parent (parent.user_id = userId via student_parents).
|
|
*/
|
|
protected function getStudentIdsForParent(int $userId): array
|
|
{
|
|
$db = \Config\Database::connect();
|
|
$rows = $db->table('student_parents AS sp')
|
|
->select('sp.student_id')
|
|
->join('parents AS p', 'p.id = sp.parent_id', 'inner')
|
|
->where('p.user_id', $userId)
|
|
->get()
|
|
->getResultArray();
|
|
return array_map('intval', array_column($rows, 'student_id'));
|
|
}
|
|
|
|
/**
|
|
* Get attendance sessions after a given ID (for SSE incremental feed), with optional role scope.
|
|
*
|
|
* @param int $afterId Only return rows with id > afterId
|
|
* @param int $limit Max rows to return
|
|
* @param array|null $userContext Current user from session (id, name, email, roles). If null, no scope filter.
|
|
* @return array<int, array{id: int, student_name: string, class_name: string, subject: string, checkin_at: string, status: string}>
|
|
*/
|
|
public function getAttendanceSinceId(int $afterId = 0, int $limit = 100, ?array $userContext = null): array
|
|
{
|
|
$db = \Config\Database::connect();
|
|
$builder = $db->table('attendance_sessions AS a');
|
|
$builder->select('
|
|
a.id,
|
|
s.name AS student_name,
|
|
c.name AS class_name,
|
|
COALESCE(sub.name, "-") AS subject,
|
|
a.checkin_at,
|
|
a.status
|
|
');
|
|
$builder->join('students AS s', 's.id = a.student_id', 'left');
|
|
$builder->join('classes AS c', 'c.id = s.class_id', 'left');
|
|
$builder->join('schedules AS sch', 'sch.id = a.schedule_id', 'left');
|
|
$builder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left');
|
|
$builder->where('a.id >', $afterId);
|
|
|
|
if ($userContext !== null) {
|
|
$this->applyScopeToBuilder($builder, $this->resolveUserScope($userContext));
|
|
}
|
|
|
|
$builder->orderBy('a.id', 'ASC');
|
|
$builder->limit($limit);
|
|
|
|
$rows = $builder->get()->getResultArray();
|
|
|
|
$out = [];
|
|
foreach ($rows as $row) {
|
|
$out[] = [
|
|
'id' => (int) $row['id'],
|
|
'student_name' => (string) ($row['student_name'] ?? '-'),
|
|
'class_name' => (string) ($row['class_name'] ?? '-'),
|
|
'subject' => (string) ($row['subject'] ?? '-'),
|
|
'checkin_at' => (string) $row['checkin_at'],
|
|
'status' => (string) $row['status'],
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Apply scope conditions to the attendance query builder (no duplicate query).
|
|
*
|
|
* @param \CodeIgniter\Database\BaseBuilder $builder
|
|
* @param array $scope From resolveUserScope()
|
|
*/
|
|
protected function applyScopeToBuilder($builder, array $scope): void
|
|
{
|
|
switch ($scope['type'] ?? '') {
|
|
case self::SCOPE_WALI_KELAS:
|
|
$classIds = $scope['class_ids'] ?? [];
|
|
if ($classIds !== []) {
|
|
$builder->whereIn('s.class_id', $classIds);
|
|
} else {
|
|
$builder->where('1 =', 0);
|
|
}
|
|
break;
|
|
case self::SCOPE_GURU_MAPEL:
|
|
$teacherUserId = (int) ($scope['teacher_user_id'] ?? 0);
|
|
if ($teacherUserId > 0) {
|
|
$builder->where('sch.teacher_user_id', $teacherUserId);
|
|
} else {
|
|
$builder->where('1 =', 0);
|
|
}
|
|
break;
|
|
case self::SCOPE_ORANG_TUA:
|
|
$studentIds = $scope['student_ids'] ?? [];
|
|
if ($studentIds !== []) {
|
|
$builder->whereIn('a.student_id', $studentIds);
|
|
} else {
|
|
$builder->where('1 =', 0);
|
|
}
|
|
break;
|
|
case self::SCOPE_ADMIN:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|