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

PRD 07 · Parent communication

The contact book (sổ liên lạc): transactional messages for attendance, fees and schedule changes, announcements to classes and branches, progress reports built from FLYER learning data, Zalo OA and ZNS configuration, and a mobile-first parent portal with OTP login.

Key facts, origin and limits
Status
Draft v1
Owner module
Sổ liên lạc
Backend
EXISTING notify channels, parent reports, parent tuitions · NEW messaging, templates, portal, consent
Phase
P1 transactional + Zalo · P2 portal, announcements, progress reports
Depends on
02 Students, 04 Attendance, 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 Zalo configuration fields (app id, secret, OA token) and which events send ZNS.
    • The list of their ZNS template types and parameters (their guide references a spreadsheet).
    • What the parent app shows (sổ liên lạc điện tử): scores, attendance, fees, chat.
    • Whether teachers can message parents directly.
    • How score entry ("nhập điểm thi") reaches parents (app, email) and whether attachments are supported.

Proposed design

The layout pairs a staff contact-book page (five tabs, KPI tiles and a message log by channel, status and cost) with a mobile-first parent portal; the most important thing to see is that every message shows its channel, delivery outcome and ZNS cost.

1 · Contact book · Sent messages

/communication · desktop

Contact book, message log tab
Four KPI tiles (sent today, failed today, unreachable guardians, ZNS spend this month against the cap), filters for date, type, channel, status, class and branch, and the log table with every specified column: Thời gian, Loại, Học viên · Phụ huynh, Kênh, Trạng thái, Nội dung, Chi phí, Thao tác. The statuses delivered, sent, failed (with Resend), skipped with reason and held by quiet hours illustrate COM-02 and the channel policy in section 3; a receipt sent on two channels produces two rows.

2 · Announcement composer

/communication/announcements/new · desktop

Announcement composer drawer
A drawer over the Announcements tab: title with character count, body with placeholder chips, attachments, audience (branch, several classes, guardian flag), channels with reach per channel and the cost if ZNS is ticked, scheduling and the Allow replies toggle for Zalo OA. The right pane previews the message as the guardian sees it; the amber warning lists the two unreachable guardians, who still get the portal inbox copy (COM-03, rule 5).

3 · Progress reports · Starters 4A batch

/communication/reports · desktop

Progress report batch
The batch table lists each student with attendance rate, assignments completed, accuracy, study time, package balance, an inline-editable teacher comment and status; the right rail previews the report page with the sections Attendance, Learning on FLYER, Teacher comment, Fees and balance, Next month schedule. The warning explains that 5 reports without a comment cannot be published unless the academic admin overrides (rule 7); the primary button publishes only the eligible reports, and a published report shows its version (COM-05).

4 · Templates and Zalo OA connection

/settings/templates · desktop

ZNS template registry
The OA connection card with status and this month's ZNS spend against the cap (rule 4), and the template table with message type, channel, Zalo template id, approval status, parameters, unit price and last edit. The rejected Fee reminder template is being edited in the right rail: body with placeholder chips, test send to a phone number, resubmit to Zalo; the red callout states which fallback channel is in use meanwhile (COM-01, rule 3).

5–8 · Parent portal (mobile)

/parent/login · /parent · /parent/attendance · /parent/tuition · mobile

OTP login
Parent home
Schedule and attendance
Invoice with QR
The parent portal runs inside the Zalo in-app browser with 16 px base text, a bottom tab bar and no sidebar. OTP login with a 60-second resend countdown and the error shown when the phone is not registered (COM-06); the home page has the children switcher across two branches, next-session card, unpaid invoice and latest report; the attendance month calendar uses status chips, an unexcused absence offers a make-up request and a room change is highlighted (COM-08); the invoice shows lines, amount due, due date and the bank QR with the transfer memo, package balance and receipts (COM-07).

1Purpose

Parents judge a center by how quickly and clearly it communicates. The contact book turns operational events into messages a guardian receives where they already are (Zalo), gives staff one place to send announcements and progress reports, and offers a portal for timetable, attendance, invoices with QR payment and learning reports. The backend already sends tuition and lesson reminders through app, email and Zalo with per-channel outcomes, and exposes parent-facing tuition and learning report endpoints. This PRD adds the messaging layer, templates, consent-aware routing, announcements, progress reports and the portal.

In scope

Out of scope

2What exists today

CapabilityEndpoint / schemaNote
Tuition notify with channels app, email, zalo and per-channel outcome with skip reasonPOST .../tuitions/{id}/notify, /tuitions/notify, TuitionNotifyOutcome, ChannelOutcome, SkipReasonReuse the channel abstraction
Class lesson reminderPOST /v3/teacher/class-schedules/{id}/remind, RemindLessonResultFLYER homework reminder
Notification config per featureNotificationConfig {feature, notify_email, notify_app, notify_zalo} in school configExtend with new features
Parent tuition viewsGET /v3/parent/tuitions, /{id}, /overviewPortal invoices
Learner report and weekly reportGET /v3/parent/learner/{id}/report, /weekly-report (assignments, self-study, learning time, skills, activity)Portal learning tab and progress report source
Exam notifyPOST /v3/teacher/exams/{exam_id}/notifykeep
Parent user with FLYER app (push)StudentParent, parent appin-app channel

3Channel policy

ChannelRequiresCostUsed forFallback order
Zalo ZNSGuardian phone with Zalo; tenant Zalo OA verified; approved template; consent type zaloPer message (about 200–800 VND by template type); shown in settingsTransactional: attendance absent/late, invoice, receipt, reminder, schedule change, credentials1
Zalo OA messageGuardian followed the OA and interacted within 48 hFreeAnnouncements, replies, progress report link2 for non-transactional
In-app pushGuardian has FLYER parent app linked to the learnerFreeAll2 for transactional
EmailGuardian emailFreeReceipts, reports, announcements3
Portal inboxPortal accountFreeEverything; always writtenalways

Every message is written to the portal inbox and then delivered through the first available channel according to the tenant policy per message type; a message type may be configured to send on all available channels (e.g. receipt). Quiet hours 21:30–07:00 hold non-urgent messages. Do-not-call on a lead (PRD 01) does not apply to a guardian of an enrolled student; consent does.

4User stories and acceptance criteria

COM-01Configure Zalo OA and ZNS templates
As an owner I connect our Zalo OA.
  • Given Settings → Tích hợp → Zalo
  • When I complete the OAuth flow (or paste app id, secret, OA id, access and refresh tokens as the current Zalo flow requires) and the connection test passes
  • Then the OA appears as connected with name and avatar; the ZNS template registry lists FLYER default templates (attendance absent, late, invoice, receipt, reminder, schedule change, credentials, welcome) with their Zalo template ids to be registered per OA; each template row shows status (Chờ duyệt / Đã duyệt / Từ chối) and parameters.
COM-02Transactional message on event
As the system I deliver event messages reliably.
  • Given an event (attendance saved with absent, payment recorded, session cancelled ...)
  • When the event is published
  • Then a message per guardian with the relevant flag (primary contact for attendance and schedule; payer for money) is created, rendered from the template with parameters, delivered per channel policy, and its status (queued, sent, delivered, failed, skipped with reason) is visible on the source record and in the message log; retries 3 times over 15 minutes on transient failures.
COM-03Send an announcement
As an academic admin I inform a class.
  • Given permission communication.announce
  • When I compose title, body (plain text with placeholders {ten_hoc_vien}, {lop}), attachments (images, PDF), audience (branch, classes, students, guardians with a flag), channels, send now or schedule
  • Then recipients are previewed with counts per channel and unreachable guardians listed; the announcement is delivered, and a read/delivered count updates; announcements appear in the portal and in the class page.
COM-04Teacher writes a session note to parents
As a teacher I share what we did today.
  • Given the attendance sheet after save
  • When I write a short class summary and choose "Gửi phụ huynh"
  • Then the summary goes as an OA message or push to guardians of present and absent students with the student's status line; teachers cannot send free-text ZNS (paid templates are admin-only).
COM-05Progress report
As an academic admin I issue monthly reports.
  • Given a class and a period
  • When I generate reports
  • Then one draft per student is built from attendance (rate, absences), FLYER data (assignments completed, accuracy per skill, study time, latest exam scores), package balance and a teacher comment field; the teacher fills comments in a list view; the admin publishes; guardians receive a link (portal) and PDF; published reports are immutable, corrections create a new version.
COM-06Parent logs in and sees children
As a guardian I use the portal without a password.
  • Given my phone is on a student's guardian record
  • When I open the portal link from a Zalo message and enter the OTP
  • Then I see all children linked to my phone across branches of the tenant, with tabs Lịch học, Điểm danh, Học phí, Kết quả, Thông báo, Hồ sơ; sessions persist 30 days on the device.
COM-07Pay from the portal
As a guardian I pay an invoice by QR.
  • Given an unpaid invoice
  • When I open it
  • Then I see lines, total, due date and a bank QR (existing payment QR) with the payment code; after the transfer is matched the status updates and the receipt PDF appears.
COM-08Request an excuse or a make-up
As a guardian I tell the center my child will be absent.
  • Given upcoming sessions
  • When I submit "Xin phép nghỉ" with session and reason, or "Đăng ký học bù" for an excused absence
  • Then a request is created for the desk (today plan and Điểm danh → Yêu cầu), the excuse is applied when accepted (PRD 04), and I receive confirmation.
COM-09Manage consent and data
As a guardian I control my data.
  • Given Hồ sơ in the portal
  • When I toggle consents (Zalo, email, marketing, photo) or request a correction
  • Then consent records are updated with timestamp and channel (PRD 12), a correction request becomes a task for academic admin, and a revoked channel is not used from the next message.

5Screens

Sổ liên lạc (staff) /communication

Tabs: Thông báo (announcements) · Tin đã gửi (message log) · Báo cáo học tập (progress reports) · Yêu cầu từ PH (requests) · Mẫu tin (templates). KPI tiles: sent today, failed today, unreachable guardians, ZNS spend this month.

Message log columns

ColumnNotes
Thời giancreated_at
Loạimessage type chip
Học viên · Phụ huynhlinks
KênhZalo ZNS / OA / App / Email / Portal
Trạng tháiĐã gửi (ok) · Đã nhận (ok) · Thất bại (abs) · Bỏ qua: lý do (grey) · Chờ (late)
Nội dungrendered preview
Thao tácGửi lại · Xem

Filters: date, type, channel, status, class, branch. Empty: "Chưa có tin nào được gửi."

Announcement composer (drawer) /communication/announcements/new

Fields: Tiêu đề (required, 120), Nội dung (required, 2,000, placeholders), Tệp đính kèm (up to 5, 10 MB), Đối tượng (branch, classes multi, students multi, guardian flag: all / primary contact / payer), Kênh (checkboxes with reach preview), Thời điểm (now / schedule date-time), "Cho phép phản hồi" toggle (OA only). Preview panel shows the message as the guardian sees it. Validation: at least one recipient reachable, else warning with list.

Progress reports /communication/reports

List of report batches (class, period, students, drafted, commented, published). Batch view: table of students with attendance rate, assignments done, avg accuracy, study time, package balance, teacher comment (inline edit), status; buttons Gửi GV nhận xét, Xuất bản, Xuất PDF. Report page template: header with center logo, student, class, period; sections Chuyên cần, Học tập trên FLYER, Nhận xét của giáo viên, Học phí và số dư, Lịch tháng tới.

Templates /settings/templates

Table: message type, channel, Zalo template id, status, parameters, last edited. Editor: subject (email), body with placeholder chips ({ten_hoc_vien}, {lop}, {ngay}, {gio}, {so_tien}, {ma_thanh_toan}, {link}), test send to my phone.

Parent portal (mobile-first) /parent/*
  • /parent/login: phone, OTP; error states (phone not found: "Số điện thoại chưa được đăng ký với trung tâm. Vui lòng liên hệ ..."), resend cooldown 60 s.
  • /parent: child switcher (avatar chips), next session card, unpaid invoices card, latest report card, announcements list.
  • /parent/schedule: week list of sessions with room and teacher; changes highlighted.
  • /parent/attendance: month calendar with chips; tap for note; buttons Xin phép nghỉ, Đăng ký học bù.
  • /parent/tuition: invoices (existing parent tuitions), package balance, QR, receipts.
  • /parent/results: FLYER report (existing), progress reports list.
  • /parent/inbox: all messages; unread badge.
  • /parent/profile: children info, guardians, consents, correction request, FLYER login credentials resend.

Design: Cloud tokens, 16 px base on mobile, bottom tab bar, no sidebar; works inside the Zalo in-app browser.

6Business rules

  1. Transactional messages go to guardians by flag: attendance and schedule to primary contact; money to payer; credentials to primary contact; announcements to the audience selection.
  2. A channel is used only with the matching consent; the portal inbox needs no consent (service consent covers it).
  3. ZNS templates are per message type and must be approved by Zalo before use; until approved, the fallback channel is used and the settings page shows the gap.
  4. ZNS spend is tracked per tenant and shown monthly; an optional monthly cap pauses ZNS and falls back.
  5. Announcements to more than 500 recipients require communication.announce_bulk.
  6. Teachers may send OA or push messages to their own classes; only staff with communication.announce use ZNS templates beyond attendance.
  7. Progress reports are published per student; a report cannot be published without a teacher comment unless the admin overrides.
  8. Portal sessions are per device, 30 days, revocable from the guardian's profile by staff.
  9. All outbound messages are logged with rendered content for 12 months.

7Data model

EntityFieldsNotes
message_templateid; tenant_id; type enum(attendance_absent, attendance_late, attendance_present, invoice_created, receipt, fee_reminder, low_balance, schedule_change, session_cancelled, trial_confirm, welcome, credentials, report_published, announcement, custom); channel enum(zns, oa, push, email); zalo_template_id; body; subject; params jsonb; status enum(draft, pending, approved, rejected); updated_atnew
messageid uuid; tenant_id; school_id; type; guardian_id; student_id null; source_type, source_id (attendance_record, tuition, payment, session, announcement, report); channel; rendered_body; status enum(queued, sent, delivered, read, failed, skipped); skip_reason; provider_msg_id; cost_vnd; attempts; created_at; sent_at; delivered_at; read_atnew; index (guardian_id, created_at), (source_type, source_id)
announcementid; tenant_id; school_id null; title; body; attachments file_id[]; audience jsonb {school_ids, class_ids, student_ids, guardian_flag}; channels[]; allow_reply; scheduled_at; sent_at; created_by; stats jsonb {recipients, sent, delivered, read, failed}new
progress_report_batch, progress_reportbatch: class_id, period_start, period_end, status, created_by; report: batch_id, student_id, data jsonb (attendance, flyer, balance), teacher_comment, status enum(draft, commented, published), version, pdf_file_id, published_atnew
parent_requestid; guardian_id; student_id; type enum(excuse, makeup, correction, other); payload jsonb; status enum(open, accepted, rejected); handled_by; note; created_atnew
zalo_oa_connectiontenant_id; oa_id; oa_name; app_id; tokens (encrypted); connected_at; status; last_errornew
portal_sessionid; guardian_id; device; created_at; expires_at; revoked_atnew
notification_policy (school config)per type: channels[], quiet_hours_apply bool; zns_monthly_capextend NotificationConfig

8API contract

MethodPathRequest / responseStatus
GET/PUT/v3/teacher/school-confignotification policy block extended per typeextend
POST/v3/teacher/school-finance/tuitions/{id}/notify, /notifyreuse; routed through the message layerexisting
GET/POST/PATCH/v3/school/message-templates[/{id}]; POST /{id}/test{to_phone}new
GET/v3/school/messagesfrom, to, type, channel, status, student_id, class_id → Message[]new
POST/v3/school/messages/{id}/resendnew
POST/v3/school/messages/send{type: custom, guardian_ids | student_ids, body, channels} ad-hoc (teacher class note)new
GET/POST/v3/school/announcements; POST /{id}/preview; POST /{id}/send; POST /{id}/cancelaudience resolution returns counts and unreachable listnew
POST/v3/school/progress-reports/batches{class_id, period_start, period_end} → batch with draftsnew
GET/PATCH/v3/school/progress-reports/{id}{teacher_comment}new
POST/v3/school/progress-reports/batches/{id}/publish{student_ids?} → messagesnew
GET/v3/school/progress-reports/{id}.pdfnew
GET/POST/PATCH/v3/school/parent-requests[/{id}]accept/reject with notenew
GET/POST/DELETE/v3/school/integrations/zaloconnect, status, disconnect; GET /zalo/templates syncs statusesnew
POST/v3/parent/auth/otp, /v3/parent/auth/verify{phone} / {phone, code} → session tokennew
GET/v3/parent/meguardian + children[] across schoolsnew
GET/v3/parent/learner/{id}/schedule, /attendance, /reports, /announcementsnew
GET/v3/parent/tuitions, /{id}, /overview; /v3/parent/learner/{id}/report, /weekly-reportunchanged; add receipts and packagesexisting / extend
GET/POST/v3/parent/inbox; POST /{id}/readnew
POST/v3/parent/requests{student_id, type, payload}new
GET/PUT/v3/parent/consentsnew
POST/v3/webhooks/zalodelivery and read events, OA user messages (stored for P3 inbox)new

9Permissions

Codeownerbranch_manageracademic_adminaccountantadmissionsteacherparent
communication.view_logallbranchbranchfinance typesown leadsown classesown inbox
communication.announceyesyesyesnonoown classes (OA/push)no
communication.announce_bulkyesyesnonononono
communication.templatesyesnononononono
communication.reports (progress)yesyesyesnonocomment own classesread own
communication.requestsyesyesyesnononocreate
integrations.zaloyesnononononono

10Analytics events

EventProperties
message_sent / message_failed / message_skippedtype, channel, reason, cost
announcement_sentaudience_size, channels, scheduled
progress_report_publishedclass_level, students, with_comment_rate
parent_loginvia (zalo_link|direct), children_count
parent_invoice_viewed / parent_qr_shown
parent_request_createdtype
consent_changedtype, granted
zalo_connected / zalo_template_approved

11Edge cases

12Open questions

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