Skoolsewa - School Management System Docs
Developer ResourcesAcademic year rollover

Academic Year Rollover API Reference

Complete API contracts for Academic Year Rollover, including routes, auth, DTOs, responses, errors, examples, and integration notes.

Academic Year Rollover - API Reference

Audience: Admin panel engineers, QA, and backend engineers integrating with rollover. Scope: Admin-only APIs owned by the Rollover module — no public, mobile, or webhook surface exists.

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllerscycles/admin, items/admin, allocation/admin, processing/admin, activation/admin, corrections/admin, settings/admin, insights/admin controllersRoutes, guards, permissions
DTOs*/dto/*.ts under each submoduleRequest/response/query shapes
Services*/core/*.service.ts, shared/*.service.tsBehavior, side effects, error conditions
Schemapackages/db/src/schema/school/rollover.ts, academic-sessions.tsEnum values, persisted fields
Error registryapps/api/src/common/types/error-codes.ts, shared/rollover-codes.tsAll error and outcome codes

2. Module Summary

FieldValue
Module nameRollover
Module slugrollover
Primary actorsadmin, superadmin, internal worker
API surfacesadmin only
Base route prefixes/api/rollover/..., /api/rollover/settings
Auth modelJwtAuthGuard (global) + RoleGuard against @Permissions() codes
PersistencePostgreSQL only — no cache, no MongoDB, no realtime
Runtime source of truthrollover_cycles, rollover_items, student_graduations, rollover_events, rollover_settings
Sibling docsBackend, Features and flows, Promotions

3. Concepts and Terminology

TermMeaningSource FileUsed By
CycleOne rollover run from a source session to a target sessionrollover-cycle.dto.tsEvery cycle/item/allocation/processing endpoint
ItemOne pupil's row within a cycle — their source snapshot, target assignment, and statusrollover-item.dto.tsItems, allocation, corrections
InclusionWhether an item participates in this run at all (included/excluded)rollover.tsItems
Display statusA computed, UI-facing status (processing, completed_with_exceptions, etc.) derived from status/runState/runOutcomerollover-cycle.dto.tsCycle detail, progress
FingerprintA hash of everything that would change the outcome of processing, used to detect a stale planrollover-fingerprint.service.tsReview, process
Proposal tokenA token returned by an allocation preview and required, unchanged, on the matching apply callrollover-allocation.dto.tsSection/roll allocation
LeaseThe exclusivity token a worker holds while processing a cyclerollover_cycles.lease_tokenProcessing, progress
PreflightThe set of blocking/warning codes checked before activation is allowedrollover-activation.dto.tsActivation

4. API Surface Map

SurfaceMethodPathActorPermissionControllerPurpose
AdminGET/rollover/overviewAdminRollover_READRolloverCyclesControllerDashboard: current/target session, active cycle, counts
AdminGET/rollover/cyclesAdminRollover_READRolloverCyclesControllerList cycles
AdminPOST/rollover/cyclesAdminRollover_CREATERolloverCyclesControllerStart a cycle
AdminGET/rollover/cycles/:publicIdAdminRollover_READRolloverCyclesControllerCycle detail with readiness
AdminDELETE/rollover/cycles/:publicIdAdminRollover_DELETERolloverCyclesControllerCancel a cycle
AdminPOST/rollover/cycles/:publicId/syncAdminRollover_UPDATERolloverCyclesControllerRe-sync roster
AdminPOST/rollover/cycles/:publicId/reviewAdminRollover_UPDATERolloverCyclesControllerPrepare fingerprint for processing
AdminGET/rollover/cycles/:publicId/itemsAdminRollover_READRolloverItemsControllerList items with filters
AdminGET/rollover/cycles/:publicId/exceptionsAdminRollover_READRolloverItemsControllerGrouped exceptions/warnings with samples
AdminPATCH/rollover/cycles/:publicId/items/:studentPublicIdAdminRollover_UPDATERolloverItemsControllerEdit one item's target class/roll
AdminPOST/rollover/cycles/:publicId/items/assign-classAdminRollover_UPDATERolloverItemsControllerBulk assign target class
AdminPOST/rollover/cycles/:publicId/items/excludeAdminRollover_UPDATERolloverItemsControllerManually exclude pupils
AdminPOST/rollover/cycles/:publicId/items/includeAdminRollover_UPDATERolloverItemsControllerRe-include pupils
AdminPOST/rollover/cycles/:publicId/items/capacity-overrideSuperadminRolloverOverrides_UPDATERolloverItemsControllerPlace beyond section capacity
AdminGET/rollover/cycles/:publicId/sectionsAdminRollover_READRolloverAllocationControllerTarget classes with demand/capacity
AdminPOST/rollover/cycles/:publicId/section-allocation/previewAdminRollover_UPDATERolloverAllocationControllerPreview section allocation
AdminPOST/rollover/cycles/:publicId/section-allocation/applyAdminRollover_UPDATERolloverAllocationControllerApply a previewed section allocation
AdminPOST/rollover/cycles/:publicId/roll-allocation/previewAdminRollover_UPDATERolloverAllocationControllerPreview roll allocation
AdminPOST/rollover/cycles/:publicId/roll-allocation/applyAdminRollover_UPDATERolloverAllocationControllerApply a previewed roll allocation
AdminPOST/rollover/cycles/:publicId/processSuperadminRolloverProcessing_CREATERolloverProcessingControllerStart/re-run background processing
AdminGET/rollover/cycles/:publicId/progressAdminRollover_READRolloverProcessingControllerPoll run progress
AdminGET/rollover/cycles/:publicId/activation-preflightAdminRollover_READRolloverActivationControllerPreflight blocking/warning codes
AdminPOST/rollover/cycles/:publicId/activateSuperadminRolloverActivation_UPDATERolloverActivationControllerMake the target session current
AdminPOST/rollover/cycles/:publicId/finalizeSuperadminRolloverActivation_UPDATERolloverActivationControllerFinalize the cycle
AdminPOST/rollover/cycles/:publicId/notify-placementsSuperadminRolloverActivation_UPDATERolloverActivationControllerRetry placement notifications stuck in failed or unsent none
AdminPOST/rollover/sessions/:sessionPublicId/lockSuperadminAcademicYearLock_UPDATERolloverActivationControllerLock a historical session
AdminPOST/rollover/sessions/:sessionPublicId/unlockSuperadminAcademicYearLock_UPDATERolloverActivationControllerUnlock a session
AdminPOST/rollover/cycles/:publicId/items/:studentPublicId/correctSuperadminRolloverCorrections_UPDATERolloverCorrectionsControllerMove, realign, or relink a pupil's placement
AdminGET/rollover/settingsSuperadminRolloverSettings_READRolloverSettingsControllerRead rollover settings
AdminPATCH/rollover/settingsSuperadminRolloverSettings_UPDATERolloverSettingsControllerUpdate rollover settings
AdminGET/rollover/cycles/:publicId/eventsAdminRollover_READRolloverInsightsControllerEvent timeline, paginated
AdminGET/rollover/cycles/:publicId/integrityAdminRollover_READRolloverInsightsControllerIntegrity cross-check report
AdminGET/rollover/cycles/:publicId/id-cardsAdminRollover_READRolloverInsightsControllerID-card readiness counts
AdminGET/rollover/cycles/:publicId/new-admissionsAdminRollover_READRolloverInsightsControllerNew admissions not covered by the cycle

All three correction actions (move, realign, relink) share the single POST .../items/:studentPublicId/correct route; the action field of the request body selects the case. See Backend §6.9 for the write sequence each action follows.

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Everyday cycle/item/allocation workJwtAuthGuard, RoleGuardreq.user.id, activeRoleRollover_*NoNot superadmin-only by default — a school can grant it to any admin role.
ProcessingJwtAuthGuard, RoleGuardreq.user.idRolloverProcessing_CREATENoSuperadmin-only by default.
Capacity overrideJwtAuthGuard, RoleGuardreq.user.idRolloverOverrides_UPDATENoSuperadmin-only by default.
CorrectionsJwtAuthGuard, RoleGuardreq.user.idRolloverCorrections_UPDATENoSuperadmin-only by default.
Activation / finalizeJwtAuthGuard, RoleGuardreq.user.idRolloverActivation_UPDATENoSuperadmin-only by default.
Lock / unlock a sessionJwtAuthGuard, RoleGuardreq.user.idAcademicYearLock_UPDATENoSuperadmin-only by default.
SettingsJwtAuthGuard, RoleGuardreq.user.idRolloverSettings_READ/_UPDATENoSuperadmin-only by default.

No endpoint in this module supports guest or unauthenticated access; there is no public or mobile surface.

6. DTO and Model Reference

6.1 CreateRolloverCycleDto — body, POST /rollover/cycles

FieldTypeRequiredNotes
sourceSessionPublicIduuidYesMust differ from targetSessionPublicId.
targetSessionPublicIduuidYesMust start after the source session.
promotionCyclePublicIduuidYesMust be finalized.

6.2 ListRolloverCyclesQueryDto — query, GET /rollover/cycles

FieldTypeRequiredNotes
statusenum (preparing,processed,activated,finalized,cancelled)NoFilters by raw status.

6.3 RolloverCycleDetailDto — response, GET /rollover/cycles/:publicId

FieldTypeNotes
publicId, code, status, displayStatusstringdisplayStatus is computed — see Backend §5.2.
sourceSession, targetSessionRolloverSessionRefDto{publicId, name}
readinessRolloverReadinessDto[]15 checks across 7 groups — see Backend §6.8.
countsobjectItem counts by status.

6.4 RolloverOverviewDto — response, GET /rollover/overview

FieldTypeNotes
currentSessionRolloverSessionRefDto | null—
suggestedTargetRolloverSessionRefDto | null—
promotionCycleRolloverPromotionCycleRefDto | null—
activeCycleRolloverCycleSummaryDto | nullReadiness omitted from this summary shape.
studentsRolloverOverviewStudentCountsDtoactiveInSource, withdrawnInSource, transferredInSource, inactiveRecords, newAdmissionsInTarget, missingSourcePlacement.
decisionsRolloverOverviewDecisionCountsDtototal, byOutcome[], withoutDecision.
recentCyclesRolloverCycleSummaryDto[]—

6.5 ListRolloverItemsQueryDto — query, GET /rollover/cycles/:publicId/items

FieldTypeRequiredNotes
viewenum (ready,exceptions,excluded,processed,all)NoPreset filter combinations.
outcomestringNoFilters by promotion outcome.
statusstringNoFilters by item status.
exceptionCodestringNoFilters items carrying a specific exception code.
sourceClassPublicIduuidNo—
targetGradePublicIduuidNo—
targetClassPublicIduuidNo—

6.6 UpdateRolloverItemDto — body, PATCH /rollover/cycles/:publicId/items/:studentPublicId

FieldTypeRequiredNotes
targetClassPublicIduuidNo—
plannedRollNumberinteger 1–999NoOnly settable when roll policy is not fully automatic; see ROLLOVER_ROLL_POLICY_AUTOMATIC.

6.7 ExcludeRolloverItemsDto / IncludeRolloverItemsDto

FieldTypeRequiredNotes
studentPublicIdsuuid[]YesBatch of pupils to exclude/include.
reasonstringYes (exclude only)Persisted as exclusionReason.

6.8 CapacityOverrideDto — body, POST /rollover/cycles/:publicId/items/capacity-override

FieldTypeRequiredNotes
studentPublicIduuidYes—
enablebooleanYesSets/clears capacityOverride.
reasonstringYes when enable=true—

6.9 SectionAllocationPreviewDto / SectionAllocationResultDto

FieldTypeRequiredNotes
targetGradePublicIduuidYes—
strategyenum (balanced,keep_previous,alphabetical,previous_roll)YesSee Backend §6.4 for algorithm detail.
onlyUnassignedbooleanNoRestricts the proposal to items with no targetClassId yet.
preserveShiftbooleanNoRestricts eligibility to same-shift classes where possible.

Result: proposalToken (string, required unchanged on apply), gradePublicId, strategy, perClass[] (per-class assignment counts), assignments[] (per-student proposed class), unassignedCount (int), applied (boolean, present once applied). Each perClass[] entry carries genderAfter: { male, female, other } only when rollover_settings.balanceGender was on for this allocation AND the caller holds Students_READ — it is omitted entirely otherwise, never returned as null.

6.10 RollAllocationPreviewDto / RollAllocationResultDto

FieldTypeRequiredNotes
targetGradePublicIduuidYes—
strategyenum (alphabetical,previous_roll,keep_previous)YesSee Backend §6.5.
scopeenum (section,grade)YesWhether roll numbers are unique per class or per grade.

Result: proposalToken, gradePublicId, strategy, scope, assignments[], warnings[], applied.

6.11 AllocationApplyDto — body, both apply endpoints

FieldTypeRequiredNotes
proposalTokenstringYesMust match the token from the matching preview call, or ROLLOVER_PROPOSAL_STALE.

6.12 RolloverProcessRequestDto — body, POST /rollover/cycles/:publicId/process

FieldTypeRequiredNotes
fingerprintstring, min length 1YesThe fingerprint returned by the review call; must equal the fingerprint recomputed live under lock.
confirmbooleanYes, must be trueROLLOVER_CONFIRMATION_REQUIRED if false/absent.

6.13 RolloverProcessResultDto — response, same endpoint

FieldTypeNotes
cyclePublicIduuid—
attemptintegerThe attempt number just started.
itemsToRunintegerCount of non-terminal included items about to be processed.

6.14 RolloverProgressDto — response, GET /rollover/cycles/:publicId/progress

FieldTypeNotes
displayStatusstringComputed display status.
runStateenum (idle,running)—
runOutcomeenum (completed,completed_with_exceptions,partially_completed) | nullSet once finished.
failureCodestring | nullOne of the run failure codes in Backend §13.
attemptinteger—
runStartedAt, runFinishedAt, lastHeartbeatAttimestamp | null—
totalIncludedinteger—
countsRolloverProgressCountsDtoPer-status item counts.
excludedinteger—

6.15 RolloverSettingsDto / UpdateRolloverSettingsDto

FieldTypeDefaultNotes
allowConditionalPlacementbooleantrue—
allowPlacementWithoutSectionbooleantrue—
capacityPolicyenum (block,allow_override)block—
defaultSectionStrategyenum (balanced,keep_previous,alphabetical,previous_roll)balanced—
preserveShiftbooleantrue—
rollStrategyenum (alphabetical,previous_roll,keep_previous)alphabetical—
rollScopeenum (section,grade)section—
allowActivationWithExceptionsbooleantrue—
notifyPlacementbooleantrue—
placementChannelsarray of email|sms|push|in_app['in_app','push']—
adminChannelsarray of email|sms|push|in_app['in_app']—
idCardReminderbooleantrue—
balanceGenderbooleanfalseWhether the balanced section strategy also evens out male/female counts per section.
graduateAccessDaysnumber180Days a graduated pupil keeps read-only portal access after graduated_on, 0–3650. Copied onto the graduation at the moment it is recorded, so changing this never shortens or extends access already granted.
versionbigint—Required on PATCH for optimistic locking; mismatch is ROLLOVER_SETTINGS_STALE.
updatedAttimestamp—Response-only.

UpdateRolloverSettingsDto makes every field above optional except version, which is always required on a write.

6.16 RolloverCorrectionDto / RolloverCorrectionResultDto — POST /rollover/cycles/:publicId/items/:studentPublicId/correct

FieldTypeRequiredNotes
actionenum (move,realign,relink)YesSelects which correction case runs.
targetClassPublicIdUUIDOnly for move, and for realign cases that place into a new classIgnored by relink.
reasonstring, 3–1000 charsYesRecorded on the rollover_events row.
allowOverCapacitybooleanNomove only. Enrolls into the target class over its capacity when rollover_settings.capacity_policy is allow_override and the caller holds RolloverOverrides_UPDATE. Checked in order: the permission first (403 ROLLOVER_OVER_CAPACITY_NOT_PERMITTED if missing, before the transaction opens, regardless of action or the current policy), then that action === "move" (400 ROLLOVER_CORRECTION_INVALID otherwise), then the capacity policy itself (409 ROLLOVER_OVERRIDE_NOT_ALLOWED if not allow_override).

RolloverCorrectionResultDto response: { item: RolloverItemDto | null } — the item's post-correction state, re-read after the transaction commits. A move performed with allowOverCapacity: true records overCapacity: true|false in the placement_corrected event's toValue, reflecting whether the destination class actually ended up over capacity after the enrollment; the field is absent from an ordinary move's event. Un-graduating a pupil through realign (cases d/e) always restores their pupil-scope role grants, and restores sign-in as well only when the actor performing the realign holds Users_UPDATE.

7. Enum Reference

EnumValuesSource
Cycle statuspreparing, processed, activated, finalized, cancelledrollover.ts
Cycle display statuscancelled, preparing, processing, completed, completed_with_exceptions, partially_completed, activated, finalizedrollover-cycles.service.ts
Run stateidle, runningrollover.ts
Run outcomecompleted, completed_with_exceptions, partially_completedrollover.ts
Placement notification statenone, queued, sent, failedrollover.ts
Item statuspending, placed, graduated, awaiting_section, needs_review, failed, withdrawnrollover.ts
Inclusionincluded, excludedrollover.ts
Exclusion sourcemanual, decisionrollover.ts
Capacity policyblock, allow_overriderollover.ts
Section strategybalanced, keep_previous, alphabetical, previous_rollrollover.ts
Roll strategyalphabetical, previous_roll, keep_previousrollover.ts
Roll scopesection, graderollover.ts
Item view filterready, exceptions, excluded, processed, allrollover-item.dto.ts
Notification channelemail, sms, push, in_approllover.ts (CHECK constraint)

Exception, warning, outcome, run-failure, integrity, preflight, and allocation-unassigned codes are enumerated in full in Backend §13 — they are not HTTP-facing enums but do appear verbatim in response payloads (exceptionCodes[], outcomeCode, failureCode, integrity report entries, preflight code lists).

8. Endpoint Reference

8.1 GET /rollover/overview

Purpose: The rollover dashboard's landing data — which session is current, what the suggested next target is, whether a cycle is already active, and headline counts of pupils and decisions. Call this first when opening the rollover screen.

Auth: Rollover_READ.

Response: RolloverOverviewDto — see §6.4.

Errors: none domain-specific — a caller lacking permission gets 403.

8.2 GET /rollover/cycles

Purpose: List past and present rollover cycles, optionally filtered by status, for the cycle-history screen.

Auth: Rollover_READ.

Query: status (optional).

Response: paginated array of RolloverCycleSummaryDto.

8.3 POST /rollover/cycles

Purpose: Start a new rollover cycle for a source/target session pair once the source's promotion cycle is finalized. This is the entry point to the whole module.

Auth: Rollover_CREATE.

Body: CreateRolloverCycleDto — see §6.1.

{
  "sourceSessionPublicId": "b1a2...",
  "targetSessionPublicId": "c3d4...",
  "promotionCyclePublicId": "e5f6..."
}

Side Effects: inserts a rollover_cycles row and syncs the roster into rollover_items in the same operation.

Error Cases

HTTP StatusError CodeCondition
400ValidationMissing/invalid public IDs
409ROLLOVER_SAME_SESSIONSource equals target
409ROLLOVER_TARGET_BEFORE_SOURCETarget does not start after source
409ROLLOVER_TARGET_LOCKEDTarget session is locked
409ROLLOVER_TARGET_INACTIVETarget session is not active
409ROLLOVER_CYCLE_EXISTSA live cycle already targets this pair
409ROLLOVER_PROMOTION_NOT_FINALIZEDSource promotion cycle isn't finalized

8.4 GET /rollover/cycles/:publicId

Purpose: The full cycle detail view, including the 15-check readiness checklist, for the cycle's own screen.

Auth: Rollover_READ. Response: RolloverCycleDetailDto — see §6.3.

Error Cases: 404 ROLLOVER_CYCLE_NOT_FOUND.

8.5 DELETE /rollover/cycles/:publicId

Purpose: Cancel a cycle before any placement or graduation has been made — for abandoning a rollover started against the wrong session pair or promotion cycle.

Auth: Rollover_DELETE.

Side Effects: status = cancelled, cancelled_at/cancelled_by_user_id/cancel_reason set, cycle_cancelled event inserted.

Error Cases: 409 ROLLOVER_CANCEL_NOT_ALLOWED if any item is already placed/graduated.

8.6 POST /rollover/cycles/:publicId/sync

Purpose: Re-pull the roster from the promotion cycle and the source session's live enrollments — call this whenever a promotion decision changes or a pupil's source enrollment changes after the cycle was created.

Auth: Rollover_UPDATE. Response: {inserted: number, updated: number}.

Side Effects: see Backend §6.3 for the exact insert/update rules, including auto-exclusion and manual-exclusion preservation.

8.7 POST /rollover/cycles/:publicId/review

Purpose: Snapshot the current settings and compute the confirmation fingerprint that POST /rollover/cycles/:publicId/process will later require, unchanged. Call this immediately before processing, after the admin is satisfied with the roster and allocations.

Auth: Rollover_UPDATE. Response (RolloverReviewResultDto): fingerprint, countsByStatus (array), countsByOutcome (array), excludedCount, exceptionCount, warningCount, toProcess, sections — a plain RolloverSectionGradeDemandDto[] of per-grade class capacity/demand (not wrapped in a {grades} object), rollConflicts, readiness (array), and summary: {sourceSessionName, targetSessionName}.

8.8 GET /rollover/cycles/:publicId/items

Purpose: List items for the cycle's roster screen, with the view/outcome/status/exceptionCode/class/grade filters.

Auth: Rollover_READ. Query: ListRolloverItemsQueryDto — see §6.5.

Edge Cases: an unrecognized view value is rejected by DTO validation (400); an empty result set for a valid filter returns an empty paginated array, not an error.

8.9 GET /rollover/cycles/:publicId/exceptions

Purpose: A grouped summary of every exception and warning code currently present in the cycle, with sample pupils per group — the screen an admin uses to triage before processing.

Auth: Rollover_READ. Response: {groups: [{code, severity: "error"|"warning", count, samples: RolloverItemSummaryDto[]}], total}. count is an exact per-code count computed in SQL over the scoped item set (included, non-terminal); samples is capped at 50 rows per code and never drives count or total. total is the count of scoped items carrying at least one exception or warning code.

8.10 PATCH /rollover/cycles/:publicId/items/:studentPublicId

Purpose: Edit a single pupil's target class and/or planned roll number by hand, for the exception-resolution workflow.

Auth: Rollover_UPDATE. Body: UpdateRolloverItemDto — see §6.6.

Error Cases: 404 ROLLOVER_ITEM_NOT_FOUND; 400 ROLLOVER_CLASS_INVALID if the class doesn't belong to the target grade/session; 400 ROLLOVER_ROLL_INVALID if the roll is out of range or already taken in scope; 409 ROLLOVER_ROLL_POLICY_AUTOMATIC if manual roll entry conflicts with settings.

8.11 POST /rollover/cycles/:publicId/items/assign-class

Purpose: Bulk-assign the same target class to a batch of pupils, for when an admin wants to hand-place a group without running the full allocation algorithm.

Auth: Rollover_UPDATE. Body: {studentPublicIds: uuid[], targetClassPublicId: uuid}.

8.12 POST /rollover/cycles/:publicId/items/exclude

Purpose: Manually exclude a batch of pupils from this rollover run — for pupils who are leaving the school, on long leave, or otherwise should not be placed even though their decision says otherwise.

Auth: Rollover_UPDATE. Body: ExcludeRolloverItemsDto — see §6.7. Requires reason.

Side Effects: inclusion = excluded, exclusionSource = manual; a later POST /rollover/cycles/:publicId/sync never reverts this.

8.13 POST /rollover/cycles/:publicId/items/include

Purpose: Reverse a manual exclusion.

Auth: Rollover_UPDATE. Body: IncludeRolloverItemsDto.

8.14 POST /rollover/cycles/:publicId/items/capacity-override

Purpose: Allow a specific pupil to be placed into a section that is already at capacity — for the rare case where a physical class genuinely needs to exceed its configured capacity.

Auth: RolloverOverrides_UPDATE (superadmin-only by default). Body: CapacityOverrideDto — see §6.8. reason required when enabling.

Error Cases: 403 ROLLOVER_OVERRIDE_NOT_ALLOWED when capacityPolicy = block and the caller lacks the permission.

8.15 GET /rollover/cycles/:publicId/sections

Purpose: Per-grade target class capacity/demand snapshot — the input an admin reviews before running a section-allocation preview.

Auth: Rollover_READ. Response: array of {classPublicId, capacity, occupied, planned, remaining} — see Backend §6.13.

8.16 POST /rollover/cycles/:publicId/section-allocation/preview

Purpose: Compute (without writing) how a chosen strategy would distribute one grade's pupils across its target classes.

Auth: Rollover_UPDATE. Body: SectionAllocationPreviewDto — see §6.9.

{
  "targetGradePublicId": "a1b2...",
  "strategy": "balanced",
  "onlyUnassigned": true,
  "preserveShift": true
}

Response: SectionAllocationResultDto with a proposalToken that must be replayed to apply.

Edge Cases: a grade with zero eligible classes returns every pupil unassigned with reason NO_CLASS; see the worked examples in Backend §6.4.

8.17 POST /rollover/cycles/:publicId/section-allocation/apply

Purpose: Commit a previously previewed section-allocation proposal.

Auth: Rollover_UPDATE. Body: AllocationApplyDto (proposalToken).

Error Cases: 409 ROLLOVER_PROPOSAL_STALE if the cycle's state changed since the matching preview.

8.18 POST /rollover/cycles/:publicId/roll-allocation/preview

Purpose: Compute (without writing) roll number assignments for one grade under a chosen strategy and scope.

Auth: Rollover_UPDATE. Body: RollAllocationPreviewDto — see §6.10.

8.19 POST /rollover/cycles/:publicId/roll-allocation/apply

Purpose: Commit a previously previewed roll-allocation proposal.

Auth: Rollover_UPDATE. Body: AllocationApplyDto.

Error Cases: 409 ROLLOVER_PROPOSAL_STALE.

8.20 POST /rollover/cycles/:publicId/process

Purpose: Start (or resume) background placement processing for every included, non-terminal item in the cycle. This is the point of no easy return — it is superadmin-only and requires an explicit, freshly-computed fingerprint.

Auth: RolloverProcessing_CREATE (superadmin-only by default). Body: RolloverProcessRequestDto — see §6.12.

{
  "fingerprint": "b6e1c9...",
  "confirm": true
}

Response: 201, RolloverProcessResultDto — see §6.13.

Side Effects: writes an outbox row that enqueues AcademicYearJob.PROCESS_ROLLOVER; no direct write to enrollments happens synchronously in this request.

Error Cases

HTTP StatusError CodeCondition
400Validationconfirm missing or false
409ROLLOVER_PREVIEW_STALEFingerprint mismatch — state changed since review
409ROLLOVER_NOTHING_TO_PROCESSNo non-terminal included items
409ROLLOVER_INVALID_STATEA run is already in progress for this cycle

8.21 GET /rollover/cycles/:publicId/progress

Purpose: Poll the state of an in-progress or just-finished processing run, for a progress bar on the admin screen.

Auth: Rollover_READ. Response: RolloverProgressDto — see §6.14.

8.22 GET /rollover/cycles/:publicId/activation-preflight

Purpose: Check whether the target year can be activated right now, and why not if it can't.

Auth: Rollover_READ. Response: {blocking: string[], warnings: string[]} drawn from the preflight code lists in Backend §13.

8.23 POST /rollover/cycles/:publicId/activate

Purpose: Make the target session current and queue placement notifications. The single most consequential action in the module — it moves the entire school forward a year.

Auth: RolloverActivation_UPDATE (superadmin-only by default).

Side Effects: academic_sessions.is_current flips (source → target) via switchCurrent(..., { via: "rollover" }); cycle status = activated; NOTIFY_PLACEMENTS enqueued if notifyPlacement is on and unnotified placements exist.

Error Cases: 409 ROLLOVER_ACTIVATION_BLOCKED if any preflight blocking code is present; 409 ROLLOVER_WARNINGS_UNACKNOWLEDGED if allowActivationWithExceptions = false and warnings remain.

8.24 POST /rollover/cycles/:publicId/finalize

Purpose: Close out an activated cycle, optionally locking the source session at the same time.

Auth: RolloverActivation_UPDATE (superadmin-only by default). Body: {lockSource?: boolean, lockReason?: string}.

Side Effects: status = finalized; if lockSource is true, the source session's locked_at/locked_by_user_id/lock_reason are set.

8.25 POST /rollover/sessions/:sessionPublicId/lock

Purpose: Lock a session independent of finalize — for locking a historical year at any later point.

Auth: AcademicYearLock_UPDATE (superadmin-only by default). Body: {reason: string} (3–1000 chars).

Error Cases: 409 if the session is currently is_current (a current session can never be locked, by database constraint).

8.26 POST /rollover/sessions/:sessionPublicId/unlock

Purpose: Reverse a lock.

Auth: AcademicYearLock_UPDATE (superadmin-only by default).

8.27 POST /rollover/cycles/:publicId/notify-placements

Purpose: Retry placement notifications for a cycle whose placementNotificationState is stuck at failed, or unsent at none on an already-activated/finalized cycle whose settings have notifyPlacement on.

Auth: RolloverActivation_UPDATE (superadmin-only by default).

Side Effects: sets rollover_cycles.placement_notification_state = 'queued', increments notification_attempt, and enqueues an outbox event (aggregateType: "rollover_cycle", eventType: "rollover.notify_placements_requested") on QueueName.ACADEMIC_YEAR with jobName: AcademicYearJob.NOTIFY_PLACEMENTS and dedupe key rollover-notify-${cyclePublicId}-${attempt} — the attempt number is part of the key, so each retry gets its own dedupe slot rather than colliding with the one it is retrying. A placement_notifications_queued row is written to rollover_events. The worker (shared/placement-notification.service.ts) then sends the rollover.placement_changed notification to guardians of every placed, un-notified item.

Error Cases: 409 ROLLOVER_NOTIFICATION_INVALID_STATE if the cycle's notification state is neither failed nor an eligible unsent none.

8.28 POST /rollover/cycles/:publicId/items/:studentPublicId/correct

Purpose: Repair a placement or graduation after processing has already run — the only way to change a placed/graduated item's outcome, since those statuses are otherwise terminal. One route handles all three corrective actions via the action field of RolloverCorrectionDto: "move" (relocate an active placed item to a different class/grade), "realign" (bring an item's status back in line with its current decision — see Backend §6.9 for the full case table), and "relink" (repoint a placed item's stale enrollment link at the pupil's current active enrollment, without changing status).

Body: RolloverCorrectionDto — { action: "move" | "realign" | "relink", targetClassPublicId?: string (UUID), reason: string (3–1000 chars) }.

Response: RolloverCorrectionResultDto — { item: RolloverItemDto | null }, message "Placement corrected.".

Auth: RolloverCorrections_UPDATE (superadmin-only by default).

Side Effects: each action follows the exact write sequence documented per-case in Backend §6.9 — always inside one transaction, always through ClassEnrollmentsService.enroll for any new placement, never a raw insert. A rollover.placement_changed notification is queued for the item's guardians when the correction leaves the item with an enrollment and the cycle is not still preparing/processed.

Error Cases: 409 ROLLOVER_INVALID_STATE if the cycle isn't processed/activated/finalized or has a run in progress; 404 ROLLOVER_ITEM_NOT_FOUND if the pupil has no item in this cycle; 409 ROLLOVER_CORRECTION_INVALID if the item's current state doesn't support the requested action; 409 ROLLOVER_CLASS_INVALID if the target class is missing, inactive, or in the wrong session; 409 ROLLOVER_CORRECTION_GRADE_MISMATCH (move only) if the target class's grade doesn't match the current decision; 409 ROLLOVER_ITEM_LINK_STALE (move only) if the item's current enrollment is no longer active — use relink first; 409 ACADEMIC_SESSION_LOCKED if the target session has since been locked; 404 ACADEMIC_SESSION_NOT_FOUND if the cycle's target session is missing.

8.29 GET /rollover/settings

Purpose: Read the school's rollover strategy and policy defaults.

Auth: RolloverSettings_READ (superadmin-only by default). Response: RolloverSettingsDto — see §6.15.

8.30 PATCH /rollover/settings

Purpose: Update rollover strategy and policy defaults.

Auth: RolloverSettings_UPDATE (superadmin-only by default). Body: UpdateRolloverSettingsDto, version required.

{
  "defaultSectionStrategy": "keep_previous",
  "capacityPolicy": "allow_override",
  "version": 4
}

Error Cases: 409 ROLLOVER_SETTINGS_STALE if version doesn't match the current row. The version is compared inside the UPDATE itself, so of two saves loaded at the same version exactly one succeeds.

8.31 GET /rollover/cycles/:publicId/events

Purpose: The full, newest-first, append-only audit timeline for a cycle.

Auth: Rollover_READ. Response: paginated array of RolloverEventDto (eventType, actorUserId, fromValue, toValue, reason, createdAt), one of the 29 event types listed in Backend §5.2.

8.32 GET /rollover/cycles/:publicId/integrity

Purpose: On-demand cross-check between rollover's own item bookkeeping and the live enrollment/graduation tables — the screen an admin checks if something looks inconsistent.

Auth: Rollover_READ. Response: array of findings, each carrying one of the 8 integrity codes in Backend §13 (DUPLICATE_ACTIVE_PLACEMENT, ITEM_LINK_STALE, DECISION_CHANGED_AFTER_PLACEMENT, ROLL_CONFLICT, SOURCE_SNAPSHOT_DRIFT, GRADUATE_WITH_PLACEMENT, ORPHAN_ITEM, ACTIVE_STUDENT_WITHOUT_ITEM).

8.33 GET /rollover/cycles/:publicId/id-cards

Purpose: Count of placed pupils who already have (or still need) an ID card for the target session — feeds the idCardReminder nudge.

Auth: Rollover_READ. Response: {needed: number, have: number}.

8.34 GET /rollover/cycles/:publicId/new-admissions

Purpose: List pupils who are active in the target session but were never part of this rollover cycle's roster — new admissions the admin should be aware of separately.

Auth: Rollover_READ. Response: paginated array of pupil references.

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence — process

9.3 Error Branch — activation

EndpointPagination TypeFiltersNotes
GET /rollover/cyclesStandard offset (PaginationUtil)statusDefault/max sizes follow the shared pagination utility.
GET /rollover/cycles/:publicId/itemsStandard offsetview, outcome, status, exceptionCode, class/grade public IDsview presets combine several filters at once.
GET /rollover/cycles/:publicId/eventsStandard offset, newest-firstNone beyond cycle scopeOrdered by created_at descending.
GET /rollover/cycles/:publicId/new-admissionsStandard offsetNone beyond cycle scope—

No rollover endpoint supports pagination=false or cursor pagination; all lists use the shared offset utility with its default 20/max 100 page-size bounds.

11. Caching, Jobs, and External Integrations

IntegrationUsed?DetailsSource
Redis cacheNoRollover reads Postgres directly on every request.—
BullMQYesQueueName.ACADEMIC_YEAR, jobs PROCESS_ROLLOVER, ROLLOVER_RECOVERY_SWEEP, PUBLISH_PROMOTION_DECISIONS, NOTIFY_PLACEMENTS. Concurrency 1.academic-year-queue.processor.ts, packages/jobs/src/index.ts
External APINo——

See Backend §9 for the full queue/job table.

13. Mandatory Deep API Documentation Pack

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Admin panelFingerprint/proposal-token replay requirement, superadmin-only routes, display-status computationShow ROLLOVER_PREVIEW_STALE/ROLLOVER_PROPOSAL_STALE as "please re-review" rather than a generic error; poll /progress rather than assuming synchronous completionStable
QAEvery superadmin-only route needs a superadmin session to test meaningfully; a plain admin session against them should always return 403Reproduce exceptions via POST /rollover/cycles/:publicId/sync against seeded promotion decisionsStable
Internal service (worker)Not directly callable — only reachable through the academic-year queueN/AStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
Preview/apply with a replay token, for both allocation and processingTwo-step commit everywhere consequentialSingle-step "just do it" endpointsPrevents committing against a plan that has since gone staleExtra round trip for the clientToken is a single required field, not a complex handshake
Async processing polled via /progressNon-blocking for large rostersLong-lived synchronous requestAvoids HTTP timeouts on large schoolsClient must implement polling/progress is cheap — a read of counts, no heavy computation
Superadmin-only defaults, reconfigurable per schoolOne documented, safe defaultHard admin/superadmin split with no overrideSchools with a flatter hierarchy aren't forced to route everything through one accountA school could misconfigure by granting too widelyDocumented explicitly in the permission catalog as a deliberate, reversible choice

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?
Adding a new exception/outcome codeAdmin panel (must render an unknown code gracefully)New branch in validation/processingNone — codes are string arrays, not enums at the DB level for itemsNo, but the panel's code-to-label map needs the new entry
Changing capacityPolicy semanticsAdmin panel, processingValidation and processing branch differentlyNoneNo
Adding a new correction caseAdmin panel corrections screenNew write-order sequence in RolloverRealignServiceNoneNo

14. Zero-Omission API Checklist

  • Every controller route across cycles/items/allocation/processing/activation/corrections/settings/insights is documented.
  • Every DTO field, enum, and default is documented.
  • Every auth guard and permission — including which are superadmin-only by default — is documented.
  • Every success and error branch documented has a corresponding error code from Backend §13.
  • Every database write, queue job, and notification side effect per endpoint is documented.
  • Route ownership, request-sequence, and error-branch diagrams are included.
  • Tradeoffs and compatibility risks are documented.
  • This doc links to backend and features/flows.

15. Integration Checklist

  • Every route from the eight controllers is documented.
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every error code relevant to this surface is documented (cross-referenced to Backend §13).
  • Every auth guard and permission is documented, including superadmin-only defaults.
  • Every queue job and its trigger is documented.
  • Diagrams match the current code.
  • Links to backend and features/flows are present.

See Also

On this page

Academic Year Rollover - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 CreateRolloverCycleDto — body, POST /rollover/cycles6.2 ListRolloverCyclesQueryDto — query, GET /rollover/cycles6.3 RolloverCycleDetailDto — response, GET /rollover/cycles/:publicId6.4 RolloverOverviewDto — response, GET /rollover/overview6.5 ListRolloverItemsQueryDto — query, GET /rollover/cycles/:publicId/items6.6 UpdateRolloverItemDto — body, PATCH /rollover/cycles/:publicId/items/:studentPublicId6.7 ExcludeRolloverItemsDto / IncludeRolloverItemsDto6.8 CapacityOverrideDto — body, POST /rollover/cycles/:publicId/items/capacity-override6.9 SectionAllocationPreviewDto / SectionAllocationResultDto6.10 RollAllocationPreviewDto / RollAllocationResultDto6.11 AllocationApplyDto — body, both apply endpoints6.12 RolloverProcessRequestDto — body, POST /rollover/cycles/:publicId/process6.13 RolloverProcessResultDto — response, same endpoint6.14 RolloverProgressDto — response, GET /rollover/cycles/:publicId/progress6.15 RolloverSettingsDto / UpdateRolloverSettingsDto6.16 RolloverCorrectionDto / RolloverCorrectionResultDto — POST /rollover/cycles/:publicId/items/:studentPublicId/correct7. Enum Reference8. Endpoint Reference8.1 GET /rollover/overview8.2 GET /rollover/cycles8.3 POST /rollover/cycles8.4 GET /rollover/cycles/:publicId8.5 DELETE /rollover/cycles/:publicId8.6 POST /rollover/cycles/:publicId/sync8.7 POST /rollover/cycles/:publicId/review8.8 GET /rollover/cycles/:publicId/items8.9 GET /rollover/cycles/:publicId/exceptions8.10 PATCH /rollover/cycles/:publicId/items/:studentPublicId8.11 POST /rollover/cycles/:publicId/items/assign-class8.12 POST /rollover/cycles/:publicId/items/exclude8.13 POST /rollover/cycles/:publicId/items/include8.14 POST /rollover/cycles/:publicId/items/capacity-override8.15 GET /rollover/cycles/:publicId/sections8.16 POST /rollover/cycles/:publicId/section-allocation/preview8.17 POST /rollover/cycles/:publicId/section-allocation/apply8.18 POST /rollover/cycles/:publicId/roll-allocation/preview8.19 POST /rollover/cycles/:publicId/roll-allocation/apply8.20 POST /rollover/cycles/:publicId/process8.21 GET /rollover/cycles/:publicId/progress8.22 GET /rollover/cycles/:publicId/activation-preflight8.23 POST /rollover/cycles/:publicId/activate8.24 POST /rollover/cycles/:publicId/finalize8.25 POST /rollover/sessions/:sessionPublicId/lock8.26 POST /rollover/sessions/:sessionPublicId/unlock8.27 POST /rollover/cycles/:publicId/notify-placements8.28 POST /rollover/cycles/:publicId/items/:studentPublicId/correct8.29 GET /rollover/settings8.30 PATCH /rollover/settings8.31 GET /rollover/cycles/:publicId/events8.32 GET /rollover/cycles/:publicId/integrity8.33 GET /rollover/cycles/:publicId/id-cards8.34 GET /rollover/cycles/:publicId/new-admissions9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence — process9.3 Error Branch — activation10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also