School Management Information System (SMIS)¶
Product Requirements Document — Core Data Model & Entity Definitions
| Field | Value |
|---|---|
| Document Version | 1.4 |
| Status | Draft |
| Date | April 2026 |
| Audience | Engineering, Product, Design |
1. Overview¶
This document defines the product requirements for the core data entities of a School Management Information System (SMIS). It covers the primary domain objects — School, Campus, Section, Classroom, Student, Family, SchoolStaff, Guardian, AcademicYear, AcademicPeriod, and AcademicHoliday — along with their attributes, relationships, and business rules.
The goal is to provide a shared reference for engineering, product, and design teams so that all implementations align on data structure, terminology, and system behaviour.
2. Scope¶
In scope for this PRD:
- Entity definitions and attributes for all eleven core domain objects
- Relationships between entities
- Business rules and constraints
- Guardian type enumeration
- Academic year configuration and term/semester structures
Out of scope:
- Authentication and access control
- Financial or fee management
- Academic grading and results
- Timetables and daily scheduling
3. Entity Definitions¶
3.1 School¶
A School is the top-level organisational entity in the system. It represents a registered educational institution and acts as the root owner of all other entities.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
name |
String | Yes | Official registered name of the school |
code |
String | Yes | Short alphanumeric code (unique across system) |
logo_url |
String | No | URL to the school's logo image |
address |
String | Yes | Physical address of the main school office |
phone |
String | Yes | Primary contact phone number |
email |
String | Yes | Official contact email address |
website |
String | No | School website URL |
accreditation_number |
String | No | National accreditation or registration number |
default_currency |
String | Yes | ISO 4217 currency code used for all financial operations (e.g., GHS, USD, NGN). Set during school creation. |
legacy_school_id |
Long | No | School ID from the legacy Smartsapp system. Used to map authenticated users to their school during the migration period. Unique when present. |
is_active |
Boolean | Yes | Whether the school is currently active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- A School must have at least one Campus.
- The school code must be unique across the entire system.
- Every school must have a
default_currencyset. This currency applies to all financial operations (canteen pricing, invoices, wallet balances) across the school. legacy_school_idmust be unique across the system when present. It maps the JWTschool_idclaim from the legacy auth system to a school record.
3.2 Campus¶
A Campus is a physical location or branch that belongs to a School. A single school may operate across multiple campuses (e.g., a main campus and a satellite campus).
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
school_id |
UUID (FK) | Yes | Reference to parent School |
name |
String | Yes | Name of the campus |
code |
String | Yes | Short code (unique within the school) |
address |
String | Yes | Physical address of the campus |
phone |
String | No | Campus-specific contact number |
email |
String | No | Campus-specific email address |
principal_name |
String | No | Name of the campus principal/head |
is_active |
Boolean | Yes | Whether the campus is active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- A Campus belongs to exactly one School.
- A Campus must contain at least one Section.
Status: Pending on enforcement — the "code unique within school" constraint (column
code) and the "at least one Section per Campus" rule are product requirements but are not yet enforced by a DB unique constraint or service-level validation. Duplicates and empty campuses can be created today.
3.3 Section¶
A Section represents a major academic division or stage within a Campus — for example, Primary, Junior High, or Senior High. Sections allow campuses to organise classrooms and students by educational stage.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
campus_id |
UUID (FK) | Yes | Reference to parent Campus |
name |
String | Yes | Display name (e.g., Primary, Junior High, Senior High) |
code |
String | Yes | Short code (unique within the campus) |
level_order |
Integer | No | Numeric ordering for display purposes |
description |
String | No | Optional description of the section |
is_active |
Boolean | Yes | Whether this section is active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Common Section Values:
- Creche / Nursery
- Kindergarten
- Primary
- Junior High School (JHS)
- Senior High School (SHS)
Business Rules:
- A Section belongs to exactly one Campus.
- Section names must be unique within a campus.
Status: Pending on enforcement — the "unique name within campus" rule is a product requirement but is not yet enforced by a DB unique constraint or service-level validation. Duplicate section names can be created today.
3.4 Classroom¶
A Classroom represents a specific class or form group within a Section. It is the unit to which students are assigned and where teaching and learning occurs.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
section_id |
UUID (FK) | Yes | Reference to parent Section |
name |
String | Yes | Class name (e.g., Primary 3A, JHS 2B) |
code |
String | Yes | Short code (unique within the section) |
academic_year |
String | Yes | Academic year the class belongs to (e.g., 2025/2026) |
capacity |
Integer | No | Maximum number of students allowed |
class_teacher_id |
UUID (FK) | No | Reference to the assigned SchoolStaff as class teacher |
room_number |
String | No | Physical room or location identifier |
is_active |
Boolean | Yes | Whether this classroom is active for the current year |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- A Classroom belongs to exactly one Section.
- Classroom codes must be unique within a section for a given academic year.
- A student can only be enrolled in one active classroom at a time.
Status: Pending on enforcement — the "classroom code unique within section per academic year" constraint and the "one active classroom per student" constraint are product requirements but are not yet enforced by DB constraints or service-level validation. Duplicate codes and multiple active enrollments can be created today.
3.5 Student¶
A Student is an individual enrolled in the school system. Students are associated with a Classroom, a Family, and one or more Guardians.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
school_id |
UUID (FK) | Yes | Reference to School (denormalised for fast lookup) |
classroom_id |
UUID (FK) | Yes | Current active classroom enrollment |
family_id |
UUID (FK) | No | Reference to the student's Family record |
student_id_number |
String | Yes | Human-readable student ID (unique per school) |
first_name |
String | Yes | Student's first name |
middle_name |
String | No | Student's middle name |
last_name |
String | Yes | Student's last name (surname) |
date_of_birth |
Date | Yes | Date of birth |
gender |
Enum | Yes | Male / Female / Other |
profile_photo_url |
String | No | URL to the student's photo |
nationality |
String | No | Student's nationality |
religion |
String | No | Student's religion (optional) |
blood_group |
String | No | Blood group for medical records |
medical_notes |
Text | No | Any relevant medical or dietary notes |
enrollment_date |
Date | Yes | Date the student was first enrolled |
status |
Enum | Yes | Active / Inactive / Graduated / Withdrawn / Suspended |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- A Student must belong to a School and a Classroom.
- Student ID numbers must be unique within a school.
- Every active student must have at least one Guardian linked.
Status: Pending on enforcement — the "student_id_number unique within school" constraint and the "active student must have ≥1 Guardian" rule are product requirements but are not yet enforced by DB constraints or service-level validation. Duplicate IDs and guardian-less active students can be created today.
3.6 Family¶
A Family groups students who share the same household or family unit. This enables communication and billing to be addressed at the family level rather than per-student.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
school_id |
UUID (FK) | Yes | Reference to School |
family_name |
String | Yes | Surname or household name (e.g., Mensah Family) |
family_code |
String | Yes | Unique family reference code within the school |
primary_address |
String | No | Primary residential address of the family |
primary_phone |
String | No | Primary contact phone number for the family |
primary_email |
String | No | Primary email address for the family |
notes |
Text | No | Any administrative notes about the family |
is_active |
Boolean | Yes | Whether this family record is active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- A Family can have multiple Students.
- A Family can have multiple Guardians.
- Family codes must be unique within a school.
Status: Pending on enforcement — the "family_code unique within school" constraint is a product requirement but is not yet enforced by a DB unique constraint or service-level validation. Duplicate family codes can be created today.
3.7 SchoolStaff¶
SchoolStaff represents all employees of the school — including teachers, administrators, support staff, and management. Staff members can be assigned roles and linked to classrooms.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
school_id |
UUID (FK) | Yes | Reference to School |
campus_id |
UUID (FK) | No | Primary campus assignment |
staff_id_number |
String | Yes | Human-readable staff ID (unique per school) |
first_name |
String | Yes | Staff member's first name |
middle_name |
String | No | Staff member's middle name |
last_name |
String | Yes | Staff member's last name |
email |
String | Yes | Official school email address |
phone |
String | No | Contact phone number |
date_of_birth |
Date | No | Date of birth |
gender |
Enum | No | Male / Female / Other |
profile_photo_url |
String | No | URL to staff photo |
role |
Enum | Yes | Teacher / Administrator / Headmaster / Support / Other |
department |
String | No | Department or subject area |
employment_type |
Enum | No | Full-time / Part-time / Contract |
hire_date |
Date | Yes | Date of first employment |
status |
Enum | Yes | Active / Inactive / On Leave / Terminated |
legacy_user_id |
Long | No | User ID from the legacy Smartsapp auth system. Used to map authenticated users to staff records during the migration period. Unique when present. |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
- Staff IDs must be unique within a school.
legacy_user_idmust be unique across the system when present. It maps the JWTuser_idclaim from the legacy auth system to a staff record.- A staff member can be assigned as the class teacher for multiple classrooms.
- Staff email addresses must be unique system-wide.
Status: Pending on enforcement — the "staff_id_number unique within school" constraint is a product requirement but is not yet enforced by a DB unique constraint or service-level validation. Duplicate staff IDs can be created today. (
legacy_user_iduniqueness is enforced.)
3.8 Guardian¶
A Guardian is an adult who is responsible for a Student. Guardians are linked to Students directly and optionally to a Family record. A student may have more than one Guardian.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
family_id |
UUID (FK) | No | Optional reference to the Family record |
guardian_type |
Enum | Yes | See Guardian Types below |
first_name |
String | Yes | Guardian's first name |
middle_name |
String | No | Guardian's middle name |
last_name |
String | Yes | Guardian's last name |
email |
String | No | Contact email address |
phone_primary |
String | Yes | Primary phone number |
phone_secondary |
String | No | Secondary phone number |
occupation |
String | No | Guardian's occupation |
employer |
String | No | Guardian's employer name |
address |
String | No | Guardian's residential address |
national_id |
String | No | National ID or passport number |
profile_photo_url |
String | No | URL to guardian's photo |
is_emergency_contact |
Boolean | Yes | Whether this guardian is an emergency contact |
can_pickup_student |
Boolean | Yes | Whether this guardian is authorised to pick up the student |
is_primary_contact |
Boolean | Yes | Designates the main contact guardian for the student |
legacy_user_id |
Long | No | Maps to the legacy auth system's user ID. Used by the Parent App to resolve the authenticated guardian's identity. Unique when set. |
is_active |
Boolean | Yes | Whether the guardian record is active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
3.8.1 Guardian Types¶
| Type Value | Description |
|---|---|
| Father | Biological or adoptive father of the student |
| Mother | Biological or adoptive mother of the student |
| Stepfather | Stepfather of the student |
| Stepmother | Stepmother of the student |
| Grandfather | Paternal or maternal grandfather |
| Grandmother | Paternal or maternal grandmother |
| Uncle | Uncle of the student |
| Aunt | Aunt of the student |
| Older Sibling | An older sibling acting as a guardian |
| Legal Guardian | Court-appointed or legally designated guardian |
| Foster Parent | Foster parent or caregiver |
| Relative | Any other relative not covered by the above types |
| Family Friend | A trusted family friend with guardianship responsibilities |
| Other | Any other guardian relationship not covered above |
Business Rules:
- Every active student must have at least one Guardian.
is_primary_contactis currently stored in two places in the data model: once on theGuardianentity and once on eachStudentGuardianjunction row. The junction-row value is the per-student flag (a guardian can be primary for one child and not another); the Guardian-level field is a vestige. A canonical single source of truth has not been decided yet — treat the duplication as a known inconsistency and avoid relying on either column in isolation until it is resolved.- Multiple guardians may be linked to the same student (e.g., both Father and Mother).
- A Guardian may be linked to multiple students (e.g., a parent with two children at the school).
3.9 AcademicYear¶
An AcademicYear defines the academic calendar configuration for a school. It is school-wide by default but can be overridden at the campus or section level, enabling different parts of the school to run different calendar structures.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
school_id |
UUID (FK) | Yes | Reference to School (derived from authenticated user's token) |
campus_id |
UUID (FK) | No | Reference to Campus. Null means school-wide default. |
section_id |
UUID (FK) | No | Reference to Section. Null means inherits from campus or school. |
name |
String | Yes | Display name (e.g., "2025-2026") |
start_date |
Date | Yes | Overall start date of the academic year |
end_date |
Date | Yes | Overall end date of the academic year |
period_type |
Enum | Yes | Type of academic periods used (see §3.9.1) |
is_active |
Boolean | Yes | Whether this academic year is currently active |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
3.9.1 Academic Period Types¶
| Type Value | Description |
|---|---|
| Term | Traditional term-based system (typically 3 per year) |
| Semester | Semester-based system (typically 2 per year) |
| Quarter | Quarter-based system (4 per year) |
| Trimester | Trimester-based system (3 per year) |
Business Rules:
end_datemust be afterstart_date.- The scope of an AcademicYear is determined by the combination of
campus_idandsection_id:- Both null → school-wide default
campus_idset,section_idnull → campus-level overridesection_idset → section-level override (system auto-populatescampus_idfrom the section's parent campus)
- Inheritance resolution: when looking up the effective academic year, the system walks up: section → campus → school-wide default, returning the first match.
- The
school_idis always derived from the authenticated user's JWT token and is never supplied in API requests. - The number of periods (terms/semesters) is a derived value — it is the count of AcademicPeriod records belonging to the year, not a stored field.
3.10 AcademicPeriod¶
An AcademicPeriod represents an individual term, semester, quarter, or trimester within an AcademicYear. Periods define the time boundaries for each segment of the academic calendar.
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
academic_year_id |
UUID (FK) | Yes | Reference to parent AcademicYear |
name |
String | Yes | Display name (e.g., "Term 1", "Fall Semester") |
start_date |
Date | Yes | Start date of the period |
end_date |
Date | Yes | End date of the period |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
end_datemust be afterstart_date.- Period dates must fall within the parent AcademicYear's date range.
- Periods within the same AcademicYear must not overlap.
- Periods are ordered by
start_date— there is no explicit sequence number.
3.11 AcademicHoliday¶
An AcademicHoliday represents a break, holiday, or non-school period within an AcademicYear. Holidays can optionally be associated with a specific AcademicPeriod (e.g., a mid-term break) or exist at the year level (e.g., a break between terms).
| Attribute | Type | Required | Description |
|---|---|---|---|
id |
UUID | Yes | Unique system identifier |
academic_year_id |
UUID (FK) | Yes | Reference to parent AcademicYear |
academic_period_id |
UUID (FK) | No | Optional reference to a specific AcademicPeriod. Null for year-level breaks (e.g., between terms). |
name |
String | Yes | Display name (e.g., "Christmas Break", "Mid-Term Break") |
start_date |
Date | Yes | Start date of the holiday |
end_date |
Date | Yes | End date of the holiday |
description |
String | No | Additional description or notes |
created_at |
Timestamp | Yes | Record creation timestamp |
updated_at |
Timestamp | Yes | Last update timestamp |
Business Rules:
end_datemust be afterstart_date.- Holiday dates must fall within the parent AcademicYear's date range.
- Holidays within the same AcademicYear must not overlap.
- Holidays inherit scope through their parent AcademicYear — a campus-specific holiday requires a campus-level AcademicYear override.
4. Entity Relationship Summary¶
| Parent Entity | Child Entity | Relationship | Notes |
|---|---|---|---|
| School | Campus | One-to-Many | A school has one or more campuses |
| Campus | Section | One-to-Many | A campus has one or more sections |
| Section | Classroom | One-to-Many | A section has one or more classrooms |
| Classroom | Student | One-to-Many | A classroom has many enrolled students |
| School | Family | One-to-Many | Families are scoped to a school |
| Family | Student | One-to-Many | A family groups multiple students |
| Family | Guardian | One-to-Many | A family has multiple guardians |
| Student | Guardian | Many-to-Many | Via student_guardian junction table |
| School | SchoolStaff | One-to-Many | Staff belong to a school |
| Classroom | SchoolStaff | Many-to-One | Class teacher assignment |
| School | AcademicYear | One-to-Many | School-wide default academic year(s) |
| Campus | AcademicYear | One-to-Many | Optional campus-level overrides |
| Section | AcademicYear | One-to-Many | Optional section-level overrides |
| AcademicYear | AcademicPeriod | One-to-Many | Terms/semesters within a year |
| AcademicYear | AcademicHoliday | One-to-Many | Holidays/breaks within a year |
| AcademicPeriod | AcademicHoliday | One-to-Many | Optional: holidays within a specific period |
5. Junction Tables¶
5.1 student_guardian¶
Links Students to their Guardians in a many-to-many relationship.
| Attribute | Type | Required | Description |
|---|---|---|---|
student_id |
UUID (FK) | Yes | Reference to Student |
guardian_id |
UUID (FK) | Yes | Reference to Guardian |
is_primary_contact |
Boolean | Yes | Marks the main contact for this student |
is_emergency_contact |
Boolean | Yes | Authorised emergency contact for this student |
can_pickup_student |
Boolean | Yes | Authorised to collect the student from school |
relationship_notes |
Text | No | Additional notes on the relationship |
6. API Endpoints¶
All endpoints require authentication via a Bearer JWT token. Responses use standard HTTP status codes: 200 (OK), 201 (Created with Location header), 204 (No Content for deletes), 404 (Not Found), 400 (Validation error).
6.1 Entity CRUD Endpoints¶
Each core entity follows a standard REST pattern with paginated list, get-by-ID, create, update, and delete.
| Entity | Base Path | Tag |
|---|---|---|
| School | /api/school/schools |
School – Schools |
| Campus | /api/school/campuses |
School – Campuses |
| Section | /api/school/sections |
School – Sections |
| Classroom | /api/school/classrooms |
School – Classrooms |
| Student | /api/school/students |
School – Students |
| Family | /api/school/families |
School – Families |
| Guardian | /api/school/guardians |
School – Guardians |
| Staff | /api/school/staff |
School – Staff |
Standard operations per entity:
| Method | Path | Description |
|---|---|---|
GET / |
List with pagination (?page=0&size=20) |
|
GET /{id} |
Get by ID | |
POST / |
Create (returns 201 with Location header) |
|
PUT /{id} |
Update | |
DELETE /{id} |
Delete (returns 204) |
6.2 Academic Calendar Endpoints¶
Academic Years — /api/school/academic-years¶
Standard CRUD (list, get, create, update, delete) plus:
| Method | Path | Description |
|---|---|---|
GET /resolve |
Resolve the effective academic year for the authenticated user's school. Walks the inheritance chain: section → campus → school-wide default. Optional query params: campusId, sectionId. |
Academic Periods — /api/school/academic-periods¶
| Method | Path | Description |
|---|---|---|
GET / |
List periods for an academic year. Required query param: academicYearId. Returns ordered by start date. |
|
GET /{id} |
Get by ID | |
POST / |
Create — validates dates fall within parent year range and do not overlap existing periods. | |
PUT /{id} |
Update | |
DELETE /{id} |
Delete |
Academic Holidays — /api/school/academic-holidays¶
| Method | Path | Description |
|---|---|---|
GET / |
List holidays for an academic year. Required query param: academicYearId. |
|
GET /{id} |
Get by ID | |
POST / |
Create — validates dates fall within parent year range and do not overlap existing holidays. | |
PUT /{id} |
Update | |
DELETE /{id} |
Delete |
6.3 Authenticated User Context Endpoints¶
These endpoints return data for the currently authenticated user based on their JWT token. They resolve the user's identity via legacy_school_id and legacy_user_id claims.
| Method | Path | Description |
|---|---|---|
GET /api/school/my-school |
Returns the school associated with the authenticated user's token. | |
GET /api/school/my-staff-profile |
Returns the staff profile associated with the authenticated user's token. |
6.4 Parent App Endpoints¶
| Method | Path | Description |
|---|---|---|
GET /api/school/parent/children |
Returns all students linked to the authenticated parent (guardian) via the student-guardian relationship. Each child includes classroom and campus information. |
7. Non-Functional Requirements¶
7.1 Data Integrity¶
- All foreign key relationships must be enforced at the database level.
- Soft deletes should be used where possible (
is_activeflag) to preserve historical data. - All timestamps should be stored in UTC.
7.2 Multi-Tenancy¶
- All entities are scoped to a
school_idto support multi-school deployments. - No data should be accessible across school boundaries without explicit permission.
7.3 Scalability¶
- The schema should support schools with up to 10,000 students and 1,000 staff members.
- Indexes should be created on
school_id,campus_id,section_id, andclassroom_idforeign keys.
8. Open Questions¶
| # | Question | Owner | Status |
|---|---|---|---|
| 1 | Should a student be allowed to appear in more than one classroom simultaneously (e.g., for cross-section subjects)? | Product | Open |
| 2 | What is the policy for archiving student records after graduation — hard delete, soft delete, or archive table? | Engineering | Open |
| 3 | Should Guardian contact information be linked to a user/login account for portal access? | Product | Open |
| 4 | Do we need to support multiple academic years per classroom, or create new classroom records each year? | Engineering | Resolved — AcademicYear entity with school → campus → section inheritance model now manages academic year configuration independently of classrooms. |
| 5 | Should SchoolStaff have login accounts by default, or is that managed separately? | Product | Open |
9. Revision History¶
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | March 2026 | — | Initial draft — all core entity definitions |
| 1.1 | March 2026 | — | Added legacy_user_id to SchoolStaff for legacy auth system integration |
| 1.2 | March 2026 | — | Added default_currency to School entity |
| 1.3 | April 2026 | — | Added AcademicYear, AcademicPeriod, and AcademicHoliday entities with school → campus → section inheritance model. Resolved Open Question #4. |
| 1.4 | April 2026 | — | Added Section 6 (API Endpoints): documented all 14 controllers including entity CRUD, academic calendar with resolve endpoint, authenticated user context (my-school, my-staff-profile), and parent app (children). Added legacy_user_id to Guardian entity. Renumbered sections 6–8 → 7–9. |
| 1.5 | 2026-04-19 | — | Sync pass against implementation: added Pending on enforcement notes to Campus (3.2), Section (3.3), Classroom (3.4), Student (3.5), Family (3.6), and SchoolStaff (3.7) to flag that several documented uniqueness and cardinality rules (campus code, section name, classroom code, student_id_number, family_code, staff_id_number, ≥1 Section per Campus, ≥1 Guardian per active student, one active classroom per student) have no DB or service-level enforcement today. Reconciled Guardian (3.8) business rules to describe the duplicate is_primary_contact storage on both Guardian and StudentGuardian — canonical source not yet resolved. No product scope changed. |