PRD 12 · Non-functional requirements
Performance, security, personal-data compliance under Decree 13/2023/ND-CP, role-based access, audit, backup, localisation and accessibility. These requirements apply to every module and are acceptance criteria for release.
Key facts, origin and limits
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-frontendcodebase and the staging/v3OpenAPI specification (182 paths), as of 10/09/2026.
Limits
- Verification pending in-app survey, because automated login to the reference product was not authorised in the drafting session:
- How the competitor handles per-branch data visibility for a user with several branches (switcher vs merged view).
- Whether their audit trail is visible to the tenant or only to their support.
- What they present as "daily backup" to the customer (self-service restore or ticket).
- Session timeout behaviour and whether concurrent logins are limited by licence tier.
Proposed design
This PRD has no screens of its own: it is shared platform material. The design for each feature sits in the "Proposed design" section that opens every module PRD (00 to 10 and 13 to 19).
1Purpose and scope
Trường Số handles children's personal data, guardians' phone numbers and money. This document fixes the minimum bar for how the product behaves under load, how it protects data, and how it proves who did what. It covers the web app (Next.js 16 on Cloudflare Pages / Node), the FLYER Rust API (/v3), the v2 GraphQL gateway, the upload service, and the notification integrations (Zalo OA / ZNS, email, in-app). It does not cover the FLYER learner apps (exam.flyer.us, study.flyer.us) beyond the identity boundary.
2Performance and capacity
| Metric | Target | Measured how |
|---|---|---|
| Tenant size supported | Up to 5 branches, 3,000 active students, 300 classes, 150 staff per tenant without degraded UI | Seeded staging tenant "Big Center" |
| List pages (students, tuition, attendance) | Time to interactive ≤ 2.0 s on a 2023 mid-range laptop over 4G; p95 API ≤ 400 ms for 50-row page | Playwright trace + API p95 from gateway logs |
| Dashboard (PRD 09) | p95 ≤ 1.5 s with 12 widgets; widgets load independently, each with its own skeleton | Same |
| Attendance marking (PRD 04) | Save a 30-student session ≤ 300 ms p95; optimistic UI, retry on failure | API p95 |
| Excel import (PRD 11) | 5,000 rows validated in ≤ 60 s; job runs asynchronously with progress; UI never blocks | Existing /v3/crm/imports job pattern |
| Timetable generation (PRD 03) | Generate 200 sessions for a class ≤ 2 s; conflict check for a branch-week ≤ 500 ms | API timing |
| Bulk notification (PRD 07) | 1,000 ZNS messages queued in ≤ 10 s; delivery is asynchronous and reported per recipient | Queue metrics |
| Exports | CSV/XLSX up to 50,000 rows generated server-side and delivered as a download link within 60 s | Job status |
| Availability | 99.5% monthly for the web app and API during 06:00–23:00 ICT; maintenance windows announced 48 h ahead in-app | Uptime monitor on /api/health |
| Bundle | Initial JS for the dashboard route ≤ 350 KB gzipped; route-level code splitting per module | next build output |
Design rules
- Every list endpoint is paginated (
page,limit≤ 200) and server-filtered; no client-side filtering of full datasets. - Aggregates that are read often (on-hand stock, student balance, attendance rate) are stored as maintained summaries, not computed on every read.
- Long operations (import, export, bulk notify, timetable generation) are jobs with status polling; the pattern already exists for
ImportJob. - ISR/caching is not used for tenant data; all tenant reads are per-request with the auth token.
3Security
Authentication
- Staff sign in with the FLYER account (email or phone + password, OTP for phone) as today; session token is sent as the existing
Auth Tokenheader through the app's/api/v3proxy so the token never reaches third-party origins. - Tokens expire after 30 days of inactivity; changing password revokes all other sessions.
- Owner and accountant roles must enable a second factor (OTP by phone or TOTP) before accessing Finance exports and Settings → Users. Phase 2.
- Parents authenticate in the parent portal by phone OTP only; no password. Each OTP is valid 5 minutes, 5 attempts.
Authorisation
- Every request is scoped by tenant (
school_id/center_id) resolved from the token, never from a client-supplied id. Cross-tenant ids return 404, not 403. - Permission codes (PRD 10) are checked server-side per endpoint; the UI hides controls but never relies on hiding.
- Branch scope: users carry a list of branch ids; data rows carry
branch_id; list endpoints apply the intersection automatically. - Teachers see only their own classes and the students in them; parents see only linked learners.
Application security
- OWASP ASVS level 2 as the checklist. Input validation on the API with typed schemas (already OpenAPI-driven).
- File uploads: allow-list of MIME types (pdf, jpg, png, xlsx, docx), 20 MB per file, virus scan before serving, private bucket with signed URLs valid 15 minutes.
- Rate limits: 60 write requests/minute per user, 10 OTP requests/hour per phone, 5 login failures then 15-minute lock.
- Secrets (Zalo OA tokens, SePay keys, e-invoice provider credentials) stored encrypted at rest per tenant; never returned to the client after save (masked as last 4 characters).
- CSP, HSTS, SameSite=Lax cookies, no inline third-party scripts on authenticated pages.
- Dependencies scanned weekly; critical CVEs patched within 7 days.
4Personal data protection (Decree 13/2023/ND-CP)
Trường Số processes basic personal data (name, birthday, phone, address) and sensitive data of children under 16 (health notes, if entered). The center is the data controller; FLYER is the processor. The product must make the controller's obligations easy to meet.
| Obligation | Product requirement | Module |
|---|---|---|
| Consent of the data subject or guardian (Art. 11) | Enrollment form and parent portal onboarding capture a consent record (text version id, timestamp, channel, guardian identity). Consent text is tenant-editable with a FLYER default. Withdrawal is possible from the parent portal and by staff. | 02, 07 |
| Children's data (Art. 20) | Student record stores guardian consent before any notification is sent to the child directly; the product never messages a student under 16 directly, only the guardian. | 02, 07 |
| Purpose limitation | Data fields are grouped by purpose in the schema documentation (PRD 11) and the consent text lists the purposes: enrollment, attendance, billing, learning reports, marketing (opt-in separately). | 11 |
| Right to access and correction (Art. 9) | Parent portal shows all stored data on the learner and guardian and allows correction requests, which land as a task for academic admin. | 07 |
| Right to deletion | Staff can anonymise a student and guardian (name to "Học viên đã xoá", phone/email nulled, documents deleted) while keeping finance records for the statutory 10 years. Action requires students.anonymise and is logged. | 02, 10 |
| Processing record and impact assessment (Art. 24) | FLYER maintains the processing record; the tenant admin can download a pre-filled "Hồ sơ đánh giá tác động" template from Settings. | 10 |
| Breach notification (72 h) | Incident runbook with tenant notification email template; audit log supports scoping which tenants were affected. | Platform |
| Cross-border transfer | Primary storage in ap-southeast-1 (Singapore) is a cross-border transfer; the tenant contract and consent text disclose it. Roadmap: Vietnam region option for enterprise tenants. | Platform |
| Data retention | Leads (PRD 01) not converted within 24 months are anonymised automatically; attendance and learning data retained while the student is active plus 5 years; finance documents 10 years. | 01, 05 |
Marketing consent is separate from service consent. Lead capture forms that feed PRD 01 must have an explicit marketing tick box; the lead record stores marketing_consent_at.
5Role-based access control
The full matrix lives in PRD 10. The platform-level rules are:
- Permissions are strings
<module>.<action>, served byGET /v3/crm/permissions-catalog(EXISTING for CRM; extended with the new modules). Roles are tenant-defined bundles; FLYER ships defaults that cannot be deleted but can be cloned. - Effective permissions = union of role permissions, intersected with branch scope and with record ownership rules (teacher-own-class, sales-own-lead).
- The
ownerrole cannot be removed from the last owner; ownership transfer is an explicit action with email confirmation. - Elevation events (role change, permission edit, branch scope change) are audited and the affected user is notified in-app.
6Audit log
The CRM already exposes GET /v3/crm/customers/{id}/audit-logs for one entity. The platform requirement generalises it.
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| tenant_id, branch_id | int64 / uuid | |
| actor_user_id, actor_display_name, actor_role_code | snapshot at time of action | |
| action | text | student.update, tuition.pay, attendance.mark, role.permissions.put ... |
| entity_type, entity_id | text, text | |
| before, after | jsonb | changed fields only; money and status fields always included |
| ip, user_agent | text | |
| request_id | text | correlates with API logs |
| created_at | timestamptz | immutable; table is append-only, no UPDATE/DELETE grants |
- Retention 5 years; exportable per tenant by owner as CSV.
- Mandatory audited actions: any write to finance (create, pay, void, refund, discount), attendance edits after the session day, student status changes, guardian contact changes, permission and role changes, exports of personal data, login from a new device.
- Viewer: Settings → Audit log (PRD 10) with filters actor, action, entity, date; row expands to a before/after diff.
7Backup, recovery and data portability
- Postgres: continuous WAL archiving, point-in-time recovery to any minute within 14 days; nightly full snapshot kept 35 days; monthly snapshot kept 12 months.
- Object storage (documents, receipts PDF): versioned bucket, 30-day soft delete.
- Recovery objectives: RPO 5 minutes, RTO 4 hours for a full region failure; tested twice a year with a written report.
- Tenant export: owner can request a full export (CSV per entity plus documents zip) from Settings; produced within 24 hours as a signed download; this also serves as the contractual off-boarding deliverable.
- Tenant deletion: 30-day grace period after contract end, then hard delete with certificate on request.
8Localisation (vi / en)
- The app ships two locales via next-intl:
vi(default, product UI) anden. Every new string is added to bothsrc/i18n/messages/vi.jsonanden.jsonunder the module namespace; a CI check fails the build when keys diverge. - API labels (statuses, permission labels, error messages) respect
X-Locale, as existing endpoints already do. - Dates display
dd/MM/yyyy, timesHH:mm24-hour, timezone Asia/Ho_Chi_Minh by default per tenant; stored in UTC. - Money: VND with no decimals, thousands separator "." in vi and "," in en; the symbol "đ" follows the number in vi.
- Vietnamese names: display order is "Họ Tên" as entered; sorting uses locale-aware collation (ICU vi) so "Đ" and "D" order correctly and accents are ignored in search.
- Phone numbers normalised to E.164 (
+84...) on save; displayed as0xxx xxx xxx. - The Vietnamese UI glossary (link in hub) is the source of truth for labels; product copy uses the formal register (Quý phụ huynh, học viên, giáo viên).
9Accessibility and device support
- WCAG 2.1 AA for staff web: contrast ≥ 4.5:1 (the Cloud palette passes with ink #212B36 on white and #637381 for secondary text), focus rings visible, all actions reachable by keyboard, tables with proper headers, forms with labels and error text linked by
aria-describedby. - Status is never conveyed by colour alone; attendance chips carry text (Có mặt / Muộn / Vắng / Có phép) and an icon.
- Minimum supported viewport 1280 px for the staff console; the teacher attendance flow and the parent portal are mobile-first (360 px) and installable as a PWA. A native app is not in scope for phase 1.
- Browsers: last two versions of Chrome, Edge, Safari, Firefox; Zalo in-app browser for parent links.
- Font sizes follow the Cloud theme (13.5 px base in dense tables, 15.5 px in documents); a tenant-level "Cỡ chữ lớn" toggle scales the console 110%.
10Observability and operations
- Structured logs with
tenant_id,user_id,request_id; no personal data in logs (phone and names masked). - Error tracking on the front end with source maps; user-facing errors show a short code (e.g.
TS-4021) that support can look up. - Product analytics events listed per PRD are sent to the existing analytics pipeline with
tenant_idand role; no student personal data in event payloads. - Health endpoint
/api/healthchecks database, queue and object storage. - Feature flags per tenant (existing
GET /v3/teacher/feature-configspattern) gate phase 2 modules: attendance, finance v2, staff, inventory, parent portal.
11Release acceptance checklist
- Performance targets in section 2 met on the seeded "Big Center" tenant, with the numbers recorded in the release note.
- Security: automated dependency scan clean of critical issues; manual check of tenant isolation on every new endpoint (attempt cross-tenant id returns 404).
- PDPD: consent capture present on every new data-entry point for guardians and students; anonymise flow tested.
- Audit: every mandatory action in section 6 writes a log entry; verified by test plan (see test plan seed).
- i18n: vi and en key sets identical; screenshots of both locales attached.
- Accessibility: axe run with zero serious violations on the module's main screens; keyboard walkthrough recorded.
- Backup: restore drill of the release week's snapshot succeeded on staging.
Trường Số (truongso.net) · FLYER internal