PRD 11 · Data model and API
Consolidated entity list with relationships, the existing /v3 surface versus the endpoints each module needs, API conventions, and the migration toolkit for importing a center from Excel: students, guardians, classes, enrollments, opening balances and staff.
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. - The existing / extend / new tags on this page come from a comparison with the staging
/v3specification at that date. - The endpoint counts per group in section 3 (Crm 148, Teacher 73, Parent 5, Payment 1) are counted from the staging spec of 10/09/2026.
- The import order in section 4 follows how the competitor guides its own customers through data onboarding.
Limits
- Verification pending in-app survey, because automated login to the reference product was not authorised in the drafting session:
- Competitor's import templates per entity (column names, required fields) to align the migration path for centers switching.
- Their export formats, to build an importer for switching customers.
- Identifier formats (student code, class code, receipt number).
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).
1API conventions
- Base: existing FLYER Rust API at
/v3; the web app proxies/api/v3to it,/api/graphqlto the v2 GraphQL gateway (auth only),/api/uploadto the upload service. Staging basehttps://staging-api.flyer.us. The frontend client isopenapi-fetchwith types generated from/v3/openapi.jsonat dev time; hooks are generated by name (useCrmCustomersByIdActivities). - Auth: cookie session; header
X-Locale: vi|en; 401 redirects to login. - Namespaces: existing
/v3/crm/*(tenant-level CRM),/v3/teacher/*(school-level teacher app),/v3/parent/*. New school-operations endpoints use/v3/school/*and takeschool_idas a query or body parameter where the resource is per branch; tenant-level resources omit it. - Envelopes:
DataResponse<T>for single,ListResponse<T>withPagination {limit, page, prev_page, next_page, total_pages, total_count}; cursor lists (kanban) usemeta.next_cursor. - Errors:
ErrorRespone {code, message, details}; domain codes listed per PRD (INSUFFICIENT_STOCK, CONFLICT, CLASS_FULL, STALE_VERSION, INVALID_TRANSITION, PENDING_APPROVAL). - Concurrency: writable detail objects carry
updated_at; PATCH may sendIf-Unmodified-Sinceorversion; conflicts return 409. - Idempotency: POST that creates money or attendance accepts
Idempotency-Key. - Jobs: long operations return
{job_id};GET /v3/school/jobs/{id}returns status, progress, result or error report (pattern ofImportJob). - Events: every domain write publishes an internal event (
student.status_changed,attendance.saved,payment.recorded...) consumed by messaging (PRD 07), aggregates (PRD 09), audit (PRD 12) and the optional outgoing webhook (PRD 10).
2Entity catalog
Origin: existing = present in the /v3 API; extend = existing with new fields; new = to be created. Key fields only; full field lists are in the module PRDs.
| Entity | Origin | Identity | Key relationships | PRD |
|---|---|---|---|---|
| Center (tenant) | extend | center_id string | has many School; Users; CRM config | 00, 10 |
| School (branch) + branch_profile | extend | school_id int64 | belongs to Center; has Students, Classes, Sessions, Tuition, Rooms, Cash accounts | 00, 10 |
| SaleProfile (staff user) + user_branch_scope | extend | sale_profile_id int64, user_id | roles; branches; owns Leads; maps to Staff | 10 |
| Role, Permission catalog | extend | role id, code | permissions with scope | 10 |
| Customer (lead) + prospect_learners | extend | uuid, code | stage, status, source, channel, tags, assignees, interactions, placement tests, learners link, trial bookings, conversions | 01 |
| Pipeline, Stage, Status, Source, Channel, Tag, CustomField, Package (sales) | existing | uuid | tenant catalogs | 01, 10 |
| Interaction (call, meeting, task, note) | existing | uuid | customer, actor | 01 |
| PlacementTest | extend | uuid | customer, learner link, FLYER report | 01 |
| TrialBooking | new | uuid | customer, prospect, class, session | 01, 03 |
| Conversion | new | uuid | customer → student, guardian, enrollment, tuition | 01 |
| LearnerProfile (FLYER) | existing | learner_profile_id int64 | StudentProfile; scores; reports | 02 |
| Student (operational record) | new | uuid, code | school; learner profile; guardians; enrollments; packages; balance; documents; notes; status history | 02 |
| Guardian, student_guardian, Consent | new | uuid | students; portal sessions; messages | 02, 07 |
| Course | extend | uuid | fee plans; classes | 03, 05 |
| Level | new | code | courses, classes, students, placement mapping | 03 |
| Class + class_profile | extend | class_id int64 | school; course; room; staff; sessions; enrollments; ClassSchedule (FLYER program) | 03 |
| Room, Holiday | new | uuid | school | 03 |
| Session | new | uuid | class; room; main and substitute staff; attendance sheet; timesheet lines; trial and make-up bookings | 03, 04, 06 |
| Enrollment | new | uuid | student; class; fee plan | 03 |
| MakeupBooking, Excuse | new | uuid | student; sessions | 03, 04 |
| AttendanceSheet, AttendanceRecord (+history) | new | uuid | session; student; package consumption; messages | 04 |
| FeePlan | new | uuid | course; tuition lines; packages | 05 |
| Tuition (invoice) + lines | extend | uuid, payment_code | student; class; course; fee plan; payments; credit notes; e-invoice; group invoice | 05 |
| Payment (TuitionTransaction) | extend | uuid, receipt_no | tuition; cash account; cashier; SePay id | 05 |
| GroupInvoice | existing | uuid | tuitions; classes | 05 |
| SessionPackage, SessionConsumption | new | uuid | student; tuition; attendance records | 05 |
| CreditNote, Refund, Expense, ExpenseCategory, CashAccount, CashClose, UnmatchedTransaction, VoucherGroup, Voucher, VoucherUse | new | uuid | finance | 05 |
| SchoolBankAccount, SchoolCompany | existing | uuid | school | 05, 10 |
| Staff, JobTitle, StaffContract, PayRule (+override), Timesheet (+lines, adjustments), EvaluationTemplate, Evaluation, StaffDocument | new | uuid | wraps TeacherProfile and SaleProfile; sessions | 06 |
| TeacherProfile, RollupTeacher | existing | int64 | classes; KPIs | 06 |
| MessageTemplate, Message, Announcement, ProgressReportBatch, ProgressReport, ParentRequest, ZaloOAConnection, PortalSession | new | uuid | guardians; students; sources | 07 |
| InventoryItem, Category, Document, Movement | new | uuid | school; students; tuition lines | 08 |
| agg_daily_school, report jobs, digest settings | new | 09 | ||
| Invitation, Integration, OnboardingChecklist, AuditLog | new | uuid | center; actors | 10, 12 |
| Notification, Mention, Discussion, CustomerFile, ViewConfig, ImportJob, TodayPlan | existing | CRM support objects, reused by new modules where noted | 01, 09, 11 |
Relationship diagram
3API surface: existing versus needed
Existing endpoints by group: Crm 148, Teacher 73, Parent 5, Payment 1. The table lists what each module reuses and how many new endpoints it introduces (from the module PRDs).
| Module | Existing reused | Extended | New | Notes |
|---|---|---|---|---|
| 01 Admissions | customers (60+), pipelines, stages, statuses, sources, channels, tags, custom fields, packages, interactions, quick actions, placement tests, imports, search, notifications, today plan, dashboard | customers (prospect_learners), interactions filters, placement tests (suggested_level), pipelines (trial stage) | do-not-call, transfer-school, trial-bookings (3), convert | closes frontend references to non-spec endpoints |
| 02 Students | school-finance students, stats, tuitions, courses; parent reports; quick-search; learners search | none | students (7), guardians (3), consents, documents, notes, balance, anonymise, credentials | student wraps learner profile |
| 03 Classes | teacher classes, class-schedules (FLYER program), courses | courses, classes list | classes (5), sessions (5), enrollments (2), roster, makeups, rooms, holidays, levels, timetable, ics | sessions materialised |
| 04 Attendance | school-config | school-config policy | attendance (9), excuses, parent attendance | no existing entity |
| 05 Finance | tuitions (10), group invoices, bank accounts, company, link-token, payment QR, parent tuitions | tuitions list/create/detail/pay/notify, group invoice create, parent tuitions | fee plans, void, approve, receipts, debt, payment plan, packages, credit notes, refunds, expenses, cashbook, cash accounts, unmatched, vouchers, e-invoice, finance report (about 25) | reuses Tuition as invoice |
| 06 Staff | schools, rollup-teachers, sale profiles, roles | none | staff (6), contracts, pay rules, timesheets (7), evaluations, documents | P2 |
| 07 Parents | tuition notify, class remind, exam notify, parent tuitions and reports, notification config | school-config notifications, parent tuitions | templates, messages, announcements, progress reports, requests, zalo integration, parent auth and views, inbox, consents, webhook (about 20) | message layer wraps existing notify |
| 08 Inventory | students, classes pickers, tuition create | none | items, categories, stock, documents, issue, movements, materials, export (13) | P2 |
| 09 Reports | crm dashboard (5), tuition stats, rollups, view configs | none | executive, home, reports/{key}, jobs, digest | aggregates |
| 10 Settings | roles, permissions catalog, sale profiles, centers, sources, channels, tags, custom fields, packages, pipelines, school-config, company | permissions catalog, roles permissions with scope, me/permissions, centers PATCH, school-config | branches, invitations, sale-profile branches, deactivate, catalogs (levels, rooms, job titles, expense categories, fee plans, holidays), audit logs, integrations, checklist, full export | replaces mocks |
| 11 Imports | imports (customers) | imports (entity types) | import templates per entity, validate, commit | below |
The full list of endpoints with request and response shapes is in each PRD's API section. The backend team should generate the OpenAPI additions first so the frontend keeps its generated-hook workflow.
4Migration and Excel import
Most centers arrive with Excel sheets or an export from another system. The existing import wizard handles leads with a template, preview, dedupe strategy (skip, merge, overwrite), round-robin assignment and an error report. The same job pattern is extended to the school entities, in a fixed order (staff, classes, students, courses, tuition, remaining sessions, attendance).
Import order and templates
| Step | Entity | Template columns (VI header · key) | Required | Dedupe key | Validation |
|---|---|---|---|---|---|
| 1 | Staff | Họ tên · full_name; Mã NV · code; Chức danh · job_title; Loại hình · employment_type; Chi nhánh · branch_code; SĐT · phone; Email · email; Ngày bắt đầu · start_date; Là giáo viên · is_teacher; Tạo tài khoản · create_account; Vai trò · role_code | full_name, phone, branch_code | phone | branch exists; role exists |
| 2 | Courses and fee plans | Tên khoá · course_name; Trình độ · level; Số buổi · lessons; Thời lượng (phút) · duration; Tên gói · plan_name; Loại gói · plan_type; Giá · price; Số buổi gói · sessions; Hạn (tháng) · validity_months | course_name, plan_name, plan_type, price | course_name + plan_name | plan_type enum |
| 3 | Rooms | Chi nhánh · branch_code; Phòng · name; Sức chứa · capacity | branch_code, name | branch + name | |
| 4 | Classes | Mã lớp · code; Tên lớp · name; Chi nhánh · branch_code; Khoá · course_name; Trình độ · level; GV chính · teacher_phone; Phòng · room; Lịch · pattern ("T3 18:00-19:30; T5 18:00-19:30"); Khai giảng · start_date; Kết thúc · end_date; Sĩ số · capacity | code, name, branch_code, pattern, start_date | code | teacher exists; room exists; pattern parses; sessions generated from max(start_date, import date) onward, earlier sessions imported as held without attendance unless step 8 provides it |
| 5 | Students and guardians | Mã HV · code; Họ tên · display_name; Ngày sinh · birthday; Giới tính · gender; Chi nhánh · branch_code; Trạng thái · status; Trình độ · level; Trường · school_name; Địa chỉ · address; PH1 họ tên · g1_name; PH1 SĐT · g1_phone; PH1 quan hệ · g1_relationship; PH1 email · g1_email; PH1 Zalo đồng ý · g1_zalo; PH2 ... · g2_*; Tài khoản FLYER · flyer_username; Ghi chú · note | display_name, birthday, branch_code, g1_phone (unless age ≥ 18) | code, else display_name + birthday + g1_phone | status enum; learner created or linked by username; guardians merged by phone |
| 6 | Enrollments | Mã HV · student_code; Mã lớp · class_code; Ngày bắt đầu · start_date; Ngày kết thúc · end_date; Gói · plan_name | student_code, class_code, start_date | student + class + start | capacity warning (not blocking on import) |
| 7 | Opening balances | Mã HV · student_code; Gói · plan_name; Buổi còn lại · sessions_remaining; Hết hạn · expires_on; Còn nợ · debt_amount; Hạn nợ · debt_due; Ghi chú · note | student_code and at least one of sessions_remaining, debt_amount | student + plan | creates a package with sessions_total = sessions_remaining and a tuition "Số dư đầu kỳ" for debt, both flagged is_opening |
| 8 | Attendance history (optional) | Mã lớp · class_code; Ngày · date; Mã HV · student_code; Trạng thái · status (Có mặt/Muộn/Vắng/Có phép) | all | class + date + student | session must exist for (class, date); creates sheets marked by "Import" |
| 9 | Inventory opening stock (P2) | Chi nhánh; Mã hàng; Tên; Danh mục; Đơn vị; Giá bán; Giá vốn; Tồn | code, name, branch | code | stock-in document "Số dư đầu kỳ" |
Wizard behaviour
- Choose entity; download template (xlsx with header row, example row, and a hidden sheet of allowed values for enums and branch codes); upload xlsx or csv.
- Column mapping: auto-map by header, manual remap, unmapped columns listed; custom fields supported for students as for leads.
- Validation preview: row-level errors and warnings with counts; download error file; "Bỏ qua dòng lỗi" toggle.
- Dedupe strategy per entity (skip, merge, overwrite) as today; for students, merge updates profile fields left blank and adds guardians.
- Commit runs as a job (existing ImportJob with new
entity_type); progress, success and failed counts; detail page with succeeded and failed tabs (existing pages reused); cancel while pending. - Rollback: an import can be reverted within 24 h if none of the created records were modified afterwards (deletes created rows, keeps merged ones with audit note).
Data quality rules
- Phones normalised to E.164; invalid phones are errors for guardians, warnings for staff email-only accounts.
- Dates accept dd/MM/yyyy and yyyy-MM-dd; Excel serial dates converted.
- Names trimmed, double spaces collapsed, casing preserved.
- Branch codes must exist; the wizard offers to create missing branches (owner only).
- Levels not in the catalog are created as custom levels with a warning.
API
| Method | Path | Request / response | Status |
|---|---|---|---|
| GET/POST | /v3/crm/imports; GET /{id}; POST /{id}/cancel; GET /{id}/errors, /error-report | add entity_type enum(customers, staff, courses, rooms, classes, students, enrollments, balances, attendance, inventory) and mapping | extend |
| GET | /v3/school/imports/templates/{entity_type}.xlsx | template with allowed values for the tenant | new |
| POST | /v3/school/imports/validate | {entity_type, rows[], mapping, dedupe_strategy} → {valid, errors[], warnings[], duplicates[]} (no write) | new |
| POST | /v3/school/imports/{id}/rollback | new | |
| POST | /v3/crm/imports/bulk-customers | unchanged for leads | existing |
5Data retention and identifiers
| Data | Retention | Identifier format |
|---|---|---|
| Leads | anonymise 24 months after last activity if not won | customer code as today |
| Students, guardians | active + 5 years, then anonymise on request or after 5 years inactive | HV-000123 per tenant |
| Attendance | 5 years | session uuid |
| Finance documents | 10 years | PT-<branch>-YYYYMM-####, PC-..., HĐ = payment_code |
| Messages | 12 months rendered content, 5 years metadata | uuid |
| Audit log | 5 years | uuid, request_id |
Trường Số (truongso.net) · FLYER internal