Skip to content

Type Deep Dive — Assignment And Distribution

1. Bức tranh tổng

CRM hiện có hai assignment engine độc lập:

  1. bulk assignment thủ công qua action assignMultipleTicket
  2. auto-distribution hằng ngày qua scheduler distribute_ticket

Chúng giống nhau ở chỗ đều chia ticket theo:

  • branch_id
  • target_id (telesales hoặc customer_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_user và cả account leaders,
  • tách danh sách telesalescustomer_service,
  • sort theo created_at desc,
  • gọi GetTicketAssignUpdates(...),
  • trả multiple_ticket_assigned.

2.3 Điểm đáng chú ý

FindingGiải thích
Leaders được append vào pool assignAction không chỉ lấy branch staff mà còn lấy leader accounts
customer_service pool có dấu hiệu buguserIDCustomerServices lại map từ userTelesales, không phải userCustomerServices
Không ghi ticket_distributeManual 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 telesalescustomer_service,
  • đọc ticket_distribute mới nhất theo branch_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_distribute khô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_to củ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_id vớ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

CaseRule thực tế
Non-admin non-BODluôn đi qua setSpecialDataByRole()
CRM leaderkhô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 ticketkhô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

  1. Manual bulk assign ticket telesalescustomer_service trong cùng branch để so assignee pool.
  2. Chạy/giả lập scheduler distribute_ticket để xác minh round-robin dùng ticket_distribute.
  3. So behavior trước và sau manual assign xem scheduler có tiếp tục fairness đúng không.
  4. Test với staff non-leader để xác minh bug visibility ở _or.concat(...).
  5. Test ticket unassigned quá hạn nhưng không có candidate user trong branch.

7. Rủi ro / Findings kỹ thuật

IDMứcFinding
AD-F01P1assignMultipleTicketdistribute_ticket nhìn giống nhau nhưng persist state khác nhau, dễ gây lệch expectation khi debug fairness.
AD-F02P1useTicketBuilder.ts có bug rõ ràng làm rơi điều kiện assignee/in-charge.
AD-F03P1assign_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-F04P2ticket_distribute chỉ giữ last-pointer, nên không đủ làm audit trail cho assignment history.