Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

08 — Form Specifications (Students Module)

Every field of every form, exactly mapped to the server DTOs. Types/validation from the DTO files (class-validator decorators); enums from schemas. All fields not listed do not exist server-side. * = required by backend.


1. Create Student (wizard step 2–4 → POST /students)

Payload DTO: create-student.dto.ts. Identity step targets POST /users (users.controller.ts:36-40).

FieldType / rules (server)SourceClient inputNotes
userId*MongoIdcreate-student.dto.ts:6-7hidden (from step 1 user)must exist; user created/reused first
admissionNumber*stringcreate-student.dto.ts:9-11AppTextField (uppercase, no spaces)unique per tenant → 409 (student.service.ts:56-62); client pre-check against roster
rollNumber?stringcreate-student.dto.ts:13-16text, mono
academicYearId*MongoIdcreate-student.dto.ts:18-20AppDropdown (academic years)
campusId?MongoIdcreate-student.dto.ts:22-25AppDropdown
gradeId*MongoIdcreate-student.dto.ts:27-29AppDropdown
sectionId*MongoIdcreate-student.dto.ts:31-33AppDropdown (by grade)
classId*MongoIdcreate-student.dto.ts:35-37AppDropdown (by grade+section+year)auto-enrolled here (student.service.ts:71-78)
houseId?MongoIdcreate-student.dto.ts:39-42AppDropdown
admissionDate?ISO date stringcreate-student.dto.ts:44-47AppDatePicker (≤ today)omitted → today (student.service.ts:66-68)
admissionType?string enum new|transfer|promotedcreate-student.dto.ts:49-52; student.schema.ts:47segmented buttonsfree string accepted by DTO; UI constrains
transportRequired?booleancreate-student.dto.ts:54-56AppSwitchschema default false
hostelRequired?booleancreate-student.dto.ts:57-59AppSwitchschema default false
medicalNotes?stringcreate-student.dto.ts:61-65multiline

Identity step (POST /users) — user schema fields (user.schema.ts:16-35): firstName* (required), middleName?, lastName* (required), displayName?, email* (unique per tenant → 409; lowercase), phone?. UI submits only firstName, lastName, email, phone?.

Server status on create: always active (student.service.ts:65), admissionType default new, enrollment ACTIVE with joinedAt = now.

2. Enroll (POST /students/:id/enroll)

DTO: enroll-student.dto.ts.

FieldType / rulesSource
classId*MongoIdenroll-student.dto.ts:5-7
academicYearId*MongoIdenroll-student.dto.ts:9-11
rollNumber?stringenroll-student.dto.ts:13-16

Side effect banner: existing ACTIVE enrollments → transferred + leftAt (student.service.ts:125-139).

3. Transfer (POST /students/:id/transfer)

DTO: transfer-student.dto.ts.

FieldType / rulesSourceClient
classId*MongoIdtransfer-student.dto.ts:5-7dropdown
academicYearId*MongoIdtransfer-student.dto.ts:9-11dropdown
gradeId?MongoId — "defaults to class grade"transfer-student.dto.ts:13-19dropdown, prefilled from class
sectionId?MongoId — "defaults to class section"transfer-student.dto.ts:20-25dropdown, prefilled from class
rollNumber?stringtransfer-student.dto.ts:27-30text

Guard: student status must be active (student.service.ts:175-179).

4. Update student (PATCH /students/:id)

DTO: update-student.dto.ts — every field optional mirror of create plus: status? (plain string, no enum validationupdate-student.dto.ts:60-63; UI must only send valid StudentStatus values), metadata? (object, update-student.dto.ts:78-80). Note: PATCH with $set writes only provided fields (student.service.ts:144). Client edit form = create fields minus class path (class changes go through transfer/enroll) plus status only via lifecycle actions, not the form.

5. Upload document (POST /students/:id/documents)

PartTypeSource
file*multipart file — field name exactly filestudent.controller.ts:83; MulterFile{buffer,originalname,mimetype,size}
category?string (body, multipart text field)upload-student-document.dto.ts:4-9

Server has no size/mime limits (OQ-9). Client (proposed) limits: ≤ 10 MB, mime ∈ {pdf, jpg, png, webp}. uploadedBy = actor userId (student.service.ts:269).

6. CSV bulk import (POST /bulk/import/students)

Adapter columns (students-import.adapter.ts:17-26):

CSV columnRequiredValidation / resolutionError string (exact)
firstNamepassed to user create"Missing required column "firstName"."
lastNamepassed to user create"Missing required column "lastName"."
emailregex ^[^\s@]+@[^\s@]+\.[^\s@]+$ + uniqueness"Invalid email format." / "Email "{email}" already registered."
admissionNumbertenant-unique"Missing required column "admissionNumber"." / "Admission number "{n}" already exists."
gradematched by name or code (students-import.adapter.ts:118-120)"Grade "{g}" not found."
sectionmatched by name"Section "{s}" not found."
academicYearmatched by name"Academic year "{y}" not found."
rollNumberpassthrough

Derived resolution: class = first class matching {gradeId, sectionId, academicYearId} (students-import.adapter.ts:131-141) — error "No class found for grade "{g}" section "{s}".".

Parsing rules (bulk-import.service.ts:26-35): header row required (first row = column names), skip_empty_lines, trim, columns: true. Empty file → 400 "CSV must include a header row and data." Malformed → 400 "Malformed CSV: could not parse file." Row numbering: index + 2 (header = row 1).

Export template (GET /bulk/export/students, students-import.adapter.ts:87-98): columns admissionNumber, rollNumber, status, admissionDate — sorted by admissionNumber asc. This is an export format, not an import template — flagged in UI (import columns differ).

7. Create parent (POST /parents)

DTO: create-parent.dto.ts.

FieldType / rulesSource
userId*MongoIdcreate-parent.dto.ts:5-7
occupation?stringcreate-parent.dto.ts:9-12
company?stringcreate-parent.dto.ts:13-16
annualIncome?numbercreate-parent.dto.ts:17-20
relationshipNotes?stringcreate-parent.dto.ts:21-24
emergencyContactPriority?numbercreate-parent.dto.ts:25-28
pickupAuthorization?booleancreate-parent.dto.ts:29-32

Guard: one parent profile per userId → 409 "Parent profile already exists for this user." (parent.service.ts:30-34).

8. Link parent (POST /parents/link/:studentId)

DTO: link-parent.dto.ts.

FieldType / rulesSource
parentId*MongoIdlink-parent.dto.ts:5-7
relationship*string enum: mother|father|guardian|grandparent|relative|foster_parentlink-parent.dto.ts:9-20; student-parent-link.schema.ts:7-14
isPrimaryGuardian?boolean (schema default false)link-parent.dto.ts:22-24
financialResponsibility?boolean (default false)link-parent.dto.ts:26-28
pickupAllowed?boolean (schema default true)link-parent.dto.ts:30-32
emergencyPriority?number (schema default 0)link-parent.dto.ts:34-36

Student existence checked (student-parent-link.service.ts:27); parent existence not checked (OQ-11).

9. Avatar upload (POST /users/:id/avatar)

Multipart field file (users.controller.ts:96); updates avatarFileId on the user doc (user.schema.ts:34-35).


Validation UX rules (all forms)

  • Run validator on submit + after first error; clear on edit (00-shared/03 B).
  • 400 VALIDATION_ERROR → map details[].field to fields (00-shared/06 §5); focus first invalid (00-shared/09 §10).
  • 409 → inline conflict with suggested action (search existing / change admission number).
  • Submit buttons: disabled while pending (anti-double-submit, 00-shared/08 §6).