Add group-class enrolment deadline with instructor late-enrolment override
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 3m2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped

Group classes gain an instructor-set enrolment deadline (new
us_offerings.enrollment_deadline column) that defaults to the first day of
the class (term_start). Past the deadline students can no longer self-enrol:
the enrolment endpoint rejects it (403 enrollment_closed) and the front-end
class list shows "Enrolment has closed." in place of the Enrol button.

Instructors keep a manual path: the "Add students directly" control on each
class's details page now renders for public classes too (not just
invite-only) and deliberately bypasses the deadline and capacity, so a
student can be added as a late enrolment after the class has closed. Past
the deadline the details page labels these as late enrolments.

Bumps USC_VERSION to 1.1.3 for the schema change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-24 10:19:38 -03:00
co-authored by Claude Opus 4.8
parent 991ed2f5ad
commit bf29162587
18 changed files with 338 additions and 54 deletions
+18 -1
View File
@@ -103,6 +103,21 @@
return [termLabel(o), timeLabel(o)].filter(Boolean).join(' · ');
}
// Today as a Y-m-d string in the visitor's local timezone, for lexicographic
// comparison against the class's Y-m-d enrolment deadline.
function todayYmd() {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
}
// Enrolment closes at the end of the deadline day — the instructor's set
// deadline, or the first class day by default. Mirrors the server-side
// Offering::isEnrollmentOpen() gate.
function isEnrollmentOpen(o) {
const deadline = o.enrollment_deadline || o.term_start || '';
return !deadline || todayYmd() <= deadline;
}
function renderClasses(offerings, enrolledOfferingIds) {
let groups = offerings.filter((o) => o.kind === 'group_class');
if (singleOfferingId) {
@@ -125,7 +140,9 @@
<p>${escHtml(Number(o.price).toFixed(2))} ${escHtml(o.currency)}</p>
${enrolledOfferingIds.has(Number(o.id))
? '<p class="us-enrolled"><strong>You are enrolled in this class.</strong></p>'
: `<button data-offering-id="${o.id}" class="us-enrol-btn">Enrol</button>`}
: (isEnrollmentOpen(o)
? `<button data-offering-id="${o.id}" class="us-enrol-btn">Enrol</button>`
: '<p class="us-enrol-closed"><strong>Enrolment has closed.</strong></p>')}
</div>
`).join('');