'integer', 'submitted_by' => 'integer', 'published_by' => 'integer', 'reopened_by' => 'integer', // If these are DATETIME columns: 'submitted_at' => 'datetime', 'published_at' => 'datetime', 'auto_publish_at' => 'datetime', 'reopened_at' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime', // If date is stored as DATE string; if it's DATETIME, change to 'datetime' 'date' => 'date', ]; /* ========================= * legacy method equivalents * ========================= */ /** * Legacy helper: True iff (section, date, term) is hard-locked for everyone. * Treats 'published' and legacy 'finalized' as finalized. */ public static function isFinalized( int $classSectionId, string $date, ?string $semester = null, ?string $schoolYear = null ): bool { $q = static::query() ->select('id') ->where('class_section_id', $classSectionId) ->whereDate('date', $date) ->where(function ($w) { $w->where('status', 'published') ->orWhere('status', 'finalized'); // legacy }); if ($semester !== null) { $q->where('semester', $semester); } if ($schoolYear !== null) { $q->where('school_year', $schoolYear); } return $q->exists(); } /** * Find day row by (section, date, term). */ public static function findBySectionDate( int $classSectionId, string $date, ?string $semester, ?string $schoolYear ): ?self { return static::query() ->where('class_section_id', $classSectionId) ->whereDate('date', $date) ->where('semester', $semester) ->where('school_year', $schoolYear) ->first(); } /** * Get draft row for (section,date,term), create it if missing. * Requires unique index: (class_section_id, date, semester, school_year) */ public static function getOrCreateDraft( int $classSectionId, string $date, ?string $semester, ?string $schoolYear, int $userId = 0 ): self { $row = static::findBySectionDate($classSectionId, $date, $semester, $schoolYear); if ($row) { return $row; } $now = now(); $normalizedDate = Carbon::parse($date)->toDateString(); try { return static::create([ 'class_section_id' => $classSectionId, 'date' => $normalizedDate, '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, ]); } catch (QueryException $e) { // Race: someone inserted same unique key. $row = static::findBySectionDate($classSectionId, $date, $semester, $schoolYear); if ($row) { return $row; } throw $e; } } /** * DEPRECATED legacy finalize => submit (teacher submit). */ public function finalize(int $userId): bool { return $this->submit($userId, null); } /** * Teacher submit: status => submitted (teacher locked, admin editable). */ public function submit(int $userId, ?string $autoPublishAt = null): bool { $now = now(); $payload = [ 'status' => 'submitted', 'submitted_by' => $userId, 'submitted_at' => $now, 'updated_at' => $now, ]; if ($autoPublishAt !== null) { $payload['auto_publish_at'] = $autoPublishAt; } return static::query()->whereKey($this->id)->update($payload) >= 0; } /** * Admin publish (hard lock): status => published. */ public function publish(int $userId): bool { $now = now(); return static::query()->whereKey($this->id)->update([ 'status' => 'published', 'published_by' => $userId, 'published_at' => $now, 'updated_at' => $now, ]) >= 0; } /** * Admin reopen a day back to 'draft' (default) or 'submitted'. */ public function reopen(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 = now(); return static::query()->whereKey($this->id)->update([ 'status' => $toStatus, 'reopened_by' => $userId, 'reopened_at' => $now, 'reopen_reason' => $reason, 'updated_at' => $now, ]) >= 0; } }