Trường Số PRD/04 Attendance
Trường Số · Product Requirements · 09/2026

PRD 04 · Attendance

Session attendance with four statuses (present, late, absent, excused), a phone-first teacher flow, admin correction with audit, guardian notification through Zalo, and the link from attendance to package balance and teacher pay. No attendance entity exists in the backend today.

Key facts, origin and limits
Status
Draft v1
Owner module
Điểm danh
Backend
NEW
Phase
P1
Depends on
03 Classes, 02 Students, 07 Parents

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 attendance statuses and whether "muộn" exists.
    • Whether absence deducts a package session by default and whether the setting is per center.
    • How teachers mark on mobile (app or web) and whether an edit window exists.
    • What the parent receives (app push, Zalo, SMS) and its template text.
    • Whether "đánh dấu ghi chú học sinh nghỉ học" is free text or reasons.

Proposed design

Three mobile screens for the teacher (today's sessions, the attendance sheet, the saved sheet) and five desktop screens for administrators. The one thing to see is the four-state control Có mặt · Muộn · Vắng · Phép on every student row, defaulting to present with 44 px targets: the teacher only taps the exceptions and presses Save.

1–2 · Teacher: today's sessions and the attendance sheet

/teach/today · /teach/sessions/:id/attendance · mobile

Teacher: today's sessions
Attendance sheet on mobile
Screen 1: today's sessions with time, class, room, roster size and a status chip (saved / marking 9/10 / unmarked); yesterday's unmarked session is pinned on top in amber (ATT-05); tab bar Hôm nay · Lịch tuần · Lớp của tôi · Tài khoản. Screen 2: the Movers 2B sheet with live counters, the "Chưa đồng bộ" offline banner (ATT-01), row flags (needs attention, low balance, birthday), a Late row exposing the minutes field with 5/10/15/30 presets (rule 3), an Excused row prefilled from an excuse (ATT-02), a Guests section where trial and make-up students have only two states plus a quick note for the consultant (ATT-07); Save and the notify toggle stay fixed at the bottom.

3 · Saved sheet: notification delivery

/teach/sessions/:id/attendance · mobile

Saved attendance sheet
After saving, the summary shows the delivery outcome per guardian: sent on Zalo, Zalo failed then email, skipped because no channel exists, not sent because the absence was excused (ATT-03 and the edge cases). The trial student gets a three-option quick note for the consultant; the make-up booking is updated and one session consumed (ATT-07). A notice states the 24-hour window for edits without a reason (rule 4).

4 · Attendance sheet on desktop (admin)

/attendance/sessions/:id · desktop

Attendance sheet on desktop
The same Movers 2B sheet seen by academic admin two days later: a strip with session facts, the session's attendance rate (7/9, excused not counted, rule 7) and the sheet log; the table carries every column, student with roster type and flags, the four-state control, minutes late, package balance (consumed / not consumed per rule 5), note and guardian notification status. The "Ngoài cửa sổ 24 giờ" chip in the header signals that any change now needs a reason and attendance.edit_any (rule 4).

5 · Attendance overview

/attendance · desktop

Attendance overview
Tabs Hôm nay · Chưa điểm danh · Theo lớp · Theo học viên · Báo cáo; four KPIs and filters for date, branch, teacher and class. The today table lists time, class, room, teacher, roster, marking status, present/late/absent/excused counts, notifications sent and the Mở phiếu action; the Flyers 1C session unmarked more than 2 hours after ending is flagged in red with a Nhắc GV button (ATT-05). The two cards below list unmarked sessions by teacher with Nhắc GV and Điểm danh thay actions, and 30-day attendance rates by class (ATT-08).

6 · Student attendance tab

/students/:id#attendance · desktop

Student attendance tab
September 2026 calendar with one chip per session (present, late 15', absent, excused, upcoming), 30- and 90-day rates, and the absences table with date, class, status, reason and make-up booking status. The warning card notes two absences in 30 days, one short of the threshold that creates a follow-up task (ATT-06); + Thêm phép is the page's primary action.

7 · Excuse form

/students/:id/excuses/new · modal

Excuse form
The dialog carries every field from section 4: a single session or a date range, reason (Ốm / Việc gia đình / Đi học trường / Khác), reported by, channel and the offer-make-up toggle. The state shown is a validation error: "Khác" selected without a description. The helper line states what happens on save: the 21/09 session is prefilled as excused on the teacher's sheet and the guardian receives a confirmation (ATT-02).

8 · Edit a record with reason and history

/attendance/sessions/:id · modal

Edit attendance with reason
Academic admin changes one record from absent to excused 42 hours later: the dialog warns that the 24-hour window has passed, requires a reason, shows the effect on the package balance (one session refunded, 6 → 7) and offers an optional correction message to the guardian; the record history lists the previous and new status with the actor (ATT-04, rules 4 and 5).

1Purpose

Attendance is the transaction that connects the timetable to money and to parents. Each held session produces one record per rostered student; those records reduce session packages, prove teacher hours for pay, and trigger the message a guardian expects within minutes of the class starting. The flow must take a teacher under one minute on a phone in a classroom with poor connectivity.

In scope

Out of scope

2Statuses

CodeVI labelChipConsumes package sessionNotifies guardianCounts in attendance rate
presentCó mặtCó mặtyesoptional (tenant setting, default off)present
lateMuộnMuộnyesyes, with minutespresent
absentVắngVắngyes (tenant setting "vắng không phép vẫn trừ buổi", default on)yes, immediatelyabsent
excusedCó phépCó phépno (default) ; make-up offeredno (guardian informed in advance)excluded
unmarkedChưa điểm danhgreynonoexcluded until marked

Colours follow the Cloud status tokens: ok #16A34A on #DCFCE7, late #D97706 on #FEF3C7, abs #DC2626 on #FEE2E2, exc #2563EB on #DBEAFE. Every chip carries its text; colour is never the only signal.

3User stories and acceptance criteria

ATT-01Teacher marks attendance on a phone
As a teacher I mark the whole class in under a minute.
  • Given I open Điểm danh on my phone at or after session start (or up to 30 min before)
  • When the session sheet loads with every rostered student defaulted to Có mặt
  • Then I tap only the exceptions (Muộn asks minutes with presets 5/10/15/30; Vắng; Có phép when an excuse already exists is preselected), add an optional class note, and press Lưu; the sheet saves with one request; if offline, the sheet is queued locally and synced when online, with a visible "Chưa đồng bộ" banner.
ATT-02Excused absence in advance
As an admin I record a parent's notice before the session.
  • Given a guardian informs the desk (call, Zalo, portal request)
  • When I add an excuse on the student for a session or a date range with reason
  • Then those sessions show Có phép pre-marked on the teacher sheet; the guardian receives confirmation; a make-up suggestion appears if policy allows.
ATT-03Guardian notification
As a guardian I learn immediately if my child is absent.
  • Given attendance saved with a student marked absent and the guardian has a Zalo consent
  • When the save completes
  • Then within 2 minutes a ZNS message "Học viên [tên] vắng buổi [lớp] ngày [dd/MM] lúc [HH:mm]. Vui lòng liên hệ [chi nhánh] [SĐT]." is sent; if Zalo fails, in-app then email fallback; delivery status is visible on the session sheet.
ATT-04Admin corrects a record
As an academic admin I fix a wrong mark.
  • Given a saved sheet
  • When I change a student's status after the session day with a reason
  • Then the record updates, an audit entry stores before/after, package balance recalculates, and if the change moves absent to present a "correction" message is optionally sent.
ATT-05Unmarked sessions are chased
As an admin I see which sessions have no attendance.
  • Given sessions ended more than 2 hours ago with no sheet saved
  • When I open Điểm danh → Chưa điểm danh
  • Then they are listed by teacher with "Nhắc GV" (in-app push) and "Điểm danh thay"; at 21:00 daily the teacher gets a reminder for any unmarked session of the day; the branch manager gets a digest of sessions unmarked for more than 24 h.
ATT-06Absence follow-up
As an admissions or care staff I follow up repeated absences.
  • Given a student absent 2 consecutive sessions or 3 in 30 days (tenant thresholds)
  • When the second/third absence is saved
  • Then a task "Liên hệ phụ huynh: [tên] vắng N buổi" is created for the student's care owner (branch default), shown in the today plan; the student list tab "Cần quan tâm" includes them.
ATT-07Trial and make-up students on the sheet
As a teacher I see who is visiting.
  • Given a session with a trial booking and a make-up booking
  • When I open the sheet
  • Then those students appear at the bottom with Học thử / Học bù chips and a note field; marking present updates the booking (PRD 01, PRD 03) and, for trials, prompts a 3-option quick note (Phù hợp / Cần lớp thấp hơn / Cần lớp cao hơn) sent to the consultant.
ATT-08Reports
As a manager I review attendance quality.
  • Given a date range and branch
  • When I open the attendance report
  • Then I see rate by class (present+late over marked), late minutes distribution, absences by reason, unmarked sessions by teacher, and a student list sortable by absences; exportable to xlsx.

4Screens

Teacher: today's sessions (mobile) /teach/today

List of my sessions today with time, class, room, roster count and a status chip (Chưa điểm danh / Đã điểm danh 11/12 / Đã lưu). Tap opens the sheet. Yesterday's unmarked sessions are pinned on top in amber. Bottom tab bar: Hôm nay · Lịch tuần · Lớp của tôi · Tài khoản.

Attendance sheet (mobile and desktop) /teach/sessions/:id/attendance · /attendance/sessions/:id
┌─────────────────────────────────────────────┐ │ ← Movers 2A · T3 10/09 · 18:00–19:30 · P.201 │ │ Có mặt 10 · Muộn 1 · Vắng 1 · Có phép 0 │ ├─────────────────────────────────────────────┤ │ ○ Nguyễn An [Có mặt][Muộn][Vắng][Phép] │ │ Cần lưu ý: dị ứng đậu phộng │ │ ○ Trần Bình Muộn 10' [Có mặt][Muộn][Vắng][Phép] │ │ ○ Lê Chi Số dư: 2 buổi ... │ │ ── Khách ──────────────────────────────────── │ │ ○ Phạm Dũng Học thử [Có mặt][Vắng] ghi chú │ ├─────────────────────────────────────────────┤ │ Ghi chú buổi học (tuỳ chọn) │ │ [ Lưu điểm danh ] Gửi thông báo PH [x] │ └─────────────────────────────────────────────┘

Rows: avatar, name, flags (Cần lưu ý, low balance, birthday today), segmented control of four statuses (44 px tall targets), per-student note icon. Header counters update live. Save button sticky. After save: summary sheet with delivery status of notifications and "Sửa" for 24 h without reason (teacher), later with reason (admin).

States: loading skeleton; empty roster "Lớp chưa có học viên"; offline banner; conflict "Buổi này đã được [tên] điểm danh lúc HH:mm" with option to view.

Attendance overview (desktop) /attendance

Tabs: Hôm nay · Chưa điểm danh · Theo lớp · Theo học viên · Báo cáo. Hôm nay: table of today's sessions (time, class, room, teacher, roster, marked, present/late/absent/excused counts, notifications sent) with action Mở phiếu. Filters: branch, teacher, class.

Student attendance tab /students/:id#attendance

Month calendar with a chip per session, rate for 30/90 days, table of absences (date, class, status, reason, make-up booked), + Thêm phép.

Excuse form (modal) /students/:id/excuses/new

Fields: session(s) (select from upcoming sessions or date range), reason (Ốm / Việc gia đình / Đi học trường / Khác + text), reported by (guardian name), channel (Zalo, gọi điện, trực tiếp, cổng PH), offer make-up toggle.

5Business rules

  1. A sheet exists per session; records exist only for rostered students (enrolled on that date, trial, make-up). Students enrolled after the date are not retro-added.
  2. Default status on open is present; saving with all-present is valid and common.
  3. Late requires minutes (1–120); late beyond a tenant threshold (default 45 min) is still late, not absent.
  4. Teachers (main or substitute of the session) may mark and edit within 24 h without a reason; after that, edits need attendance.edit_any and a reason.
  5. Package consumption: present and late always consume; absent consumes per tenant setting; excused never consumes; make-up consumes per booking flag.
  6. Notification rules per status are tenant settings with defaults in section 2; quiet hours 21:30–07:00 hold messages until morning except absence during evening classes, which send immediately (parents expect it).
  7. Attendance rate = (present + late) / (present + late + absent) over marked sessions.
  8. A cancelled session has no sheet; cancelling a session with a saved sheet requires deleting the sheet first (admin only, audited).

6Data model

EntityFieldsNotes
attendance_sheetid uuid; session_id (unique); school_id; class_id; marked_by_staff_id; marked_at; note; notify_guardians bool; present_count; late_count; absent_count; excused_count; version intnew
attendance_recordid uuid; sheet_id; session_id; student_id; roster_type enum(enrolled, trial, makeup); status enum(present, late, absent, excused); late_minutes int null; note; excuse_id null; consumed_session bool; notified_at null; notify_status enum(sent, failed, skipped) null; updated_by; updated_atunique (session_id, student_id)
attendance_record_historyid; record_id; from_status; to_status; reason; actor_id; created_ataudit detail
excuseid; student_id; session_id null; date_from; date_to; reason_code enum(sick, family, school, other); reason_text; reported_by; channel; offer_makeup bool; created_by; created_atnew
attendance_policy (tenant settings)absent_consumes bool; late_threshold_min; edit_window_hours; consecutive_absent_threshold; monthly_absent_threshold; notify: {present, late, absent, excused, correction}; quiet_hoursstored in school config

7API contract

MethodPathRequest / responseStatus
GET/v3/school/sessions/{id}/attendance→ {session, sheet | null, roster: [{student, roster_type, flags, balance, prefilled_status, excuse}]}new
PUT/v3/school/sessions/{id}/attendance{records: [{student_id, status, late_minutes?, note?}], note?, notify_guardians, version?, client_saved_at?} → sheet with records and notify statuses; 409 STALE_VERSION; idempotent by (session_id, client_saved_at)new
PATCH/v3/school/attendance/records/{id}{status, late_minutes?, note?, reason} (reason required after edit window)new
DELETE/v3/school/sessions/{id}/attendanceadmin, auditednew
GET/v3/school/attendance/sessionsschool_id, date_from, date_to, class_id, staff_id, marked=false → sessions with countsnew
POST/v3/school/attendance/sessions/{id}/remind-teacherin-app pushnew
GET/POST/DELETE/v3/school/students/{id}/excuses{session_id | date_from, date_to, reason_code, reason_text, reported_by, channel, offer_makeup}new
GET/v3/school/students/{id}/attendancefrom, to → records + ratenew
GET/v3/school/attendance/reportschool_id, from, to, group_by=class|student|teacher → aggregates; format=xlsx for exportnew
GET/v3/school/me/sessions?date=teacher's sessions with sheet statusnew
GET/PUT/v3/teacher/school-configattendance_policy block addedextend
GET/v3/parent/learner/{id}/attendanceguardian viewnew

8Permissions

Codeownerbranch_manageracademic_adminteacheraccountantparent
attendance.viewallbranchbranchown sessionsbranch readown children
attendance.markyesyesyesown sessionsnono
attendance.edit_anyyesyesyesnonono
attendance.delete_sheetyesyesnononono
excuses.manageyesyesyesnonorequest only
attendance.report, attendance.exportyesbranchbranchown classesreportno
attendance.policyyesnonononono

9Analytics events

EventProperties
attendance_sheet_openeddevice (mobile|desktop), minutes_from_start, roster_size
attendance_savedseconds_to_save, counts, offline_queued, notify
attendance_record_editedhours_after_session, by_role, from, to
attendance_notification_sentstatus, channel, outcome
excuse_createdreason_code, channel, offer_makeup
absence_followup_task_createdtrigger (consecutive|monthly)
unmarked_reminder_senthours_late

10Edge cases

11Open questions

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