Trường Số PRD/02 Students
Trường Số · Product Requirements · 09/2026

PRD 02 · Students (SIS)

The student record as the center of operations: profile, guardians, status lifecycle, enrollment history, session balance, documents, notes and learning data from FLYER. Extends the existing school student and parent objects.

Key facts, origin and limits
Status
Draft v1
Owner module
Học viên
Backend
EXTEND SchoolStudent/StudentParent · NEW student record, guardians, documents
Phase
P1
Depends on
01 Admissions, 03 Classes, 05 Finance

Drafted by an AI assistant in a single session on 10/09/2026, from the original request quoted verbatim on the hub page; the Proposed design section was added on 16/09/2026. A proposal for team discussion, not a decision of the product team.

Sources

  • Public competitor research (PSE ONE, pse4.one), the center-crm-frontend codebase and the staging /v3 OpenAPI specification (182 paths), as of 10/09/2026.
  • The existing / extend / new tags on this page come from a comparison with the staging /v3 specification at that date.

Limits

  • Verification pending in-app survey, because automated login to the reference product was not authorised in the drafting session:
    • Competitor's student statuses and how "bảo lưu, nghỉ học" affect packages and reminders.
    • Whether they store several guardians and which one receives messages.
    • Student code format and whether it is per branch.
    • How "báo cáo học sinh sắp hết hạn" is defined (sessions, days, or amount).
    • Which documents they attach to a student and whether a photo is required.

Proposed design

A student list with every column and status chip, a two-column student profile with a fixed right rail (primary guardian, session balance, debt, FLYER account) and three status-change dialogs that always require a reason; the key point is that every manual transition (pause, withdraw, transfer branch) previews its effect on enrollments, session packages and finance before confirmation.

1 · Student list

/students · desktop

Student list
The table carries all 11 columns from section 5 (code, name with age, class, status, guardian, sessions left, debt, 30-day attendance, branch, enrollment date, actions) with 8 counted tabs and filters by branch, class, level, Zalo consent and enrollment date range. Shown here: status chips per the lifecycle table (Active, Paused, Waiting, Trial, Withdrawn), the dashed "Not placed" chip, sessions left in red at ≤ 3, overdue debt in red, "–" for monthly packages, the bulk-action bar with 2 students selected and the row menu Place in class · Collect tuition · Send message.

2 · Student profile · Overview

/students/:id · desktop

Student profile · Overview
The profile header shows avatar, code, age, status chip, branch, class chip and the "Attention" chip driven by the allergy note (STU-09, rule 8). Right rail: primary guardian with Zalo status, session balance, debt, FLYER account. The kebab menu opens Pause · Withdraw · Transfer branch; Anonymise is greyed out because the student is not yet Withdrawn (STU-10). The Overview tab holds personal details, the versioned consent summary (rule 7) and the timeline.

3 · Student profile · Classes and history

/students/:id · tab Lớp & lịch sử · desktop

Student profile · Classes and history
Enrollment table with class, level, teacher, start date, end date, status and sessions attended / total; buttons Transfer class, End, Place in class (STU-04). Below it, the list of class transfers, branch transfers (transfer-in ledger entry, STU-07) and a completed pause with the number of days added to the package (STU-05).

4 · Student profile · Documents and notes

/students/:id · tab Tài liệu, Ghi chú · desktop

Student profile · Documents and notes
Documents are typed per the STU-09 list (birth certificate, contract, image consent, ID card) and stored privately behind signed URLs. Notes show author, time and visibility (staff only or teachers too); the "Attention" note is the source of the chip on the profile header and on the teacher's class roster (rule 8).

5 · Student profile · Learning (FLYER data)

/students/:id · tab Học tập · desktop

Student profile · Learning (FLYER data)
The Learning tab reads existing endpoints (STU-08): study time this month, assignments given / completed, latest score, four-skill scores from StudentScoreEntry, placement test result and recent attempts. When the student has not used FLYER yet the area is replaced by the empty state "No learning data yet".

6 · Pause dialog

/students/:id · kebab › Bảo lưu · desktop

Pause dialog
Pausing requires a start date, an expected return date and a reason (rule 4). The case shown lasts 107 days, over the 90-day default: a warning says the branch manager must approve and the primary button becomes "Submit pause for approval" (rule 5). The impact list mirrors STU-05: status change, hidden from the class roster, package frozen and expiry extended, reminder created 7 days before return.

7 · Withdraw dialog

/students/:id · kebab › Nghỉ học · desktop

Withdraw dialog
Withdrawal needs a date and a reason from the STU-06 list (cost, moved away, unhappy, goal reached, other). A validation error is shown: "Other" is selected but the free-text reason is empty. The summary shows unused sessions, the estimated refund from PRD 05 and the "Create refund voucher" link; the note reminds that withdrawal deletes nothing (rule 6).

8 · Transfer-branch dialog

/students/:id · kebab › Chuyển chi nhánh · desktop

Transfer-branch dialog
Transferring needs the target branch, transfer date and an optional target class (STU-07). The impact list expresses rule 1: the enrollment at the old branch ends, a new student record is created at the target branch sharing the FLYER profile and guardians, the session balance moves with "transfer out" / "transfer in" ledger entries, and both profiles get a timeline entry.

1Purpose

Every downstream module (classes, attendance, tuition, communication) needs one authoritative student record per branch with a clear status, verified guardian contacts and a history of what the student bought and attended. The FLYER backend already has the learner profile (StudentProfile, int64 id), a single StudentParent, finance stats and score entries. This PRD adds the operational wrapper: status lifecycle, several guardians with consent, enrollment history, documents, notes, and the session balance derived from PRD 05 packages and PRD 04 attendance.

In scope

Out of scope

2What exists today

Object / endpointFieldsUse
GET /v3/teacher/school-finance/students (SchoolStudent)id, first_name, last_name, display_name, avatar_url, phone_number, email, user_name, student_name, class {id, name, code, class_type}Base list, filter by class_ids and q
GET /v3/teacher/school-finance/students/{id} (StudentDetail)user {phone, email, user_name}, parent {id, display_name}, courses[]Base detail
.../students/{id}/stats (StudentStats)current_class_name, total_amount, paid_amount, unpaid_amount, payment_countFinance summary
.../students/{id}/tuitions, /coursesTuition[], Course[]Finance tab
StudentScoreEntry via exam score reportoverall, four skills, Cambridge shields, CEFR, IELTS bandLearning tab
GET /v3/parent/learner/{id}/report, /weekly-reportassignments, self-study, learning time, skillsLearning tab and parent report
CustomerLearner, GET /v3/crm/customers/learners/searchlead to learner linkConversion (PRD 01)
GET /v3/teacher/students/quick-searchPickers

Missing: a student status, several guardians, enrollment as an object (today a student is simply "in a class"), documents, notes, consent, branch transfer, session balance.

3Status lifecycle

StatusVI labelMeaningEnters byLeaves to
prospectTiềm năngNot a student yet; lead record only (PRD 01). Not stored in student table.trial, waiting, active
trialHọc thửHas a trial booking, no enrollmenttrial booking from leadwaiting, active, dropped
waitingChờ xếp lớpConverted, no class yetconversion without classactive
activeĐang họcAt least one active enrollmentenrollment startpaused, completed, withdrawn
pausedBảo lưuEnrollment suspended with an expected return date; package sessions frozenstaff action with reason and datesactive, withdrawn
completedHoàn thànhAll enrollments ended by course completionclass endactive (re-enroll), alumni
withdrawnĐã nghỉLeft before completion; reason recordedstaff actionactive (re-enroll)
droppedKhông nhập họcTrial or waiting student who never enrolledstaff action or 60 days idleactive

Status is stored on the student record and recomputed by the system when enrollments change; manual transitions are limited to paused, withdrawn and dropped and always require a reason. A student who is Active in one branch and transfers keeps one record per branch (school); the profile shows both.

4User stories and acceptance criteria

STU-01Browse and find students
As an academic admin I find any student in seconds.
  • Given the student list
  • When I type a name, phone (student or guardian), code or username
  • Then results filter server-side within 300 ms, accents ignored, and the guardian phone match is highlighted.
STU-02Create a student directly
As an admin I add a walk-in student without a lead.
  • Given permission students.create
  • When I submit the student form with at least name, birthday, branch, one guardian with phone
  • Then a learner profile is created on FLYER (username generated, credentials shown once and sent to the guardian on request), the student record is Waiting, and a lead is not created.
STU-03Manage guardians
As an admin I keep guardian contacts correct.
  • Given a student profile
  • When I add a guardian with name, relationship, phone, email, Zalo consent, flags "Liên hệ chính" and "Người thanh toán"
  • Then the guardian is saved; exactly one guardian is primary contact and one is payer (may be the same); a guardian shared by siblings is one record linked to several students; changing the phone writes an audit entry and re-verifies Zalo reachability (PRD 07).
STU-04Enroll into a class
As an admin I place a student in a class from the profile.
  • Given a Waiting or Active student
  • When I choose class, start date and (optionally) fee plan
  • Then an enrollment is created (PRD 03) if capacity allows; the student appears in the class roster from the start date; sessions before the start date are not counted as absences; status becomes Active.
STU-05Pause and resume
As an admin I record a pause (bảo lưu).
  • Given an Active student
  • When I set pause from date, expected return, reason
  • Then status Paused; the roster hides the student from sessions in the pause window; the session package expiry (PRD 05) is extended by the pause length; a reminder is created 7 days before expected return; resume re-activates and asks for the class (same or new).
STU-06Withdraw
As an admin I end a student's study.
  • Given an Active or Paused student
  • When I withdraw with date and reason (select: chi phí, chuyển nhà, không hài lòng, hoàn thành mục tiêu, khác + free text)
  • Then all enrollments end on that date; unused sessions are shown with the refund estimate from PRD 05 and a link "Tạo phiếu hoàn"; the churn report in PRD 09 records the reason.
STU-07Transfer to another branch
As a branch manager I move a student to another branch.
  • Given a student at branch A
  • When I transfer to branch B with date and target class (optional)
  • Then the A enrollment ends, a B student record is created or reused with the same learner profile, guardians are shared, package balance moves to B (finance entry at A "chuyển đi", at B "chuyển đến"), and the profile shows a timeline entry.
STU-08Read learning data
As a teacher or admin I see how the student is doing on FLYER.
  • Given a linked learner profile
  • When I open the Học tập tab
  • Then I see study time this month, assignments assigned/completed, latest scores per skill, placement result and latest exam entries, all from existing endpoints, with an empty state "Chưa có dữ liệu học tập" when the learner has not used FLYER.
STU-09Documents and notes
As staff I keep documents and notes on the profile.
  • Given permission students.edit
  • When I upload a file with type (birth certificate, ID, agreement, photo consent, medical, other) or write a note with visibility (staff, teachers too)
  • Then the file is stored privately with signed URLs; notes show author and time; notes marked "Cần lưu ý" appear as a chip on the roster for teachers (e.g. allergy).
STU-10Anonymise on request
As an owner I fulfil a deletion request.
  • Given permission students.anonymise and a Withdrawn or Dropped student
  • When I confirm with the request reference
  • Then personal fields are replaced (PRD 12), documents deleted, finance records retained with the anonymised name, and the action logged.

5Screens

Student list /students

Header: h1 Học viên, buttons Nhập từ Excel (PRD 11), + Thêm học viên. Tabs with counts: Tất cả · Đang học · Chờ xếp lớp · Học thử · Bảo lưu · Sắp hết buổi · Nợ học phí · Đã nghỉ. Filters: search, branch, class, level, guardian consent, start date range. Bulk: Gửi thông báo (PRD 07), Xuất Excel, Chuyển lớp.

ColumnSourceNotes
Mã HVstudent.codeHV-000123 per tenant
Họ têndisplay_name + avatarlink; age in small text "8 tuổi"
Lớpactive enrollmentschips; dashed "Chưa xếp lớp"
Trạng tháistatuschip per lifecycle table
Phụ huynhprimary guardian name + phonephone click-to-copy
Buổi còn lạisession balance (PRD 05)red when ≤ 3; "-" for monthly plans
Công nợunpaid_amount (StudentStats)red when > 0 and overdue
Điểm danh 30 ngàyattendance ratepercent; late/absent counts on hover
Chi nhánhschool nameonly when "Tất cả chi nhánh"
Ngày nhập họcfirst enrollment start
Thao tácXếp lớp · Thu học phí · Gửi tin

Empty state: "Chưa có học viên. Chuyển đổi từ khách hàng hoặc thêm trực tiếp." Rows 44 px; 50 per page; column visibility saved via view-configs (view key student_list).

Student profile /students/:id

Header: avatar, name, code, age, status chip, branch, class chips, "Cần lưu ý" chip if any flagged note. Buttons: Sửa · Xếp lớp · Thu học phí · Gửi tin · kebab (Bảo lưu, Nghỉ học, Chuyển chi nhánh, Ẩn danh hoá). Right rail: Phụ huynh chính (name, relationship, phone, Zalo status), Số dư buổi, Công nợ, Tài khoản FLYER (username, last active).

Tabs: Tổng quan · Phụ huynh · Lớp & lịch sử · Điểm danh · Học phí · Học tập · Tài liệu · Ghi chú · Nhật ký.

  • Tổng quan: personal info (birthday, gender, school, address, level, allergies), consent summary, timeline of key events (created, converted, enrolled, paused, transferred).
  • Phụ huynh: cards per guardian with flags; + Thêm phụ huynh; "Gửi lại thông tin đăng nhập".
  • Lớp & lịch sử: table of enrollments (class, level, teacher, start, end, status, sessions attended / total) and actions Chuyển lớp, Kết thúc.
  • Điểm danh: month view with chips per session, rate, absences list with excuses (PRD 04).
  • Học phí: package balance, tuition list with status, receipts, "Tạo hoá đơn" (PRD 05).
  • Học tập: FLYER data (STU-08).
  • Tài liệu, Ghi chú, Nhật ký (audit).
Student form (drawer) /students/new · /students/:id/edit
FieldTypeValidation
Họ và têntextrequired 2–80
Ngày sinhdaterequired; age 3–80
Giới tínhNam/Nữ/Khácoptional
Chi nhánhselectrequired
Trường đang học, Lớp (phổ thông)textoptional
Trình độ hiện tạiselect CEFR/Cambridge level list (PRD 03 catalog)optional
Địa chỉtextoptional
Lưu ý sức khoẻtextoptional; flagged note
Phụ huynh 1name, relationship, phone, email, consentphone required; existing guardian suggested by phone
Tài khoản FLYERradio: tạo mới / liên kết có sẵn (search by username or phone)required
Ảnhuploadoptional, 5 MB

6Business rules

  1. One student record per (school, learner_profile). The same learner at two branches is two records sharing guardians and learning data.
  2. Student code is tenant-wide sequential and never reused.
  3. A student must have at least one guardian with a phone unless age ≥ 18 (adult learner, self as contact).
  4. Status is derived from enrollments except for manual Paused, Withdrawn, Dropped; manual transitions need a reason.
  5. Pause maximum 90 days by default (tenant setting); longer pauses require branch manager approval.
  6. Withdrawal does not delete anything; re-enrollment reuses the record.
  7. Guardian consent is versioned; a message channel is used only when consent for that channel exists (PRD 07).
  8. Notes flagged "Cần lưu ý" are visible to teachers of the student's classes; other notes are staff-only.

7Data model

EntityFieldsNotes
student (new; wraps learner profile)id uuid; tenant_id (center_id); school_id int64; learner_profile_id int64; code; display_name; birthday; gender; school_name; grade; level_code; address; health_note; status enum (lifecycle); status_reason; status_changed_at; first_enrolled_at; avatar_file_id; created_by; created_at; updated_atunique (school_id, learner_profile_id)
guardian (new; supersedes single StudentParent for operations)id uuid; tenant_id; full_name; phone (E.164); email; zalo_user_id null; address; parent_user_id int64 null (FLYER parent account); created_atunique (tenant_id, phone)
student_guardianstudent_id; guardian_id; relationship enum(father, mother, grandparent, sibling, self, other); is_primary_contact; is_payer; created_atPK (student_id, guardian_id)
consentid; guardian_id; student_id null; type enum(service, zalo, email, marketing, photo); version; granted_at; revoked_at; channel enum(web, portal, paper); actor_id
student_status_historyid; student_id; from_status; to_status; reason; effective_date; expected_return_date null; actor_id; created_at
student_documentid; student_id; type enum(birth_cert, id, agreement, photo_consent, medical, other); file_id; name; uploaded_by; created_at
student_noteid; student_id; body; is_flagged; visibility enum(staff, teachers); author_id; created_at
enrollment (PRD 03 owns)see PRD 03
student_balance (maintained summary)student_id; sessions_purchased; sessions_used; sessions_remaining; sessions_expire_on; unpaid_amount; overdue_amount; attendance_rate_30d; updated_atrecomputed on package, attendance, tuition writes

8API contract

MethodPathRequest / responseStatus
GET/v3/school/studentsq, school_id, status[], class_ids[], level, has_debt, low_balance, consent_zalo, enrolled_from, enrolled_to, sort, page, limit → StudentRow[] (student + primary guardian + balance)new (wraps existing school-finance/students)
POST/v3/school/students{school_id, display_name, birthday, gender, school_name, grade, level_code, address, health_note, learner: {learner_profile_id} | {create: true}, guardians: [{guardian_id | {full_name, phone, email}, relationship, is_primary_contact, is_payer, consents: [type]}]} → Studentnew
GET/v3/school/students/{id}Student + guardians + enrollments + balance + flagsnew
PATCH/v3/school/students/{id}partial profilenew
POST/v3/school/students/{id}/status{to: paused|withdrawn|dropped|active, reason, effective_date, expected_return_date?, class_id?} → Student; 409 INVALID_TRANSITIONnew
POST/v3/school/students/{id}/transfer{to_school_id, effective_date, class_id?, note} → {new_student_id}new
GET/POST/v3/school/students/{id}/guardiansadd or link guardian; PATCH /v3/school/guardians/{id}; DELETE linknew
GET/POST/v3/school/guardians?phone=lookup by phone for reusenew
POST/v3/school/consents{guardian_id, student_id?, type, version, channel} ; DELETE to revokenew
GET/POST/DELETE/v3/school/students/{id}/documents{type, file_id, name}new
GET/POST/PATCH/DELETE/v3/school/students/{id}/notes{body, is_flagged, visibility}new
GET/v3/school/students/{id}/balancestudent_balancenew
POST/v3/school/students/{id}/anonymise{request_ref}new
GET/v3/teacher/school-finance/students/{id}/stats | /tuitions | /coursesfinance tabexisting
GET/v3/parent/learner/{id}/report, /weekly-reportlearning tab (staff call with school auth)existing (needs staff access grant)
GET/v3/teacher/students/quick-searchpickersexisting
POST/v3/school/students/{id}/credentials/resendsends FLYER login to guardian channelnew

9Permissions

Codeownerbranch_manageracademic_adminadmissionsaccountantteacher
students.viewallbranchbranchread (name, class, guardian)branch readown classes
students.createyesyesyesvia convertnono
students.edityesyesyesnononotes only
students.status (pause, withdraw, drop)yesyesyesnonono
students.transferyesyesnononono
students.guardiansyesyesyesnopayer flagno
students.documentsyesyesyesnonono
students.learning.viewyesyesyesnonoown classes
students.anonymiseyesnonononono
students.exportyesyesyesnoyesno

10Analytics events

EventProperties
student_createdvia (convert|form|import), age_bucket, with_class
guardian_addedrelationship, consents[]
student_status_changedfrom, to, reason_code, manual (bool)
student_transferredfrom_school, to_school
student_profile_viewedtab, role
student_note_flagged
student_anonymised

11Edge cases

12Open questions

Trường Số (truongso.net) · FLYER internal