Giao diện
Feature Inventory — SAPP-23954: Ticket Owner (Query & Refresh tickets · Check snapshot) ​
Kiểm kê tính năng đã triển khai, dựng từ code. Đầu vào cho FS as-built (Mode B). Mọi dòng có bằng chứng
file:dòng; không có bằng chứng → §8 Câu hỏi.Công thức truy vết:
ba/system-profile.md§3.
| Field | Value |
|---|---|
| Project | ba/projects/2026-SAPP-Works-tools |
| Mode | B – As-built |
| Ngày đọc code | 2026-09-30 |
| Commit | e77c47a |
| Điểm vào BA cung cấp | 2 sub-tool dưới /works/sapp/tools: sapp-23954 (sapp.008, "SAPP 23954: Query & Refresh tickets") và sapp-23954-snapshot (sapp.009, "SAPP 23954: Check snapshot"); API doc shared/api-docs/sapp.md § "SAPP-23954 tool" |
| Thành phần chạm tới | FE · BE (+ hệ thống ngoài HubSpot, Redis). APP: không có màn nào (Grep 23954 trong app/ không cần — không nằm trong điểm vào) |
| Status | Draft |
1. Phạm vi đã truy vết ​
- Trong phạm vi:
- Màn
/works/sapp/tools/sapp-23954— subtool 1 (truy vấn ticket), subtool 2 (làm mới hàng loạtcap_nhat_owner), lịch sử làm mới + "Tải lại" từng dòng, subtool 3 (tự kiểm tra thay đổi sau 15s). - Màn
/works/sapp/tools/sapp-23954-snapshot— subtool 4 (check snapshot: chạy thử chỉ-đọc chuỗi điều kiện gán owner cho 1 ticket, tại mọi mốccap_nhat_ownerđổi giá trị + "hiện tại"). - 12 route BE
/api/v1/sapp/{prod|dev}/sapp-23954/tickets/*, serviceSappCommonService(phần 23954), DTO, hằng số, mã lỗi, nơi lưu lịch sử (Redis).
- Màn
- Ngoài phạm vi (dừng truy vết tại đây):
- Cổng truy cập tool (ẩn / khoá / secret theo
codesapp.008 / sapp.009,ToolGatetrong shell) → nền tảng. - Cơ chế
@Public()/ guard toàn cục → nền tảng. - Lấy access token HubSpot từ
env_systems(getAccessToken) → nền tảng. HubSpotCommonService(search / getObject / updateObject / getPipelines / getOwners / getPropertyHistory) — chỉ ghi cách tool gọi, không đi sâu.- 2 lookup dùng lại của nhóm HubSpot: danh sách pipeline ticket (
GET .../hubspot/tickets/pipelines) và tuỳ chọncap_nhat_owner(GET .../hubspot/tickets/properties/:property) → nhóm HubSpot (sapp.001–004). - Automation phía HubSpot bị "kích hoạt lại" và logic gốc
assignTicketOwnerFromClassOwnerở reposapp-platform-lms-be— ngoài repo, không truy vết được.
- Cổng truy cập tool (ẩn / khoá / secret theo
2. Điểm vào (Entry points) ​
| ID | Loại | Tên / đường dẫn người dùng thấy | Bằng chứng |
|---|---|---|---|
| E-01 | Màn web | /works/sapp/tools/sapp-23954 — menu "SAPP 23954 - Ticket Owner" › "Query & Refresh tickets" (code sapp.008) | fe/app/works/sapp/tools/sapp-23954/page.tsx:12-14; fe/src/ui/pages/works/sapp/tools/registry.ts:86-89; fe/src/data/constants/tool-codes.ts:39; be/libs/database/migrations/1783700000000-create-tools.ts:13 |
| E-02 | Màn web | /works/sapp/tools/sapp-23954-snapshot — menu "SAPP 23954 - Ticket Owner" › "Check snapshot" (code sapp.009) | fe/app/works/sapp/tools/sapp-23954-snapshot/page.tsx:12-14; fe/src/ui/pages/works/sapp/tools/registry.ts:90-93; fe/src/data/constants/tool-codes.ts:40; be/libs/database/migrations/1783700000000-create-tools.ts:14 |
| E-03 | API | POST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/query | be/apps/api/src/sapp/sapp.controller.ts:267, :285 |
| E-04 | API | POST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-cap-nhat-owner | be/apps/api/src/sapp/sapp.controller.ts:321, :339 |
| E-05 | API | POST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/verify-changes | be/apps/api/src/sapp/sapp.controller.ts:373, :391 |
| E-06 | API | GET /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-history | be/apps/api/src/sapp/sapp.controller.ts:419, :434 |
| E-07 | API | POST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-history/:id/reload | be/apps/api/src/sapp/sapp.controller.ts:463, :480 |
| E-08 | API | POST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/check-snapshot | be/apps/api/src/sapp/sapp.controller.ts:515, :533 |
Ghép FE → URL: fe/src/data/constants/api.ts:451-466; service FE fe/src/data/services/sapp/sapp-23954.service.ts:47-145. Không có job/cron, webhook, MQTT hay event nào cho nhóm này (Grep 23954 chỉ trúng be/apps/api, be/libs, fe/).
3. Hành động người dùng / hệ thống ​
| ID | Ai (role) | Làm gì | Điểm vào | Kết quả quan sát được | Bằng chứng |
|---|---|---|---|---|---|
| A-01 | Người dùng tool (xem §6 Quyền) | Chọn môi trường Dev hoặc Prod bằng tab; mặc định Dev. Đổi tab thì dựng lại toàn bộ panel | E-01, E-02 | Mọi thao tác sau đó gọi route của portal đã chọn | fe/src/ui/components/works/sapp/sapp-23954-tool.tsx:58-61, :913, :957; fe/src/ui/components/works/sapp/sapp-23954-snapshot-tool.tsx:38-40, :618, :662 |
| A-02 | Hệ thống (FE) | Khi mở panel, tải danh sách pipeline ticket và danh sách tuỳ chọn cap_nhat_owner của portal để làm bộ lọc (bỏ tuỳ chọn bị ẩn) | E-01 → lookup nhóm HubSpot | Hai nhóm checkbox "Pipeline" và "Cập nhật owner" | sapp-23954-tool.tsx:447-476; be/apps/api/src/sapp/sapp.controller.ts:1128, :1194 |
| A-03 | Người dùng | Subtool 1 — Truy vấn ticket theo pipeline (nhiều), cap_nhat_owner (nhiều), có/chưa có owner, khoảng ngày tạo; bỏ trống = lấy tất cả | E-01 → E-03 | Bảng ticket (ID, tên, pipeline, cap_nhat_owner, Owner ID), "hiển thị N / tổng", sắp xếp ngày tạo mới nhất; truy vấn mới xoá lựa chọn và kết quả làm mới cũ | sapp-23954-tool.tsx:527-570, :644-647, :684-760; be/libs/modules/src/lib/sapp/common.service.ts:314-415 |
| A-04 | Người dùng | Bấm "Tải thêm" để lấy trang kế (100 ticket/trang) | E-01 → E-03 | Nối thêm dòng, giữ lựa chọn | sapp-23954-tool.tsx:66, :648-653, :861-875 |
| A-05 | Người dùng | Chọn ticket (từng dòng / "Chọn tất cả" — chỉ các dòng đã tải) | E-01 | Nút "Làm mới cap_nhat_owner (N)" bật khi N > 0 | sapp-23954-tool.tsx:673-678, :773-787, :814-834 |
| A-06 | Người dùng | Subtool 2 — Làm mới cap_nhat_owner hàng loạt; có hộp xác nhận "Sẽ reset về rỗng rồi khôi phục cap_nhat_owner cho N ticket trong portal {env}…" | E-01 → E-04 | Mỗi ticket: đọc giá trị hiện tại A → ghi rỗng lên HubSpot → ghi lại A. Hiện tổng "đã làm mới / bỏ qua / lỗi" + danh sách lý do; xoá lựa chọn | sapp-23954-tool.tsx:572-604, :655-671, :145-176; common.service.ts:909-1005 |
| A-07 | Hệ thống (BE) | Sau mỗi lượt làm mới, ghi thêm 1 dòng lịch sử cho mỗi ticket làm mới thành công vào Redis theo portal | E-04 | Danh sách lịch sử có thêm dòng (ticket, cap_nhat_owner trước → sau, owner trước → sau, thời gian) | common.service.ts:972-977, :989, :1016-1072 |
| A-08 | Hệ thống (FE) | Subtool 3 — tự kiểm tra thay đổi: nếu có ≥1 ticket làm mới thành công, đếm ngược 15 giây rồi tự gọi kiểm tra với baseline là giá trị lúc làm mới | E-01 → E-05 | Bảng "Kết quả kiểm tra thay đổi": cap_nhat_owner trước → sau, Owner ID trước → sau, "X/N có thay đổi", đánh dấu "không đọc được" | sapp-23954-tool.tsx:180-181, :584-590, :606-642, :209-274, :882-897; common.service.ts:1340-1407 |
| A-09 | Người dùng | Mở "Lịch sử làm mới" của portal đang chọn | E-01 → E-06 | Modal bảng lịch sử, mới nhất trước; "Chưa có lịch sử làm mới." khi rỗng | sapp-23954-tool.tsx:309-438, :747-754; common.service.ts:1078-1096 |
| A-10 | Người dùng | Bấm "Tải lại" trên 1 dòng lịch sử | E-01 → E-07 | BE đọc lại ticket trên HubSpot, ghi đè tên ticket, cap_nhat_owner "sau" và owner "sau" của dòng đó bằng giá trị hiện tại, lưu lại Redis; FE thay dòng đó | sapp-23954-tool.tsx:326-354, :414-427; common.service.ts:1214-1278 |
| A-11 | Người dùng | Subtool 4 — Check snapshot: nhập 1 Ticket ID (Enter hoặc nút "Kiểm tra"); chú thích "Chạy thử (chỉ đọc)… Không thay đổi gì trên HubSpot" | E-02 → E-08 | Danh sách "mốc đánh giá — X dừng, Y sẽ chạy"; hai kiểu xem "Theo mốc" / "Dòng thời gian"; mỗi mốc: kết luận Sẽ chạy / Dừng, bước dừng, lý do, giá trị ticket, contact (có email, SĐT), các bước | sapp-23954-snapshot-tool.tsx:533-613, :479-529, :203-288, :345-346; common.service.ts:689-897 |
| A-12 | Hệ thống (BE) | Với snapshot: đọc ticket + contact đầu tiên gắn ticket + lịch sử thuộc tính của cả hai; lập 1 mốc cho mỗi lần cap_nhat_owner đổi giá trị (bỏ giá trị rỗng, gộp giá trị lặp liên tiếp), giá trị các thuộc tính lấy "tại thời điểm" đó; thêm mốc "hiện tại" cuối | E-08 | snapshots[] cũ → mới, mốc "hiện tại" cuối | common.service.ts:696-705, :763-786, :827-888, :658-672 |
4. Dữ liệu ​
Không có bảng DB / entity nào cho nhóm này (Grep 23954|refreshHistory trong be/libs/database/src/lib/entities/ = 0 kết quả). Dữ liệu gồm (a) thuộc tính HubSpot được đọc/ghi, (b) danh sách lịch sử trong Redis.
(a) Thuộc tính HubSpot
| Entity / bảng | Field | Type | Bắt buộc | Mặc định / enum | Bằng chứng |
|---|---|---|---|---|---|
| HubSpot ticket | subject (tên ticket) | string | — | — | be/libs/constants/src/lib/sapp/sapp-23954.constants.ts:12 |
| HubSpot ticket | hs_pipeline (pipeline) | string (id) | — | — | sapp-23954.constants.ts:13 |
| HubSpot ticket | cap_nhat_owner — thuộc tính duy nhất tool ghi | dropdown | — | 5 giá trị trong code: "Xử lý ticket thông thường", "Xử lý ticket chào mừng", "Cập nhật thành công", "Cập nhật thất bại", "Rotate Ticket Owner" | sapp-23954.constants.ts:14, :88-94 |
| HubSpot ticket | hubspot_owner_id (owner) | string (id) | — | — | sapp-23954.constants.ts:15 |
| HubSpot ticket | createdate | datetime | — | — | sapp-23954.constants.ts:16 |
| HubSpot ticket | phan_loai_yeu_cau_dich_vu_test (chỉ snapshot đọc) | dropdown | — | 36 giá trị "phân loại yêu cầu dịch vụ" hợp lệ | sapp-23954.constants.ts:53-54, :133-170 |
| HubSpot ticket | ma_lop_hv_can_ho_tro (chỉ snapshot đọc) | string | — | — | sapp-23954.constants.ts:55-56 |
| HubSpot contact | firstname, email, phone (chỉ snapshot đọc — hiển thị) | string | — | — | sapp-23954.constants.ts:59-61 |
| HubSpot contact | van_de_can_duoc_ho_tro (chỉ snapshot đọc) | dropdown | — | "Phản hồi về trải nghiệm học tập", "Hỗ trợ dịch vụ học tập khác" | sapp-23954.constants.ts:62-63, :173-176 |
| HubSpot contact | yeu_cau_dich_vu_noi_bo (chỉ snapshot đọc) | dropdown | — | "Bổ sung học viên vào lớp sau khai giảng" | sapp-23954.constants.ts:64-65, :179-181 |
(b) Lịch sử làm mới — Redis key sapp:23954:refresh-history:{env}, TTL 7 ngày (cả danh sách)
| Entity / bảng | Field | Type | Bắt buộc | Mặc định / enum | Bằng chứng |
|---|---|---|---|---|---|
| Refresh-history item | id | UUID | Có | sinh ngẫu nhiên khi ghi | be/libs/interface/src/lib/dto/sapp/sapp-23954-ticket.dto.ts:173-177; common.service.ts:1051 |
| Refresh-history item | ticketId | string | Có | — | sapp-23954-ticket.dto.ts:179-180 |
| Refresh-history item | ticketName | string | null | Không | tên lúc làm mới; bị ghi đè khi "Tải lại" | sapp-23954-ticket.dto.ts:182-183; common.service.ts:1249-1251 |
| Refresh-history item | prevCapNhatOwner | string | null | Không | = latestCapNhatOwner của dòng gần nhất cùng ticket; null lần đầu | sapp-23954-ticket.dto.ts:185-190; common.service.ts:1033-1045, :1054 |
| Refresh-history item | latestCapNhatOwner | string | null | Không | giá trị đã khôi phục; bị ghi đè bằng giá trị hiện tại khi "Tải lại" | sapp-23954-ticket.dto.ts:192-195; common.service.ts:1055, :1252-1253 |
| Refresh-history item | prevTicketOwner / latestTicketOwner | string | null | Không | dạng "{tên} ({id})", chỉ id nếu không tra được tên, null nếu chưa gán | sapp-23954-ticket.dto.ts:197-209; common.service.ts:1296-1303, :1056-1057, :1254-1257 |
| Refresh-history item | refreshedAt | ISO datetime | Có | cùng 1 thời điểm cho cả lượt | sapp-23954-ticket.dto.ts:211-212; common.service.ts:1047, :1058 |
| (key) | sapp:23954:refresh-history:{env} | Redis list (JSON array) | — | TTL 7 × 24h ms | sapp-23954.constants.ts:38-42; be/libs/services/src/lib/redis/cache-common.service.ts:27-29 |
(c) Request chính
| Entity / bảng | Field | Type | Bắt buộc | Mặc định / enum | Bằng chứng |
|---|---|---|---|---|---|
| Query body | pipelines, capNhatOwner | string[] | Không | khớp bất kỳ (IN) | sapp-23954-ticket.dto.ts:16-26 |
| Query body | noOwner | boolean | Không | true = có owner, false = chưa có owner | sapp-23954-ticket.dto.ts:28-34 |
| Query body | createdFrom / createdTo | Date | Không | ≥ / ≤ | sapp-23954-ticket.dto.ts:36-44 |
| Query body | limit | number | Không | mặc định 100, 1–200 | sapp-23954-ticket.dto.ts:46-52; sapp-23954.constants.ts:28-31 |
| Query body | after | string | Không | con trỏ trang HubSpot | sapp-23954-ticket.dto.ts:54-57 |
| Refresh body | ticketIds | string[] | Có | không giới hạn số phần tử, cho phép mảng rỗng | sapp-23954-ticket.dto.ts:114-120; be/libs/interface/src/lib/dto/decorators/dto.decorator.ts:156-189 |
| Verify body | tickets[] {ticketId bắt buộc, previousCapNhatOwner, previousTicketOwner tuỳ chọn} | array | Có | — | sapp-23954-ticket.dto.ts:229-258 |
| Snapshot body | ticketId | string | Có | — | sapp-23954-ticket.dto.ts:299-302 |
5. Rule & validation tìm thấy ​
| ID | Rule (ngôn ngữ nghiệp vụ) | Loại | Áp dụng ở | Bằng chứng | Nghi là bug? |
|---|---|---|---|---|---|
| R-01 | Mọi bộ lọc truy vấn đều tuỳ chọn và kết hợp VÀ; trong 1 bộ lọc nhiều giá trị thì khớp bất kỳ. Không lọc gì = lấy mọi ticket của portal | điều kiện | A-03 | common.service.ts:322-342, :364 | Không |
| R-02 | Bộ lọc owner: "Có owner" gửi noOwner=true → chỉ ticket có owner; "Chưa có owner" gửi noOwner=false → chỉ ticket chưa có owner. Tên field ngược nghĩa với hành vi | điều kiện | A-03 | common.service.ts:337-342; sapp-23954-tool.tsx:531-533; sapp-23954-ticket.dto.ts:28-34 | ⚠️ Observed — tên gây hiểu nhầm (→ Q-05) |
| R-03 | Khoảng ngày tạo tính trọn ngày theo UTC: "từ ngày" = 00:00:00.000Z, "đến ngày" = 23:59:59.999Z; ô "từ" không được sau ô "đến" | điều kiện / validation | A-03 | sapp-23954-tool.tsx:534-535, :717-729; common.service.ts:343-356 | ⚠️ Observed — lệch 7 giờ so với giờ VN (→ Q-06) |
| R-04 | Mỗi trang tối đa 200 ticket (HubSpot giới hạn), mặc định 100; FE luôn xin 100 | giới hạn | A-03, A-04 | sapp-23954.constants.ts:27-31; common.service.ts:358-361; sapp-23954-tool.tsx:66, :528 | Không |
| R-05 | Kết quả luôn sắp theo ngày tạo mới nhất trước | điều kiện | A-03 | common.service.ts:365-370 | Không |
| R-06 | Tên pipeline tra từ danh sách pipeline ticket của portal; không tra được thì hiển thị id | công thức | A-03 | common.service.ts:376-394; sapp-23954-tool.tsx:843 | Không |
| R-07 | "Chọn tất cả" chỉ chọn các ticket đã tải trên màn, không phải toàn bộ total | giới hạn | A-05 | sapp-23954-tool.tsx:673-678 | Không |
| R-08 | Làm mới chỉ chạy khi đã chọn ≥1 ticket và người dùng bấm "Làm mới" trong hộp xác nhận | validation / xác nhận | A-06 | sapp-23954-tool.tsx:655-671, :776 | Không |
| R-09 | Không giới hạn số ticket mỗi lượt làm mới (không có min/max ở DTO, không chia batch); BE xử lý tuần tự từng ticket, mỗi ticket 3 lời gọi HubSpot (1 đọc + 2 ghi) trong cùng 1 request HTTP | giới hạn | A-06 | sapp-23954-ticket.dto.ts:114-120; common.service.ts:925-987 | ⚠️ Observed — rủi ro timeout / rate-limit với lượt lớn (→ Q-08) |
| R-10 | Ticket có cap_nhat_owner đang rỗng → bỏ qua (lý do "cap_nhat_owner is empty"), không ghi gì | điều kiện | A-06 | common.service.ts:944-947 | Không |
| R-11 | "Làm mới" = ghi cap_nhat_owner = rỗng rồi ghi lại đúng giá trị cũ, không đổi giá trị cuối; mục đích (theo chú thích code) là khiến automation HubSpot bắt sự kiện đổi giá trị chạy lại | trạng thái | A-06 | common.service.ts:899-903, :949-959 | Không |
| R-12 | Nếu bước ghi lại thất bại → ticket vào "lỗi" với lý do "Failed to restore cap_nhat_owner (left empty)" — ticket bị bỏ rỗng trên HubSpot, không có thử lại/hoàn tác | trạng thái | A-06 | common.service.ts:953-969 | ⚠️ Observed — để lại dữ liệu sai trên prod (→ Q-09) |
| R-13 | Mỗi ticket xử lý độc lập: 1 ticket lỗi không dừng cả lượt; lỗi đọc/ghi từng ticket (kể cả thiếu token portal) được đưa vào "lỗi" chứ không trả lỗi cho cả request | điều kiện | A-06 | common.service.ts:926, :978-986 | Không (xem DR-03) |
| R-14 | Tên owner được tra từ danh sách owner của portal ("Tên Họ", không có thì email); tra lỗi thì chỉ dùng id | công thức | A-06, A-10 | common.service.ts:1281-1319 | Không |
| R-15 | Lịch sử chỉ ghi ticket làm mới thành công; bỏ qua/lỗi không ghi. Ghi lịch sử lỗi thì chỉ log, không báo lỗi cho người dùng | điều kiện | A-07 | common.service.ts:971-977, :1025, :1067-1071 | Không |
| R-16 | "Trước" của 1 dòng lịch sử = "sau" của dòng gần nhất cùng ticket (kể cả khi dòng đó đã bị "Tải lại" ghi đè); lần đầu = trống | công thức | A-07 | common.service.ts:1033-1045, :1049 | Không |
| R-17 | Lịch sử lưu riêng từng portal (prod / dev), hạn 7 ngày tính lại cho cả danh sách mỗi lần ghi (làm mới hoặc "Tải lại"); không có giới hạn số dòng, không xoá từng dòng cũ | giới hạn | A-07, A-10 | sapp-23954.constants.ts:38-42; common.service.ts:1062-1066, :1261-1265 | ⚠️ Observed — danh sách chỉ hết hạn nếu 7 ngày không ai dùng; có thể phình vô hạn (→ Q-11) |
| R-18 | Danh sách lịch sử trả về mới nhất trước (lưu theo thứ tự ghi, đảo khi đọc) | công thức | A-09 | common.service.ts:1086 | Không |
| R-19 | "Tải lại" không tìm thấy id dòng → lỗi 404 SAPP_23954_REFRESH_HISTORY_ITEM_NOT_FOUND; "Tải lại" chỉ sửa tên, "sau" cap_nhat_owner, "sau" owner — giữ nguyên "trước" và refreshedAt | validation / trạng thái | A-10 | common.service.ts:1225-1231, :1247-1259; be/libs/constants/src/lib/error-codes/error-codes.constants.ts:664-667 | Không |
| R-20 | Kiểm tra thay đổi chạy ngay khi gọi; việc chờ 15 giây là của FE. Chỉ tự chạy khi có ≥1 ticket làm mới thành công; không có nút chạy lại thủ công | điều kiện | A-08 | sapp-23954-tool.tsx:180-181, :584-590, :631-642; common.service.ts:1332-1339 | Không |
| R-21 | Cờ "có thay đổi" = so baseline với giá trị đọc lại; không có baseline hoặc ticket không đọc được → "không xác định" (null). FE gửi owner baseline = "không có" khi ticket chưa có owner lúc làm mới → ticket chưa có owner mà automation gán owner mới sẽ không được đếm là "có thay đổi" | công thức | A-08 | common.service.ts:1321-1330; sapp-23954-tool.tsx:614-618, :216-218 | ⚠️ Observed — bỏ sót đúng ca "gán owner lần đầu" (→ Q-10) |
| R-22 | Snapshot: bước cap_nhat_owner — chỉ đi tiếp khi giá trị là "Xử lý ticket thông thường" hoặc "Xử lý ticket chào mừng"; khác → dừng "cap_nhat_owner không thuộc nhóm được xử lý." | điều kiện | A-11 | common.service.ts:487-509 | Không |
| R-23 | Snapshot: bước pipeline — ticket "thông thường" phải thuộc 8 pipeline prod (hoặc 1 pipeline dev); ticket "chào mừng" phải thuộc 1 pipeline prod "Learner Class Operation" (hoặc cùng pipeline dev). Danh sách pipeline cứng trong code, sao chép từ repo gốc | điều kiện | A-11 | common.service.ts:511-531; sapp-23954.constants.ts:96-127 | Không (→ Q-13) |
| R-24 | Snapshot: bước contact — ticket phải có contact gắn kèm (lấy contact đầu tiên); đọc contact lỗi → bước "failed" và dừng | điều kiện | A-11 | common.service.ts:533-587, :782-783 | Không |
| R-25 | Snapshot: ticket "chào mừng" bỏ qua bước điều kiện rule → kết luận "Sẽ chạy" | điều kiện | A-11 | common.service.ts:589-602 | Không |
| R-26 | Snapshot: ticket "thông thường" chỉ "Sẽ chạy" khi thoả ít nhất 1 trong: (cond1) phân loại yêu cầu dịch vụ thuộc 36 giá trị hợp lệ; (cond2) "vấn đề cần được hỗ trợ" của contact thuộc 2 giá trị; (cond3) "yêu cầu dịch vụ nội bộ" = "Bổ sung học viên vào lớp sau khai giảng". Vế thứ 4 "(cond1 VÀ có mã lớp HV cần hỗ trợ)" thừa — đã bị cond1 bao hàm, mã lớp không ảnh hưởng kết quả | công thức | A-11 | common.service.ts:604-655 | ⚠️ Observed (→ Q-12) |
| R-27 | Snapshot: mốc lịch sử = mỗi lần cap_nhat_owner đổi sang giá trị khác rỗng và khác giá trị giữ trước đó (nên 1 lần "làm mới" rỗng→A không tạo mốc mới); giá trị thuộc tính tại mốc lấy từ lịch sử thuộc tính; contact luôn là contact hiện tại (không du hành thời gian liên kết) | công thức | A-12 | common.service.ts:417-422, :827-870 | Không (→ Q-14) |
| R-28 | Snapshot: không đọc được ticket → trả 1 mốc "failed" (không báo lỗi HTTP); không đọc được lịch sử ticket → chỉ còn mốc "hiện tại"; không tra được tên pipeline → vẫn chạy | điều kiện | A-11, A-12 | common.service.ts:698-741, :749-761, :766-778 | Không (xem DR-04) |
| R-29 | Snapshot chỉ đọc: không gọi hàm ghi HubSpot nào, không truy vấn DB lớp, dừng trước bước tra class owner | trạng thái | A-11 | common.service.ts:674-688, :689-897 (không có updateObject); sapp-23954.constants.ts:44-50 | Không |
| R-30 | Email / SĐT contact trong snapshot chỉ hiển thị, không được xuất file; FE không có nút xuất/sao chép | quyền dữ liệu | A-11 | sapp-23954-ticket.dto.ts:340-348; common.service.ts:687; sapp-23954-snapshot-tool.tsx:287-288 | Không |
| R-31 | Snapshot: nút "Kiểm tra" chỉ bật khi Ticket ID (đã bỏ khoảng trắng) khác rỗng | validation | A-11 | sapp-23954-snapshot-tool.tsx:550-553, :575 | Không |
| R-32 | Chỉ 2 portal: prod và dev (portal chọn qua đoạn path). Có chạy trên prod, kể cả ghi hàng loạt; không có chặn/ xác nhận bổ sung riêng cho prod ngoài hộp xác nhận chung (có ghi tên portal) | điều kiện | A-01, A-06 | be/apps/api/src/sapp/sapp.controller.ts:321-337; sapp-23954-tool.tsx:58-61, :661 | Không (→ Q-03) |
| R-33 | Chuyển tab Dev/Prod dựng lại panel → mất bộ lọc, kết quả, lựa chọn, đếm ngược kiểm tra của tab trước (chú thích code lại nói "mỗi tab giữ riêng") | trạng thái | A-01 | sapp-23954-tool.tsx:955-957; sapp-23954-snapshot-tool.tsx:662 | ⚠️ Observed (→ Q-15) |
Trạng thái cap_nhat_owner của 1 ticket trong subtool 2:
text
[A (≠ rỗng)] --ghi ""--> [RỖNG] --ghi A thành công--> [A] → processed (+ 1 dòng lịch sử)
\--ghi A thất bại--> [RỖNG] → failed "left empty"
[RỖNG] ----------------------------------------------> [RỖNG] → skipped
(automation HubSpot sau đó có thể đổi tiếp cap_nhat_owner / owner — ngoài hệ thống, subtool 3 đo lại sau 15s)Kết quả mỗi mốc snapshot:
text
source → cap_nhat_owner --(không phải thông thường/chào mừng)--> DỪNG
→ pipeline --(ngoài danh sách)--> DỪNG
→ contact-association --(không có contact)--> DỪNG
→ fetch-contact --(đọc lỗi)--> DỪNG (failed)
→ rule-eligibility --(chào mừng)--> SẼ CHẠY (skipped)
--(thông thường, cond1|cond2|cond3)--> SẼ CHẠY
--(không thoả)--> DỪNG6. Quyền, tích hợp, thông báo, job ​
| Loại | Chi tiết | Bằng chứng |
|---|---|---|
| Quyền (API) | Cả 12 route đều @Public() — không cần đăng nhập / permission, kể cả route ghi hàng loạt lên prod. Doc ghi "Public (temporary)". Cơ chế guard → nền tảng | be/apps/api/src/sapp/sapp.controller.ts:268, :286, :322, :340, :374, :392, :420, :435, :464, :481, :516, :534; shared/api-docs/sapp.md:365 |
| Quyền (màn) | Ẩn / khoá / secret theo code sapp.008 / sapp.009 qua ToolGate của shell → nền tảng | fe/app/works/sapp/tools/sapp-23954/page.tsx:9-11; fe/app/works/sapp/tools/sapp-23954-snapshot/page.tsx:9-11; fe/src/ui/pages/works/sapp/tools/shell.tsx:427 |
| Tích hợp ngoài | HubSpot CRM (portal prod / dev): Search tickets, đọc ticket/contact (+ association contacts), ghi ticket (cap_nhat_owner), đọc pipelines, owners, lịch sử thuộc tính. Token lấy từ env_systems → nền tảng; thiếu token → HUBSPOT_ENV_NOT_CONFIGURED | common.service.ts:376-379, :927-959, :699-705, :751-754, :768-773, :789-806, :1311; be/libs/modules/src/lib/hubspot/common.service.ts:47-58, :917-981 |
| Tích hợp ngoài | Redis (qua cache service) lưu lịch sử làm mới | common.service.ts:1027-1031, :1062-1066; be/libs/services/src/lib/redis/cache-common.service.ts:23-29 |
| Thông báo (mail / push / MQTT / Discord) | Không có. Chỉ thông báo trên màn (hộp thành công / lỗi / xác nhận) | sapp-23954-tool.tsx:73-92, :591-601, :658-670 |
| Job / cron | Không có. Đếm ngược 15s là timer trên trình duyệt | sapp-23954-tool.tsx:631-642 |
| Mã lỗi | 13022 truy vấn · 13023 làm mới · 13024 kiểm tra · 13025 không thấy dòng lịch sử (404) · 13026 tải lại · 13028 snapshot. Đọc lịch sử lỗi dùng lại 13022 (mã của truy vấn) | be/libs/constants/src/lib/error-codes/error-codes.constants.ts:649-682; common.service.ts:1091-1094 |
7. Drift — tài liệu nói khác code ​
| ID | Tài liệu (file + section) | Tài liệu nói | Code đang làm | Bằng chứng |
|---|---|---|---|---|
| DR-01 | shared/api-docs/sapp.md:395, :425, :472, :508-509, :579 (ví dụ) | Giá trị cap_nhat_owner ví dụ là "Đã cập nhật" / "Chưa cập nhật" | Hằng số code có 5 giá trị khác hẳn ("Xử lý ticket thông thường", "Xử lý ticket chào mừng", "Cập nhật thành công", "Cập nhật thất bại", "Rotate Ticket Owner") | sapp-23954.constants.ts:88-94 |
| DR-02 | shared/api-docs/sapp.md:692 (ví dụ snapshot) | Mốc "now" với cap_nhat_owner "Xử lý ticket chào mừng", pipeline 877604025 → willProceed: true | Pipeline chào mừng hợp lệ ở prod chỉ có 898314365; với 877604025 code sẽ dừng ở bước pipeline | sapp-23954.constants.ts:118-127; common.service.ts:512-524 |
| DR-03 | shared/api-docs/sapp.md:485, :614 | Lỗi HUBSPOT_ENV_NOT_CONFIGURED (thiếu token) được ném ra ở refresh / verify ("systemic failure (e.g. missing token)") | Thiếu token làm lỗi từng ticket và bị bắt trong vòng lặp → trả 201 với mọi ticket vào failed (refresh) hoặc found:false (verify); tra owner lỗi cũng bị nuốt | common.service.ts:923, :926-986, :1305-1319, :1346-1393; be/libs/modules/src/lib/hubspot/common.service.ts:917-924 |
| DR-04 | shared/api-docs/sapp.md:714 | Thiếu token ở check-snapshot ném HUBSPOT_ENV_NOT_CONFIGURED (12001) | Lời gọi đầu tiên (đọc ticket) bắt mọi lỗi → trả 1 mốc fetch-ticket "failed", không ném 12001 | common.service.ts:697-741 |
| DR-05 | Swagger trong code be/apps/api/src/sapp/sapp.controller.ts:466-467 (reload) | Reload chỉ ghi lại subject và cap_nhat_owner | Code còn ghi đè latestTicketOwner (doc sapp.md:529 thì đúng) | common.service.ts:1254-1257 |
| DR-06 | Swagger be/apps/api/src/sapp/sapp.controller.ts:423-424 (refresh-history) | Mỗi dòng có ticketId, ticketName, prevCapNhatOwner, latestCapNhatOwner | Còn có id, prevTicketOwner, latestTicketOwner, refreshedAt | sapp-23954-ticket.dto.ts:173-213 |
| DR-07 | shared/api-docs/sapp.md:491 | "TTL 7 days" (không nói rõ phạm vi) | TTL áp cho cả danh sách và gia hạn lại mỗi lần ghi; từng dòng không có hạn riêng | common.service.ts:1062-1066, :1261-1265 |
| DR-08 | FE type fe/src/data/models/sapp-23954.interface.ts:15, :19, :210; fe/src/data/models/sapp-hubspot.interface.ts:11; fe/src/data/constants/api.ts:452-466 | ticketName / capNhatOwner là string bắt buộc; env gồm cả staging | BE trả null được; BE không có route staging cho 23954 (chỉ prod/dev). UI hiện không hiện tab staging nên chưa gây lỗi | sapp-23954-ticket.dto.ts:74-84; be/apps/api/src/sapp/sapp.controller.ts:267-534 |
| DR-09 | Chú thích fe/src/ui/components/works/sapp/sapp-23954-tool.tsx:955 | "mỗi tab giữ riêng bộ lọc, kết quả, lựa chọn" | key={env} dựng lại panel → trạng thái tab cũ bị huỷ | sapp-23954-tool.tsx:957 |
8. Câu hỏi cho BA (code không trả lời được) ​
| ID | Câu hỏi | Vì sao cần | Chặn bước sau? |
|---|---|---|---|
| Q-01 | Mục đích nghiệp vụ của SAPP-23954: vấn đề gì khiến cần "reset + khôi phục" cap_nhat_owner? Automation HubSpot nào được kích hoạt lại và kỳ vọng kết quả là gì (gán owner theo class owner)? | Viết BR / US Statement; code chỉ nói "re-trigger automations" | Có |
| Q-02 | Ai được dùng 2 tool này (vai trò)? Có chủ đích để API @Public() ("temporary") — kể cả route ghi hàng loạt lên HubSpot prod — hay là rủi ro cần chặn? | Viết mục Quyền / rủi ro bảo mật | Có |
| Q-03 | Chạy trên prod là mục đích chính hay chỉ dev để thử? Có cần bước xác nhận riêng/mạnh hơn cho prod (hiện chỉ có 1 hộp xác nhận chung ghi tên portal)? | BR về kiểm soát thao tác prod | Có |
| Q-04 | Doc nói "Four subtools", menu chia 2 mục (sapp.008 gồm subtool 1–3, sapp.009 là subtool 4). BA muốn FS tách theo sub-tool màn hay theo subtool nghiệp vụ? | Cấu trúc FS | Không |
| Q-05 | noOwner=true lại nghĩa là "có owner" — cố ý (giữ tương thích) hay bug đặt tên? | Quyết định ghi BR hay OQ | Không |
| Q-06 | Lọc ngày tạo theo UTC (lệch 7h so với giờ VN: ticket tạo 00:00–06:59 giờ VN bị tính vào ngày trước) — cố ý hay bug? | Rule lọc ngày chính xác | Có |
| Q-07 | Các giá trị cap_nhat_owner nào là hợp lệ để làm mới? Hiện tool làm mới bất kỳ giá trị khác rỗng (kể cả "Cập nhật thành công"/"Cập nhật thất bại") — có cần giới hạn không? | BR điều kiện làm mới | Không |
| Q-08 | Có giới hạn số ticket mỗi lượt làm mới không (hiện không giới hạn, xử lý tuần tự trong 1 request)? Lượt lớn nhất từng chạy là bao nhiêu, có từng timeout / bị HubSpot rate-limit? | NFR / giới hạn | Có |
| Q-09 | Khi khôi phục thất bại ticket bị bỏ rỗng cap_nhat_owner trên prod — đã từng xảy ra chưa, ai xử lý, có cần tự thử lại / hoàn tác? Cố ý hay thiếu sót? | Quyết định BR hay OQ/rủi ro | Có |
| Q-10 | Kiểm tra thay đổi không đánh dấu "có thay đổi" cho ticket chưa có owner lúc làm mới mà sau đó được gán owner (baseline owner bị gửi thành "không có") — cố ý hay bug? Đây có phải chính ca nghiệp vụ cần đo? | Rule so sánh ở subtool 3 | Có |
| Q-11 | Lịch sử làm mới giữ bao lâu là đúng ý đồ: 7 ngày kể từ lần ghi cuối của cả danh sách (hiện tại) hay 7 ngày mỗi dòng? Có cần giới hạn số dòng / lưu bền (DB) không? | BR lưu trữ lịch sử | Không |
| Q-12 | Điều kiện rule `(cond1 | cond2) | |
| Q-13 | Danh sách pipeline, giá trị enum cap_nhat_owner / phân loại dịch vụ cứng trong code, "cloned verbatim" từ sapp-platform-lms-be. Ai chịu trách nhiệm đồng bộ khi repo gốc đổi? Bản sao hiện còn khớp không (ngày sao chép)? | Độ tin cậy của subtool 4 | Có |
| Q-14 | Snapshot giả định contact gắn ticket không đổi theo thời gian và luôn lấy contact đầu tiên — chấp nhận được về nghiệp vụ không (ticket có nhiều contact)? | Giới hạn đã biết ghi vào FS | Không |
| Q-15 | Đổi tab Dev/Prod làm mất toàn bộ trạng thái (kể cả đếm ngược kiểm tra đang chạy) trong khi chú thích nói "mỗi tab giữ riêng" — cố ý hay bug? | Quyết định BR hay OQ | Không |
| Q-16 | Đọc lịch sử lỗi trả mã 13022 ("Failed to query SAPP-23954 tickets") thay vì mã riêng — cố ý hay sót? | Bảng mã lỗi trong FS | Không |
| Q-17 | Tool snapshot có cần hiển thị email / SĐT contact không (quy định: chỉ hiển thị, không xuất file) — có đủ tối thiểu hoá dữ liệu? | Mục dữ liệu nhạy cảm | Không |
9. Nhật ký nguồn đã đọc ​
| File / section | Mục đích |
|---|---|
ba/_workflow/templates/feature-inventory/Template.md | Khung §1–§9 |
ba/system-profile.md §3 (dòng 51–66) | Công thức truy vết |
ba/projects/2026-SAPP-Works-tools/project-context.md | Phạm vi dự án, tên file nhóm |
fe/app/works/sapp/tools/sapp-23954/page.tsx, fe/app/works/sapp/tools/sapp-23954-snapshot/page.tsx | Route màn, chú thích gate |
fe/src/ui/pages/works/sapp/tools/registry.ts:84-96 | Menu nhóm 23954 |
fe/src/ui/pages/works/sapp/tools/shell.tsx (Grep ToolGate) | Chỉ xác định điểm dừng → nền tảng |
fe/src/data/constants/tool-codes.ts:39-40 | Code sapp.008 / sapp.009 |
fe/src/data/constants/api.ts:451-466 | URL FE |
fe/src/data/services/sapp/sapp-23954.service.ts (toàn bộ) | Service FE |
fe/src/data/services/sapp/pipeline.service.ts, get-property.service.ts (Grep) | Lookup dùng lại (nhóm HubSpot) |
fe/src/data/models/sapp-23954.interface.ts, sapp-hubspot.interface.ts:11 | Type FE, env |
fe/src/ui/components/works/sapp/sapp-23954-tool.tsx (toàn bộ) | Màn subtool 1–3 + lịch sử |
fe/src/ui/components/works/sapp/sapp-23954-snapshot-tool.tsx (Grep + dòng 479–666) | Màn subtool 4 |
be/apps/api/src/sapp/sapp.controller.ts:150-170, :252-548, Grep tickets/pipelines, tickets/properties | Controller, decorator quyền |
be/libs/modules/src/lib/sapp/common.service.ts:300-1407 (phần 23954) | Service nghiệp vụ |
be/libs/modules/src/lib/hubspot/common.service.ts:40-58, :917-1000 | Cách lỗi token / HubSpot được ném |
be/libs/interface/src/lib/dto/sapp/sapp-23954-ticket.dto.ts (toàn bộ) | DTO |
be/libs/interface/src/lib/dto/decorators/dto.decorator.ts:156-189, :336-374 | Validation mảng / số |
be/libs/constants/src/lib/sapp/sapp-23954.constants.ts (toàn bộ) | Thuộc tính, enum, pipeline, Redis key, TTL |
be/libs/constants/src/lib/error-codes/error-codes.constants.ts:645-685 | Mã lỗi |
be/libs/services/src/lib/redis/cache-common.service.ts | Redis get/set + TTL |
be/libs/database/migrations/1783700000000-create-tools.ts:13-14 | Seed tool sapp.008/009 |
Grep 23954|refreshHistory trong be/libs/database/src/lib/entities/ | Xác nhận không có entity |
shared/api-docs/sapp.md:361-717 | Đối chiếu Drift |