PRD 11 · Mô hình dữ liệu và API
Danh sách thực thể tổng hợp kèm quan hệ, bề mặt /v3 hiện có so với các endpoint mà từng mô-đun cần, quy ước API, và bộ công cụ di chuyển dữ liệu để nhập một trung tâm từ Excel: học viên, phụ huynh, lớp học, lượt ghi danh, số dư đầu kỳ và nhân sự.
Xuất xứ / Prompt / Giới hạn
Tài liệu do trợ lý AI soạn trong một phiên làm việc ngày 10/09/2026, theo yêu cầu gốc được trích nguyên văn ở trang mục lục; mục Thiết kế đề xuất được bổ sung ngày 16/09/2026. Đây là bản đề xuất để đội thảo luận, chưa phải quyết định của đội sản phẩm.
Nguồn
- Nghiên cứu công khai về đối thủ cạnh tranh (PSE ONE, pse4.one), mã nguồn
center-crm-frontendvà đặc tả OpenAPI/v3trên staging (182 endpoint), tính đến ngày 10/09/2026. - Nhãn existing / extend / new trong trang được xác định bằng cách đối chiếu với đặc tả
/v3trên staging tại thời điểm trên. - Số endpoint theo nhóm ở mục 3 (Crm 148, Teacher 73, Parent 5, Payment 1) đếm từ đặc tả staging ngày 10/09/2026.
- Thứ tự nhập liệu ở mục 4 lấy theo cách đối thủ cạnh tranh hướng dẫn khách hàng của họ đưa dữ liệu vào phần mềm.
Giới hạn
- Cần xác minh khi khảo sát trực tiếp trong ứng dụng, vì đăng nhập tự động vào sản phẩm tham chiếu chưa được cấp phép trong phiên soạn:
- Mẫu nhập liệu của đối thủ theo từng thực thể (tên cột, trường bắt buộc) để tham chiếu lộ trình di chuyển dữ liệu cho các trung tâm chuyển đổi.
- Định dạng xuất dữ liệu của họ, để xây dựng công cụ nhập liệu cho khách hàng chuyển đổi.
- Định dạng mã (mã học viên, mã lớp, số phiếu thu).
Thiết kế đề xuất
PRD này không có màn hình riêng: nội dung là nền tảng dùng chung. Thiết kế của từng tính năng nằm ở mục "Thiết kế đề xuất" đầu mỗi PRD mô-đun (00 đến 10 và 13 đến 19).
1Quy ước API
- Nền tảng: API Rust của FLYER đã có sẵn tại
/v3; ứng dụng web proxy/api/v3đến API này,/api/graphqlđến GraphQL gateway v2 (chỉ dùng cho auth),/api/uploadđến dịch vụ upload. Base của staging làhttps://staging-api.flyer.us. Client phía frontend làopenapi-fetchvới type được sinh từ/v3/openapi.jsontại thời điểm phát triển; hook được sinh theo tên (useCrmCustomersByIdActivities). - Auth: session dạng cookie; header
X-Locale: vi|en; 401 chuyển hướng về trang đăng nhập. - Namespace:
/v3/crm/*đã có sẵn (CRM ở cấp tenant),/v3/teacher/*(ứng dụng giáo viên ở cấp school),/v3/parent/*. Các endpoint vận hành school mới dùng/v3/school/*và nhậnschool_idnhư query hoặc body parameter khi resource thuộc từng chi nhánh; resource ở cấp tenant thì bỏ tham số này. - Envelope:
DataResponse<T>cho một đối tượng,ListResponse<T>kèmPagination {limit, page, prev_page, next_page, total_pages, total_count}; danh sách dùng cursor (kanban) dùngmeta.next_cursor. - Lỗi:
ErrorRespone {code, message, details}; mã lỗi theo từng miền được liệt kê trong từng PRD (INSUFFICIENT_STOCK, CONFLICT, CLASS_FULL, STALE_VERSION, INVALID_TRANSITION, PENDING_APPROVAL). - Concurrency: đối tượng chi tiết có thể ghi mang
updated_at; PATCH có thể gửiIf-Unmodified-Sincehoặcversion; xung đột trả về 409. - Idempotency: POST tạo ra dữ liệu tiền hoặc điểm danh chấp nhận header
Idempotency-Key. - Jobs: các tác vụ dài trả về
{job_id};GET /v3/school/jobs/{id}trả về status, progress, result hoặc error report (theo mẫu củaImportJob). - Events: mỗi lượt ghi dữ liệu miền sẽ phát ra một event nội bộ (
student.status_changed,attendance.saved,payment.recorded...) được tiêu thụ bởi tầng gửi tin (PRD 07), tầng tổng hợp (PRD 09), nhật ký (PRD 12) và webhook gửi ra ngoài không bắt buộc (PRD 10).
2Danh mục thực thể
Nguồn gốc: existing = đã có trong API /v3; extend = đã có kèm trường mới; new = cần được tạo mới. Chỉ liệt kê trường chính; danh sách trường đầy đủ nằm trong từng PRD mô-đun.
| Thực thể | Nguồn gốc | Định danh | Quan hệ chính | 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 |
Sơ đồ quan hệ
3Bề mặt API: hiện có so với cần bổ sung
Số endpoint hiện có theo nhóm: Crm 148, Teacher 73, Parent 5, Payment 1. Bảng dưới đây liệt kê những gì mỗi mô-đun tái sử dụng và số lượng endpoint mới mà mô-đun đó bổ sung (theo từng PRD mô-đun).
| Mô-đun | Existing reused | Extended | New | Ghi chú |
|---|---|---|---|---|
| 01 Tuyển sinh | 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 | đóng các tham chiếu frontend đến những endpoint không theo đặc tả |
| 02 Học viên | school-finance students, stats, tuitions, courses; parent reports; quick-search; learners search | none | students (7), guardians (3), consents, documents, notes, balance, anonymise, credentials | student bao lấy learner profile |
| 03 Lớp học | teacher classes, class-schedules (FLYER program), courses | courses, classes list | classes (5), sessions (5), enrollments (2), roster, makeups, rooms, holidays, levels, timetable, ics | sessions được sinh thành các bản ghi cụ thể (materialised) |
| 04 Điểm danh | school-config | school-config policy | attendance (9), excuses, parent attendance | chưa có entity nào có sẵn |
| 05 Tài chính | 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) | tái sử dụng Tuition làm hoá đơn |
| 06 Nhân sự | schools, rollup-teachers, sale profiles, roles | none | staff (6), contracts, pay rules, timesheets (7), evaluations, documents | P2 |
| 07 Phụ huynh | 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) | tầng message bao lấy notify đã có sẵn |
| 08 Kho | students, classes pickers, tuition create | none | items, categories, stock, documents, issue, movements, materials, export (13) | P2 |
| 09 Báo cáo | crm dashboard (5), tuition stats, rollups, view configs | none | executive, home, reports/{key}, jobs, digest | dữ liệu tổng hợp (aggregate) |
| 10 Cài đặt | 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 | thay thế các bản giả (mock) |
| 11 Nhập liệu | imports (customers) | imports (entity types) | import templates per entity, validate, commit | xem phần dưới |
Danh sách đầy đủ các endpoint kèm cấu trúc request và response nằm trong phần API của từng PRD. Đội backend nên sinh các bổ sung OpenAPI trước để đội frontend tiếp tục quy trình sinh hook tự động.
4Di chuyển dữ liệu và nhập từ Excel
Hầu hết các trung tâm đến với dữ liệu là các sheet Excel hoặc file xuất từ một hệ thống khác. Wizard nhập liệu hiện có xử lý lead với mẫu file, xem trước, chiến lược khử trùng (bỏ qua, gộp, ghi đè), phân bổ round-robin và một báo cáo lỗi. Cùng một mẫu job này được mở rộng sang các thực thể của school, theo một thứ tự cố định (nhân sự, lớp học, học viên, khoá học, học phí, số buổi còn lại, điểm danh).
Thứ tự nhập liệu và mẫu file
| Bước | Thực thể | Cột trong mẫu (tiêu đề tiếng Việt · key) | Bắt buộc | Khoá khử trùng | Kiểm tra dữ liệu |
|---|---|---|---|---|---|
| 1 | Nhân sự | 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 | chi nhánh tồn tại; vai trò tồn tại |
| 2 | Khoá học và gói học phí | 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 theo enum |
| 3 | Phòng học | Chi nhánh · branch_code; Phòng · name; Sức chứa · capacity | branch_code, name | branch + name | |
| 4 | Lớp học | 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 | giáo viên tồn tại; phòng học tồn tại; pattern phân tích được; sessions được sinh từ max(start_date, ngày nhập liệu) trở về sau, các session trước đó được nhập với trạng thái đã học nhưng chưa có điểm danh trừ khi bước 8 cung cấp dữ liệu điểm danh |
| 5 | Học viên và phụ huynh | 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 theo enum; learner được tạo mới hoặc liên kết theo username; guardian được gộp theo số điện thoại |
| 6 | Lượt ghi danh | 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 | cảnh báo sĩ số (không chặn khi nhập liệu) |
| 7 | Số dư đầu kỳ | 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 | tạo một package với sessions_total = sessions_remaining và một tuition "Số dư đầu kỳ" cho khoản nợ, cả hai đều được gắn cờ is_opening |
| 8 | Lịch sử điểm danh (tuỳ chọn) | 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 phải tồn tại cho (class, date); tạo attendance sheet được đánh dấu bởi "Import" |
| 9 | Tồn kho đầu kỳ (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 | chứng từ nhập kho "Số dư đầu kỳ" |
Cách hoạt động của wizard
- Chọn thực thể; tải mẫu file (xlsx có dòng tiêu đề, dòng ví dụ, và một sheet ẩn chứa các giá trị hợp lệ cho enum và mã chi nhánh); tải lên file xlsx hoặc csv.
- Ánh xạ cột: tự động ánh xạ theo tiêu đề, ánh xạ lại thủ công, các cột chưa ánh xạ được liệt kê; trường tuỳ chỉnh được hỗ trợ cho học viên như đối với lead.
- Xem trước kiểm tra dữ liệu: lỗi và cảnh báo theo từng dòng kèm số lượng; tải file lỗi; công tắc "Bỏ qua dòng lỗi".
- Chiến lược khử trùng theo từng thực thể (bỏ qua, gộp, ghi đè) như hiện tại; đối với học viên, gộp sẽ cập nhật các trường hồ sơ còn để trống và thêm phụ huynh.
- Commit chạy dưới dạng một job (ImportJob đã có sẵn kèm entity_type mới); tiến độ, số dòng thành công và thất bại; trang chi tiết có tab thành công và thất bại (tái sử dụng trang đã có sẵn); có thể huỷ khi đang chờ xử lý.
- Rollback: một lượt nhập liệu có thể được hoàn tác trong 24 giờ nếu không có bản ghi nào được tạo bị sửa sau đó (xoá các dòng đã tạo, giữ lại các dòng đã gộp kèm ghi chú trong nhật ký).
Quy tắc chất lượng dữ liệu
- Số điện thoại được chuẩn hoá theo E.164; số điện thoại không hợp lệ là lỗi đối với phụ huynh, là cảnh báo đối với tài khoản nhân sự chỉ dùng email.
- Ngày nhận cả hai định dạng dd/MM/yyyy và yyyy-MM-dd; ngày dạng serial của Excel được chuyển đổi.
- Tên được cắt khoảng trắng đầu/cuối, gộp khoảng trắng đôi, giữ nguyên cách viết hoa/thường.
- Mã chi nhánh phải tồn tại; wizard cho phép tạo các chi nhánh còn thiếu (chỉ owner).
- Trình độ không có trong danh mục được tạo thành trình độ tuỳ chỉnh kèm cảnh báo.
API
| Method | Path | Request / response | Status |
|---|---|---|---|
| GET/POST | /v3/crm/imports; GET /{id}; POST /{id}/cancel; GET /{id}/errors, /error-report | thêm entity_type enum(customers, staff, courses, rooms, classes, students, enrollments, balances, attendance, inventory) và mapping | extend |
| GET | /v3/school/imports/templates/{entity_type}.xlsx | template kèm các giá trị hợp lệ cho tenant | new |
| POST | /v3/school/imports/validate | {entity_type, rows[], mapping, dedupe_strategy} → {valid, errors[], warnings[], duplicates[]} (không ghi dữ liệu) | new |
| POST | /v3/school/imports/{id}/rollback | new | |
| POST | /v3/crm/imports/bulk-customers | không thay đổi đối với lead | existing |
5Thời hạn lưu trữ dữ liệu và định danh
| Dữ liệu | Thời hạn lưu trữ | Định dạng mã |
|---|---|---|
| Lead | ẩn danh hoá sau 24 tháng không có hoạt động nếu chưa ở trạng thái Thành công | mã customer như hiện tại |
| Học viên, phụ huynh | còn hoạt động + 5 năm, sau đó ẩn danh hoá theo yêu cầu hoặc sau 5 năm không hoạt động | HV-000123 theo từng tenant |
| Điểm danh | 5 năm | session uuid |
| Chứng từ tài chính | 10 năm | PT-<branch>-YYYYMM-####, PC-..., HĐ = payment_code |
| Tin nhắn | 12 tháng đối với nội dung đã hiển thị, 5 năm đối với metadata | uuid |
| Nhật ký hệ thống | 5 năm | uuid, request_id |
Trường Số (truongso.net) · Nội bộ FLYER