Skip to content

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.

FieldValue
Projectba/projects/2026-SAPP-Works-tools
ModeB – As-built
Ngày đọc code2026-09-30
Commite77c47a
Điểm vào BA cung cấp2 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ớiFE · 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)
StatusDraft

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ạt cap_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ốc cap_nhat_owner đổi giá trị + "hiện tại").
    • 12 route BE /api/v1/sapp/{prod|dev}/sapp-23954/tickets/*, service SappCommonService (phần 23954), DTO, hằng số, mã lỗi, nơi lưu lịch sử (Redis).
  • Ngoài phạm vi (dừng truy vết tại đây):
    • Cổng truy cập tool (ẩn / khoá / secret theo code sapp.008 / sapp.009, ToolGate trong 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ọn cap_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 ở repo sapp-platform-lms-be — ngoài repo, không truy vết được.

2. Điểm vào (Entry points) ​

IDLoạiTên / đường dẫn người dùng thấyBằng chứng
E-01Mà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-02Mà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-03APIPOST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/querybe/apps/api/src/sapp/sapp.controller.ts:267, :285
E-04APIPOST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-cap-nhat-ownerbe/apps/api/src/sapp/sapp.controller.ts:321, :339
E-05APIPOST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/verify-changesbe/apps/api/src/sapp/sapp.controller.ts:373, :391
E-06APIGET /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-historybe/apps/api/src/sapp/sapp.controller.ts:419, :434
E-07APIPOST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/refresh-history/:id/reloadbe/apps/api/src/sapp/sapp.controller.ts:463, :480
E-08APIPOST /api/v1/sapp/{prod|dev}/sapp-23954/tickets/check-snapshotbe/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 ​

IDAi (role)Làm gìĐiểm vàoKết quả quan sát đượcBằng chứng
A-01Ngườ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ộ panelE-01, E-02Mọi thao tác sau đó gọi route của portal đã chọnfe/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-02Hệ 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 HubSpotHai 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-03Người dùngSubtool 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-03Bả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-04Người dùngBấm "Tải thêm" để lấy trang kế (100 ticket/trang)E-01 → E-03Nối thêm dòng, giữ lựa chọnsapp-23954-tool.tsx:66, :648-653, :861-875
A-05Người dùngChọn ticket (từng dòng / "Chọn tất cả" — chỉ các dòng đã tải)E-01Nút "Làm mới cap_nhat_owner (N)" bật khi N > 0sapp-23954-tool.tsx:673-678, :773-787, :814-834
A-06Người dùngSubtool 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-04Mỗ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ọnsapp-23954-tool.tsx:572-604, :655-671, :145-176; common.service.ts:909-1005
A-07Hệ 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 portalE-04Danh 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-08Hệ 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ớiE-01 → E-05Bả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-09Người dùngMở "Lịch sử làm mới" của portal đang chọnE-01 → E-06Modal bảng lịch sử, mới nhất trước; "Chưa có lịch sử làm mới." khi rỗngsapp-23954-tool.tsx:309-438, :747-754; common.service.ts:1078-1096
A-10Người dùngBấm "Tải lại" trên 1 dòng lịch sửE-01 → E-07BE đọ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-11Người dùngSubtool 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-08Danh 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ướcsapp-23954-snapshot-tool.tsx:533-613, :479-529, :203-288, :345-346; common.service.ts:689-897
A-12Hệ 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ốiE-08snapshots[] cũ → mới, mốc "hiện tại" cuốicommon.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ảngFieldTypeBắt buộcMặc định / enumBằng chứng
HubSpot ticketsubject (tên ticket)string——be/libs/constants/src/lib/sapp/sapp-23954.constants.ts:12
HubSpot tickeths_pipeline (pipeline)string (id)——sapp-23954.constants.ts:13
HubSpot ticketcap_nhat_owner — thuộc tính duy nhất tool ghidropdown—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 tickethubspot_owner_id (owner)string (id)——sapp-23954.constants.ts:15
HubSpot ticketcreatedatedatetime——sapp-23954.constants.ts:16
HubSpot ticketphan_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 ticketma_lop_hv_can_ho_tro (chỉ snapshot đọc)string——sapp-23954.constants.ts:55-56
HubSpot contactfirstname, email, phone (chỉ snapshot đọc — hiển thị)string——sapp-23954.constants.ts:59-61
HubSpot contactvan_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 contactyeu_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ảngFieldTypeBắt buộcMặc định / enumBằng chứng
Refresh-history itemidUUIDCósinh ngẫu nhiên khi ghibe/libs/interface/src/lib/dto/sapp/sapp-23954-ticket.dto.ts:173-177; common.service.ts:1051
Refresh-history itemticketIdstringCó—sapp-23954-ticket.dto.ts:179-180
Refresh-history itemticketNamestring | nullKhôngtê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 itemprevCapNhatOwnerstring | nullKhông= latestCapNhatOwner của dòng gần nhất cùng ticket; null lần đầusapp-23954-ticket.dto.ts:185-190; common.service.ts:1033-1045, :1054
Refresh-history itemlatestCapNhatOwnerstring | nullKhônggiá 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 itemprevTicketOwner / latestTicketOwnerstring | nullKhôngdạng "{tên} ({id})", chỉ id nếu không tra được tên, null nếu chưa gánsapp-23954-ticket.dto.ts:197-209; common.service.ts:1296-1303, :1056-1057, :1254-1257
Refresh-history itemrefreshedAtISO datetimeCócùng 1 thời điểm cho cả lượtsapp-23954-ticket.dto.ts:211-212; common.service.ts:1047, :1058
(key)sapp:23954:refresh-history:{env}Redis list (JSON array)—TTL 7 × 24h mssapp-23954.constants.ts:38-42; be/libs/services/src/lib/redis/cache-common.service.ts:27-29

(c) Request chính

Entity / bảngFieldTypeBắt buộcMặc định / enumBằng chứng
Query bodypipelines, capNhatOwnerstring[]Khôngkhớp bất kỳ (IN)sapp-23954-ticket.dto.ts:16-26
Query bodynoOwnerbooleanKhôngtrue = có owner, false = chưa có ownersapp-23954-ticket.dto.ts:28-34
Query bodycreatedFrom / createdToDateKhông≥ / ≤sapp-23954-ticket.dto.ts:36-44
Query bodylimitnumberKhôngmặc định 100, 1–200sapp-23954-ticket.dto.ts:46-52; sapp-23954.constants.ts:28-31
Query bodyafterstringKhôngcon trỏ trang HubSpotsapp-23954-ticket.dto.ts:54-57
Refresh bodyticketIdsstring[]Cókhông giới hạn số phần tử, cho phép mảng rỗngsapp-23954-ticket.dto.ts:114-120; be/libs/interface/src/lib/dto/decorators/dto.decorator.ts:156-189
Verify bodytickets[] {ticketId bắt buộc, previousCapNhatOwner, previousTicketOwner tuỳ chọn}arrayCó—sapp-23954-ticket.dto.ts:229-258
Snapshot bodyticketIdstringCó—sapp-23954-ticket.dto.ts:299-302

5. Rule & validation tìm thấy ​

IDRule (ngôn ngữ nghiệp vụ)LoạiÁp dụng ởBằng chứngNghi là bug?
R-01Mọ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ệnA-03common.service.ts:322-342, :364Không
R-02Bộ 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ệnA-03common.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-03Khoả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 / validationA-03sapp-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-04Mỗi trang tối đa 200 ticket (HubSpot giới hạn), mặc định 100; FE luôn xin 100giới hạnA-03, A-04sapp-23954.constants.ts:27-31; common.service.ts:358-361; sapp-23954-tool.tsx:66, :528Không
R-05Kết quả luôn sắp theo ngày tạo mới nhất trướcđiều kiệnA-03common.service.ts:365-370Không
R-06Tên pipeline tra từ danh sách pipeline ticket của portal; không tra được thì hiển thị idcông thứcA-03common.service.ts:376-394; sapp-23954-tool.tsx:843Khô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ộ totalgiới hạnA-05sapp-23954-tool.tsx:673-678Không
R-08Là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ậnvalidation / xác nhậnA-06sapp-23954-tool.tsx:655-671, :776Không
R-09Khô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 HTTPgiới hạnA-06sapp-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-10Ticket có cap_nhat_owner đang rỗng → bỏ qua (lý do "cap_nhat_owner is empty"), không ghi gìđiều kiệnA-06common.service.ts:944-947Khô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ạitrạng tháiA-06common.service.ts:899-903, :949-959Không
R-12Nế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áctrạng tháiA-06common.service.ts:953-969⚠️ Observed — để lại dữ liệu sai trên prod (→ Q-09)
R-13Mỗ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ệnA-06common.service.ts:926, :978-986Không (xem DR-03)
R-14Tê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 idcông thứcA-06, A-10common.service.ts:1281-1319Không
R-15Lị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ệnA-07common.service.ts:971-977, :1025, :1067-1071Khô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ốngcông thứcA-07common.service.ts:1033-1045, :1049Không
R-17Lị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ạnA-07, A-10sapp-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-18Danh 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ứcA-09common.service.ts:1086Khô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à refreshedAtvalidation / trạng tháiA-10common.service.ts:1225-1231, :1247-1259; be/libs/constants/src/lib/error-codes/error-codes.constants.ts:664-667Không
R-20Kiể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ệnA-08sapp-23954-tool.tsx:180-181, :584-590, :631-642; common.service.ts:1332-1339Không
R-21Cờ "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ứcA-08common.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-22Snapshot: 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ệnA-11common.service.ts:487-509Không
R-23Snapshot: 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ệnA-11common.service.ts:511-531; sapp-23954.constants.ts:96-127Không (→ Q-13)
R-24Snapshot: 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ệnA-11common.service.ts:533-587, :782-783Không
R-25Snapshot: ticket "chào mừng" bỏ qua bước điều kiện rule → kết luận "Sẽ chạy"điều kiệnA-11common.service.ts:589-602Không
R-26Snapshot: 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ứcA-11common.service.ts:604-655⚠️ Observed (→ Q-12)
R-27Snapshot: 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ứcA-12common.service.ts:417-422, :827-870Không (→ Q-14)
R-28Snapshot: 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ệnA-11, A-12common.service.ts:698-741, :749-761, :766-778Không (xem DR-04)
R-29Snapshot 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 ownertrạng tháiA-11common.service.ts:674-688, :689-897 (không có updateObject); sapp-23954.constants.ts:44-50Không
R-30Email / 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épquyền dữ liệuA-11sapp-23954-ticket.dto.ts:340-348; common.service.ts:687; sapp-23954-snapshot-tool.tsx:287-288Không
R-31Snapshot: nút "Kiểm tra" chỉ bật khi Ticket ID (đã bỏ khoảng trắng) khác rỗngvalidationA-11sapp-23954-snapshot-tool.tsx:550-553, :575Không
R-32Chỉ 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ệnA-01, A-06be/apps/api/src/sapp/sapp.controller.ts:321-337; sapp-23954-tool.tsx:58-61, :661Không (→ Q-03)
R-33Chuyể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áiA-01sapp-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ỪNG

6. Quyền, tích hợp, thông báo, job ​

LoạiChi tiếtBằ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ảngbe/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ảngfe/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àiHubSpot 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_CONFIGUREDcommon.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àiRedis (qua cache service) lưu lịch sử làm mớicommon.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 / cronKhông có. Đếm ngược 15s là timer trên trình duyệtsapp-23954-tool.tsx:631-642
Mã lỗi13022 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 ​

IDTài liệu (file + section)Tài liệu nóiCode đang làmBằng chứng
DR-01shared/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-02shared/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: truePipeline chào mừng hợp lệ ở prod chỉ có 898314365; với 877604025 code sẽ dừng ở bước pipelinesapp-23954.constants.ts:118-127; common.service.ts:512-524
DR-03shared/api-docs/sapp.md:485, :614Lỗ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ốtcommon.service.ts:923, :926-986, :1305-1319, :1346-1393; be/libs/modules/src/lib/hubspot/common.service.ts:917-924
DR-04shared/api-docs/sapp.md:714Thiế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 12001common.service.ts:697-741
DR-05Swagger trong code be/apps/api/src/sapp/sapp.controller.ts:466-467 (reload)Reload chỉ ghi lại subject và cap_nhat_ownerCode còn ghi đè latestTicketOwner (doc sapp.md:529 thì đúng)common.service.ts:1254-1257
DR-06Swagger be/apps/api/src/sapp/sapp.controller.ts:423-424 (refresh-history)Mỗi dòng có ticketId, ticketName, prevCapNhatOwner, latestCapNhatOwnerCòn có id, prevTicketOwner, latestTicketOwner, refreshedAtsapp-23954-ticket.dto.ts:173-213
DR-07shared/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êngcommon.service.ts:1062-1066, :1261-1265
DR-08FE 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-466ticketName / capNhatOwner là string bắt buộc; env gồm cả stagingBE 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ỗisapp-23954-ticket.dto.ts:74-84; be/apps/api/src/sapp/sapp.controller.ts:267-534
DR-09Chú 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) ​

IDCâu hỏiVì sao cầnChặn bước sau?
Q-01Mụ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-02Ai đượ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ậtCó
Q-03Chạ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 prodCó
Q-04Doc 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 FSKhông
Q-05noOwner=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 OQKhông
Q-06Lọ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ácCó
Q-07Cá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ớiKhông
Q-08Có 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ạnCó
Q-09Khi 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 roCó
Q-10Kiể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 3Có
Q-11Lị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 `(cond1cond2)
Q-13Danh 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 4Có
Q-14Snapshot 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 FSKhô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 OQKhô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 FSKhông
Q-17Tool 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ảmKhông

9. Nhật ký nguồn đã đọc ​

File / sectionMục đích
ba/_workflow/templates/feature-inventory/Template.mdKhung §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.mdPhạ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.tsxRoute màn, chú thích gate
fe/src/ui/pages/works/sapp/tools/registry.ts:84-96Menu 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-40Code sapp.008 / sapp.009
fe/src/data/constants/api.ts:451-466URL 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:11Type 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/propertiesController, 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-1000Cá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-374Validation 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-685Mã lỗi
be/libs/services/src/lib/redis/cache-common.service.tsRedis get/set + TTL
be/libs/database/migrations/1783700000000-create-tools.ts:13-14Seed 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