Appearance
Module Overview — CRM Ticket Workflow
1. Scope
Flow này không nằm gọn trong một page hay một bảng. Nó cắt qua:
- frontend
crmroutes cho list, create, update, detail tabs, multiple create, - backend
crm-apiaction/event/scheduler, - Hasura metadata của domain
crm, - runtime call center qua
incall_call,incall_call_log,incall_extension, - join sang
account,appointment, labels và customer workspace.
Mental model nên giữ từ đầu:
ticketlà work item trung tâm,changeStatusTicketlà lifecycle engine thật,ticket_distributechỉ lưu last-assignee pointer cho auto-distribution,incall_*là runtime touchpoint, không chỉ là log phụ.
2. Bức tranh kiến trúc
text
Lead / hotline / scheduler / CRM staff action
-> create or update ticket
-> optional assign or unassign transition
-> optional complete transition via changeStatusTicket
-> optional Duplicate(...) tạo new ticket
-> ticket history / customer note / related people enrichment
-> reminder / distribution schedulers
Call center runtime
SIP / extension / incall_call_log
-> action secureIncall / callcenterCallData / callcenterCallOutEvent
-> FE newCallObj / initiateCall / ticket binding
-> update incall_call.ticket_id3. Frontend Surface
3.1 Route families
| Family | Route group | Boundary |
|---|---|---|
| Ticket list shell | /crm/ticket | Search, filters, quick action, call integration |
| Ticket create/update | /crm/ticket/create, /crm/ticket/:id/edit | Main save orchestration |
| Ticket detail shell | /crm/ticket/:id/:type | Detail tabs được gắn chặt vào route param |
| Detail tabs | ticket_note, detail, ticket_history, edit_history | Note, summary, lifecycle history, edit history |
| Multiple create | /crm/ticket/multiple-create | Batch creation surface |
| Hotline mapping | /crm/mapping-acc* | incall_extension mapping cho extension runtime |
3.2 Permission/runtime map thực tế
| Boundary | Gate hoặc runtime đáng chú ý |
|---|---|
| Ticket list | CrmRoles, nhưng visibility thực tế còn bị bó bởi TicketBuildWhere.setSpecialDataByRole() |
| Unassigned vs assigned edit | TicketDetailForm cho edit nếu unassigned, hoặc là assignee/in-charge, hoặc có leader role |
| Missed ticket | Có nhánh canEditMissingTicket riêng theo AssignedTicketRoles |
| Call center call-out | FE check localStorage extension trước khi bật initiateCall() |
| Detail shell | :type param quyết định tab, nên route semantics và UI semantics đang coupled chặt |
4. Backend Boundary Map
| Lớp | File chính | Vai trò |
|---|---|---|
| Action | crm-api/action/change_status_ticket.go | Validate transition và xử lý Duplicate(...) khi complete |
| Action | crm-api/action/assign_ticket.go | Bulk assign tickets theo branch + target role |
| Event | crm-api/event/ticket_insert.go | Enrich keywords, KPI log, related people |
| Event | crm-api/event/ticket_update.go | Customer note khi completed, enrich keywords, related people |
| Event | crm-api/event/ticket_interest_delete.go | Refresh service groups khi interest đổi |
| Scheduler | crm-api/scheduler/distribute_ticket.go | Auto-assign ticket chưa có assignee |
| Scheduler | crm-api/scheduler/consolidate_ticket_4.go | Batch synthesize source 4 tickets |
| Scheduler | crm-api/scheduler/consolidate_ticket_8.go | Batch synthesize source 8 tickets |
| Scheduler | crm-api/scheduler/remind_ticket_today.go | Notify assignee với ticket due/overdue |
| Scheduler | crm-api/scheduler/remind_ticket_tomorrow.go | Notify ticket có appointment ngày mai |
| Action | crm-api/action/secure_incall.go, callcenter_calldata.go, callcenter_callout.go | Hotline/callcenter runtime contracts |
5. Data Layer Map
| Layer | Object chính | Vai trò |
|---|---|---|
| Core workflow | ticket, ticket_history_log | Work item, history, edit trail |
| Result context | ticket_interest, ticket_campaign | Sản phẩm dịch vụ/campaign context của ticket |
| Assignment memory | ticket_distribute | Last assignee pointer cho auto round-robin |
| Hotline runtime | incall_call, incall_call_log, incall_extension, incall_call_contact_mapping | Call metadata, log, extension mapping |
| Knowledge / support | knowledge_question, knowledge_question_tag | Call center knowledge base |
| Customer relation | account.related_people | Side effect enrichment từ assignee / in-charge |
6. Semantic Rules Cần Nhớ
- Save ticket ở FE có thể kéo theo 2 mutation khác sau CRUD:
changeStatusTicketvàupdateIncallCall(TicketCreate.tsx:197-320). changeStatusTicketkhông đồng nghĩa “đổi status hiện tại”; nhánh complete có thể nhân bản ticket mới (change_status_ticket.go:49-57).- Source 4 và source 8 là batch-generated tickets qua cron, không phải manual create.
- Assignment thủ công và auto-distribution không dùng chung state pointer; chỉ scheduler mới persist vào
ticket_distribute. - Reminder jobs và source generation jobs đều là một phần của workflow thật, nên test lifecycle phải bao gồm cron side effects.
7. Scheduler / Metadata Snapshot
| Job / Action | Contract | Ghi chú |
|---|---|---|
consolidate_ticket_4 | cron 30 18 * * * | Source 4 synthesis |
consolidate_ticket_8 | cron 20 18 * * * | Birthday/revenue tickets |
distribute_ticket | cron 0 21 * * * | Auto-assign ticket unassigned quá hạn trong ngày |
remind_ticket_today | cron 0 1 * * * | Nhắc ticket due/overdue đang assigned |
remind_ticket_tomorrow | cron 30 9 * * * | Nhắc ticket gắn appointment ngày mai |
assignMultipleTicket | Hasura action | Bulk assignment từ UI |
changeStatusTicket | Hasura action | Transition engine |
callcenterCallData / callcenterCallOutEvent | Hasura action + REST endpoint | Push call log runtime |
secureIncall | Hasura action | Call initiation contract |
8. Rủi ro / Findings
| ID | Mức | Finding |
|---|---|---|
| F-01 | P1 | TicketBuildWhere.setSpecialDataByRole() có bug thật: _or.concat(...) không reassign, khiến condition assignee/in-charge bị rơi (useTicketBuilder.ts:162-175). |
| F-02 | P1 | FE create/update flow không thuần CRUD; nếu dev chỉ test mutation chính sẽ bỏ sót status transition và new_ticket_id chain (TicketCreate.tsx:223-320). |
| F-03 | P1 | Assignment engine đang tách đôi: manual bulk assign không persist ticket_distribute, còn scheduler có persist, nên fairness/history không đồng nhất. |
| F-04 | P1 | Source taxonomy nhìn như label UI, nhưng thực tế source 4 và 8 được materialize bằng cron job riêng. |
| F-05 | P2 | Call-center runtime phụ thuộc dữ liệu extension trong localStorage, nên environment/runtime mismatch có thể làm FE mất khả năng gọi dù permission đúng. |