Giao diện
US-S09: Đồng bộ lại qua ticket HubSpot (luồng 5) ​
Thuộc FS: 00-overview.md — SAPP-26297 Chuyển nhượng học viên (tool
sapp.011) Phần chung (tổng quan, roles, flow tổng, related features, NFR, Open Questions, References) xem ở Overview. File này chỉ chứa nội dung riêng của US.Cơ chế chạy bước, tự chấm, xác nhận GHI, ghi ngược fixture → US-S01. Câu SQL
hs5,hs5s→ US-S02 §4. Đồng bộ enrollment và logic hoàn tác thuộc hubspot-service của SAPP (SAPP-28978), không do mind_keep cài.
| Field | Value |
|---|---|
| US ID | US-S09 |
| Role thực hiện | R1 - Người dùng tool — Assumption (OQ về G-01 → OQ-S-01) |
| Phase | As-built (đã triển khai @ e77c47a) |
| Priority | N/A — as-built |
| Status | Đã triển khai — chưa chạy được end-to-end (guard "Ticket đã được xử lý" phía SAPP chặn phần hoàn tác; pipeline/stage kích hoạt đồng bộ chờ SAPP — OQ-S-10) · Draft |
| NFR liên quan | NFR-S-01, NFR-S-02 — xem Overview §5 |
| OQ liên quan | OQ-S-01, OQ-S-02, OQ-S-06, OQ-S-08, OQ-S-10, OQ-S-12, OQ-S-14, OQ-S-19 — xem Overview §6 |
1. US Statement ​
text
As a Người dùng tool [Assumption — OQ-S-01]
I want tạo contact + ticket HubSpot test, kéo ticket sang Close success để kích hoạt đồng bộ, rồi xoá mã lớp khỏi ticket
và đồng bộ lại, đọc lại trạng thái đồng bộ và dòng enrollment trong DB Ops
So that kiểm SAPP-28978: "CX xóa mã lớp khỏi ticket rồi đồng bộ lại → BE hoàn tác đúng những gì lần trước đã làm cho mã đó"
(tóm tắt luồng trên màn; mục đích nghiệp vụ rộng hơn là [Assumption] — OQ-S-18)2. Dependencies & Definition Of Ready ​
N/A — đã triển khai. Dependency runtime (đang thiếu → chưa chạy được end-to-end):
| # | Cần sẵn sàng | Ai cung cấp | Trạng thái |
|---|---|---|---|
| D1 | Pipeline enrollment + stage "Close success" mà hubspot-service lắng nghe (dev có 21 pipeline, nhiều cái có stage tên "Close success") | SAPP | ⏸ Chờ SAPP câu 4 — OQ-S-10 |
| D2 | SAPP mở / nới guard "Ticket đã được xử lý" (hoặc có bản build bỏ guard trên dev) | SAPP | ⏸ Chờ SAPP câu 5 — OQ-S-10 |
| D3 | Ý nghĩa cột user_hubspot_enrollments (xoá mềm hay xoá hẳn; giá trị sync_status_hubspot; case 5.5 dựa vào gì) | SAPP | ⏸ Chờ SAPP câu 6 — OQ-S-10 |
| D4 | Credentials HubSpot trong env_systems của môi trường chọn (thiếu ⇒ lỗi cấu hình HubSpot) | Admin CMS | — |
3. Business Rules ​
| ID | Rule | Áp dụng ở | Đã cover bởi (AC) | Nguồn code (Mode B/C) |
|---|---|---|---|---|
| BR-01 | Bước tạo contact gửi email = {contact_email}, firstname = "Test", lastname = "SAPP-26297", lop_dang_ky (mã lớp tách bằng dấu chấm phẩy); đạt khi response có contact.id và ghi ngược contact_id | M1 - bước tạo contact | AC-M1.1 | fe/src/ui/components/works/sapp/sapp-26297-flows.ts:1530-1551 |
| BR-02 | Email đã tồn tại trên HubSpot ⇒ dùng lại contact đó và ghi đè các property gửi lên; response báo reused; lỗi khác ⇒ lỗi tạo HubSpot của mind_keep | M1 - bước tạo contact | AC-M1.2 | be/libs/modules/src/lib/hubspot/common.service.ts:607-661 |
| BR-03 | Bước tạo ticket gửi: tiêu đề "SAPP-26297 test <ngày hôm nay>", pipeline, stage mở, owner, phân loại yêu cầu mặc định "Bảo lưu khoá học", lớp đăng ký chính = mã lớp cũ, lớp mới = mã 1, mã 2 (tách bằng dấu phẩy), khu vực; gắn contact vào ticket; đạt khi có id và ghi ngược ticket_id; HubSpot từ chối ⇒ lỗi 12006 kèm câu từ chối | M1 - bước tạo ticket | AC-M1.1 | sapp-26297-flows.ts:1553-1593 · be/libs/modules/src/lib/sapp/sapp-26297.common.service.ts:244-254 · be/libs/constants/src/lib/hubspot/hubspot.constants.ts:57 |
| BR-04 | Bước "Kéo ticket sang Close success" sửa stage của ticket = {hs_stage_close_id}; đạt khi stage đọc lại = giá trị đó. Theo ghi chú code, đổi stage là thứ kích hoạt đồng bộ thật phía SAPP | M1 - bước kéo stage | AC-M1.1, AC-M1.3 | sapp-26297-flows.ts:1595-1613 · sapp-26297.common.service.ts:256 |
| BR-05 | Bước đọc ticket chấm trạng thái đồng bộ LMS của ticket = SYNC_SUCCESS (A.1); ở case B và 5.x không có check; trạng thái trống = đồng bộ chưa xong, chờ rồi chạy lại | M1 - bước đọc ticket | AC-M1.1, AC-M1.3 | sapp-26297-flows.ts:1615-1630 |
| BR-06 | Case B (xác nhận blocker): bỏ mã lớp mới 1 khỏi lớp mới (check lớp mới = mã 2), kéo ticket ra stage mở, kéo lại Close success, đọc ticket, SQL hs5; hướng dẫn: DB không đổi + log hubspot-service có "Ticket đã được xử lý" = đã trúng guard | M1 - case B | AC-M1.3 | sapp-26297-flows.ts:1710-1751 |
| BR-07 | Case 5.1–5.8 (hoàn tác khi xoá mã lớp) đều gắn cảnh báo BLOCKED; bước: [SQL hs5s dựng, ở 5.3] · hướng dẫn · sửa mã lớp (không check) · kéo lại Close success · đọc ticket (không check) · SQL hs5; kết quả DB / mail kiểm bằng mắt | M1 - case 5.1 → 5.8 | AC-M1.4 | sapp-26297-flows.ts:1632-1678,1752-1815 |
| BR-08 | SQL hs5 câu 1 đọc dòng enrollment của ticket và ghi ngược hs_user_id từ cột user_id | M1 - SQL hs5 | AC-M1.1 | sapp-26297-flows.ts:1632-1643 · be/libs/constants/src/lib/sapp/sapp-26297-sql.constants.ts:271-294 |
4. Design ​
a. Mối liên hệ giữa các màn & User Flow ​
text
[Tab "5 · HubSpot đồng bộ lại"]
5.0: [pipeline (đọc)] → [owner (đọc)] → tester chép id vào fixture → [tạo contact (GHI HubSpot)] → contact_id
A.1: [tạo ticket (GHI)] → ticket_id → [kéo Close success (GHI) ⇒ SAPP đồng bộ] → [đọc ticket: SYNC_SUCCESS] → [SQL hs5]
B: [bỏ MA_LOP_MOI_1 (GHI)] → [ra stage mở (GHI)] → [vào lại Close (GHI)] → [đọc ticket] → [SQL hs5] → đọc: trúng guard
5.1–5.8: [(SQL hs5s)] → [sửa mã lớp (GHI)] → [vào lại Close (GHI)] → [đọc ticket] → [SQL hs5] ⏸ BLOCKED
Ticket HubSpot: [stage mở] --(sửa stage = Close success)--> đồng bộ SAPP --> trạng thái đồng bộ = SYNC_SUCCESS | SYNC_FAILUREb. Chi tiết từng màn ​
Màn M1 - Tab "5 · HubSpot đồng bộ lại" ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Bấm tab "5 · HubSpot đồng bộ lại" |
| Mô tả ngắn | Tóm tắt: "SAPP-28978. CX xóa mã lớp khỏi ticket rồi đồng bộ lại → BE hoàn tác đúng những gì lần trước đã làm cho mã đó. ⛔ Phần end-to-end đang BLOCKED bởi guard 'Ticket đã được xử lý'. Tool dựng được đồng bộ lần đầu (A.1) và xác nhận blocker (B)." 11 case |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Vào tab | Tóm tắt + 11 case đóng; fixture hiện ô HubSpot của luồng 5 | N/A |
| Loading | Bước đang chạy | Theo US-S01 | N/A |
| Has data | Bước chạy xong | ✓/✗ + "x/y đạt"; B và 5.1–5.8 có khung cảnh báo BLOCKED | N/A |
| Empty | Bước pipeline / owner / sửa mã lớp / đọc ticket ở 5.x không có check | Chỉ response thô | N/A |
| Error | HubSpot từ chối / thiếu cấu hình | Hộp đỏ "Không gọi được Ops qua API: <status> <mã> …" | N/A |
| Disabled | Fixture thiếu contact_id / ticket_id / stage | Nút khoá "Chưa điền: …" | N/A |
Fields ​
| Field | Required | Type | Default | Format | Validation | Error Message | Visibility | Depends On |
|---|---|---|---|---|---|---|---|---|
email (contact) | Yes | Text | {contact_email} | US-S01 BR-10 | "Chưa điền: …" | Bước tạo contact | Fixture | |
firstname, lastname | No | Text | "Test", "SAPP-26297" | — | Trống ⇒ không gửi (xem EC-M1.1) | — | Bước tạo contact | — |
lop_dang_ky | No | Text | {class_code_old} | mã lớp tách ; | — | — | Bước tạo contact | Fixture |
contact_id (gắn ticket) | Yes | Text | {contact_id} | id HubSpot | US-S01 BR-10 | "Chưa điền: …" | Bước tạo ticket | Ghi ngược từ tạo contact |
subject | No | Text | "SAPP-26297 test {today}" | {today} = ngày giờ VN YYYY-MM-DD | — | — | Bước tạo ticket | — |
hs_pipeline, hs_pipeline_stage, hubspot_owner_id | Yes | Text | {hs_pipeline_id}, {hs_stage_open_id}, {hs_owner_id} | id HubSpot | US-S01 BR-10 | "Chưa điền: …" | Bước tạo ticket | Tester chép từ 5.0 |
phan_loai_yeu_cau_dich_vu | No | Text | "Bảo lưu khoá học" | — | — | — | Bước tạo ticket | — |
lop_dang_ki_chinh, lop_moi | No | Text | {class_code_old}, {class_code_1},{class_code_2} | mã lớp tách , | Trống ⇒ không gửi (xem EC-M1.1) | — | Bước tạo / sửa ticket | Fixture |
khu_vuc | No | Text | {khu_vuc} | từ thứ 2 là chương trình | — | — | Bước tạo ticket | Fixture |
ticket_id (chọn bản ghi) | Yes | Text | {ticket_id} | id HubSpot | US-S01 BR-10 | "Chưa điền: …" | Bước sửa / đọc ticket | Ghi ngược từ tạo ticket |
Actions ​
A1 - Chạy các bước của một case ​
| Item | Description |
|---|---|
| Trigger | Mở case, chạy từng bước (US-S01) |
| Behavior | Thao tác dùng: pipeline, owner, đọc ticket (đọc); tạo contact, tạo ticket, sửa ticket (GHI HubSpot); SQL hs5 (đọc), hs5s (GHI DB Ops). Ghi vào: HubSpot theo môi trường chọn — dev = portal HubSpot dev; staging = portal HubSpot PROD (xem cảnh báo dưới); DB Ops dev/staging (SQL). Kéo stage kích hoạt đồng bộ thật phía SAPP |
| Success Result | Các check đạt theo bảng case |
⚠️ Ghi PROD — Hành vi hiện tại: route
staging/sapp-26297/hubspot/*dùng môi trường HubSpot "staging", và cấu hình nền tảng cố ý cho "staging" dùng lại credentials portal prod (không có portal staging riêng) (be/apps/api/src/sapp/sapp-26297.controller.ts:431,498,540·be/libs/constants/src/lib/hubspot/hubspot.constants.ts:19-26). Vì vậy ở tabstaging, tạo contact (kể cả ghi đè contact thật trùng email — BR-02), tạo ticket và kéo stage ghi vào HubSpot production, trái với banner "Không có route production" — OQ-S-19.
Error cases:
| Case | Expected Handling |
|---|---|
| HubSpot từ chối tạo ticket | Hộp đỏ mã 12006 + câu từ chối (BR-03) |
| Thiếu credentials HubSpot của môi trường | Hộp đỏ lỗi cấu hình HubSpot (nền tảng) |
| Đọc ticket khi đồng bộ chưa xong | Check SYNC_SUCCESS ✗, trạng thái trống — chờ rồi chạy lại |
Bảng case (11 case):
| Mã case | Tên | Bước | Kỳ vọng tool chấm |
|---|---|---|---|
| 5.0 | Chuẩn bị HubSpot | pipeline · owner · tạo contact (lop_dang_ky = mã lớp cũ) | Có contact.id (ghi contact_id); pipeline/owner không chấm — tester chép id |
| A.1 | Đồng bộ lần đầu (Bảo lưu) | tạo ticket · kéo Close · đọc ticket · SQL hs5 | Có id (ghi ticket_id) · stage = close · SYNC_SUCCESS; 3 dòng enrollment (mã cũ → RESERVED, 2 mã mới → REASSIGNED) đọc SQL |
| B | Xác nhận blocker (đồng bộ lần 2) | sửa lớp mới = mã 2 · ra stage mở · vào lại Close · đọc ticket · SQL hs5 · hướng dẫn | lop_moi = mã 2 · stage = close; hiện tại DB không đổi + log "Ticket đã được xử lý" (đọc tay) |
| 5.1 | Bỏ mã lớp cũ khỏi lớp đăng ký chính | hướng dẫn "để trống" · sửa mã lớp · vào lại Close · đọc · SQL hs5 | BLOCKED; kỳ vọng (văn bản): reverted, lớp cũ + Foundation về NORMAL, xoá dòng enrollment |
| 5.2 | Bỏ mã mới 1 (lớp chưa khai giảng) | như trên | BLOCKED; (văn bản) reverted, học viên bị gỡ khỏi lớp, xoá dòng enrollment |
| 5.3 | Bỏ mã mới 2 (đã khai giảng + có tiến độ) | SQL hs5s (GHI) · như trên | BLOCKED; (văn bản) manual, giữ học viên, mail E05, ticket vẫn SYNC_SUCCESS |
| 5.4 | Học viên đã sang trạng thái khác ở lớp cũ | như trên (dựng bằng hs5s câu 2) | BLOCKED; (văn bản) failed, không ghi đè, giữ dòng enrollment, mail E03, SYNC_FAILURE |
| 5.5 | Mã lớp chưa từng được tạo trên LMS | như trên | BLOCKED; (văn bản) reverted, chỉ xoá dòng enrollment — cột guide dùng không có trên dev |
| 5.6 | Học viên đang TRANSFERED_TO ở lớp mới | như trên (dựng bằng hs5s câu 3) | BLOCKED; (văn bản) failed, mail E03 |
| 5.7 | Xoá toàn bộ mã lớp | hướng dẫn "để trống cả hai" · như trên | BLOCKED; (văn bản) tất cả hoàn tác, không báo thành công giả |
| 5.8 | Xoá 2 mã, 1 thành công 1 lỗi | như trên | BLOCKED; (văn bản) mã ok reverted, mã lỗi failed, không rollback mã ok, SYNC_FAILURE |
Acceptance Criteria của màn M1 ​
AC-M1.1 - Đồng bộ lần đầu (5.0 + A.1) ​
gherkin
Given fixture có contact_email, class_code_old, class_code_1, class_code_2, hs_pipeline_id, hs_stage_open_id,
hs_stage_close_id, hs_owner_id, khu_vuc
When người dùng chạy "Tạo contact học viên" và xác nhận GHI
Then response có contact.id và fixture contact_id được ghi
When người dùng chạy "Tạo ticket Bảo lưu"
Then ticket được tạo ở stage mở, gắn contact, response có id và fixture ticket_id được ghi
When người dùng chạy "Kéo ticket sang Close success"
Then properties.hs_pipeline_stage = hs_stage_close_id
When người dùng chạy "Đọc lại ticket" sau khi đồng bộ xong
Then properties.trang_thai_dong_bo_lms = "SYNC_SUCCESS"
And SQL hs5 câu 1 ghi hs_user_id vào fixtureMaps to:
- Business Rule: BR-01, BR-03, BR-04, BR-05, BR-08
AC-M1.2 - Contact trùng email ​
gherkin
Given HubSpot đã có contact với email = contact_email
When người dùng chạy bước tạo contact
Then tool dùng lại contact đó (reused), ghi đè firstname, lastname, lop_dang_ky bằng giá trị gửi lên
And fixture contact_id = id contact cũMaps to:
- Business Rule: BR-02
AC-M1.3 - Xác nhận blocker (case B) ​
gherkin
Given A.1 đã đồng bộ SYNC_SUCCESS
When người dùng chạy "Xóa MA_LOP_MOI_1 khỏi lop_moi"
Then properties.lop_moi = class_code_2
When người dùng chạy "Kéo ticket ra khỏi Close success" rồi "Kéo lại vào Close success"
Then properties.hs_pipeline_stage = hs_stage_close_id
And bước đọc ticket không có check; tester đối chiếu SQL hs5 và log hubspot-service theo hướng dẫnMaps to:
- Business Rule: BR-04, BR-05, BR-06
AC-M1.4 - Case hoàn tác bị chặn ​
gherkin
Scenario Outline: case 5.x
When người dùng mở case <case>
Then hiện khung cảnh báo "BLOCKED: guard 'Ticket đã được xử lý' ..."
And các bước sửa mã lớp và đọc ticket không có check tự động
And "Kỳ vọng" hiển thị văn bản <kỳ vọng> để tester kiểm bằng mắt
Examples:
| case | kỳ vọng |
| 5.1 | reverted, lớp cũ + Foundation về NORMAL |
| 5.2 | reverted, học viên bị gỡ khỏi lớp |
| 5.3 | manual, giữ học viên, mail E05 |
| 5.4 | failed, giữ dòng enrollment, mail E03, SYNC_FAILURE |
| 5.5 | reverted, chỉ xoá dòng enrollment |
| 5.6 | failed, mail E03 |
| 5.7 | tất cả hoàn tác, không báo thành công giả |
| 5.8 | mã ok reverted, mã lỗi failed, SYNC_FAILURE |Maps to:
- Business Rule: BR-07
Negative AC / Edge Cases của màn M1 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M1.1 | ⚠️ Observed — 5.1, 5.7 "để trống" lớp đăng ký chính / lớp mới | Hành vi hiện tại: ô trống bị bỏ khỏi payload ⇒ request sửa ticket không chứa property đó ⇒ HubSpot giữ nguyên giá trị cũ; case không dựng được như mô tả (API mind_keep coi chuỗi rỗng là "xoá property" nhưng màn không gửi được chuỗi rỗng) (sapp-26297-step-call.tsx:110-116 · sapp-26297-flows.ts:1757-1758,1808 · hubspot/common.service.ts:947-950) — OQ-S-02 |
| EC-M1.2 | ⚠️ Observed — bước pipeline / owner | Hành vi hiện tại: không chấm, không tự ghi fixture; tester tự tìm trong JSON (dev có 21 pipeline) và chép tay (sapp-26297-flows.ts:1512-1528) — OQ-S-08 |
| EC-M1.3 | ⚠️ Observed — route đọc contact | Hành vi hiện tại: route đọc contact có nhưng không bước nào dùng — OQ-S-08 |
| EC-M1.4 | ⚠️ Ghi PROD — chạy ở tab staging | Hành vi hiện tại: mọi thao tác HubSpot ghi vào portal HubSpot production (xem cảnh báo ở A1) — OQ-S-19 |
| EC-M1.5 | Mã lớp đăng ký chính không có trong lop_dang_ky của contact | Theo ghi chú code: đồng bộ dừng với E01 (dùng có chủ đích ở US-S10 case 6.1) |
| EC-M1.6 | Chạy lại A.1 trên cùng ticket | Tool gửi lại; kết quả phụ thuộc guard phía SAPP (trúng guard như case B) |
5. Cross-feature AC ​
AC-X.1 - Kích hoạt đồng bộ thật phía SAPP ​
gherkin
Given ticket đang ở stage mở và gắn contact
When người dùng kéo ticket sang Close success của pipeline enrollment
Then hubspot-service của SAPP nhận sự kiện và ghi trạng thái đồng bộ ngược lên ticket (theo ghi chú code; pipeline nào kích hoạt chờ SAPP câu 4)Maps to:
- REL-07 (Affects - Hệ thống SAPP)
- REL-06 (Affects - Dịch vụ HubSpot dùng chung)