'integer', 'active' => 'boolean', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; /* ============================================================ * Scopes * ============================================================ */ public function scopeForSection(Builder $q, int $sectionId): Builder { return $q->where('class_section_id', $sectionId); } public function scopeForYear(Builder $q, string $year): Builder { return $q->where('school_year', trim($year)); } public function scopeForSemester(Builder $q, string $semester, bool $allowNullSemester = false): Builder { $sem = trim($semester); if ($sem === '') { return $q; } if ($allowNullSemester) { return $q->where(function (Builder $w) use ($sem) { $w->where('semester', $sem) ->orWhereNull('semester'); }); } return $q->where('semester', $sem); } public function scopeActive(Builder $q): Builder { return $q->where('active', 1); } public function scopeInactive(Builder $q): Builder { return $q->where('active', 0); } public function scopeHasInviteLink(Builder $q): Builder { return $q->whereNotNull('invite_link') ->where('invite_link', '!=', ''); } /* ============================================================ * Methods (legacy-compatible behavior) * ============================================================ */ /** * Get the link for a specific section in a specific term. * * @param int $sectionId Class/section code (not PK). * @param string $year School year, e.g. "2025-2026". * @param string $sem Semester, e.g. "Fall". * @param bool $onlyActive If true, require active=1. * @param bool $allowNullSemester If true, accept rows with semester IS NULL as well. */ public static function getLinkForSection( int $sectionId, string $year, string $sem, bool $onlyActive = true, bool $allowNullSemester = false ): ?self { $q = static::query() ->forSection($sectionId) ->forYear($year) ->forSemester($sem, $allowNullSemester) ->hasInviteLink(); if ($onlyActive) { $q->active(); } return $q->orderByDesc('updated_at') ->orderByDesc('id') ->first(); } /** * Get all links for a term. * * @param string $year * @param string $sem * @param bool|null $onlyActive true: active only, false: inactive only, null: both * @param bool $allowNullSemester If true, include rows with semester IS NULL. * * @return array */ public static function getAllForTerm( string $year, string $sem, ?bool $onlyActive = null, bool $allowNullSemester = false ): array { $q = static::query() ->forYear($year) ->forSemester($sem, $allowNullSemester); if ($onlyActive === true) { $q->active(); } elseif ($onlyActive === false) { $q->inactive(); } return $q->orderByDesc('updated_at') ->orderByDesc('id') ->get() ->all(); } /** * Convenience: upsert a link row for a section/term. * Returns the model after save. */ public static function upsertLinkForSection( int $sectionId, string $sectionName, string $year, string $sem, string $inviteLink, bool $active = true ): self { $payload = [ 'class_section_name' => $sectionName, 'invite_link' => trim($inviteLink), 'active' => $active ? 1 : 0, ]; // NOTE: semester is part of the uniqueness key like your legacy version. return static::query()->updateOrCreate( [ 'class_section_id' => $sectionId, 'school_year' => trim($year), 'semester' => trim($sem), ], $payload ); } /* ============================================================ * Optional validation helper * ============================================================ */ public static function rules(bool $updating = false): array { $req = $updating ? 'sometimes' : 'required'; return [ 'class_section_id' => [$req, 'integer', 'min:1'], 'class_section_name' => [$req, 'string', 'max:255'], 'school_year' => [$req, 'string', 'max:20'], 'semester' => ['nullable', 'string', 'max:20'], 'invite_link' => [$req, 'string', 'max:2000'], 'active' => ['nullable', 'boolean'], ]; } }