Giao diện
Feature Inventory — HubSpot tools (Clone · Snapshot · Property · Pipeline) ​
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. Mọi đường dẫn tính từ root repomind_keep/. AI draft — chưa qua Gate R1. Không có số liệu đo từ API chạy thật; mọi hành vi suy từ đọc source.
| 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 | 4 sub-tool dưới /works/sapp/tools: hs-clone-prod-to-dev (sapp.001), hs-snapshot-property (sapp.002), hs-get-property (sapp.003), hs-pipeline (sapp.004) |
| Thành phần chạm tới | FE · BE (api) · HubSpot CRM API ngoài (portal prod + portal dev) |
| Status | Draft |
1. Phạm vi đã truy vết ​
- Trong phạm vi:
- 4 màn web: Clone property prod → dev, Property snapshot & history, Property info, Pipeline manager.
- Các API BE mà 4 màn gọi: get property (deals/contacts/tickets × prod/dev/staging), clone property prod → dev, property-history (6 route), list/get pipeline, clone pipeline prod → dev, duplicate pipeline trong cùng portal (prod và dev).
- Service BE
HubSpotCommonService(be/libs/modules/src/lib/hubspot/common.service.ts) và HTTP clientHubSpotService(be/libs/services/src/lib/hubspot/hubspot.service.ts) đến mức endpoint HubSpot được gọi. - Ghi chú: lời giao việc trỏ tới
be/libs/modules/src/lib/sapp/common.service.ts; thực tế 4 tool không đi qua file đó — controller gọi thẳngHubSpotCommonService(be/apps/api/src/sapp/sapp.controller.ts:163).
- Ngoài phạm vi (dừng truy vết tại đây):
- Gate truy cập tool (ẩn / tắt / khoá secret theo
code) → nền tảng (fe/src/ui/pages/works/sapp/tools/shell.tsx:427ToolGate; bảngtoolsseed tạibe/libs/database/migrations/1783700000000-create-tools.ts:6-9). - Lưu "tool dùng gần nhất" / chọn tool hạ cánh → nền tảng (
fe/src/ui/pages/works/sapp/tools/registry.ts:115-122,fe/app/works/sapp/tools/page.tsx:10). - Đọc credential từ bảng
env_systems→ nền tảng (be/libs/modules/src/lib/hubspot/common.service.ts:47-58). - Cơ chế
@Public()/ guard toàn cục → nền tảng (be/apps/api/src/sapp/sapp.controller.ts:114). hubspot-property-field.tsx: không được 4 tool này dùng — chỉ được import bởisapp-24395-tool,sap-log-trace, các step củasapp-26878(Grephubspot-property-fieldtrongfe/src). Chỉ ghi tên.- Route thêm option cho property (
POST dev/hubspot/{deals|contacts}/properties/:property/options,be/apps/api/src/sapp/sapp.controller.ts:821,1101) — dùng bởisapp-26878-step-option.tsx:86, không thuộc 4 tool. - Suggested trace properties + Property history timeline (trace) (
sapp.controller.ts:1538-1701) — Grepproperty-suggestions|property-timelinetrongfe/srckhông có kết quả → không màn nào trong 4 tool gọi. Chỉ ghi tên (xem Q-12).
- Gate truy cập tool (ẩn / tắt / 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/hs-clone-prod-to-dev — sidebar "Clone", nhóm "SAPP Common Hubspot Tools"; tiêu đề "Clone property: prod → dev" | fe/app/works/sapp/tools/hs-clone-prod-to-dev/page.tsx:10; fe/src/ui/pages/works/sapp/tools/registry.ts:43-46; fe/src/ui/components/works/sapp/clone-tool.tsx:96; code sapp.001 tại fe/src/data/constants/tool-codes.ts:32 |
| E-02 | Màn web | /works/sapp/tools/hs-snapshot-property — sidebar "Snapshot"; tiêu đề "Property snapshot & history" | fe/app/works/sapp/tools/hs-snapshot-property/page.tsx:10; registry.ts:47; fe/src/ui/components/works/sapp/snapshot-tool.tsx:134; code sapp.002 tại tool-codes.ts:33 |
| E-03 | Màn web | /works/sapp/tools/hs-get-property — sidebar "Property"; tiêu đề "Property info" | fe/app/works/sapp/tools/hs-get-property/page.tsx:10; registry.ts:48; fe/src/ui/components/works/sapp/property-tool.tsx:115; code sapp.003 tại tool-codes.ts:34 |
| E-04 | Màn web | /works/sapp/tools/hs-pipeline — sidebar "Pipeline"; tiêu đề "Pipeline manager" | fe/app/works/sapp/tools/hs-pipeline/page.tsx:10; registry.ts:49; fe/src/ui/components/works/sapp/pipeline-tool.tsx:223; code sapp.004 tại tool-codes.ts:35 |
| E-05 | API | GET /api/v1/sapp/{prod|dev|staging}/hubspot/deals/properties/:property; GET …/{prod|dev|staging}/hubspot/contacts/properties/:property; GET …/{prod|dev}/hubspot/tickets/properties/:property (8 route; tickets không có staging) | be/apps/api/src/sapp/sapp.controller.ts:550,571,592,1012,1033,1054,1128,1149; URL FE fe/src/data/constants/api.ts:179-183 |
| E-06 | API | POST /api/v1/sapp/dev/hubspot/{deals|contacts|tickets}/properties/clone-prod/:property (3 route) | sapp.controller.ts:615,1077,1170; api.ts:184-187 |
| E-07 | API | POST /api/v1/sapp/{prod|dev}/hubspot/{deals|contacts|tickets}/:objectId/property-history (6 route) | sapp.controller.ts:1380,1402,1424,1446,1468,1490; api.ts:196-200 |
| E-08 | API | GET /api/v1/sapp/{prod|dev}/hubspot/{deals|tickets}/pipelines (4 route) | sapp.controller.ts:848,867,1194,1213; api.ts:201-204 |
| E-09 | API | GET /api/v1/sapp/{prod|dev}/hubspot/{deals|tickets}/pipelines/:pipelineId (4 route) | sapp.controller.ts:886,907,1232,1253; api.ts:205-209 |
| E-10 | API | POST /api/v1/sapp/dev/hubspot/{deals|tickets}/pipelines/clone-prod/:pipelineId (2 route) | sapp.controller.ts:928,1274; api.ts:210-213 |
| E-11 | API | POST /api/v1/sapp/dev/hubspot/{deals|tickets}/pipelines/:pipelineId/duplicate (2 route, ghi vào dev) | sapp.controller.ts:984,1330; api.ts:214-219 |
| E-12 | API | POST /api/v1/sapp/prod/hubspot/{deals|tickets}/pipelines/:pipelineId/duplicate (2 route, ghi vào prod) | sapp.controller.ts:956,1302; api.ts:214-219 |
3. Hành động người dùng / hệ thống ​
Môi trường: "prod" và "staging" dùng cùng token portal prod; "dev" dùng token portal dev (be/libs/constants/src/lib/hubspot/hubspot.constants.ts:14-31). Mọi lời gọi HubSpot đi tới cùng một host https://api.hubapi.com, phân biệt portal chỉ bằng token (be/libs/services/src/lib/hubspot/hubspot.service.ts:35, hubspot.constants.ts:4).
| 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 qua được gate của tool (role chưa rõ — Q-01) | Mở một trong 4 tool từ sidebar hoặc link chia sẻ | E-01..E-04 | Tool hiển thị trong khung chung; ẩn/tắt/khoá do gate → nền tảng | fe/app/works/sapp/tools/layout.tsx:25-26; pipeline-tool.tsx:47-50 |
| A-02 | Như A-01 | Tra cứu định nghĩa một property: chọn môi trường (Prod/Dev), loại object (Deal/Ticket/Contact), nhập internal name, bấm "Get property" | E-03 → E-05 | Chỉ đọc portal đã chọn. Hiện name, label, Type, Field type, Group, Description, bảng options (Label/Value/Order/Hidden) và Raw JSON có nút Copy JSON | property-tool.tsx:25-34,95-107,170-237; fe/src/data/services/sapp/get-property.service.ts:17-31; be/libs/modules/src/lib/hubspot/common.service.ts:489-507; HubSpot GET /crm/v3/properties/{object}/{name} tại hubspot.service.ts:75-83 |
| A-03 | Như A-01 | Clone một property từ prod sang dev: chọn loại object, nhập internal name, bấm "Clone to dev" | E-01 → E-06 | Đọc prod, ghi dev. Nếu dev đã có property cùng tên → trả property dev hiện có, không sửa. Nếu chưa có → tạo mới ở dev. Popup "Clone completed — Property … is now available in the dev portal" và JSON property dev | clone-tool.tsx:23-27,62-88,141-158; fe/src/data/services/sapp/clone-property.service.ts:15-27; sapp.controller.ts:624-631; common.service.ts:820-875; HubSpot GET /crm/v3/properties/{object}/{name} (prod + dev) và POST /crm/v3/properties/{object} (dev) tại hubspot.service.ts:75-95 |
| A-04 | Như A-01 | Xem lịch sử thay đổi của một hoặc nhiều property trên một bản ghi (deal/ticket/contact): chọn môi trường, loại object, nhập object id, danh sách property, tuỳ chọn "Target time", bấm "Check snapshot" | E-02 → E-07 | Chỉ đọc portal đã chọn. Mỗi property: bảng Value/Timestamp/Source; nếu có target time thì hiện "value at target" và tô dòng khớp, gắn nhãn "target". Không có dữ liệu → "No history returned." / "No history entries" | snapshot-tool.tsx:93-126,218-300; fe/src/data/services/sapp/property-history.service.ts:24-42; sapp.controller.ts:1358-1378; common.service.ts:105-170; HubSpot GET /crm/v3/objects/{object}/{id}?propertiesWithHistory=…&archived=false tại hubspot.service.ts:121-135 |
| A-05 | Như A-01 | Tải danh sách pipeline: chọn môi trường (Prod/Dev), loại object (Deal/Ticket), bấm "Load pipelines" | E-04 → E-08 | Chỉ đọc. Mỗi pipeline hiện label, "order N", "N stages", id; rỗng → "No pipelines found." | pipeline-tool.tsx:82-94,257-300; fe/src/data/services/sapp/pipeline.service.ts:13-22; common.service.ts:973-986; HubSpot GET /crm/v3/pipelines/{object} tại hubspot.service.ts:226-230 |
| A-06 | Như A-01 | Xem stage của một pipeline: bấm "View stages" | E-04 → E-09 | Chỉ đọc. Bảng Order/Label/Metadata (deal: probability/isClosed; ticket: ticketState/isClosed), nút Copy JSON | pipeline-tool.tsx:96-115,304-315,357-416; pipeline.service.ts:25-39; common.service.ts:1007-1025; HubSpot GET /crm/v3/pipelines/{object}/{id} tại hubspot.service.ts:234-242 |
| A-07 | Như A-01 | Clone pipeline prod sang dev: khi môi trường = Prod, bấm "Clone to dev" trên một pipeline → modal "Clone pipeline to dev", sửa "New label (optional)" (điền sẵn label nguồn), bấm "Clone to dev" | E-04 → E-10 | Đọc prod, ghi dev. Nếu dev đã có pipeline cùng label đích → trả pipeline đó, không sửa; chưa có → tạo pipeline mới ở dev cùng toàn bộ stage. Popup "Clone completed" | pipeline-tool.tsx:117-146,187-198,317-335,424-507; pipeline.service.ts:52-66; sapp.controller.ts:937-948; common.service.ts:1058-1099; HubSpot GET /crm/v3/pipelines/{object}/{id} (prod), GET /crm/v3/pipelines/{object} (dev), POST /crm/v3/pipelines/{object} (dev) tại hubspot.service.ts:226-254 |
| A-08 | Như A-01 | Nhân bản pipeline trong portal dev: môi trường = Dev, bấm "Duplicate", nhập "New label" (điền sẵn "<label> (copy)"), bấm "Duplicate" | E-04 → E-11 | Ghi dev. Tạo pipeline mới trong dev với cùng stage; nếu label đã tồn tại → trả pipeline đó. Popup "Duplicate completed — … was created in the dev portal", tự tải lại danh sách | pipeline-tool.tsx:148-181,201-214,337-353; pipeline.service.ts:78-94; sapp.controller.ts:993-1004,1339-1350; common.service.ts:1058-1099 |
| A-09 | Như A-01 | Nhân bản pipeline trong portal prod: môi trường = Prod, bấm "Duplicate", nhập label, bấm "Duplicate" | E-04 → E-12 | ⚠️ Observed — Ghi vào portal HubSpot prod: tạo pipeline mới cùng toàn bộ stage trong prod. Chỉ có modal nhập label, không có bước xác nhận riêng cho prod; subtitle modal ghi "into prod portal" | pipeline-tool.tsx:31-34,337-353,431-433,500-502; sapp.controller.ts:956-977,1302-1323; HubSpot POST /crm/v3/pipelines/{object} với token prod tại hubspot.service.ts:246-254 |
| A-10 | Bất kỳ ai gọi được API (không cần đăng nhập — R-01) | Gọi trực tiếp các route E-05..E-12, kể cả biến thể staging và route ticket/contact mà UI không mở | E-05..E-12 | Các route hoạt động độc lập với gate FE; route staging trả cùng dữ liệu như prod | sapp.controller.ts:551,572,593,616,849,868,887,908,929,957,985,1381; sapp.controller.ts:596-597 |
| A-11 | Hệ thống | Báo lỗi khi gọi API thất bại | E-01..E-04 | Popup lỗi tiêu đề theo thao tác ("Failed to clone property", "Failed to fetch property", "Failed to fetch property history", "Failed to load pipelines", "Failed to load pipeline", "Failed to clone pipeline", "Failed to duplicate pipeline"), nội dung message (code) | clone-tool.tsx:46-60; property-tool.tsx:72-86; snapshot-tool.tsx:70-84; pipeline-tool.tsx:64-80,87,111,132,165 |
4. Dữ liệu ​
Không có bảng DB riêng cho 4 tool — dữ liệu là định nghĩa property / pipeline / lịch sử property sống trong HubSpot. Bảng tools (trạng thái tool) và env_systems (credential) → nền tảng.
| Entity / bảng | Field | Type | Bắt buộc | Mặc định / enum | Bằng chứng |
|---|---|---|---|---|---|
| Tham số môi trường (path) | env | enum | Có | prod · staging · dev (UI 4 tool chỉ cho chọn Prod/Dev, mặc định Prod) | be/libs/constants/src/lib/hubspot/hubspot.enum.ts:1-5; property-tool.tsx:25-28,52; snapshot-tool.tsx:26-29,45; pipeline-tool.tsx:31-34,54 |
| Tham số loại object (path) | object | enum | Có | Property/History: deals · contacts · tickets (mặc định Deal). Pipeline: deals · tickets | fe/src/data/models/sapp-hubspot.interface.ts:12,50; clone-tool.tsx:23-27,32; pipeline-tool.tsx:36-39,55 |
| Property HubSpot (response) | name, label, type, fieldType, groupName | string | Có (theo DTO) | — | be/libs/interface/src/lib/dto/hubspot/hubspot-property.dto.ts:27-41 |
| Property HubSpot (response) | description, options[] (label, value, description, displayOrder, hidden), displayOrder, hidden | string / array / number / boolean | Không | — | hubspot-property.dto.ts:10-57 |
| Payload tạo property khi clone | name, label, type, fieldType, description, groupName, options[] | — | — | groupName luôn = nhóm chuẩn theo object: deals → dealinformation, contacts → contactinformation, tickets → ticketinformation; option hidden mặc định false | be/libs/modules/src/lib/hubspot/common.service.ts:674-700; hubspot.constants.ts:39-43 |
| Request property-history | properties | string[] | Có | Chuỗi CSV cũng được tách theo dấu phẩy | be/libs/interface/src/lib/dto/hubspot/hubspot-property-history.dto.ts:10-14; be/libs/interface/src/lib/dto/decorators/dto.decorator.ts:156-185 |
| Request property-history | targetTime | Date (ISO) | Không | Bỏ trống = "hiện tại" | hubspot-property-history.dto.ts:16-20; snapshot-tool.tsx:123-124 |
| Response property-history (mỗi property) | history[] (value, timestamp, sourceType, …), targetValue, targetEntry | map theo tên property | — | targetValue = null khi không có mốc nào trước target time | hubspot-property-history.dto.ts:23-73; common.service.ts:152-170 |
| Pipeline (response) | id, label, displayOrder, stages[] (id, label, displayOrder, metadata), archived | — | label bắt buộc | — | be/libs/interface/src/lib/dto/hubspot/hubspot-pipeline.dto.ts:10-50 |
| Request clone pipeline | label | string | Không | Bỏ trống → giữ label nguồn | hubspot-pipeline.dto.ts:57-63 |
| Request duplicate pipeline | label | string | Có (phải là string; chuỗi rỗng vẫn qua validate) | — | hubspot-pipeline.dto.ts:52-55; dto.decorator.ts:17-25 |
| Payload tạo pipeline | label, displayOrder, stages[] (label, displayOrder, metadata) | — | — | Bỏ id pipeline và id stage (HubSpot cấp mới) | common.service.ts:1034-1046,1086-1087 |
| Mã lỗi | 12001 HUBSPOT_ENV_NOT_CONFIGURED (400), 12002 HUBSPOT_API_ERROR (400), 12003 HUBSPOT_PROPERTY_NOT_FOUND (404), 12004 FAILED_CLONE_HUBSPOT_PROPERTY (400), 12005 HUBSPOT_OBJECT_NOT_FOUND (404), 12007 HUBSPOT_PIPELINE_NOT_FOUND (404), 12008 FAILED_CLONE_HUBSPOT_PIPELINE (400) | — | — | — | be/libs/constants/src/lib/error-codes/error-codes.constants.ts:466-505 |
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 API của 4 tool mở công khai, không cần đăng nhập, kể cả API ghi vào dev và ghi vào prod | điều kiện (quyền) | A-02..A-10 | sapp.controller.ts:551,572,593,616,849,868,887,908,929,957,985,1013,1034,1055,1078,1129,1150,1171,1195,1214,1233,1254,1275,1303,1331,1381,1403,1425,1447,1469,1491 | ⚠️ Observed — Q-02 |
| R-02 | Phải nhập tên property (sau khi cắt khoảng trắng) mới được clone / tra cứu; thiếu → popup "Missing property name" | validation | A-02, A-03 | clone-tool.tsx:76-85; property-tool.tsx:95-104 | Không |
| R-03 | Clone property luôn đi một chiều prod → dev; UI không cho chọn chiều khác | điều kiện | A-03 | sapp.controller.ts:626-630; fe/src/data/constants/api.ts:184-187 | Không |
| R-04 | Clone property không ghi đè: nếu dev đã có property cùng tên thì trả nguyên property dev, không đổi label/options | trạng thái | A-03 | common.service.ts:841-848 (tuỳ chọn ghi đè overrideExisting tồn tại nhưng controller không bật: sapp.controller.ts:626-631) | Không |
| R-05 | Khi clone property, nhóm property (group) của nguồn bị bỏ; property mới luôn nằm ở nhóm chuẩn của object | công thức | A-03 | common.service.ts:684-689; hubspot.constants.ts:39-43 | Không |
| R-06 | Khi clone property, chỉ chép name, label, type, fieldType, description và options (mỗi option: label, value, description, displayOrder, hidden — hidden thiếu thì = false) | công thức | A-03 | common.service.ts:678-698 | Không |
| R-07 | Popup thành công của clone property luôn nói "is now available in the dev portal", không phân biệt vừa tạo mới hay đã có sẵn | hiển thị | A-03 | clone-tool.tsx:62-71; common.service.ts:848 | ⚠️ Observed — Q-05 |
| R-08 | Tra cứu property: mọi lỗi từ HubSpot (không chỉ "không tồn tại") đều trả về "HubSpot property not found" (12003, HTTP 404) | điều kiện | A-02, A-03 | common.service.ts:500-505 | ⚠️ Observed — Q-06 |
| R-09 | Môi trường "staging" dùng cùng credential portal prod, nên đọc staging = đọc prod | công thức | A-10 | hubspot.constants.ts:19-26; sapp.controller.ts:596-597 | Không |
| R-10 | Thiếu access token của môi trường → lỗi 12001 "HubSpot access token is not configured for this environment" | validation | A-02..A-09 | common.service.ts:47-58 | Không |
| R-11 | Snapshot: phải có object id và ít nhất một tên property; danh sách property tách bằng dấu phẩy hoặc xuống dòng, bỏ khoảng trắng và mục rỗng | validation | A-04 | snapshot-tool.tsx:94-115 | Không |
| R-12 | Snapshot (BE): danh sách property rỗng → lỗi 12003 "HubSpot property not found" | validation | A-04 | common.service.ts:112-117 | Không |
| R-13 | Snapshot: object id không tồn tại (HubSpot 404) → 12005; lỗi HubSpot khác → 12002 | điều kiện | A-04 | common.service.ts:127-138 | Không |
| R-14 | Giá trị tại mốc thời gian = bản ghi lịch sử có thời điểm muộn nhất nhưng không sau mốc; không có bản ghi nào trước mốc → giá trị null | công thức | A-04 | common.service.ts:79-94,160-166 | Không |
| R-15 | Không nhập mốc thời gian → "giá trị tại mốc" = bản ghi đầu tiên trong lịch sử (HubSpot trả mới nhất trước) | công thức | A-04 | common.service.ts:160-162; hubspot.service.ts:118-119 | Không |
| R-16 | Mốc thời gian nhập trên UI không kèm múi giờ, được hiểu theo giờ trình duyệt rồi đổi sang ISO (UTC); cột Timestamp hiển thị nguyên chuỗi HubSpot trả về, không đổi múi giờ | công thức / hiển thị | A-04 | snapshot-tool.tsx:37-40,123-124,278 | ⚠️ Observed — Q-08 |
| R-17 | Nhãn "value at target" hiển thị theo ô Target time hiện tại trên form, không theo giá trị đã gửi; kết quả không bị xoá khi đổi/xoá ô này | hiển thị | A-04 | snapshot-tool.tsx:232-245 | ⚠️ Observed — Q-09 |
| R-18 | Snapshot chỉ lấy bản ghi chưa lưu trữ (archived=false) | điều kiện | A-04 | hubspot.service.ts:131-134 | Không |
| R-19 | Pipeline chỉ có cho Deal và Ticket; Contact không có | điều kiện | A-05..A-09 | pipeline-tool.tsx:36-39; fe/src/data/models/sapp-hubspot.interface.ts:49-50 | Không |
| R-20 | Nút "Clone to dev" chỉ hiện khi môi trường đang chọn là Prod; nút "Duplicate" hiện ở cả Prod và Dev | điều kiện | A-07, A-08, A-09 | pipeline-tool.tsx:317-353 | ⚠️ Observed (Duplicate ở Prod) — Q-03 |
| R-21 | Clone / duplicate pipeline không ghi đè: pipeline đích được so khớp theo label (chính xác, sau khi cắt khoảng trắng label mới); đã có → trả nguyên pipeline đó | trạng thái | A-07..A-09 | common.service.ts:1068,1081-1084 | Không |
| R-22 | Clone pipeline: label mới để trống → giữ label nguồn. UI điền sẵn label nguồn vào ô | công thức | A-07 | common.service.ts:1068; pipeline-tool.tsx:191-197,327 | Không |
| R-23 | Duplicate pipeline: UI bắt buộc nhập label (thiếu → "Missing label"); UI điền sẵn "<label> (copy)" | validation | A-08, A-09 | pipeline-tool.tsx:201-208,346 | Không |
| R-24 | Duplicate qua API với label rỗng/chỉ khoảng trắng vẫn qua validate; BE rơi về label nguồn, tìm thấy chính pipeline nguồn và trả nó về "thành công" mà không tạo gì | điều kiện | A-10 | hubspot-pipeline.dto.ts:52-55; dto.decorator.ts:17-25; common.service.ts:1068,1081-1084 | ⚠️ Observed — Q-10 |
| R-25 | Pipeline tạo mới giữ nguyên displayOrder và metadata stage của nguồn; id pipeline/stage do HubSpot cấp mới | công thức | A-07..A-09 | common.service.ts:1034-1046 | Không |
| R-26 | Trong lúc một thao tác pipeline đang chạy, mọi nút và ô chọn bị khoá; modal không đóng được khi đang clone/duplicate | điều kiện | A-05..A-09 | pipeline-tool.tsx:183-185,434-439,448 | Không |
| R-27 | Sau duplicate thành công, danh sách pipeline tự tải lại; sau clone sang dev thì không (danh sách đang xem là prod) | trạng thái | A-07, A-08 | pipeline-tool.tsx:133-145,177-178 | Không |
| R-28 | Các khoá SAPP_*_HUBSPOT_API_URL được khai báo nhưng HTTP client luôn gọi host cố định https://api.hubapi.com | công thức | A-02..A-09 | hubspot.constants.ts:4,15,28; hubspot.service.ts:35 | Không (chỉ ghi nhận) |
Trạng thái & chuyển trạng thái (clone / duplicate — áp dụng cho property và pipeline):
text
[CHƯA CÓ Ở ĐÍCH] --(bấm Clone/Duplicate, nguồn tồn tại)--> [ĐÃ TẠO Ở ĐÍCH]
[ĐÃ CÓ Ở ĐÍCH (cùng name / cùng label)] --(bấm Clone/Duplicate)--> [ĐÃ CÓ Ở ĐÍCH] (không đổi, trả bản hiện có)
[NGUỒN KHÔNG TỒN TẠI] --(bấm)--> lỗi 12003 (property) / 12007 (pipeline)Ma trận môi trường đọc/ghi:
| Tool | Đọc | Ghi | Ghi vào prod? |
|---|---|---|---|
| hs-get-property (sapp.003) | prod hoặc dev (UI); API còn có staging (= prod) | — | Không |
| hs-snapshot-property (sapp.002) | prod hoặc dev | — | Không |
| hs-clone-prod-to-dev (sapp.001) | prod (nguồn) + dev (kiểm tra tồn tại) | dev (tạo property) | Không |
| hs-pipeline (sapp.004) | prod hoặc dev | dev (clone prod → dev; duplicate trong dev) và prod (duplicate trong prod) | Có — A-09 / E-12 |
6. Quyền, tích hợp, thông báo, job ​
| Loại | Chi tiết | Bằng chứng |
|---|---|---|
| Quyền (BE) | Tất cả route E-05..E-12 gắn @Public() — không yêu cầu xác thực, không kiểm tra permission code | sapp.controller.ts:114 (import) và các dòng liệt kê ở R-01 |
| Quyền (FE) | Truy cập tool do gate chung theo code (ẩn / tắt / khoá secret) → nền tảng. Comment trong pipeline-tool nói rõ không đặt gate cứng ở FE | fe/src/ui/pages/works/sapp/tools/shell.tsx:427-435; pipeline-tool.tsx:47-50; seed be/libs/database/migrations/1783700000000-create-tools.ts:6-9 (mặc định isEnabled=true, isVisible=true, secretType='none' tại dòng 33) |
| Tích hợp ngoài | HubSpot CRM v3: GET/POST /crm/v3/properties/{object}[/{name}], GET /crm/v3/objects/{object}/{id}?propertiesWithHistory, GET/POST /crm/v3/pipelines/{object}[/{id}]. Portal prod (token SAPP_PROD_HUBSPOT_ACCESS_TOKEN, cũng dùng cho staging) và portal dev (SAPP_DEV_HUBSPOT_ACCESS_TOKEN) | hubspot.service.ts:67-135,226-254; hubspot.constants.ts:10-32 |
| Credential | Đọc từ env_systems mỗi lần gọi → nền tảng | common.service.ts:47-58 |
| Thông báo (mail / push / MQTT / Discord) | Không tìm thấy. Chỉ popup trên UI | clone-tool.tsx:65-71; pipeline-tool.tsx:137-143,170-176 |
| Log | Lỗi được ghi log BE theo tên handler | vd sapp.controller.ts:566,634,951,979,1375 |
| Job / cron | Không tìm thấy | — |
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:14 §Overview | "the three routes below are currently marked @Public()" | Toàn bộ route HubSpot của 4 tool (≥ 29 route, gồm cả route ghi prod) đều @Public() | sapp.controller.ts các dòng ở R-01 |
| DR-02 | shared/api-docs/sapp.md:12 §Overview | Làm việc với "two portals (prod / dev)" | Có thêm env staging (dùng credential prod) cho get property deals/contacts; tài liệu nhắc staging ở :74-80, :119 nhưng mô tả tổng quan chưa cập nhật | hubspot.enum.ts:1-5; sapp.controller.ts:592,1054 |
| DR-03 | shared/api-docs/sapp.md:150 §Pipeline endpoints | "There are 5 routes per object type" | Bảng ngay dưới liệt kê 6 dòng; thực tế mỗi object có 7 route (list prod/dev, get prod/dev, clone, duplicate prod, duplicate dev) | sapp.controller.ts:848-1010 (deals), 1194-1356 (tickets) |
| DR-04 | shared/api-docs/sapp.md:148 §Pipeline endpoints (câu mở đầu) | "Clones a pipeline definition … from prod to dev" | Nhóm route này còn tạo pipeline trong portal prod (duplicate prod); tài liệu chỉ nhắc ở :176 mà không cảnh báo đây là thao tác ghi prod | sapp.controller.ts:956-977,1302-1323 |
| DR-05 | shared/api-docs/sapp.md:177-181 §Duplicate behavior | Body label "required" | DTO chỉ kiểm tra là string; chuỗi rỗng/khoảng trắng qua được và BE trả lại chính pipeline nguồn (R-24) — tài liệu không nói | hubspot-pipeline.dto.ts:52-55; common.service.ts:1068,1081-1084 |
| DR-06 | shared/api-docs/sapp.md:60 §Get a prod deal property — Errors | Lỗi 12003 / 12001 | 12003 được trả cho mọi lỗi HubSpot (token hỏng, rate limit, lỗi 5xx), không chỉ property không tồn tại | common.service.ts:500-505 |
| DR-07 | shared/api-docs/sapp.md:16-25 §Required env_systems keys | SAPP_*_HUBSPOT_API_URL "(reserved)" | Khớp — code không dùng, host cố định. Ghi lại để FS không mô tả sai là có cấu hình host theo môi trường | hubspot.service.ts:35; hubspot.constants.ts:4 |
| DR-08 | shared/api-docs/sapp.md §Contacts endpoints :121-126, §Overview | Không có route thêm option | Có route POST dev/hubspot/{deals|contacts}/properties/:property/options chưa được mô tả trong sapp.md (ngoài 4 tool, dùng bởi sapp-26878) | sapp.controller.ts:821,1101; fe/src/data/constants/api.ts:192 |
| DR-09 | Swagger DTO hubspot-property-history.dto.ts:33-45,62-65 | Response property-history có sourceId, sourceLabel, updatedByUserId, valueLabel, targetValueLabel | Service property-history chỉ dựng history / targetEntry / targetValue, không gắn valueLabel / targetValueLabel (chỉ timeline làm việc đó); sapp.md:222-241 khớp code, Swagger thì không | common.service.ts:140-170 |
| DR-10 | be/.agents/maps/modules/hubspot.md (bảng đầu file) | Module HubSpot nằm ở apps/hubspot-service, apps/event-processor-service, apps/background-job-service, apps/ops; client @hubspot/api-client | Repo này chỉ có be/apps/api, hook, schedule; 4 tool đi qua SappController + HubSpotCommonService + client REST tự viết. Map không đề cập SAPP tools | be/apps/ (ls); hubspot.service.ts:21-35 |
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 | Ai (role/nhóm người dùng) được dùng 4 tool này? Có phân biệt người chỉ được đọc (Property, Snapshot) và người được ghi (Clone, Pipeline)? | Viết Actor + quyền trong FS; code chỉ có gate chung theo tool, không có role | Có |
| Q-02 | Toàn bộ API (kể cả ghi dev và ghi prod) đang @Public(), tài liệu nói "temporary". Đây là trạng thái chấp nhận được hay là rủi ro cần ghi thành OQ/defect? Có kế hoạch siết @AuthRequired không? | Quyết định ghi BR quyền hay OQ bảo mật | Có |
| Q-03 | Duplicate pipeline trong prod (A-09 / E-12) là cố ý hay bug? Nếu cố ý: ai được làm, có cần bước xác nhận riêng khi ghi prod không? | Tool duy nhất trong nhóm ghi vào prod; quyết định BR vs OQ | Có |
| Q-04 | Mục đích nghiệp vụ của từng tool là gì (vd clone prod → dev để dựng dữ liệu test cho dự án nào)? | Viết mục tiêu / US Statement — không suy từ code | Không |
| Q-05 | Khi property đã tồn tại ở dev nhưng khác định nghĩa prod (options khác), tool trả bản dev cũ và vẫn báo "Clone completed". Người dùng có cần được báo "đã có sẵn, không cập nhật" không? Có nhu cầu ghi đè (code có sẵn overrideExisting nhưng không bật)? | Quyết định BR "không ghi đè" và thông báo | Không |
| Q-06 | Mọi lỗi HubSpot khi đọc property đều hiện "property not found" (12003). Cố ý hay bug? | Thông báo lỗi trong FS; ảnh hưởng chẩn đoán khi token hết hạn | Không |
| Q-07 | Nhập tên property không tồn tại trong Snapshot: code không báo lỗi riêng, trả lịch sử rỗng ("No history entries") — cần xác nhận bằng chạy thật HubSpot phản hồi thế nào. Hành vi mong muốn là gì? | Acceptance criteria cho Snapshot | Không |
| Q-08 | Target time hiểu theo giờ trình duyệt, còn Timestamp hiển thị nguyên chuỗi UTC từ HubSpot. Người dùng có nhầm múi giờ (VN +7) không? Cố ý hay cần đổi? | Rule hiển thị thời gian | Không |
| Q-09 | Nhãn "value at target" phụ thuộc ô Target time hiện tại, không theo lần tra đã gửi (R-17). Cố ý hay bug? | Quyết định ghi BR hay defect | Không |
| Q-10 | Gọi API duplicate với label rỗng trả về chính pipeline nguồn như thành công (R-24). Cố ý hay bug? | Quyết định BR hay defect | Không |
| Q-11 | UI cho chọn Prod/Dev, không có Staging, trong khi API get property có staging (= prod). Có cần Staging trên 4 tool này không? | Phạm vi FS | Không |
| Q-12 | Route "Suggested trace properties" và "Property history timeline (trace)" (sapp.controller.ts:1538-1701) có trong tài liệu cạnh property-history nhưng không màn nào trong fe/src gọi. Chúng thuộc tool nào / có nằm trong phạm vi tài liệu này không? | Chốt phạm vi | Không |
| Q-13 | So khớp pipeline theo label phân biệt hoa thường và chỉ cắt khoảng trắng ở label mới — nếu dev có "Sales Pipeline" và prod là "Sales pipeline", tool sẽ tạo thêm một pipeline. Đây là hành vi chấp nhận? | Rule idempotent trong FS | Không |
9. Nhật ký nguồn đã đọc ​
| File / section | Mục đích |
|---|---|
ba/_workflow/templates/feature-inventory/Template.md | Khung tài liệu |
ba/system-profile.md §3–§5 | Công thức truy vết, ranh giới ghi, quy ước |
fe/app/works/sapp/tools/{hs-clone-prod-to-dev,hs-snapshot-property,hs-get-property,hs-pipeline}/page.tsx | Route màn |
fe/app/works/sapp/tools/layout.tsx, fe/app/works/sapp/tools/page.tsx | Khung chung, index (→ nền tảng) |
fe/src/ui/pages/works/sapp/tools/registry.ts | Nhóm sidebar, nhãn, slug |
fe/src/ui/pages/works/sapp/tools/shell.tsx (Grep ToolGate) | Vị trí gate (→ nền tảng) |
fe/src/data/constants/tool-codes.ts:20-40 | Code sapp.001–004 |
fe/src/ui/components/works/sapp/clone-tool.tsx (đọc hết) | Màn Clone |
fe/src/ui/components/works/sapp/snapshot-tool.tsx (đọc hết) | Màn Snapshot |
fe/src/ui/components/works/sapp/property-tool.tsx (đọc hết) | Màn Property |
fe/src/ui/components/works/sapp/pipeline-tool.tsx (đọc hết) | Màn Pipeline |
fe/src/ui/components/works/sapp/hubspot-property-field.tsx (Grep export + nơi import) | Xác nhận không dùng bởi 4 tool |
fe/src/data/services/sapp/{clone-property,get-property,pipeline,property-history,add-property-option}.service.ts | Service FE |
fe/src/data/constants/api.ts:170-240 | URL "SAPP HUBSPOT TOOLS" |
fe/src/data/models/sapp-hubspot.interface.ts | Kiểu dữ liệu FE |
be/apps/api/src/sapp/sapp.controller.ts:1-175, 545-645, 845-1015, 1296-1425 + Grep route hubspot và fromEnv/toEnv dòng 1012-1520 | Route, môi trường, @Public() |
be/libs/modules/src/lib/hubspot/common.service.ts:1-180, 489-513, 674-724, 820-895, 973-1109 | Logic get/clone property, history, pipeline |
be/libs/services/src/lib/hubspot/hubspot.service.ts (Grep method/path; đọc 118-142) | Endpoint HubSpot thực gọi |
be/libs/constants/src/lib/hubspot/hubspot.constants.ts:1-50, hubspot.enum.ts | Map env → credential, nhóm property mặc định, host |
be/libs/constants/src/lib/error-codes/error-codes.constants.ts:466-505 | Mã lỗi |
be/libs/interface/src/lib/dto/hubspot/{hubspot-pipeline,hubspot-property-history,hubspot-property}.dto.ts | DTO request/response |
be/libs/interface/src/lib/dto/decorators/dto.decorator.ts:17-25, 156-185, 397-445 | Hành vi validate của decorator |
be/libs/database/migrations/1783700000000-create-tools.ts:1-40 | Seed code tool + mặc định trạng thái |
shared/api-docs/sapp.md:1-370 | Đối chiếu Drift |
be/.agents/maps/modules/hubspot.md:1-40 + ls be/apps | Đối chiếu map module |
Grep property-suggestions|property-timeline trong fe/src | Xác nhận không màn nào gọi (Q-12) |