Skip to content

Enrolment gating and staff overrides

Active enrolment (and waitlist to active conversion) runs a single evaluation that gathers all blocking issues at once rather than failing on the first check. The API exposes both a preview (dry run, HTTP 200) and enforce paths that return a problem details document when rules still block the action.

The following checks run for create active and waitlist to active:

  1. Age - Activity minimum/maximum age from effective fields; members without a date of birth may fail with enrollments.date_of_birth_required when any bound applies.
  2. Prerequisites - Level prerequisites via completed levels, required skills, or historical enrolments; MemberLevelOverride bypasses prerequisite checks for a level.
  3. Enrolment policies - Required policies for the target activity, scoped via family billing context; if the member is in more than one family, policy evaluation may require an explicit family_id (enrollments.family_context_required).
  4. Capacity - Peak concurrent spot-consuming enrolments over the proposed datetime_range must not exceed effective activity capacity. If any instant in that window would be over capacity (including future-dated enrolments already on the activity), enrollments.capacity_full blocks placement (unless overridden by staff). Blocking issues may include meta.peak_count, meta.capacity, meta.peak_at, and meta.proposed_upper_source.

Each issue is returned with severity (for example blocking), human-readable title / detail, optional pointer and meta, plus can_override and required_permission after annotation.

Staff with the correct Django permission can attest that a specific gate may be bypassed for a given request by sending staff_overrides: a map from issue code to a truthy value (for example { "enrollments.capacity_full": true }).

Rules:

  • Overrides apply per issue code. If staff_overrides[code] is not set, the issue remains.
  • The server checks required_permission for that code. If the user lacks it, the issue is replaced with enrollments.insufficient_permission_to_override (meta.original_code records the gate).
  • Warnings are not overridable via this map (can_override stays false).

Permissions used for enrolment gates (codenames on ActivityEnrollment):

Gate areaPermission codename
Age / DOBactivities.override_enrollment_age
Prerequisitesactivities.override_enrollment_prerequisites
Policiesactivities.override_enrollment_policies
Capacityactivities.override_enrollment_capacity

Capacity uses a Postgres tstzrange peak query over the proposed enrolment window (datetime_range on the preview or create payload). Which enrolment types count (active, casual, trial, makeup) is configurable per tenant, location, and category.

When the proposed upper bound is omitted (ongoing enrolment), the server normalises the window using sibling enrolments on the activity (latest finite end or latest start), or an open-ended range when none exist.

Waitlist enrolments do not consume capacity today. Committed destination enrolments from transfers are included via their stored datetime_range rows.

POST .../eligibility-preview evaluates gates and returns:

  • issues: the same annotated issue objects you would see inside a problem errors array (field names align for client reuse).
  • can_proceed: true when there are no blocking issues.

The preview does not mutate enrolments. It does not apply staff_overrides; it only reflects whether the current actor is staff for can_override hints.

Create enrolment / convert waitlist (enforce)

Section titled “Create enrolment / convert waitlist (enforce)”

Creating an active enrolment or converting waitlist to active applies staff_overrides for staff users. If blocking issues remain, the API responds with 422 Unprocessable Entity, Content-Type: application/problem+json, and problem type:

https://docs.keja.co/troubleshooting/status-codes/enrollments.gates_failed

See the dedicated page for that type and full JSON shape: Enrolment gates failed (enrollments.gates_failed).

  • Drive UI and retry logic from each issue’s code, can_override, and required_permission, not from parsing detail strings.
  • For policy gates when a member belongs to multiple families, pass family_id on create/preview payloads when the API expects family-scoped policy context.
  • Re-run preview after changes (DOB, policy acceptance, family selection) before submitting an enrolment.