recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
+463
View File
@@ -0,0 +1,463 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class AttendanceDataModel extends Model
{
protected $table = 'attendance_data';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $allowedFields = [
'class_id',
'class_section_id',
'school_id',
'student_id',
'date',
'status',
'reason',
'reported',
'is_reported',
'is_notified',
'semester',
'school_year',
'modified_by',
'created_at',
'updated_at'
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
private ?array $reportedColumns = null;
protected $validationRules = [
'class_id' => 'required|is_natural_no_zero',
'class_section_id'=> 'required|is_natural_no_zero',
'student_id' => 'required|is_natural_no_zero',
'status' => 'required|in_list[present,absent,late]',
'is_reported' => 'required|in_list[no,yes]',
'is_notified' => 'required|in_list[no,yes]',
'date' => 'required|valid_date[Y-m-d]',
'semester' => 'required',
'school_year' => 'required',
];
/**
* Retrieve attendance by class and section for a specific date.
*
* @param int $classId
* @param int $classSectionId
* @param string $date
* @return array
*/
public function getAttendanceByClass($classId, $classSectionId, $date)
{
return $this->where([
'class_id' => $classId,
'class_section_id' => $classSectionId,
'date' => $date
])->findAll();
}
public function unreportedTermRows(array $studentIds, string $schoolYear, ?string $semester): array
{
$qb = $this->builder()
->select('id, student_id, class_id, class_section_id, date, status, reason, school_year, semester, is_reported')
->where('school_year', $schoolYear);
if ($semester !== null && $semester !== '') {
$qb->where('semester', $semester);
}
if (!empty($studentIds)) {
$qb->whereIn('student_id', $studentIds);
}
$this->applyUnreportedFilter($qb);
return $qb->orderBy('date', 'DESC')->get()->getResultArray();
}
/**
* Update attendance record.
*
* @param int $attendanceId
* @param array $data
* @return bool
*/
public function updateAttendance($attendanceId, $data)
{
return $this->update($attendanceId, $data);
}
/**
* Retrieve attendance for a specific student, class section, and date.
*
* @param int $student_id
* @param int $classSectionId
* @param string $date
* @return array|null
*/
public function getAttendance($student_id, $classSectionId, $date)
{
return $this->where([
'student_id' => $student_id,
'class_section_id' => $classSectionId,
'date' => $date
])->first();
}
/**
* Retrieve reported attendance for a specific date or range.
*
* @param string $startDate
* @param string|null $endDate
* @return array
*/
public function getReportedAttendance($startDate, $endDate = null)
{
$builder = $this->where(['is_reported' => 1]);
if ($endDate) {
$builder->where('date >=', $startDate)->where('date <=', $endDate);
} else {
$builder->where('date', $startDate);
}
return $builder->findAll();
}
public function getTodayAbsentees($classSectionId = null, $schoolYear = null, $semester = null)
{
$builder = $this->where('status', 'Absent')
->where('date', date('Y-m-d'));
if ($classSectionId !== null) {
$builder->where('class_section_id', $classSectionId);
}
if ($schoolYear !== null) {
$builder->where('school_year', $schoolYear);
}
if ($semester !== null) {
$builder->where('semester', $semester);
}
return $builder->findAll();
}
public function getTodayLates($classSectionId = null, $schoolYear = null, $semester = null)
{
$builder = $this->where('status', 'Late')
->where('date', date('Y-m-d'));
if ($classSectionId !== null) {
$builder->where('class_section_id', $classSectionId);
}
if ($schoolYear !== null) {
$builder->where('school_year', $schoolYear);
}
if ($semester !== null) {
$builder->where('semester', $semester);
}
return $builder->findAll();
}
public function getAbsencesByClassSection($classSectionId, $schoolYear, $semester)
{
return $this->where('status', 'absent')
->where('class_section_id', $classSectionId)
->where('school_year', $schoolYear)
->where('semester', $semester)
->orderBy('date', 'DESC')
->findAll();
}
/**
* Fetch attendance rows for a student, optionally filtered by date range.
*
* @param int $studentId
* @param string|null $startDate 'YYYY-MM-DD' inclusive
* @param string|null $endDate 'YYYY-MM-DD' inclusive
* @param string|null $schoolYear optional filter
* @param string|null $semester optional filter
* @return array
*/
public function getStudentAttendance(
int $studentId,
?string $startDate = null,
?string $endDate = null,
?string $schoolYear = null,
?string $semester = null
): array {
$qb = $this->asArray()->where('student_id', $studentId);
if (!empty($startDate)) {
$qb->where('date >=', $startDate);
}
if (!empty($endDate)) {
$qb->where('date <=', $endDate);
}
if (!empty($schoolYear)) {
$qb->where('school_year', $schoolYear);
}
if (!empty($semester)) {
$qb->where('semester', $semester);
}
return $qb->orderBy('date', 'DESC')->findAll();
}
/**
* Convenience: attendance for an exact date.
*/
public function getStudentAttendanceOnDate(int $studentId, string $date): ?array
{
return $this->asArray()
->where(['student_id' => $studentId, 'date' => $date])
->first(); // returns null if none
}
/**
* NOT-REPORTED absences/lates for ALL students in optional date/term filters.
* - Not reported: is_reported = 0 OR '0' OR NULL
* - Status: ABS, ABSENT, LATE, A, L, plus prefixes like ABS_3 / LATE_2
* - Date inclusive: >= start AND < (end + 1 day) to handle DATE/DATETIME columns
*/
public function getUnreportedAbsencesAndLates(
?string $schoolYear = null,
?string $semester = null
): array {
$b = $this->db->table($this->table);
$b->select('id, student_id, class_id, class_section_id, date, status, reason, school_year, semester, is_reported')
// Not reported: 0 / '0' / NULL (supports legacy "reported" column)
;
$this->applyUnreportedFilter($b);
// Not notified yet: exclude rows already marked notified (handles enum/string/numeric)
$b->groupStart()
->where('is_notified', 0)
->orWhere('is_notified', '0')
->orWhere('is_notified', null)
->orWhere("LOWER(TRIM(COALESCE(is_notified, ''))) !=", 'yes')
->groupEnd();
// Optional term filters (no date constraints)
if ($schoolYear !== null && $schoolYear !== '') {
$b->where("{$this->table}.school_year", $schoolYear);
}
if ($semester !== null && $semester !== '') {
$b->where("{$this->table}.semester", $semester);
}
// Status filter: exact + prefixes (ABS_3, LATE_2, etc.)
$b->groupStart()
->whereIn("{$this->table}.status", ['ABS','ABSENT','LATE','A','L'])
->orLike("{$this->table}.status", 'ABS', 'after')
->orLike("{$this->table}.status", 'LATE', 'after')
->groupEnd();
// Sort for readability
$b->orderBy("{$this->table}.student_id", 'ASC')
->orderBy("{$this->table}.date", 'DESC');
$rows = $b->get()->getResultArray();
// Group per student (dates included in each record)
$out = [];
foreach ($rows as $r) {
$sid = (int) $r['student_id'];
if (!isset($out[$sid])) {
$out[$sid] = [
'absences' => [],
'lates' => [],
'counts' => ['absences' => 0, 'lates' => 0, 'total' => 0],
];
}
$st = strtoupper((string)($r['status'] ?? ''));
$isAbs = ($st === 'ABS' || $st === 'ABSENT' || $st === 'A' || str_starts_with($st, 'ABS'));
$isLate = ($st === 'LATE' || $st === 'L' || str_starts_with($st, 'LATE'));
$rec = [
'id' => $r['id'],
'date' => $r['date'],
'status' => $r['status'],
'reason' => $r['reason'],
'class_id' => $r['class_id'],
'class_section_id' => $r['class_section_id'],
'school_year' => $r['school_year'],
'semester' => $r['semester'],
];
if ($isAbs) {
$out[$sid]['absences'][] = $rec;
$out[$sid]['counts']['absences']++;
} elseif ($isLate) {
$out[$sid]['lates'][] = $rec;
$out[$sid]['counts']['lates']++;
}
$out[$sid]['counts']['total'] =
$out[$sid]['counts']['absences'] + $out[$sid]['counts']['lates'];
}
return $out;
}
public function markRuleAsNotifiedData(
int $studentId,
string $date,
?string $semester = null,
?string $schoolYear = null
): bool {
$day = substr($date, 0, 10);
$start = $day . ' 00:00:00';
$endEx = date('Y-m-d', strtotime($day . ' +1 day')) . ' 00:00:00';
$builder = $this->db->table($this->table);
$builder->where('student_id', $studentId)
->where('date >=', $start)
->where('date <', $endEx);
if (!empty($semester)) {
$builder->where('semester', $semester);
}
if (!empty($schoolYear)) {
$builder->where('school_year', $schoolYear);
}
// ✅ Update ENUM field properly (compatible with legacy numeric flags)
$builder->set('is_notified', 'yes');
$builder->set('updated_at', utc_now());
return $builder->update();
}
/**
* Mark specific attendance dates as reported + notified for a student.
*
* @param int $studentId
* @param array $dates Array of YYYY-MM-DD strings
*/
public function markReportedAndNotified(int $studentId, array $dates, ?string $semester = null, ?string $schoolYear = null): bool
{
$dates = array_values(array_unique(array_filter($dates, static function ($d) {
return is_string($d) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
})));
if (empty($dates)) {
return true;
}
$builder = $this->db->table($this->table)
->where('student_id', $studentId)
->whereIn('date', $dates)
->where("LOWER(TRIM(status)) IN ('absent','late')", null, false);
if (!empty($semester)) {
$builder->where('semester', $semester);
}
if (!empty($schoolYear)) {
$builder->where('school_year', $schoolYear);
}
$cols = $this->reportedColumns();
if (!empty($cols['is_reported'])) {
$builder->set('is_reported', 'yes');
}
if (!empty($cols['reported'])) {
$builder->set('reported', 1);
}
if ($this->db->fieldExists('is_notified', $this->table)) {
$builder->set('is_notified', 'yes');
}
return (bool) $builder
->set('updated_at', utc_now())
->update();
}
/**
* Fetch unreported dates for a student within N days for the given status set.
*
* @param int $studentId
* @param string[] $statuses ['absent','late']
* @param string $incidentDate YYYY-MM-DD reference (defaults today)
* @param int $lookbackDays days window backward from incidentDate (inclusive)
*/
public function getRecentUnreportedDates(
int $studentId,
array $statuses,
?string $incidentDate = null,
int $lookbackDays = 35,
?string $semester = null,
?string $schoolYear = null
): array {
if ($studentId <= 0 || empty($statuses)) {
return [];
}
$statuses = array_map('strtolower', $statuses);
$day = $incidentDate && preg_match('/^\d{4}-\d{2}-\d{2}$/', $incidentDate)
? $incidentDate
: date('Y-m-d');
$start = date('Y-m-d', strtotime($day . ' -' . max(0, $lookbackDays) . ' days'));
$qb = $this->db->table($this->table)
->select('date')
->where('student_id', $studentId)
->where('date >=', $start)
->where('date <=', $day)
->whereIn('LOWER(TRIM(status))', $statuses);
$this->applyUnreportedFilter($qb);
if (!empty($semester)) {
$qb->where('semester', $semester);
}
if (!empty($schoolYear)) {
$qb->where('school_year', $schoolYear);
}
$rows = $qb->orderBy('date', 'DESC')->get()->getResultArray();
return array_values(array_unique(array_map(static function ($r) {
return substr((string)($r['date'] ?? ''), 0, 10);
}, $rows)));
}
private function reportedColumns(): array
{
if ($this->reportedColumns !== null) {
return $this->reportedColumns;
}
$this->reportedColumns = [
'is_reported' => $this->db->fieldExists('is_reported', $this->table),
'reported' => $this->db->fieldExists('reported', $this->table),
];
return $this->reportedColumns;
}
private function applyUnreportedFilter($qb): void
{
$cols = $this->reportedColumns();
if (!empty($cols['is_reported'])) {
$qb->groupStart()
->where('is_reported', 'no')
->orWhere('is_reported', 0)
->orWhere('is_reported', '0')
->orWhere('is_reported', null)
->groupEnd();
}
if (!empty($cols['reported'])) {
$qb->where("(LOWER(TRIM(COALESCE(reported, ''))) NOT IN ('yes','1','true','y','on'))", null, false);
}
}
}