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.
Gates evaluated (active enrolment)
Section titled “Gates evaluated (active enrolment)”The following checks run for create active and waitlist to active:
- Age - Activity minimum/maximum age from effective fields; members without a date of birth may fail with
enrollments.date_of_birth_requiredwhen any bound applies. - Prerequisites - Level prerequisites via completed levels, required skills, or historical enrolments;
MemberLevelOverridebypasses prerequisite checks for a level. - 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). - Capacity - Peak concurrent spot-consuming enrolments over the proposed
datetime_rangemust 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_fullblocks placement (unless overridden by staff). Blocking issues may includemeta.peak_count,meta.capacity,meta.peak_at, andmeta.proposed_upper_source. - Session time clash (warning) - For active and casual enrolments with a proposed
datetime_range, when the member already has another active or casual enrolment whose timetable sessions overlap in UTC,enrollments.schedule_conflictis returned withseverity: warning. This does not block checkout or conversion; staff and family portal clients may proceed without an override.metaincludesconflicting_activity_idandconflicting_activity_name. This is separate fromenrollments.overlapping_enrollment, which blocks duplicate enrolment periods on the same activity.
Each issue is returned with severity (for example blocking or warning), human-readable title / detail, optional pointer and meta, plus can_override and required_permission after annotation.
Staff overrides
Section titled “Staff overrides”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_permissionfor that code. If the user lacks it, the issue is replaced withenrollments.insufficient_permission_to_override(meta.original_coderecords the gate). - Warnings are not overridable via this map (
can_overridestays false).
Permissions used for enrolment gates (codenames on ActivityEnrollment):
| Gate area | Permission codename |
|---|---|
| Age / DOB | activities.override_enrollment_age |
| Prerequisites | activities.override_enrollment_prerequisites |
| Policies | activities.override_enrollment_policies |
| Capacity | activities.override_enrollment_capacity |
Preview vs enforce
Section titled “Preview vs enforce”Capacity look-ahead
Section titled “Capacity look-ahead”Capacity uses a Postgres tstzrange peak query over the proposed enrolment window (datetime_range on the preview or create payload). Structural toggles control whether active and casual enrolments count (configurable per organisation, location, and category). Enrolments that carry labels (for example Trial or Makeup) can also be capped by per-label capacity rules resolved for the activity.
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.
Eligibility preview (HTTP 200)
Section titled “Eligibility preview (HTTP 200)”POST .../eligibility-preview evaluates gates and returns:
issues: the same annotated issue objects you would see inside a problemerrorsarray (field names align for client reuse).can_proceed:truewhen 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.
Commercial enrolment create (Checkout)
Section titled “Commercial enrolment create (Checkout)”New active, casual, trial, and waitlist enrolments with initial charges must be created through staff Checkout, not POST .../enrollments/. That nested create endpoint returns 405 Method Not Allowed.
Use POST .../eligibility-preview (and live Cart preview via GET /api/v1/carts/{id}/) to evaluate gates before starting Checkout. Staff may attest overrides per issue code when freezing the Cart (POST /api/v1/carts/{id}/checkout/) by sending staff_overrides: a map from cart line id (or client_key) to { "<issue_code>": true }. The server re-validates gates at freeze and applies the same override rules as direct enrolment create.
Convert waitlist (enforce)
Section titled “Convert waitlist (enforce)”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/reference/problem/enrollments.gates_failed
See the dedicated page for that type and full JSON shape: Enrolment gates failed (enrollments.gates_failed).
Client integration tips
Section titled “Client integration tips”- Drive UI and retry logic from each issue’s
code,can_override, andrequired_permission, not from parsingdetailstrings. - For policy gates when a member belongs to multiple families, pass
family_idon 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.