Trường Số PRD/01 Admissions CRM
Trường Số · Product Requirements · 09/2026

PRD 01 · Admissions CRM

Extends the live lead pipeline: sources and channels, call log, trial bookings, placement tests, and the conversion of a won lead into an enrolled student with a guardian, a class and a first receipt. Most of the backend exists; this PRD closes the gaps between the pipeline and the school.

Key facts, origin and limits
Status
Draft v1
Owner module
Tuyển sinh
Backend
EXISTING (148 /v3/crm paths) · NEW conversion, trials, DNC
Phase
P1
Depends on
02 Students, 03 Classes, 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 lead fields and "phân loại khách hàng" categories; whether guardian and child are separate objects.
    • Their "nhật ký chăm sóc" activity types and whether call results are a fixed list.
    • How trial ("học thử") is booked: from the lead or from the class, and whether it counts in attendance.
    • Whether entrance test ("kiểm tra đầu vào") stores per-skill scores and maps to a class level automatically.
    • Their Facebook lead integration behaviour (page inbox vs lead form).

Proposed design

Every admissions screen sits in the School OS shell with the Admissions group in the sidebar; the thing to see is the chain from callback queue, trial and placement test to the four-step conversion wizard, where a won lead becomes a student, a guardian, an enrollment and a first invoice in one flow.

1 · Customer list

/crm/customers · desktop

Customer list
Pipeline table with the five quick-filter tabs above it (All · Mine · Callbacks today · Trials this week · Won this month) and a Table/Kanban switch. The three new columns from section 5: Prospect learner, Trial (chip for the nearest session) and Placement (level and score). The "Has callback" filter is active, one lead carries the Do-not-call chip (ADM-03), one is 30 days from anonymisation (rule 5); the bulk bar has Send ZNS counting only consented leads.

2 · Lead detail

/crm/customers/:id · desktop

Lead detail
Drawer over the list: the header shows the Won status and Convert to student is the primary button (ADM-06). The timeline records calls, the placement result auto-filled from FLYER and the "Trial done" activity with the teacher's note (ADM-04). The sidebar has the Prospect learners group with two children, one already converted and shown only as a link (rule 2), plus the Trials and placement block with the Book trial and Create test actions.

3 · Schedules and calls, Callback tab

/crm/schedules · desktop

Schedules and calls, Callback tab
Callback queue sorted overdue first (due time in red), then today, then upcoming (ADM-02). All columns from section 5: Customer, Phone, Callback time, Overdue, Assignee, Last call and a Call button that opens the quick-log dialog with the customer preselected. Filters by assignee and date range; the footer notes that Do-not-call leads never appear in the queue (rule 4).

4 · Trials, list

/crm/trials · desktop

Trials, list
Trial bookings table with all eight columns from section 5 and the four statuses Booked, Attended, No-show, Cancelled. Teacher notes are truncated to one line; the two attended bookings expose a highlighted Convert action. The stat tiles count free trial seats with the default of 2 per class (rule 3).

5 · Trials, booking dialog

/crm/trials (dialog) · desktop

Trials, booking dialog
Booking dialog for prospect Lê Gia Hân: class picker showing level, teacher and remaining trial seats, with a full class disabled (rule 3); one of the next five sessions; a note to the teacher and the Send Zalo confirmation toggle, enabled only because the guardian has consented (ADM-04). The automation note says the lead will move to the Trial stage (rule 7).

6 · Placement tests

/crm/placement-tests · desktop

Placement tests
List with the eight columns from section 5 and status labels aligned with the API enum: Not started, In progress, Completed, Cancelled (ADM-05). Tests with a linked learner account carry the FLYER auto-filled chip; the Suggested class column warns when the suggested level is lower than the registered one; Enter result appears only on tests not started.

7 · Placement test, submit result

/crm/placement-tests (dialog) · desktop

Placement test, submit result
Result dialog with the four skills Listening, Speaking, Reading, Writing out of 25, an auto-summed Total, Result link and Notes. Speaking shows a validation error for exceeding the maximum; the suggested level is computed client-side from the PRD 03 mapping table and is read-only (ADM-05).

8 · Conversion wizard, step 1 Student

/crm/customers/:id/convert · desktop

Conversion wizard, step 1 Student
Four-step modal. Step 1 picks the prospect learner (an already converted child is hidden and shown only as a link, rule 2), then either the linked learner account or a new student with Full name, Date of birth, Gender, Current school; Branch defaults to the lead's. Date of birth, required under 18, is in the error state; the soft duplicate warning on child name and birth year follows ADM-01.

9 · Conversion wizard, step 3 Class and plan

/crm/customers/:id/convert · desktop

Conversion wizard, step 3 Class and plan
Step 3 picks a class filtered by branch and suggested level, showing free seats over capacity with a full class disabled and a Waiting for placement option; Start date, Fee plan from PRD 05, a percentage Discount with reason, and Voucher code. The 15% discount exceeds the consultant's limit, so the Needs approval callout explains that an approval request goes to the branch manager (ADM-06); the amount-due summary updates immediately.

10 · Consultant callback queue

/crm/schedules (mobile) · di động

Consultant callback queue
Phone frame for the consultant: the callback queue grouped into Overdue, Today, Upcoming with a Call button on each row. The log-call bottom sheet opens with the customer preselected, a result picker, notes and a reminder task; saving any result removes the row from the queue (ADM-02).

1Purpose

The admissions consultant already works in b2c.truongso.net: leads arrive by import or manual entry, move through pipeline stages on a Kanban or table, receive calls and notes, and are marked won or lost. What is missing is the last mile. A won lead today stays a customer record with a linked learner; the school side (student record, class seat, tuition) is entered elsewhere. This PRD adds trial bookings, a callback queue, do-not-call handling that the UI already expects, and a conversion wizard that creates the student, guardian, enrollment and first invoice in one flow.

In scope

Out of scope

2What exists today

CapabilityFrontendAPI
Lead list: Kanban per stage with cursor pagination and drag to stage; table with 17 columns plus custom fields; filters stage, q, status[], priority[], call_status[], assigned_to[], tag_ids[], source[], channel[]; column visibility saved per viewfeatures/crm/customers/list/*GET /v3/crm/customers, /customers/kanban, PATCH /customers/{id}/kanban-position, /view-configs/customer_list
Create and edit drawer with duplicate check by phone; inline create of source, channel, stage, statuscustomer-form.tsx, duplicate-check-modal.tsxPOST /customers, /customers/check-duplicate(-multi), POST /sources|channels|stages|statuses
Detail shell: value, deal info, contact, marketing, learner account groups; tabs Hoạt động, Sản phẩm, Thảo luận, Tài liệu, Phân công, Học viên liên kết; Won, Lost, Archive, Star, Delete, Assigncustomer-detail-shell.tsxGET/PATCH /customers/{id}, /activities, /audit-logs, /packages, /discussions, /customer-files, /assignments, /learners
Interactions: call, meeting, task, note with statuses done/cancelled/missed and call outcomesfeatures/crm/calls/*, timeline tab/interactions, /quick-actions/log-call, /quick-actions/log-note
Placement tests: list, create, submit resultfeatures/crm/placement-tests/*/placement-tests, /{id}/submit-result, /v3/teacher/placement-test/send
Bulk: archive, unarchive, reassign, import with dedupe strategy, export xlsxtable bulk bar, import wizard/customers/bulk, /assignments/bulk-reassign, /imports/*
Today plan, notifications (WebSocket), universal search, mentionswidgets/me/today-plan, /me/notifications, /search, /me/mentions
Dashboard: sale summary, manager summary, funnel, trend, top sales, drill-downfeatures/crm/dashboard5 dashboard endpoints

Referenced in the frontend but absent from the API and therefore specified here as NEW: POST /customers/{id}/convert, POST /customers/{id}/do-not-call, POST /customers/{id}/transfer-school. Not carried forward: tasks, meetings and saved-views endpoints; tasks and meetings are Interactions, saved views are view-configs.

3Personas and roles

PersonaRoleScope
Admissions consultantadmissionsown leads; team leads if scope team
Admissions lead / branch managerbranch_managerall leads targeting the branch; reassign; approve discounts at conversion
Academic adminacademic_adminread leads; schedules trials and placement tests; receives converted students
Ownerownerall

4User stories and acceptance criteria

ADM-01Capture a lead with guardian and learner details
As a consultant I create a lead where the contact is the guardian and the prospective student is a child.
  • Given the create drawer
  • When I enter the guardian name and phone, and under "Học viên dự kiến" enter the child's name, birth year and current level
  • Then the customer is created with guardian and relationship as today, and a new prospect_learners JSON block stores child name, birth year, school, level; the duplicate check runs on phone as today and, additionally, on child name + birth year within the center (soft warning only).
ADM-02Work the callback queue
As a consultant I see all leads that asked for a callback, sorted by requested time.
  • Given calls logged with call_status = callback_requested and request_callback_at
  • When I open Lịch hẹn & cuộc gọi → tab Gọi lại
  • Then I see rows overdue first (red), then today, then upcoming, each with a one-click "Gọi" that opens the quick-log dialog with the customer preselected; logging any outcome removes the row.
ADM-03Mark do-not-call
As a consultant I mark a lead who asked not to be contacted.
  • Given a lead
  • When I choose Đánh dấu không gọi with a reason
  • Then do_not_call_at/by/reason are set; the quick-log-call dialog shows the existing DNC guard; bulk notifications in PRD 07 skip the phone; removing the mark requires customers.force_call.
ADM-04Book a trial class
As a consultant I book a prospective student into a real session of a real class.
  • Given classes with upcoming sessions (PRD 03) that have allow_trial = true and free trial seats
  • When I pick a class and session in the Học thử dialog on the lead
  • Then a trial_booking is created with status booked, the session roster shows the trial student with a Học thử chip, the guardian receives a ZNS confirmation (PRD 07) if consent exists, and the lead moves to the stage configured as "trial stage" for the pipeline (if set).
  • Given the session has been held
  • When the teacher marks attendance (PRD 04)
  • Then the trial booking status becomes attended or no_show, an activity is written on the lead, and the consultant receives a notification "Học thử xong: Nguyễn An (Movers 2A)" with the teacher's note.
ADM-05Schedule and record a placement test
As a consultant I schedule a placement test and record the result.
  • Given a lead with or without a linked learner
  • When I create a placement test with level and scheduled time
  • Then status is not_started; if a linked learner exists the FLYER test is sent (/v3/teacher/placement-test/send) and the result auto-fills on completion; otherwise the consultant submits scores manually; the result block shows overall score, four skills and a suggested class level (mapping table in PRD 03).
  • Given the i18n mismatch
  • When the page renders statuses
  • Then labels are Chưa bắt đầu / Đang làm / Hoàn thành / Đã huỷ, matching the API enum.
ADM-06Convert a won lead into an enrolled student
As a consultant I convert in one wizard.
  • Given a lead in a terminal won status or any open lead with permission customers.convert
  • When I press Chuyển đổi
  • Then a 4-step wizard opens: (1) Học viên: choose linked learner or create new learner (name, birthday, gender) and pick target branch; (2) Phụ huynh: confirm guardian name, phone, relationship, Zalo consent; (3) Lớp & gói: choose class (filtered by branch and level suggestion) or "Chờ xếp lớp", choose fee plan or package from PRD 05, start date, discount (approval if above the consultant's limit); (4) Xác nhận: summary and "Tạo hoá đơn ngay" toggle.
  • When I confirm
  • Then in one transaction: learner profile created or reused and linked (CustomerLearner), student record created (PRD 02) with status Active or Waiting, guardian record created, enrollment created (PRD 03) if a class was chosen, tuition created (PRD 05) if requested, the lead status set to won with won_at, an activity "Đã chuyển thành học viên" written, and the wizard shows links to the student and invoice.
  • Given a failure in any step
  • Then nothing is persisted and the error names the step.
ADM-07Transfer a lead to another branch
As a branch manager I move a lead to the branch nearest the family.
  • Given a lead with school_id A
  • When I choose Chuyển chi nhánh and pick B and optionally a new assignee
  • Then school_id becomes B, assignments at A are ended with reason "transfer", the new assignee is notified, and an audit entry is written.
ADM-08Manage sources and channels from settings
As an owner I maintain the source and channel lists.
  • Given Settings → Nguồn or Kênh
  • When I add, rename, recolour, reorder or deactivate
  • Then the page reads and writes /v3/crm/sources and /v3/crm/channels (replacing the localStorage mock), deactivated items stay on historical leads but disappear from pickers.

5Screens

Khách hàng list (existing, adjusted) /crm/customers

Keep the current Kanban and table. Adjustments: add columns Học viên dự kiến (from prospect_learners), Học thử (next or last trial with chip), Xếp lớp (placement level); add filter "Có gọi lại" and "Không gọi"; add tab-like quick filters above the table: Tất cả · Của tôi · Gọi lại hôm nay · Học thử tuần này · Thắng tháng này. Bulk bar gains "Gửi ZNS" (PRD 07) for leads with consent.

Lead detail (existing, adjusted) /crm/customers/:id

Add a "Học viên dự kiến" group in the sidebar (child name, birth year, level, school) and a "Học thử & xếp lớp" tab listing trial bookings and placement tests with actions Đặt lịch học thử, Tạo bài kiểm tra. The Chuyển đổi button becomes primary once status is won; it is enabled for open leads too when the user has customers.convert. Remove the feature flag.

Lịch hẹn & cuộc gọi /crm/schedules

Tabs: Hôm nay (today plan items) · Gọi lại (callback queue) · Cuộc hẹn (meetings) · Học thử (trial bookings). Table for Gọi lại: Khách hàng, SĐT, Giờ hẹn gọi, Quá hạn (chip), Người phụ trách, Lần gọi gần nhất, action Gọi. Filters: assignee, date range. Empty: "Không có yêu cầu gọi lại."

Học thử (trial bookings) /crm/trials
ColumnNotes
Học viênprospect name; link to lead
Phụ huynh / SĐT
Lớpclass name + level chip
Buổisession date and time
Trạng tháiĐã đặt (exc) · Đã học (ok) · Vắng (abs) · Đã huỷ (grey)
Nhận xét GVteacher note, truncated
Người phụ trách
Thao tácĐổi buổi · Huỷ · Chuyển đổi

Trial dialog fields: class (search, shows level, teacher, free trial seats), session (next 5 sessions), note for teacher, "Gửi xác nhận Zalo" toggle. Validation: session in the future; trial seats > 0; same prospect cannot hold two bookings on the same day.

Kiểm tra xếp lớp (existing, labels fixed) /crm/placement-tests

Columns: Khách hàng, Học viên, Trình độ, Thời gian, Trạng thái, Điểm, Đề xuất lớp, Thao tác. All form labels move to i18n: Trình độ, Thời gian kiểm tra, Hạn hoàn thành, Giám khảo, Mã đề FLYER, Ghi chú. Result modal: Điểm tổng, Nghe, Nói, Đọc, Viết, Link kết quả, Ghi chú; suggested level computed client-side from the PRD 03 mapping and shown read-only.

Conversion wizard (modal, 4 steps) /crm/customers/:id/convert
StepFieldsValidation
1 Học viênLinked learner radio list (from /customers/{id}/learners) or "Tạo học viên mới": Họ tên, Ngày sinh, Giới tính, Trường đang học; Chi nhánh (select from accessible schools, default lead's school_id)name required; birthday required for under-18; branch required
2 Phụ huynhHọ tên, SĐT (prefilled from lead phone), Quan hệ (Bố/Mẹ/Ông bà/Khác), Zalo: "Đồng ý nhận thông báo" checkbox with consent text version, Emailphone required and valid; consent optional but recorded
3 Lớp & góiLớp (search; filtered by branch; shows seats free / capacity, schedule, level; "Chờ xếp lớp" option), Ngày bắt đầu, Gói học phí (from PRD 05 fee plans of the course), Giảm giá (percent or amount, reason), Mã voucherclass capacity check; discount above limit shows "Cần duyệt" and creates approval request
4 Xác nhậnSummary; "Tạo hoá đơn ngay" toggle (default on); "Gửi thông báo nhập học" toggle

Result screen: three link cards Học viên · Lớp · Hoá đơn, and "Chuyển đổi tiếp" for siblings.

6Business rules

  1. One phone number is one lead per center; the duplicate check is mandatory on create and import (existing). Siblings are additional prospect_learners entries on the same lead, and conversion can run once per prospect.
  2. A lead may be converted at most once per prospect learner; the wizard hides prospects already converted and shows the student link.
  3. Trial bookings require a class session in the future and free trial seats (class.capacity_trial minus booked). Default trial seats per class = 2.
  4. Do-not-call blocks quick-log-call (except force), bulk ZNS and reminders; it does not block transactional messages to the guardian of an enrolled student.
  5. Lead retention: leads not won and without activity for 24 months are anonymised by a nightly job (PRD 12); a warning chip appears 30 days before.
  6. Won requires either a conversion or an explicit "Thắng không chuyển đổi" reason (e.g. enrolled at another branch outside the system).
  7. Stage automation (optional per pipeline): trial booked moves to "trial stage"; conversion moves to the terminal won status; no other automatic moves.

7Data model changes

EntityChangeFields
customer (Customer)extendprospect_learners jsonb [{key, name, birth_year, gender, school, level, converted_student_id}], marketing_consent_at, target_school_id (nullable; defaults to school_id)
trial_bookingnewid uuid; center_id; school_id; customer_id; prospect_key; class_id int64; session_id uuid (PRD 03); status enum(booked, attended, no_show, cancelled); teacher_note; consultant_id (sale_profile_id); notified_at; created_at; updated_at
placement_test (PlacementTest)extendsuggested_level text (computed), prospect_key
pipeline (PipelineItem)extendtrial_stage_id nullable, won_status_id (terminal), lost_status_id
conversionnewid; customer_id; prospect_key; student_id (PRD 02); guardian_id; enrollment_id null; tuition_id null; converted_by; created_at

8API contract

MethodPathRequest / responseStatus
GET/POST/PATCH/DELETE/v3/crm/customers[...]as today; prospect_learners, target_school_id accepted on create and updateextend
GET/v3/crm/interactions?interaction_type=call&call_status=callback_requested&status=callback queue; add query callback_from, callback_to, overdueextend
POST/v3/crm/customers/{id}/do-not-call{reason} → Customer; DELETE to clear (requires customers.force_call)new
POST/v3/crm/customers/{id}/transfer-school{school_id, assignee_id?, note} → Customernew
GET/v3/crm/trial-bookingsstatus, class_id, from, to, assignee_id, page, limit → TrialBooking[] with customer, class, sessionnew
POST/v3/crm/trial-bookings{customer_id, prospect_key, class_id, session_id, note, notify} → TrialBooking; 409 NO_TRIAL_SEATS / DUPLICATE_TRIALnew
PATCH/v3/crm/trial-bookings/{id}{session_id?, status?, teacher_note?}new
GET/POST/PATCH/v3/crm/placement-tests[...]as today; response adds suggested_levelextend
POST/v3/crm/customers/{id}/convert{prospect_key, learner: {learner_profile_id} | {display_name, birthday, gender, school_name}, school_id, guardian: {name, phone, relationship, email?, zalo_consent: bool, consent_version}, enrollment?: {class_id, start_date}, tuition?: {fee_plan_id, discount_pct?, discount_amount?, discount_reason?, voucher_code?, due_date}, notify: bool} → {student_id, guardian_id, enrollment_id?, tuition_id?, approval_request_id?}new
PATCH/v3/crm/pipelines/{id}add trial_stage_id, won_status_id, lost_status_idextend
GET/POST/PATCH/DELETE/v3/crm/sources, /v3/crm/channels (+reorder)used by settings pagesexisting

9Permissions

Codeownerbranch_manageradmissionsacademic_admin
customers.view (scope own/branch/all)allbranchown or teambranch read
customers.create, customers.edityesyesownno
customers.deleteyesyesnono
customers.force_callyesyesnono
customers.transferyesyesnono
customers.convertyesyesyesyes
trials.manageyesyesyesyes
crm.placement_tests.create (existing code)yesyesyesyes
pipeline.manage, config.manage (sources, channels, tags)yesnonono
finance.discount.approve (used by wizard)yesyesnono

10Analytics events

EventProperties
lead_createdsource, channel, has_prospect, via (form|import|api)
call_loggedcall_type, call_result, duration_bucket, from (quick|timeline|queue)
callback_queue_viewedoverdue_count
trial_booked / trial_attended / trial_no_showclass_level, days_from_lead_created
placement_test_created / placement_result_submittedauto (bool), suggested_level
lead_convertedwith_class, with_tuition, discount_pct_bucket, days_from_lead_created, trial_before (bool)
lead_won_without_conversionreason
lead_transferredfrom_school, to_school

11Edge cases

12Open questions

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