173 lines
5.6 KiB
PHP
Executable File
173 lines
5.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use CodeIgniter\Model;
|
|
|
|
class AttendanceDayModel extends Model
|
|
{
|
|
protected $table = 'attendance_day';
|
|
protected $primaryKey = 'id';
|
|
protected $returnType = 'array';
|
|
protected $useTimestamps = false;
|
|
|
|
protected $allowedFields = [
|
|
'class_section_id',
|
|
'date',
|
|
'status', // 'draft','submitted','published' (legacy: 'finalized')
|
|
'submitted_by',
|
|
'submitted_at',
|
|
'published_by',
|
|
'published_at',
|
|
'auto_publish_at',
|
|
'reopened_by',
|
|
'reopened_at',
|
|
'reopen_reason',
|
|
'semester',
|
|
'school_year',
|
|
'created_at',
|
|
'updated_at',
|
|
];
|
|
|
|
/**
|
|
* Legacy helper kept for compatibility.
|
|
* True iff this (section, date, term) is hard-locked for everyone.
|
|
* Treats 'published' (new) and 'finalized' (old) as finalized.
|
|
*/
|
|
public function isFinalized(int $classSectionId, string $date, ?string $semester = null, ?string $schoolYear = null): bool
|
|
{
|
|
$builder = $this->builder()
|
|
->select('id')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('date', $date)
|
|
->groupStart()
|
|
->where('status', 'published')
|
|
->orWhere('status', 'finalized') // tolerate legacy data
|
|
->groupEnd();
|
|
|
|
if ($semester !== null) $builder->where('semester', $semester);
|
|
if ($schoolYear !== null) $builder->where('school_year', $schoolYear);
|
|
|
|
return (bool) $builder->get()->getFirstRow();
|
|
}
|
|
|
|
/**
|
|
* Find a day row by (section, date, term). Returns array|null.
|
|
*/
|
|
public function findBySectionDate(int $classSectionId, string $date, ?string $semester, ?string $schoolYear): ?array
|
|
{
|
|
return $this->where([
|
|
'class_section_id' => $classSectionId,
|
|
'date' => $date,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
])->first() ?: null;
|
|
}
|
|
|
|
/**
|
|
* Get the draft row for (section,date,term), creating it if missing.
|
|
* Relies on the unique key (class_section_id, date, semester, school_year) to prevent duplicates.
|
|
*/
|
|
public function getOrCreateDraft(
|
|
int $classSectionId,
|
|
string $date,
|
|
?string $semester,
|
|
?string $schoolYear,
|
|
int $userId = 0
|
|
): array {
|
|
// Try existing
|
|
$row = $this->findBySectionDate($classSectionId, $date, $semester, $schoolYear);
|
|
if ($row) return $row;
|
|
|
|
$now = utc_now();
|
|
$data = [
|
|
'class_section_id' => $classSectionId,
|
|
'date' => $date,
|
|
'status' => 'draft',
|
|
'submitted_by' => null,
|
|
'submitted_at' => null,
|
|
'published_by' => null,
|
|
'published_at' => null,
|
|
'auto_publish_at' => null,
|
|
'reopened_by' => null,
|
|
'reopened_at' => null,
|
|
'reopen_reason' => null,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
try {
|
|
$id = $this->insert($data, true);
|
|
return $this->find($id);
|
|
} catch (\Throwable $e) {
|
|
// Race: someone else inserted the same unique key. Fetch it.
|
|
$row = $this->findBySectionDate($classSectionId, $date, $semester, $schoolYear);
|
|
if ($row) return $row;
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED: Prior code used "finalize" to lock immediately.
|
|
* In the new workflow, "finalize" means "teacher submit"
|
|
* (locks teachers, keeps admins editable). Use submit() or publish().
|
|
*/
|
|
public function finalize(int $id, int $userId): bool
|
|
{
|
|
// Alias to submit() to avoid hard-locking admins by legacy calls.
|
|
return $this->submit($id, $userId, null);
|
|
}
|
|
|
|
/**
|
|
* Teacher submit: status => submitted (teacher locked, admin editable).
|
|
* Optionally set auto_publish_at (controller can compute second-Sunday rule).
|
|
*/
|
|
public function submit(int $id, int $userId, ?string $autoPublishAt = null): bool
|
|
{
|
|
$now = utc_now();
|
|
return (bool) $this->update($id, [
|
|
'status' => 'submitted',
|
|
'submitted_by' => $userId,
|
|
'submitted_at' => $now,
|
|
'auto_publish_at' => $autoPublishAt,
|
|
'updated_at' => $now,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Admin publish (hard lock): status => published.
|
|
*/
|
|
public function publish(int $id, int $userId): bool
|
|
{
|
|
$now = utc_now();
|
|
return (bool) $this->update($id, [
|
|
'status' => 'published',
|
|
'published_by' => $userId,
|
|
'published_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Admin reopen a submitted/published day back to 'draft' (default) or 'submitted'.
|
|
* Provide an optional reason for audit.
|
|
*/
|
|
public function reopen(int $id, int $userId, string $toStatus = 'draft', ?string $reason = null): bool
|
|
{
|
|
if (!in_array($toStatus, ['draft', 'submitted'], true)) {
|
|
throw new \InvalidArgumentException('toStatus must be "draft" or "submitted".');
|
|
}
|
|
|
|
$now = utc_now();
|
|
return (bool) $this->update($id, [
|
|
'status' => $toStatus,
|
|
'reopened_by' => $userId,
|
|
'reopened_at' => $now,
|
|
'reopen_reason' => $reason,
|
|
'updated_at' => $now,
|
|
]);
|
|
}
|
|
}
|