FLYER Teacher · PRD · Sprint 108

Điểm danh & Báo cáo chuyên cần

Cho phép giáo viên điểm danh học sinh theo từng buổi ngay trong lớp, và cho chủ trường / quản lý chi nhánh theo dõi chuyên cần toàn chi nhánh — kèm cảnh báo học sinh cần chú ý.

Phiên bản
1.0
Ngày
12/08/2026
Owner
Tùng (Product)
Trạng thái
Prototype FE đã chạy
Cần
Backend API + Design review
🔗 Link mẫu trên productVị trí: Lớp học → tab Điểm danh ↗prototype đang chạy local partner-web — chưa lên production

1Bối cảnh & mục tiêu

Trung tâm / trường (B2B) đang điểm danh trên giấy hoặc Excel rời rạc — chủ trường không có bức tranh chuyên cần theo lớp / chi nhánh, và không phát hiện sớm học sinh sắp bỏ học (chuyên cần thấp là tín hiệu churn mạnh nhất).

Trạng thái hiện tại Prototype frontend hoàn chỉnh đã chạy trên branch dev (dữ liệu tạm lưu localStorage, chưa có backend). Toàn bộ luồng UI, business rules và edge cases trong tài liệu này đã được implement + audit — team backend chỉ cần thay lớp lưu trữ bằng API (mục 7).

2Người dùng & user stories

Vai tròUser storyƯu tiên
Giáo viênLà GV, tôi muốn đánh dấu Có mặt / Đi muộn / Nghỉ phép / Vắng cho từng học sinh trong buổi học hôm nay, kèm ghi chú lý do, để lưu hồ sơ chuyên cần.P0
Giáo viênLà GV, tôi muốn nhấn “Tất cả có mặt” rồi chỉ sửa vài học sinh vắng, để điểm danh thật nhanh.P0
Giáo viênLà GV, tôi muốn mở lại buổi cũ để sửa sai, và xem lịch sử chuyên cần của lớp.P1
Chủ trườngLà chủ trường, tôi muốn xem % chuyên cần từng lớp trong chi nhánh theo khoảng thời gian, để đánh giá vận hành.P0
Chủ trườngLà chủ trường, tôi muốn thấy danh sách học sinh chuyên cần < 80% (cần chú ý), để CS gọi phụ huynh sớm.P0
Chủ trườngLà chủ trường, tôi muốn xuất Excel điểm danh theo buổi / theo lớp.P1

3Phạm vi & phase

PhaseNội dungTrạng thái
Phase 1Tab “Điểm danh” trong chi tiết lớp (4 trạng thái + ghi chú + lịch sử + xuất Excel) và trang “Báo cáo điểm danh” cấp chi nhánh trong menu Báo Cáo. Lưu localStorage.Done — prototype
Phase 2Backend API + DB (mục 7): dữ liệu dùng chung giữa các giáo viên / thiết bị; phân quyền theo role; audit log.Cần build
Phase 3Tự động thông báo phụ huynh khi con vắng (ZNS / Zalo OA — tận dụng hạ tầng Sprint 107), báo cáo chuyên cần trong weekly/monthly report.Đề xuất

4Luồng sản phẩm

4a · Giáo viên điểm danh (tab trong chi tiết lớp)

  1. Vào lớp → Quản Lý Lớp → chọn lớp → tab “Điểm danh” (tab cuối, value 8).
  2. Chọn ngày (mặc định hôm nay; không chọn được ngày tương lai).
  3. Đánh dấu: mỗi học sinh có 4 pill trạng thái; nhấn chọn — nhấn lại lần nữa để bỏ chọn. Nút “Tất cả có mặt” đánh cả lớp một phát. Ô ghi chú tự do cho từng học sinh (vd lý do vắng).
  4. Theo dõi: 4 chip đếm (Có mặt / Đi muộn / Nghỉ phép / Vắng) cập nhật realtime + dòng “Đã điểm danh x/y học sinh”.
  5. Lưu điểm danh → toast “Đã lưu điểm danh”. Lưu một ngày mà không đánh dấu gì = xoá buổi đó (cách xoá buổi lưu nhầm).
  6. Lịch sử (toggle góc phải): bảng tổng hợp mỗi học sinh (số buổi từng trạng thái + % chuyên cần, tô màu theo ngưỡng) + danh sách các buổi đã điểm danh — nhấn 1 buổi để mở lại và sửa.
Màn điểm danh trong lớp: danh sách học sinh với 4 nút trạng thái màu, ô ghi chú, chip đếm và nút Lưu
Prototype thực tế — tab Điểm danh trong chi tiết lớp (dữ liệu lớp thật).

4b · Chủ trường xem báo cáo (menu Báo Cáo → Báo cáo điểm danh)

  1. Mở sidebar Báo Cáo → “Báo cáo điểm danh” (route /report/attendance). Phạm vi = chi nhánh đang chọn (đúng mô hình mỗi chi nhánh là 1 school).
  2. Tổng quan: 4 stat — số lớp đã điểm danh · tổng buổi · % chuyên cần chung (tô màu ngưỡng) · tổng lượt vắng.
  3. Chuyên cần theo lớp: bảng từng lớp (số buổi, đếm 4 trạng thái, % chuyên cần) — sort lớp thấp nhất lên đầu.
  4. Học sinh cần chú ý: mọi học sinh có chuyên cần < 80% (tên, lớp, số buổi vắng, %).
  5. Lọc khoảng ngày (mặc định 30 ngày; xoá = toàn thời gian) + Xuất Excel.
Trang báo cáo điểm danh: 4 stat tổng quan, bảng chuyên cần theo lớp và danh sách học sinh cần chú ý
Prototype thực tế — Báo cáo điểm danh cấp chi nhánh (dữ liệu demo).
Demo mode Khi chi nhánh chưa có dữ liệu, trang hiển thị nút “✨ Tạo dữ liệu demo” (sinh 8 lớp × 12 buổi, tên học sinh giả) phục vụ demo bán hàng; nút “Xoá dữ liệu demo” gỡ riêng dữ liệu demo của chi nhánh đó, không đụng dữ liệu thật. Giữ tính năng này ở Phase 2 sau một feature flag nội bộ.

5Spec UI — cho Designer

Trạng thái điểm danh (4 mức)

Trạng tháiPill (chưa chọn)Pill (đang chọn)Màu chữ/nền chưa chọnNền khi chọn
Có mặt · PresentCó mặtCó mặt#16A34A / #DCFCE7#16A34A, chữ trắng
Đi muộn · LateĐi muộnĐi muộn#D97706 / #FEF3C7#D97706, chữ trắng
Nghỉ phép · ExcusedNghỉ phépNghỉ phép#2563EB / #DBEAFE#2563EB, chữ trắng
Vắng · AbsentVắngVắng#DC2626 / #FEE2E2#DC2626, chữ trắng

Thành phần & hành vi

6Business rules

RuleĐịnh nghĩa
% chuyên cần(Có mặt + Đi muộn) / tổng lượt đã đánh dấu × 100 — làm tròn số nguyên. Nghỉ phép không được tính là chuyên cần nhưng vẫn nằm trong mẫu số.
Buổi (session)Một ngày có ≥ 1 học sinh được đánh trạng thái. Ngày lưu rỗng không tính là buổi và bị xoá khỏi lịch sử.
Học sinh cần chú ýChuyên cần < 80% trong khoảng ngày đang lọc; sort tăng dần theo %.
Lưu (save)Merge theo từng học sinh vào dữ liệu đã có của ngày đó — không ghi đè cả ngày, để không mất record của học sinh đã rời lớp / ngoài trang danh sách.
Phạm vi báo cáoChi nhánh (school) đang chọn. Đổi chi nhánh ở school switcher để xem chi nhánh khác. (Gộp nhiều chi nhánh: Phase 3.)
NgàyKey dạng YYYY-MM-DD theo giờ địa phương (không dùng UTC/ISO — đã dính bug lệch ngày).

7Data & API — cho Developer

Hiện tại (Phase 1 — localStorage)

flyer_attendance_<classId> = {
  "2026-08-12": {                       // date key, giờ địa phương
    "<learnerProfileId>": { "status": "present|late|excused|absent",
                             "note": "…", "name": "Nguyễn Văn A" }
  }, …
}
flyer_attendance_index = {              // để báo cáo gom theo chi nhánh
  "<classId>": { "name": "KET 1", "schoolId": "…", "schoolName": "…", "demo": true? }
}

Toàn bộ đọc/ghi đi qua src/utils/attendance.ts (single source of truth) — Phase 2 chỉ cần thay ruột các hàm này bằng API call, UI giữ nguyên.

Đề xuất Phase 2 — DB

attendance_sessions   id · class_id · date (unique class_id+date) · created_by · timestamps
attendance_records    id · session_id · learner_profile_id
                      · status ENUM(present, late, excused, absent)
                      · note TEXT NULL · marked_by · timestamps

Đề xuất API (REST v3, cùng pattern openapi hiện có)

EndpointMô tả
GET /v3/teacher/classes/{classId}/attendance?date=Bản ghi 1 buổi (hoặc range from/to cho lịch sử).
PUT /v3/teacher/classes/{classId}/attendance/{date}Upsert cả buổi (merge từng học sinh, xoá record khi client gửi status null; buổi rỗng → xoá session). Idempotent.
GET /v3/teacher/schools/{schoolId}/attendance/summary?from=&to=Tổng hợp cho báo cáo: per-class stats + per-student stats (server tính theo rules mục 6).

8Edge cases đã xử lý (từ audit 26-agent)

CaseHành vi chuẩn (đã implement)
Sửa buổi cũ khi có học sinh đã rời lớpMerge-save: record của học sinh ngoài danh sách hiện tại được giữ nguyên, không bị xoá ngầm.
Đổi ngày khi chưa lưuModal xác nhận trước khi bỏ thay đổi (áp dụng cả khi nhấn chip buổi cũ từ Lịch sử).
Đánh dấu nhầmNhấn lại pill để bỏ chọn; lưu ngày trống = xoá buổi (không còn “buổi ma 0%”).
Buổi không có trạng thái nàoKhông được tính là session trong mọi thống kê.
Lọc ngày không có dữ liệuThông báo “ngoài khoảng ngày” (khác với “chưa có dữ liệu”).
Múi giờDate key theo giờ địa phương — demo seed cũng vậy, không lệch ngày/cuối tuần.
Xoá demoChỉ xoá dữ liệu demo của chi nhánh hiện tại.

Còn mở (chưa làm, cần quyết ở Phase 2): gate theo role & feature flag; nhấn dòng lớp trong báo cáo để nhảy vào lớp; phân trang lớp > 200 học sinh; đồng bộ đa thiết bị (đến khi có backend).

9Acceptance criteria (Phase 2)

  1. GV mở tab Điểm danh của lớp có N học sinh → thấy đủ N hàng, mặc định ngày hôm nay, chưa đánh dấu gì.
  2. Nhấn “Tất cả có mặt” → N pill Có mặt bật; sửa 1 học sinh thành Vắng + ghi chú; Lưu → reload trang → dữ liệu đúng như đã lưu (từ server, thấy được trên thiết bị khác).
  3. Nhấn lại pill đang chọn → bỏ đánh dấu; lưu buổi không còn đánh dấu nào → buổi biến mất khỏi Lịch sử và báo cáo.
  4. Đổi ngày khi có thay đổi chưa lưu → hiện modal xác nhận; “Ở lại” giữ nguyên, “Bỏ thay đổi” chuyển ngày.
  5. Học sinh rời lớp sau khi đã được điểm danh → sửa và lưu lại buổi cũ → record của em đó vẫn còn trong báo cáo.
  6. Báo cáo chi nhánh: % chuyên cần lớp = công thức mục 6 (viết unit test); lớp thấp nhất đứng đầu bảng; học sinh < 80% xuất hiện trong “Cần chú ý”.
  7. Lọc khoảng ngày không trùng dữ liệu → hiện thông báo “ngoài khoảng ngày”; xoá lọc → dữ liệu hiện lại.
  8. Xuất Excel ở cả 2 màn ra đúng cột, đúng ngôn ngữ đang chọn.
  9. GV không phải admin không thấy “Báo cáo điểm danh” trong menu (role gate).
  10. Toàn bộ text hiển thị đúng ở cả VI và EN.

10Ngoài phạm vi / tương lai


Prototype chạy tại môi trường dev nội bộ (branch dev, tab Điểm danh trong chi tiết lớp + menu Báo Cáo → Báo cáo điểm danh). Liên hệ Tùng để xem demo trực tiếp.