Giao diện
US-I04: Bulk product — bắt đầu session, nạp và tạo course category ​
Thuộc FS: 00-overview.md — SAPP-24395 Contact / Deal instances & Bulk product creation 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. As-built tại commit
e77c47a— suy từ code, chưa chạy thử.
| Field | Value |
|---|---|
| US ID | US-I04 |
| Role thực hiện | R1 - Người dùng tool (Assumption — OQ-I-16 về G-01) |
| Phase | As-built (đã triển khai) |
| Priority | N/A — as-built |
| Status | Draft |
| NFR liên quan | NFR-I-01, NFR-I-03, NFR-I-04, NFR-I-05, NFR-I-07 — xem Overview §5 |
| OQ liên quan | OQ-I-04, OQ-I-10, OQ-I-12, OQ-I-13, OQ-I-14, OQ-I-16 — xem Overview §6 |
Nguồn inventory: A-09, A-10, A-11, A-16, A-17. Ghi: SAPP Ops — môi trường theo SAPP_BASE_API_URL (❓ OQ-I-04) (chỉ bước 1.5, qua API); Redis mind_keep.
1. US Statement ​
text
As a Người dùng tool (Assumption — OQ-I-16)
I want bắt đầu một session tạo product hàng loạt, nạp toàn bộ course category từ SAPP Ops và biết category nào trong danh sách sản phẩm cố định đang thiếu
So that [Assumption] các bước sau (subject, product) tra được category đúng, và tôi có thể dừng / quay lại tiếp tục trong vòng 6 giờ mà không mất tiến trình2. Dependencies & Definition Of Ready ​
N/A — đã triển khai (Mode B). Dependency runtime:
| # | Cần sẵn sàng | Ai cung cấp | Trạng thái |
|---|---|---|---|
| D1 | SAPP_BASE_API_URL + SAPP_API_TOKEN (JWT tĩnh) trong env_systems (US-P04) — không dùng auto-login của US-P08 | Admin CMS | N/A — runtime; môi trường ❓ OQ-I-04 |
| D2 | Redis mind-keep hoạt động | Hạ tầng BE | N/A — runtime |
3. Business Rules ​
| ID | Rule | Áp dụng ở | Đã cover bởi (AC) | Nguồn code (Mode B/C) |
|---|---|---|---|---|
| BR-01 | Bắt đầu = tạo session mới (mã session do hệ thống sinh), nạp toàn bộ course category của SAPP Ops vào session, tính danh sách category thiếu; session ở bước 1. Bấm "Re-run" ở bước 1 cũng tạo một session mới thay cho session đang xem | M1 - A1 | AC-M1.1, AC-M1.2 | be/libs/modules/src/lib/sapp/common.service.ts:2614-2638; fe/src/ui/components/works/sapp/sapp-24395-products-tool.tsx:260-273, :575-590 |
| BR-02 | Category "thiếu" = tên category có trong danh sách sản phẩm cố định nhưng không khớp (không phân biệt hoa thường, bỏ khoảng trắng đầu cuối) với category nào trên SAPP Ops | M1 - A1 | AC-M1.1 | be/libs/modules/src/lib/sapp/common.service.ts:2640-2696 |
| BR-03 | Tool dùng một cấu hình kết nối SAPP Ops duy nhất (URL gốc + token tĩnh trong env_systems); màn không có lựa chọn môi trường. Thiếu URL hoặc token → lỗi 13012 "SAPP API base URL or token is not configured" | M1 - mọi action | AC-M1.4 | be/libs/modules/src/lib/sapp/common.service.ts:278-302; be/libs/constants/src/lib/sapp/sapp.constants.ts:16-25; be/libs/constants/src/lib/error-codes/error-codes.constants.ts:599-603 |
| BR-04 | Session sống 6 giờ tính từ lần ghi gần nhất (mỗi bước ghi đều đặt lại hạn); quá hạn → mọi thao tác trên session trả 13017 "SAPP-24395 product session not found or expired" (HTTP 404) | M1 - A2 | AC-M1.5, EC-M1.2 | be/libs/modules/src/lib/sapp/common.service.ts:2585-2608; be/libs/constants/src/lib/sapp/sapp.constants.ts:57-61; be/libs/constants/src/lib/error-codes/error-codes.constants.ts:624-628 |
| BR-05 | Trình duyệt nhớ mã session sau mỗi thao tác thành công; mở lại màn → tự tải lại session ("Đang khôi phục session…") và nhảy tới bước session đã đạt | M1 - A2 | AC-M1.3 | fe/src/ui/components/works/sapp/sapp-24395-products-tool.tsx:165-245; fe/src/data/constants/local-storage.ts:6 |
| BR-06 | "Bắt đầu lại" chỉ xoá mã session trong trình duyệt và trạng thái màn, quay về bước 1; session trên server không bị xoá, còn tới khi hết hạn (BR-04) | M1 - A4 | AC-M1.6 | fe/src/ui/components/works/sapp/sapp-24395-products-tool.tsx:126-141, :498-507 |
| BR-07 | (Chỉ qua API — bước 1.5) Tạo category thiếu: gửi danh sách tên, rỗng = tạo tất cả category thiếu; tên không nằm trong danh sách thiếu bị bỏ qua; lỗi từng mục được ghi vào "failed", không dừng cả lượt; category tạo được thêm vào session và bỏ khỏi danh sách thiếu; bước của session giữ nguyên; không kiểm tra session đang ở bước nào | M1 - A3 | AC-M1.7 | be/libs/modules/src/lib/sapp/common.service.ts:2739-2799; be/libs/interface/src/lib/dto/sapp/sapp-instance.dto.ts:247-254 |
| BR-08 | Nạp category theo trang 100 dòng tới khi trang cuối < 100 dòng; lỗi đọc → 13013 "Failed to get SAPP course categories" | M1 - A1 | AC-M1.4 | be/libs/modules/src/lib/sapp/common.service.ts:2496-2518, :2632-2637 |
Không phải BR (⚠️ Observed → OQ): môi trường SAPP Ops thực tế không xác định từ code, có thể là prod (inventory R-23 → OQ-I-04); màn không hiện category thiếu và không có nút bước 1.5 (R-35, DR-I-07 → OQ-I-13); nhận diện "session hết hạn" có thể không bao giờ khớp (R-34 → OQ-I-10); cảnh báo CORS có thể sai (R-38 → OQ-I-14); tạo thành công không kèm id vẫn tính "created" (R-32 → OQ-I-12).
4. Design ​
a. Mối liên hệ giữa các màn & User Flow ​
text
[Workspace SAPP › "Bulk product creation" (sapp.007)]
↓ vào màn
├─ trình duyệt có mã session ─→ "Đang khôi phục session…" ─┬─ OK → nhảy tới bước đã đạt (US-I05 / US-I06)
│ └─ lỗi → popup / (hết hạn) xoá mã — xem EC-M1.2
└─ không có ─→ [M1: Bước 1 · Bắt đầu session & nạp categories]
↓ "Bắt đầu — Nạp categories"
[Đã nạp n categories] ── "Tiếp" ──→ [US-I05 Bước 2]
(Bước 1.5 tạo category thiếu: chỉ API, không có trên màn)
"Bắt đầu lại" (mọi bước) ──→ về M1 trạng thái trốngLink flow chi tiết: sơ đồ trạng thái session ở inventory §5b.
b. Chi tiết từng màn ​
Màn M1 - Bulk product creation: khung màn + Bước 1 ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Mở tool "Bulk product creation" (/works/sapp/tools/sapp-24395-products) |
| Mô tả ngắn | Header "SAPP-24395 · Bulk product creation": "Tạo hàng loạt payment products trên SAPP Ops theo từng bước… dùng base URL & token cấu hình trong env_systems. Tiến trình được lưu và tự khôi phục (session sống 6 giờ)". Khối cảnh báo "Lưu ý: API gốc chặn CORS" (3 điều kiện: chạy dev local; expose qua ngrok; sửa SAPP_BASE_API_URL). Stepper 3 bước (Categories · Subjects · Create products — quy tắc ở US-I06 BR-07). Nội dung bước. Chân: "Bắt đầu lại" · "Quay lại" · "Tiếp" |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Chưa có session | Bước 1: "Bước 1 · Bắt đầu session & nạp categories" — "Tạo một session mới và nạp toàn bộ danh mục khóa học (category) từ SAPP. Không cần nhập gì." + nút "Bắt đầu — Nạp categories"; bước 2, 3 khoá | N/A |
| Loading | Đang khôi phục / đang nạp | "Đang khôi phục session…" / nút "Đang nạp…"; mọi nút khoá | N/A |
| Has data | Đã nạp | "Đã nạp n categories." + nút "Re-run"; "Session: <mã>"; tối đa 24 tên category dạng thẻ + "+k khác"; bước 1 có dấu hoàn thành; "Tiếp" bật | N/A |
| Empty | SAPP Ops không có category | "Đã nạp 0 categories." — mọi category cố định thành "thiếu" (không hiển thị — OQ-I-13) | N/A |
| Error | API lỗi | Popup <tiêu đề thao tác> — <message> (<code>) (NFR-I-07) | N/A |
| Disabled | Đang bận | Stepper, "Bắt đầu lại", "Quay lại", "Tiếp" khoá | N/A |
Fields ​
| Field | Required | Type | Default | Format | Validation | Error Message | Visibility | Depends On |
|---|---|---|---|---|---|---|---|---|
| (không có ô nhập) | — | — | — | — | — | — | — | — |
| Số category đã nạp | — | Readonly | — | "Đã nạp n categories." | — | — | Khi đã có session | Session |
| Mã session | — | Readonly | — | UUID | — | — | Khi đã có session | Session |
| Danh sách tên category | — | Readonly (tối đa 24 + "+k khác") | — | — | — | — | Khi có category | Session |
Filter / Search / Sort (chỉ màn List/Report — xóa nếu không phải) ​
N/A — màn wizard, không có danh sách lọc.
Actions ​
A1 - Bắt đầu — Nạp categories (Bước 1) ​
| Item | Description |
|---|---|
| Trigger | Bấm "Bắt đầu — Nạp categories" (hoặc "Re-run" khi đã nạp) |
| Behavior | BR-03 lấy cấu hình → BR-08 nạp category → BR-01, BR-02 tạo session → lưu Redis (BR-04) → trình duyệt nhớ mã (BR-05). Chỉ đọc SAPP Ops; ghi Redis mind_keep + localStorage |
| Success Result | Popup "Đã bắt đầu session" — "Đã nạp n categories. Bấm "Tiếp" để nạp subjects." |
Error cases:
| Case | Expected Handling |
|---|---|
| Thiếu URL / token | Popup "Bắt đầu session thất bại" — "SAPP API base URL or token is not configured (13012)" |
| SAPP Ops lỗi / token hết hạn | Popup "Bắt đầu session thất bại" — "Failed to get SAPP course categories (13013)" |
A2 - Khôi phục session khi mở màn ​
| Item | Description |
|---|---|
| Trigger | Mở màn khi trình duyệt còn mã session |
| Behavior | Đọc session theo mã (Redis), hiện bước đã đạt (BR-05). Không ghi |
| Success Result | Màn hiện đúng bước + dữ liệu session |
Error cases:
| Case | Expected Handling |
|---|---|
| Session hết hạn | Thiết kế: xoá mã đã lưu, im lặng. ⚠️ Observed — so mã lỗi sai kiểu → rơi vào popup "Không khôi phục được session" — "SAPP-24395 product session not found or expired (13017)", mã cũ không bị xoá (OQ-I-10) |
| Lỗi khác | Popup "Không khôi phục được session" |
A3 - Tạo category thiếu (Bước 1.5 — chỉ API) ​
| Item | Description |
|---|---|
| Trigger | Gọi POST /api/v1/sapp/sapp-24395/products/sessions/<mã>/categories/create với danh sách tên (hoặc rỗng). Không có nút trên màn, FE không gọi (OQ-I-13, DR-I-07) |
| Behavior | BR-07. Ghi vào: SAPP Ops — course category mới tại SAPP_BASE_API_URL (⚠️ môi trường ❓ — có thể là PROD, OQ-I-04) + Redis mind_keep |
| Success Result | Session trả về có thêm category mới và kết quả created / failed của lượt tạo |
Error cases:
| Case | Expected Handling |
|---|---|
| Session hết hạn | 13017 (HTTP 404) |
| Một category tạo lỗi | Vào "failed" kèm lý do, các mục khác vẫn chạy |
| Lỗi chung | 13020 "Failed to create SAPP course category" |
A4 - Bắt đầu lại ​
| Item | Description |
|---|---|
| Trigger | Bấm "Bắt đầu lại" ở chân màn (khi không bận) |
| Behavior | BR-06. Chỉ xoá localStorage + trạng thái màn; không gọi API |
| Success Result | Màn về bước 1 trống |
Error cases:
| Case | Expected Handling |
|---|---|
| — | Không có |
Acceptance Criteria của màn M1 ​
AC-M1.1 - Bắt đầu session ​
gherkin
Given chưa có session, SAPP Ops có 30 course category
When bấm "Bắt đầu — Nạp categories"
Then hệ thống tạo session mới ở bước 1 chứa 30 category và danh sách category thiếu
And popup "Đã bắt đầu session" — "Đã nạp 30 categories. Bấm "Tiếp" để nạp subjects."
And màn hiện "Session: `<mã>`" và tối đa 24 tên category + "+6 khác"Maps to:
- Business Rule: BR-01, BR-02, BR-08
- Field/Action: A1
AC-M1.2 - Re-run tạo session mới ​
gherkin
Given đang có session S1 ở bước 1
When bấm "Re-run"
Then một session mới S2 được tạo và mã S2 được lưu trong trình duyệt
And S1 vẫn còn trên server tới khi hết hạnMaps to:
- Business Rule: BR-01, BR-04
AC-M1.3 - Khôi phục khi mở lại màn ​
gherkin
Given session S đang ở bước 2, được ghi lần cuối cách đây 1 giờ
When đóng rồi mở lại màn trên cùng trình duyệt
Then màn hiện "Đang khôi phục session…" rồi hiển thị bước 2 với dữ liệu của SMaps to:
- Business Rule: BR-05
- Field/Action: A2
AC-M1.4 - Thiếu cấu hình ​
gherkin
Given `SAPP_API_TOKEN` chưa cấu hình trong env_systems
When bấm "Bắt đầu — Nạp categories"
Then popup "Bắt đầu session thất bại" — "SAPP API base URL or token is not configured (13012)"Maps to:
- Business Rule: BR-03
AC-M1.5 - Hạn 6 giờ trượt theo lần ghi cuối ​
gherkin
Given session tạo lúc 08:00, nạp subjects (ghi) lúc 13:00
When gọi thao tác trên session lúc 18:30
Then session vẫn còn (hạn tính từ 13:00 → 19:00)
And gọi lúc 19:01 không có thao tác ghi nào khác thì trả 13017 (HTTP 404)Maps to:
- Business Rule: BR-04
AC-M1.6 - Bắt đầu lại không xoá session server ​
gherkin
Given session S ở bước 2
When bấm "Bắt đầu lại"
Then màn về bước 1 trống, trình duyệt không còn mã S
And gọi GET session S qua API vẫn trả dữ liệu tới khi hết hạnMaps to:
- Business Rule: BR-06
- Field/Action: A4
AC-M1.7 - Bước 1.5 qua API ​
gherkin
Given session S có category thiếu ["CGMA", "IFRS"]
When gọi API tạo category thiếu với danh sách ["CGMA", "XYZ"]
Then chỉ "CGMA" được tạo trên SAPP Ops ("XYZ" bị bỏ qua vì không nằm trong danh sách thiếu)
And session có thêm category "CGMA", danh sách thiếu còn ["IFRS"], bước session không đổiMaps to:
- Business Rule: BR-07
- Field/Action: A3
Negative AC / Edge Cases của màn M1 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M1.1 | Không biết SAPP Ops đang trỏ môi trường nào | ⚠️ Observed — màn không cho biết; phụ thuộc giá trị SAPP_BASE_API_URL (OQ-I-04) |
| EC-M1.2 | Mở lại màn sau khi session hết hạn | ⚠️ Observed (suy từ code) — thay vì xoá mã im lặng, có thể hiện popup "Không khôi phục được session … (13017)" mỗi lần mở màn cho tới khi bấm "Bắt đầu lại" (OQ-I-10) |
| EC-M1.3 | Có category thiếu | ⚠️ Observed — màn không hiện danh sách thiếu, không có cách tạo; product của category đó sẽ "failed" ở bước 3 (OQ-I-13) |
| EC-M1.4 | Làm theo cảnh báo CORS (ngrok) | ⚠️ Observed — BE gọi SAPP Ops từ server nên CORS không áp dụng; cảnh báo có thể không còn đúng (OQ-I-14) |
| EC-M1.5 | Token JWT tĩnh hết hạn | Nạp lỗi 13013; phải thay token tay trong env_systems (OQ-I-04) |
| EC-M1.6 | Mở màn ở 2 tab cùng trình duyệt | Hai tab dùng chung mã session trong localStorage; tab bấm "Re-run" sau cùng ghi đè mã của tab kia |
| EC-M1.7 | Trình duyệt chặn localStorage (chế độ riêng tư) | Vẫn chạy được trong phiên; không khôi phục được khi mở lại |
5. Cross-feature AC ​
AC-X.1 - Cấu hình kết nối lấy từ env_systems ​
gherkin
Given Admin đổi `SAPP_BASE_API_URL` trong env_systems (US-P04)
When bấm "Bắt đầu — Nạp categories"
Then category được nạp từ URL mới — và mọi ghi sau đó (bước 1.5, 2.5, 3) đi vào hệ thống tại URL đóMaps to:
- REL-02 (Affects - Nền tảng cấu hình env_systems)
AC-X.2 - Category mới xuất hiện trên SAPP Ops ​
gherkin
Given gọi bước 1.5 thành công
When mở danh mục course category trên SAPP Ops
Then thấy category mới với tên trùng tên trong danh sách cố địnhMaps to:
- REL-07 (Be Affected - Danh mục SAPP Ops)