Appearance
Type Deep Dive — Assignment And Distribution
1. Bức tranh tổng
CRM hiện có hai assignment engine độc lập:
- bulk assignment thủ công qua action
assignMultipleTicket - auto-distribution hằng ngày qua scheduler
distribute_ticket
Chúng giống nhau ở chỗ đều chia ticket theo:
branch_idtarget_id(telesaleshoặccustomer_service)- danh sách user trong branch theo role
Nhưng chúng khác nhau ở persistence và fairness memory.
2. Bulk Assignment Từ UI
2.1 Contract
assignMultipleTicket(data: { ticket_ids[] }) là Hasura action được FE dùng để giao nhiều ticket cùng lúc (actions.graphql:51-55).
2.2 Logic backend
assign_ticket.go đang làm:
- query danh sách ticket theo ids,
- collect
branch_id, - query
branch_uservà cả account leaders, - tách danh sách
telesalesvàcustomer_service, - sort theo
created_at desc, - gọi
GetTicketAssignUpdates(...), - trả
multiple_ticket_assigned.
2.3 Điểm đáng chú ý
| Finding | Giải thích |
|---|---|
| Leaders được append vào pool assign | Action không chỉ lấy branch staff mà còn lấy leader accounts |
customer_service pool có dấu hiệu bug | userIDCustomerServices lại map từ userTelesales, không phải userCustomerServices |
Không ghi ticket_distribute | Manual assign không persist fairness pointer |
Bug thứ hai xuất hiện trực tiếp trong assign_ticket.go, nơi userIDCustomerServices đang lấy nhầm từ userTelesales.
3. Auto Distribution
3.1 Trigger và phạm vi
distribute_ticket chạy lúc 21:00 hằng ngày và lấy các ticket:
due_date < endOfDay(today)assignee_id is null
(distribute_ticket.go:31-45)
Nghĩa là đây không phải scheduler “giao ticket mới ngay lập tức”, mà là batch assign cho ticket chưa có người nhận khi đã đến/ngang hết ngày.
3.2 Fairness logic
Scheduler:
- gom branch users theo role,
- tách ticket
telesalesvàcustomer_service, - đọc
ticket_distributemới nhất theobranch_id + target_id, - round-robin từ assignee gần nhất,
- update ticket hàng loạt sang
status = assigned, - ghi lại assignee cuối cùng vào
ticket_distribute.
3.3 Persistence rule
Chỉ assignee cuối cùng của mỗi target/branch được insert vào ticket_distribute.
Hệ quả:
ticket_distributekhông phải assignment history đầy đủ,- nó chỉ là last-pointer để lần sau round-robin tiếp.
4. Visibility Và Filter Theo Role
TicketBuildWhere.setSpecialDataByRole() đang cố tạo rule:
- ticket theo
belong_tocủa role hiện tại, - unassigned ticket nếu user không phải leader,
- ticket assign trực tiếp cho user,
- ticket user đang
in_charge, - ticket theo
target_idvới assigned roles.
Nhưng do _or.concat(...) không gán lại, condition assignee/in-charge bị mất (useTicketBuilder.ts:162-175).
Điều này có thể làm:
- staff không thấy ticket đáng ra họ đang được giao,
- hoặc chỉ thấy pool unassigned/target-level thay vì owned tickets,
- khiến người đọc nhầm là Hasura permission sai trong khi bug nằm ở FE filter builder.
5. Leader / Staff Boundary
| Case | Rule thực tế |
|---|---|
| Non-admin non-BOD | luôn đi qua setSpecialDataByRole() |
| CRM leader | không bị gắn assignee_id is null trong nhánh belong_to |
| Assigned roles | được thêm nhánh target_id = mapped-role |
| Edit ticket | không chỉ dựa trên list visibility; detail form còn có rule riêng cho assignee/in-charge/leader |
List visibility và edit permission vì thế không đồng nghĩa với nhau.
6. QA Focus
- Manual bulk assign ticket
telesalesvàcustomer_servicetrong cùng branch để so assignee pool. - Chạy/giả lập scheduler
distribute_ticketđể xác minh round-robin dùngticket_distribute. - So behavior trước và sau manual assign xem scheduler có tiếp tục fairness đúng không.
- Test với staff non-leader để xác minh bug visibility ở
_or.concat(...). - Test ticket unassigned quá hạn nhưng không có candidate user trong branch.
7. Rủi ro / Findings kỹ thuật
| ID | Mức | Finding |
|---|---|---|
| AD-F01 | P1 | assignMultipleTicket và distribute_ticket nhìn giống nhau nhưng persist state khác nhau, dễ gây lệch expectation khi debug fairness. |
| AD-F02 | P1 | useTicketBuilder.ts có bug rõ ràng làm rơi điều kiện assignee/in-charge. |
| AD-F03 | P1 | assign_ticket.go có dấu hiệu bug khi customer_service pool lấy nhầm từ danh sách telesales, có thể làm giao sai người ở manual path. |
| AD-F04 | P2 | ticket_distribute chỉ giữ last-pointer, nên không đủ làm audit trail cho assignment history. |