Appearance
UI Spec: Quản lý vật tư theo Subtask
Feature slug: subtask-materialVersion: 1.0 Ngày: 2026-03-26
B1) Screen Map
CustomerDetail (Tab "Công Việc")
└── TaskDetail (Chi tiết công việc cha)
├── SCR-03: SubtaskTable — Bảng subtask (fix button label + delete confirm)
├── SCR-04: AggregateView — Bảng VẬT TƯ DỰ KIẾN (aggregate)
├── SCR-05: LegacyMaterialView — Bảng VẬT TƯ ĐÃ XUẤT KHO (backward compat)
└── TaskCreate Popup (isChild=true)
├── SCR-02: SubtaskForm — Form subtask (existing + thêm MaterialForm)
└── SCR-01: MaterialForm — Section vật tư (NEW)B2) Component Inventory
| SCR | Component | File | Loại | FR |
|---|---|---|---|---|
| SCR-01 | MaterialForm | TaskForm/MaterialForm.tsx | New | FR-001, FR-002, FR-003, FR-004, FR-008 |
| SCR-02 | TaskForm (sửa) | TaskForm/index.tsx | Sửa | FR-001 |
| SCR-03 | SubtaskTable (sửa) | TaskDetail/SubtaskTable.tsx | Sửa | FR-007 |
| SCR-04 | General.tsx — aggregate section | TaskDetail/General.tsx | Sửa | FR-005, FR-006 |
| SCR-05 | MaterialTable (giữ cũ) | TaskDetail/MaterialTable.tsx | Sửa nhẹ | FR-006 |
B3) User Flows
Flow 1: Tạo subtask với vật tư
[SubtaskTable] Click "Thêm công việc"
→ [TaskCreate popup] mở (isChild=true, isCreateForm=true)
→ [SubtaskForm] nhập tên, assignees, due_date
→ [Chọn subtask definition]
→ Auto-fill vật tư mẫu vào MaterialForm
→ [MaterialForm] KTV chỉnh SL / xóa / search thêm
→ Click "Lưu"
→ Insert project_task (subtask)
→ Insert project_task_material[] (materials)
→ Popup đóng
→ [SubtaskTable] refresh
→ [AggregateView] refreshFlow 2: Sửa vật tư trên subtask
[SubtaskTable] Click icon sửa subtask
→ [TaskCreate popup] mở (isChild=true, có taskId)
→ Load task_materials từ DB → hiển thị trong MaterialForm
→ [MaterialForm] KTV sửa SL / xóa / thêm
→ Click "Lưu"
→ Delete all materials by task_id → Re-insert
→ Popup đóng → refreshFlow 3: Xóa subtask có materials
[SubtaskTable] Click icon xóa subtask
→ [Confirm dialog] "Bạn có chắc chắn muốn xóa công việc [tên]?
(Sẽ xóa 3 vật tư đi kèm)"
→ Click "Xác nhận"
→ Delete project_task → CASCADE delete project_task_material
→ [SubtaskTable] refresh
→ [AggregateView] refresh (materials giảm)B4) Notification Spec
Không có notification trong phase 1 (Ref: DEC-B04, Non-goals).
B5) Permission Matrix
| Màn hình | Action | Staff (KTV) | Manager | Admin | Call Center |
|---|---|---|---|---|---|
| SCR-01 MaterialForm | Xem vật tư subtask | Subtask mình assign/tạo | Tất cả trong project | Tất cả | Ẩn |
| SCR-01 MaterialForm | Thêm vật tư | Subtask mình assign/tạo, status ≠ done/canceled | Tất cả, status ≠ done/canceled | Tất cả | Ẩn |
| SCR-01 MaterialForm | Sửa SL vật tư | Subtask mình assign/tạo, status ≠ done/canceled | Tất cả, status ≠ done/canceled | Tất cả | Ẩn |
| SCR-01 MaterialForm | Xóa vật tư | Subtask mình assign/tạo, status ≠ done/canceled | Tất cả, status ≠ done/canceled | Tất cả | Ẩn |
| SCR-04 AggregateView | Xem tổng hợp | Theo quyền xem task cha | Tất cả | Tất cả | Ẩn |
Permission reuse: theo
useProjectTaskPermissionhiện tại — Staff xem subtask mình assign/tạo, Manager xem tất cả trong project, Admin full.
B6) State Matrix
| Màn hình | Loading | Empty | Error | No Permission | Partial |
|---|---|---|---|---|---|
| SCR-01 MaterialForm | Skeleton rows (3 dòng) | Bảng trống + text "Chưa có vật tư. Tìm kiếm để thêm." | Toast error "Không thể tải vật tư" | Ẩn section hoàn toàn (Call Center) | Hiển thị rows đã load, search disabled |
| SCR-01 MaterialForm (readonly) | Skeleton rows | Bảng trống + text "Không có vật tư" | Toast error | Ẩn | — |
| SCR-04 AggregateView | Spinner | Text "Chưa có vật tư dự kiến" | Toast error | Ẩn section | Hiển thị partial data |
| SCR-05 LegacyMaterialView | Spinner | Ẩn section hoàn toàn | Toast error | Ẩn | — |
B7) Copy Text Dictionary
| Key (i18n) | Text (VI) | Màn hình | Ghi chú |
|---|---|---|---|
project.label.material_section | VẬT TƯ | SCR-01 | Heading section trong form subtask |
project.label.material_planned | VẬT TƯ DỰ KIẾN | SCR-04 | Heading aggregate ở task cha |
project.label.material_exported | VẬT TƯ (ĐÃ XUẤT KHO) | SCR-05 | Heading backward compat |
project.label.material_name | Vật tư | SCR-01, SCR-04 | Cột tên |
project.label.material_sku | Mã vật tư | SCR-01, SCR-04 | Cột SKU |
project.label.material_unit | ĐVT | SCR-01, SCR-04 | Cột đơn vị |
project.label.material_qty_planned | SL dự kiến | SCR-04 | Cột số lượng aggregate |
project.label.material_qty | SL | SCR-01 | Cột số lượng input |
project.label.material_search | Tìm kiếm thêm vật tư... | SCR-01 | Placeholder search |
project.label.material_empty | Chưa có vật tư. Tìm kiếm để thêm. | SCR-01 | Empty state |
project.label.material_empty_aggregate | Chưa có vật tư dự kiến | SCR-04 | Empty state aggregate |
project.label.material_disabled | Ngưng KD | SCR-01 | Badge product disabled |
project.label.add_task | Thêm công việc | SCR-03 | Button label (fix) |
project.label.delete_task_with_materials | Sẽ xóa {count} vật tư đi kèm | SCR-03 | Delete confirm dialog |
project.label.material_aggregate_title | tổng hợp từ {count} công việc con | SCR-04 | Subtitle aggregate |
B8) Analytics Events
| Event | Trigger | Data | Mục đích |
|---|---|---|---|
subtask_material_added | KTV thêm vật tư vào subtask | `{ subtask_id, product_id, quantity, source: "auto_fill" | "search" }` |
subtask_material_deleted | KTV xóa vật tư khỏi subtask | { subtask_id, product_id } | Track vật tư bị bỏ thường xuyên |
B9) Tooltip Dictionary
| Màn hình | Field/Icon | Tooltip Text | Điều kiện hiện |
|---|---|---|---|
| SCR-01 | SL (input) | Số lượng vật tư dự kiến sử dụng cho công việc này | Hover label "SL" |
| SCR-01 | Icon 🗑 | Xóa vật tư khỏi danh sách | Hover icon xóa |
| SCR-01 | Badge "Ngưng KD" | Sản phẩm này đã ngưng kinh doanh. Vật tư vẫn được giữ để theo dõi. | Hover badge |
| SCR-04 | SL dự kiến | Tổng số lượng dự kiến từ tất cả công việc con | Hover label "SL dự kiến" |
| SCR-04 | "(tổng hợp từ N công việc con)" | Dữ liệu được cộng dồn từ vật tư của các công việc con | Luôn hiện (subtitle) |
B-Desktop: Wireframes
Wireframe dùng dữ liệu thực tế ngành spa. Không placeholder.
SCR-01: MaterialForm (trong form subtask)
┌─── VẬT TƯ ──────────────────────────────────────────────┐
│ │
│ ┌───┬──────────────────────┬────────┬───────┬─────┬───┐ │
│ │ # │ Vật tư │ Mã │ ĐVT │ SL │ │ │
│ ├───┼──────────────────────┼────────┼───────┼─────┼───┤ │
│ │ 1 │ [img] Serum Laser X │ SP001 │ ml │ [2] │ 🗑│ │
│ │ 2 │ [img] Gel làm mát │ SP042 │ tube │ [1] │ 🗑│ │
│ │ 3 │ [img] Mask dưỡng da │ SP018 │ miếng │ [1] │ 🗑│ │
│ └───┴──────────────────────┴────────┴───────┴─────┴───┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔍 Tìm kiếm thêm vật tư... │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘SCR-01: MaterialForm — Readonly (subtask done)
┌─── VẬT TƯ ──────────────────────────────────────────────┐
│ │
│ ┌───┬──────────────────────┬────────┬───────┬─────────┐ │
│ │ # │ Vật tư │ Mã │ ĐVT │ SL │ │
│ ├───┼──────────────────────┼────────┼───────┼─────────┤ │
│ │ 1 │ [img] Serum Laser X │ SP001 │ ml │ 2 │ │
│ │ 2 │ [img] Gel làm mát │ SP042 │ tube │ 1 │ │
│ └───┴──────────────────────┴────────┴───────┴─────────┘ │
│ │
│ (Không cho sửa — công việc đã hoàn thành) │
│ │
└───────────────────────────────────────────────────────────┘SCR-01: MaterialForm — Product disabled
│ 1 │ [img] Serum Laser X [Ngưng KD] │ SP001 │ ml │ 2 │ 🗑│SCR-04: AggregateView (task cha)
┌─── VẬT TƯ DỰ KIẾN (tổng hợp từ 2 công việc con) ───────┐
│ │
│ ┌───┬──────────────────────┬────────┬───────┬──────────┐ │
│ │ # │ Vật tư │ Mã │ ĐVT │ SL dự │ │
│ │ │ │ │ │ kiến │ │
│ ├───┼──────────────────────┼────────┼───────┼──────────┤ │
│ │ 1 │ Serum Laser X │ SP001 │ ml │ 4 │ │
│ │ 2 │ Gel làm mát │ SP042 │ tube │ 1 │ │
│ │ 3 │ Mask dưỡng da │ SP018 │ miếng │ 2 │ │
│ └───┴──────────────────────┴────────┴───────┴──────────┘ │
│ │
└───────────────────────────────────────────────────────────┘SCR-03: Delete Confirm Dialog (có materials)
┌─────────────────────────────────────────┐
│ Xóa công việc │
│ │
│ Bạn có chắc chắn muốn xóa công việc │
│ "BS - Laser sắc tố nám, đồi mồi"? │
│ (Sẽ xóa 3 vật tư đi kèm) │
│ │
│ [Hủy] [Xác nhận] │
└─────────────────────────────────────────┘B-Edge Cases
| # | Tình huống | Behavior | SCR |
|---|---|---|---|
| EC-01 | Auto-fill trả về 0 results | Bảng trống + text "Chưa có vật tư. Tìm kiếm để thêm." | SCR-01 |
| EC-02 | Search không tìm thấy product | Dropdown trống + text "Không tìm thấy" | SCR-01 |
| EC-03 | SL nhập = 0 hoặc âm | Validate: SL phải > 0, hiện error inline | SCR-01 |
| EC-04 | Thêm product đã có trong bảng | Toast warning "Vật tư đã có trong danh sách" — không thêm duplicate | SCR-01 |
| EC-05 | Task cha không có subtask nào có materials | Ẩn section "VẬT TƯ DỰ KIẾN" hoàn toàn | SCR-04 |
| EC-06 | Task cha có order_materials cũ nhưng không có project_task_material mới | Chỉ hiện bảng "VẬT TƯ (ĐÃ XUẤT KHO)" | SCR-05 |
| EC-07 | Manual board subtask (automate=false) | MaterialForm hiển thị, auto-fill không chạy, KTV tự search | SCR-01 |