Trường Số PRD/06 Teachers and staff
Trường Số · Product Requirements · 09/2026

PRD 06 · Teachers and staff

Staff directory, teacher profiles, contracts, pay rules per session or hour, timesheets derived from attendance, and periodic evaluations. Builds on the existing TeacherProfile and school membership; HR entities are new.

Key facts, origin and limits
Status
Draft v1
Owner module
Staff (Nhân sự)
Backend
EXTEND TeacherProfile · NEW HR entities
Phase
P1 (directory, assignment) / P2 (pay, timesheets, evaluations)
Depends on
03 Classes, 04 Attendance, 10 Settings

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:
    • How the competitor's "thống kê giờ dạy giáo viên" is computed (from attendance or from timetable) and whether substitutes are handled.
    • Their "quản lý tiền lương" fields: fixed salary, per-session, allowances, deductions; whether payslips are generated.
    • Whether staff without login exist in their HR list.
    • Teacher weekly schedule screen layout ("xếp lịch giáo viên theo tuần").

Proposed design

Nine screens share the School OS shell with the Staff nav item active, moving from directory to profile, contracts, pay rules, timesheets and evaluations; the key thing to see is that payable amounts are derived from attendance data, while every exception (substitute, late cancellation, teacher flag, adjustment, locked month) appears as its own line with a reason rather than being typed in.

1 · Staff list

/staff · desktop

Staff list
All nine columns from section 5 (code, name, job title, type, branches, classes taught, contract, account, actions) with the four tabs Active · Contract expiring · No profile yet · Left and filters for branch, job title and employment type. Branch overflow shows "+2"; the contract column pairs the end date with an Expiring chip for the two contracts under 30 days (STF-03); the account column separates Activated / Invite pending / None, so the cleaner is in the directory without a login (goal 1). Default sort is by name.

2 · Staff form · step 1

/staff/new · desktop

Staff form · step 1
Two-step modal (Staff details → Login account) with every field from section 5: auto-generated NV-#### code, job title with quick add, multi-select branches, start date capped at today + 90 days. The state shown is the duplicate-phone error: the form points to the existing record and offers "Open profile" instead of creating a duplicate (STF-01, scenario 2).

3 · Staff profile · Overview

/staff/:id · desktop

Staff profile · Overview
Header with avatar, name, code, job title, status chip and branch plus the three actions Deactivate · Reset password · Edit profile; seven tabs as in section 5. The Overview tab splits personal details (partially masked ID number) from job details (pay rule summary, contract, account role); the right column holds the four RollupTeacher KPIs because this person is a teacher.

4 · Staff profile · Classes and schedule

/staff/:id · tab Lớp & lịch · desktop

Staff profile · Classes and schedule
Current classes table (name, schedule as text, students, room, branch, role, session progress) and the 14–20/09 week view fed by PRD 03. Substitute sessions are marked amber with the primary teacher named; sessions already held turn green with the attendance count, the same data the timesheet will consume.

5 · Contracts · add-contract drawer

/staff/:id · tab Hợp đồng · desktop

Contracts · add-contract drawer
The Contracts tab lists type, start, end, pay rule, document and status, with the 30-day warning right above the table. The Add contract drawer shows a validation error because the 01/10/2026 start overlaps HĐ-2025-03, still active until 15/10/2026 (rule 2); the pay rule is assigned here rather than on the staff record (STF-03, STF-04).

6 · Pay rules

/staff/pay-rules · desktop

Pay rules
Rule table per section 5: name, type Per session / Per hour / Monthly, base rate, exception count, effective from → to and the number of staff using it through contracts; the expired 2025 rule is kept. The right column opens the selected rule: class-type exceptions (IELTS Junior 350,000 ₫, a 1.4 coefficient), 50% pay for late cancellations, and "also pay the primary teacher when substituted" off by default (rule 4); the note reminds that rates are snapshotted into each timesheet line (rule 5).

7 · Monthly timesheets

/staff/timesheets?month=2026-09 · desktop

Monthly timesheets
Four KPIs for projected cost, paid sessions, timesheets awaiting confirmation and approved; month picker, branch filter and status chips Draft / Awaiting confirmation / Confirmed / Approved. The table carries all eight columns: teacher, sessions, hours, base amount, adjustments, total, status, flags. The note above states that August 2026 is approved and locked: no regeneration, and any correction goes into the next month with a reference (edge case 4).

8 · Timesheet detail for one teacher

/staff/timesheets/:id · desktop

Timesheet detail for one teacher
Banner "2 sessions changed after the timesheet was generated" with a Recalculate button (STF-05). Each line is one attendance-backed session with date, class, session name, duration, role, snapshotted rate and amount: a Substitute line paid to the substitute, a Late cancellation line at 50%, an IELTS Junior exception at 350,000 ₫, and one line flagged by the teacher. The adjustments area carries a reason and a reference to the locked August timesheet; Approve is blocked while a flag is open (STF-06, STF-07).

9 · Evaluation form

/staff/:id/evaluations/new · desktop

Evaluation form
Template and period (quarter) pickers, six criteria rows with 1–5 radios, visible weights and a weighted score per row; the 4.15 total is computed live and repeated in the top card of the right column with the five-period trend. The "Share with teacher" toggle is off: the teacher only sees the result once the manager shares it (STF-08, rule 8).

1Purpose

A center's largest cost is teaching staff, and its most frequent operational question is "who taught how many sessions this month and what do we owe them". The module keeps one staff record per person across branches, links teachers to classes and sessions, computes payable sessions from attendance already recorded in PRD 04, and stores contracts and evaluations so that the owner can decide on renewals with evidence.

Goals

Out of scope

2What exists today

CapabilityWhereStatus
School membership with roles OWNER / ADMIN / TEACHERSchoolRole enum; GET /v3/teacher/schools, GET /v3/crm/me/accessible-schoolsexisting
Teacher profile (name, phone, email, user_name)TeacherProfile schema; embedded on Tuition and Class objectsexisting
Teacher performance rollup (students, tests assigned, completion rate)GET /v3/teacher/viewer/rollup-teachers (RollupTeacher)existing
CRM sales staff (SaleProfile) with CRM roles/v3/crm/sale-profiles, /v3/crm/rolesexisting
Contracts, pay rules, timesheets, evaluations, documents, branch assignment of staffnonenew

Decision: introduce a staff entity that wraps an optional user_id, an optional teacher_profile_id and an optional sale_profile_id, so that one person shows once in the directory regardless of how many FLYER profiles they carry.

3Personas and roles

PersonaRoleNeeds
OwnerownerHeadcount and cost per branch, contract renewals, evaluation results.
Branch managerbranch_managerAssign teachers to classes, approve timesheets, run evaluations.
AccountantaccountantMonthly payroll sheet, pay rule maintenance, adjustments.
Academic adminacademic_adminFind substitute teachers, keep profiles and documents current.
TeacherteacherSee own schedule, own timesheet, own evaluation; confirm sessions.

4User stories and acceptance criteria

STF-01Create a staff record
As an academic admin I add a staff member with or without system access.
  • Given permission staff.manage
  • When I submit full name, staff code, job title, employment type, branches, start date, phone, and optionally "Tạo tài khoản đăng nhập" with role
  • Then the staff record is created; if an account was requested, an invitation is sent by email or SMS, the user is added to the school with the chosen role, and a TeacherProfile is created when the role includes teaching.
  • Given the phone already belongs to an existing staff in the tenant
  • When I submit
  • Then the form shows the existing record and offers "Mở hồ sơ" instead of creating a duplicate.
STF-02Link an existing FLYER teacher account
As an admin I link a teacher who already has a FLYER account and classes.
  • Given teachers exist in the school (from /v3/teacher/schools membership) without a staff record
  • When I open Staff → "Chưa có hồ sơ (N)"
  • Then each unlinked teacher is listed with a "Tạo hồ sơ" action that pre-fills the form from TeacherProfile.
STF-03Record a contract
As an accountant I attach a contract with dates and pay rule.
  • Given a staff record
  • When I add contract type, start, end, pay rule, signed document
  • Then the contract is saved; contracts cannot overlap in time for the same staff; 30 days before end the staff row shows Sắp hết hạn and a notification goes to branch_manager and accountant.
STF-04Define pay rules
As an accountant I define how a teacher is paid.
  • Given permission staff.pay_rules
  • When I create a rule of type per_session, per_hour, or monthly with rate, optional per-class-type overrides, and effective dates
  • Then the rule applies to sessions whose date falls in the effective range; a session uses the most specific rule (class-type override > default).
STF-05Generate a monthly timesheet
As an accountant I generate the timesheet for a month.
  • Given sessions in PRD 04 with status held and a recorded teacher (main or substitute)
  • When I click "Tạo bảng công tháng 9/2026"
  • Then one timesheet per teacher is created with one line per held session (date, class, duration, rate, amount) plus lines for cancelled-late sessions if the pay rule pays them; totals are computed; status draft.
  • Given a session was edited in PRD 04 after the timesheet was generated
  • When the timesheet is viewed
  • Then a banner "Có 2 buổi thay đổi sau khi tạo bảng công" offers "Tính lại".
STF-06Teacher confirms timesheet
As a teacher I check my sessions before payroll.
  • Given a draft timesheet for me
  • When I open Bảng công of the month
  • Then I see every line, can flag a line with a comment ("tôi dạy thay buổi này"), and press "Xác nhận"; flagged lines go to branch_manager for resolution; the timesheet moves to confirmed only when no flags remain open.
STF-07Approve and export payroll
As a branch manager I approve; as an accountant I export.
  • Given timesheets in confirmed
  • When the branch manager approves
  • Then status approved, lines locked; the accountant exports XLSX with one row per teacher (code, name, sessions, hours, base amount, adjustments, total) and per-session detail sheet; the export is logged.
STF-08Evaluate a teacher
As a branch manager I run a periodic evaluation.
  • Given an evaluation template (criteria with weights, 1–5 scale)
  • When I score a teacher for a period and add comments
  • Then a weighted score is stored; the teacher sees the result after the manager marks it "Đã chia sẻ"; the profile shows a trend across periods alongside the existing performance rollup (students, completion rate).
STF-09Off-board
As an admin I end employment.
  • Given a staff record with an active account
  • When I set status Đã nghỉ with end date
  • Then future class assignments are cleared and listed for reassignment, the account is deactivated at end of day on the end date, history remains visible.

5Screens

Staff list /staff

Tabs: Đang làm việc (N) · Sắp hết hạn HĐ (N) · Chưa có hồ sơ (N) · Đã nghỉ. Filters: search (name, code, phone), branch, job title, employment type. Actions: + Thêm nhân sự, Xuất Excel.

ColumnNotes
Mã NVstaff.code
Họ tênavatar + name; link to profile
Chức danhjob_title
Loạichip: Toàn thời gian / Bán thời gian / Cộng tác viên
Chi nhánhchips, "+2" overflow
Lớp đang dạycount (teachers only), link
Hợp đồngend date + state chip
Tài khoảnchip: Đã kích hoạt / Chờ mời / Không có
Thao táckebab: Sửa, Vô hiệu hoá, Gửi lại lời mời

Empty state: "Chưa có nhân sự. Thêm giáo viên và nhân viên để phân lớp và tính công." Sort default by name (vi collation).

Staff profile /staff/:id

Header: avatar, name, code, job title, status chip, branches, buttons Sửa · Tạo tài khoản/Đặt lại mật khẩu · Vô hiệu hoá. Tabs: Tổng quan · Lớp & lịch · Hợp đồng · Bảng công · Đánh giá · Tài liệu · Nhật ký.

  • Tổng quan: two columns: personal info (birthday, gender, ID number masked, address, emergency contact) and work info (start date, employment type, pay rule summary, account role). Right rail: KPI from RollupTeacher when the staff is a teacher: students, assigned tests, completion rate, growth rate.
  • Lớp & lịch: table of current classes (name, schedule text, students, room) and a week view of sessions (from PRD 03) with substitute markers.
  • Hợp đồng: list with type, start, end, pay rule, document, status; + Thêm hợp đồng.
  • Bảng công: month selector; table of lines; totals; status; teacher sees own only.
  • Đánh giá: list of evaluations with score and trend sparkline; + Đánh giá mới for managers.
  • Tài liệu: uploads with type (CCCD, bằng cấp, chứng chỉ, hợp đồng, khác), expiry date, expiring soon chip.
  • Nhật ký: audit entries for this staff.
Staff form (modal, 2 steps) /staff/new · /staff/:id/edit
FieldTypeValidation
Họ và têntextrequired, 2–80
Mã nhân viêntextauto NV-####, editable, unique
Chức danhselect (tenant list) + inline addrequired
Loại hìnhenum full_time / part_time / contractorrequired
Chi nhánhmulti-select≥ 1
Ngày bắt đầudaterequired, ≤ today + 90 d
Số điện thoạitelrequired, VN format, duplicate check
Emailemailrequired if creating account
Ngày sinh, Giới tính, CCCD, Địa chỉoptional; CCCD 12 digits
Liên hệ khẩn cấpname + phoneoptional
Step 2: Tạo tài khoản đăng nhậptoggleif on: role (from PRD 10 roles), branches scope, "Là giáo viên" flag creating TeacherProfile
Pay rules /staff/pay-rules

Table: name, type (Theo buổi / Theo giờ / Theo tháng), base rate, overrides count, effective from/to, staff count using it. Form: name, type, rate (VND), overrides table (class type or course → rate), pay cancelled-late sessions (toggle, percent), effective dates. Assigning a rule to staff happens on the contract.

Timesheets /staff/timesheets?month=2026-09

Month picker, branch filter, status chips (Nháp / Chờ xác nhận / Đã xác nhận / Đã duyệt). Table: teacher, sessions, hours, base amount, adjustments, total, status, flags count. Row opens the timesheet detail: line table (date, class, session title, duration, teacher role main/substitute, rate, amount, flag), adjustments section (+ Thêm điều chỉnh: amount signed, reason), buttons per status: Tính lại · Gửi GV xác nhận · Duyệt · Xuất Excel.

Evaluation form /staff/:id/evaluations/new

Template selector, period (month or quarter), criteria rows with 1–5 radio and weight display, overall comment, "Chia sẻ với giáo viên" toggle. Score computed live in the header.

6Business rules

  1. A staff record belongs to one tenant and one or more branches; class assignment is limited to the staff's branches.
  2. A person may have several contracts over time but never two active contracts overlapping.
  3. Timesheet lines come only from PRD 04 sessions with status held or cancelled_late (when the rule pays them); sessions with status planned or cancelled never pay.
  4. The substitute teacher is paid for the session, not the main teacher, unless the pay rule has "trả cả GV chính khi có dạy thay" enabled (default off).
  5. Rate snapshot: each timesheet line stores the rate used; later rule edits do not change approved timesheets.
  6. Adjustments require a reason and permission staff.timesheets.adjust; they are audited.
  7. Deactivating an account does not delete the staff record; the code remains reserved.
  8. Evaluation results are visible to the teacher only after sharing; owners and branch managers of the staff's branches always see them.

7Data model

EntityFieldsNotes
staffid uuid; tenant_id; code; full_name; job_title_id; employment_type enum(full_time, part_time, contractor); status enum(active, on_leave, terminated); user_id int64 null; teacher_profile_id int64 null; sale_profile_id int64 null; phone; email; birthday; gender; id_number (encrypted); address; emergency_name; emergency_phone; start_date; end_date; branch_ids uuid[]; created_at; updated_atunique (tenant_id, code); unique (tenant_id, phone)
job_titleid; tenant_id; name; is_teaching bool; positiondefaults: Giáo viên, Trợ giảng, Giáo vụ, Tư vấn, Kế toán, Quản lý
staff_contractid; staff_id; type enum(labour, service, probation); start_date; end_date null; pay_rule_id; document_file_id; status enum(active, expired, terminated); noteexclusion constraint on overlapping [start, end]
pay_ruleid; tenant_id; name; type enum(per_session, per_hour, monthly); base_rate numeric; pay_cancelled_late bool; cancelled_late_pct int; pay_main_when_substituted bool; effective_from; effective_to
pay_rule_overrideid; pay_rule_id; class_type text null; course_id uuid null; rate numericmost specific wins
timesheetid; tenant_id; branch_id; staff_id; year; month; status enum(draft, pending_confirm, confirmed, approved); base_amount; adjustment_amount; total_amount; generated_at; confirmed_at; approved_by; approved_atunique (staff_id, year, month)
timesheet_lineid; timesheet_id; session_id (PRD 04); date; class_id; title; duration_min; role enum(main, substitute); rate; amount; flag_comment null; flag_status enum(none, open, resolved)session_id unique per timesheet
timesheet_adjustmentid; timesheet_id; amount signed; reason; created_by; created_at
evaluation_templateid; tenant_id; name; criteria jsonb [{key, label, weight}]
evaluationid; staff_id; template_id; period_start; period_end; scores jsonb {key: 1..5}; weighted_score numeric; comment; evaluator_id; shared_at null; created_at
staff_documentid; staff_id; type enum(id_card, degree, certificate, contract, other); file_id; expires_on null; created_at

8API contract

MethodPathRequest / notesStatus
GET/v3/teacher/schoolsmembership and roles of the current userexisting
GET/v3/teacher/viewer/rollup-teachersteacher KPIs for the profile railexisting
GET/v3/school/staffq, branch_id, job_title_id, employment_type, status, unlinked=true, page, limitnew
POST/v3/school/staff{full_name, code?, job_title_id, employment_type, branch_ids, start_date, phone, email?, ..., account?: {role_id, branch_ids, is_teacher}}new
GET / PATCH/v3/school/staff/{id}detail includes contracts, current pay rule, account statusnew
POST/v3/school/staff/{id}/account{role_id, branch_ids, is_teacher} creates user invite + membership + TeacherProfilenew
POST/v3/school/staff/{id}/link{user_id | teacher_profile_id | sale_profile_id}new
POST/v3/school/staff/{id}/terminate{end_date, reason}new
GET / POST/v3/school/staff/{id}/contracts{type, start_date, end_date?, pay_rule_id, document_file_id?}new
PATCH/v3/school/staff/contracts/{id}new
GET / POST / PATCH/v3/school/pay-rules[/{id}]with overrides[]new
POST/v3/school/timesheets/generate{year, month, branch_id?, staff_ids?} idempotent: regenerates drafts onlynew
GET/v3/school/timesheetsyear, month, branch_id, status, staff_idnew
GET/v3/school/timesheets/{id}lines, adjustments, flagsnew
POST/v3/school/timesheets/{id}/submit | confirm | approve | recalculatestate transitions; 409 on invalid transitionnew
POST/v3/school/timesheets/{id}/lines/{line_id}/flag{comment}; resolve via PATCH flag_statusnew
POST/v3/school/timesheets/{id}/adjustments{amount, reason}new
GET/v3/school/timesheets/exportyear, month, branch_id, format=xlsxnew
GET / POST/v3/school/evaluation-templatesnew
GET / POST/v3/school/staff/{id}/evaluations{template_id, period_start, period_end, scores, comment, share}new
GET / POST / DELETE/v3/school/staff/{id}/documents{type, file_id, expires_on}new

9Permissions

Codeownerbranch_manageraccountantacademic_adminteacher
staff.viewyesbranchyesbranchself + colleagues' name/title only
staff.manageyesbranchnobranchno
staff.accounts (invite, deactivate)yesbranchnonono
staff.contractsyesviewyesnoself view
staff.pay_rulesyesnoyesnono
staff.timesheets.viewyesbranchyesnoself
staff.timesheets.generateyesnoyesnono
staff.timesheets.approveyesbranchnonono
staff.timesheets.adjustyesnoyesnono
staff.evaluationsyesbranchnonoself view when shared
staff.exportyesnoyesnono

10Analytics events

EventProperties
staff_createdemployment_type, with_account, is_teacher
staff_linkedlink_type
contract_createdtype, has_document, pay_rule_type
contract_expiring_notifieddays_before
timesheet_generatedmonth, staff_count, line_count
timesheet_flaggedline_count
timesheet_approvedtotal_amount_bucket
payroll_exportedstaff_count
evaluation_sharedscore_bucket

11Edge cases

12Open questions

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