Appearance
UI Spec: Kho vật tư
Feature slug: material-warehouseVersion: 3.0 Ngày: 2026-03-27 Design doc: docs/superpowers/specs/2026-03-26-material-warehouse-design.md
B1) Screen Map
Sidebar
├── Kho
│ ├── Sản phẩm (existing)
│ ├── Nhập kho (existing)
│ ├── Xuất kho (existing)
│ └── Kho vật tư ★ NEW
│ ├── SCR-01: MaterialWarehouseList — Danh sách vật tư + tồn kho
│ ├── SCR-02: MaterialPriceConfigForm — Form cấu hình vật tư (popup/drawer)
│ ├── SCR-03: MaterialDetail — Chi tiết vật tư + lịch sử giá + lịch sử movement + Danh sách lô (batch list)
│ ├── SCR-06: StockImportForm — Nhập kho manual / import Excel
│ ├── SCR-07: StockInventoryForm — Kiểm kê + điều chỉnh
│ └── SCR-08: TransferForm — Chuyển kho chi nhánh → kho vật tư ★ NEW v3.0
│
CustomerDetail (Tab "Công Việc")
└── TaskDetail (Chi tiết công việc cha)
├── SCR-05: AggregateView — Bảng VẬT TƯ DỰ KIẾN (aggregate, nâng cấp +giá)
└── TaskCreate Popup (isChild=true)
└── SCR-04: MaterialForm — Section vật tư (nâng cấp +ĐVT/giá/thành tiền)B2) Component Inventory
| SCR | Component | File path | Loại | FR reference |
|---|---|---|---|---|
| SCR-01 | MaterialWarehouseList | src/pages/ecommerce/material-warehouse/MaterialWarehouseList.vue | New | FR-001 (tạo kho), FR-002 (danh sách), FR-003 (cấu hình), FR-015 (cảnh báo tồn), BR-11 |
| SCR-02 | MaterialPriceConfigForm | src/pages/ecommerce/material-warehouse/MaterialPriceConfigForm.vue | New | FR-004 (config giá), FR-005 (ĐVT), FR-006 (versioning), BR-01, BR-02 |
| SCR-03 | MaterialDetail | src/pages/ecommerce/material-warehouse/MaterialDetail.vue | New | FR-002 (danh sách), FR-006 (versioning — lịch sử giá), FR-012 (auto-deduct — lịch sử movement), DEC-D02 |
| SCR-03 | BatchList | src/pages/ecommerce/material-warehouse/MaterialDetail/BatchList.tsx | New | FR-018 (lô), FR-019 (HSD), FR-021 (disposal) |
| SCR-04 | MaterialForm (nâng cấp) | src/components/project/TaskForm/MaterialForm.vue | Sửa | FR-009 (chọn ĐVT), FR-010 (Two-Phase Pricing), FR-011 (hiển thị giá), DEC-D05, DEC-D10, DEC-D28 |
| SCR-05 | AggregateView (nâng cấp) | src/components/project/TaskDetail/General.vue | Sửa | FR-011 (hiển thị giá/amount aggregate), FORMULA-005 |
| SCR-06 | StockImportForm | src/pages/ecommerce/material-warehouse/StockImportForm.vue | New | FR-007 (nhập kho manual), FR-008 (import Excel), FR-018 (tạo batch), DEC-D13 |
| SCR-07 | StockInventoryForm | src/pages/ecommerce/material-warehouse/StockInventoryForm.vue | New | FR-014 (kiểm kê), FR-015 (cảnh báo tồn), EC-12, EC-13 |
| SCR-08 | TransferForm | src/pages/ecommerce/material-warehouse/TransferForm.vue | New | DEC-D31 (không duyệt), DEC-D34 (quy đổi), DEC-D36 (chọn lô) |
B3) User Flows — v3.0
Kiến trúc 4 cấp: NCC → Kho tổng → Kho chi nhánh → Kho vật tư → KTV sử dụng Dual-input: ① Chuyển kho từ kho chi nhánh (primary) ② Nhập tay/Excel (fallback)
Flow 1: Tạo kho vật tư cho chi nhánh
[Admin/Manager] Sidebar > Kho > Kho vật tư
→ SCR-01 load (lần đầu: empty state)
→ Hệ thống tự tạo material_warehouse khi vào trang lần đầu
(1 branch = 1 warehouse, auto-create nếu chưa có)
→ SCR-01 hiển thị danh sách trống + "Chưa có vật tư. Nhấn [★ Chuyển kho] hoặc [Nhập tay] để bắt đầu."Flow 2: Thêm vật tư + cấu hình quy đổi đơn vị (1 lần per product)
[Admin] SCR-01 > Click [+ Thêm vật tư]
→ SCR-02 mở (popup/drawer)
→ [Bước 1] Chọn sản phẩm từ danh sách product (search/dropdown)
→ [Bước 2] Nhập thông tin kho:
- Đơn vị kho (ml/g/miếng)
- Giá nhập tham chiếu (source_price) — chỉ tham khảo, giá thực tế từ lô
- SL/đơn vị mua (source_quantity) — VD: "1 chai = 500ml" (DEC-D34)
- Hao hụt hệ thống (wastage_rate %)
- Ngưỡng cảnh báo (min_stock)
→ Hệ thống auto-tính: Giá tham chiếu/ĐV kho = FORMULA-001
→ [Bước 3] Thêm đơn vị sử dụng:
- Tên (giọt, muỗng, ml...)
- Hệ số (to_stock_factor)
- Rời? (is_discrete)
- Mặc định? (is_default)
→ Hệ thống auto-tính: Giá tham chiếu/ĐV sử dụng = FORMULA-002
→ Click [Lưu]
→ INSERT material_price_config + material_usage_unit[]
→ SCR-01 refresh → vật tư mới xuất hiện (tồn kho = 0, chờ chuyển kho)Note: Config này bắt buộc trước khi chuyển kho (DEC-D34). Chưa config → block chuyển kho.
Flow 3: Chuyển kho chi nhánh → Kho vật tư (★ PRIMARY — v3.0, FR-022)
[Manager/Admin] SCR-01 > Click [★ Chuyển kho]
→ SCR-08 mở (full-page form)
→ Từ: Kho chi nhánh [tên] (readonly)
→ Đến: Kho vật tư [tên] (readonly, auto)
→
→ [Bước 1] Chọn sản phẩm từ kho chi nhánh
→ Reuse ProductLotNumberSelect component
→ Chỉ hiện SP có tồn > 0 trong kho chi nhánh
→
→ [Bước 2] Chọn lô nguồn (BẮT BUỘC — DEC-D36)
→ Dropdown hiện: [Mã lô – NCC] | HSD | Giá nhập | Tồn lô
→ Chọn lô → auto-fill: Tồn lô, Giá nhập (product_supplying.price), HSD
→
→ [Bước 3] Nhập SL chuyển (đơn vị mua: chai/hộp — DEC-D32)
→ Validate: > 0, ≤ Tồn lô
→ Hệ thống auto-tính quy đổi: "2 chai × 500ml = 1,000ml" (DEC-D34)
→
→ Có thể thêm nhiều dòng SP (mỗi dòng = 1 lô nguồn = 1 batch mới)
→
→ Click [Xác nhận chuyển kho] (không cần duyệt — DEC-D31)
→ BEGIN TRANSACTION
→ Tạo inventory_document (behavior: export_material_warehouse, status: released)
→ FOR each dòng:
→ Tạo product_supplying (type: export, lot_number) → trừ tồn kho chi nhánh per lô
→ Tạo material_batch (purchase_price từ lô nguồn, purchase_quantity quy đổi,
expiry_date từ lô nguồn, status=active)
→ Tạo material_stock_movement (source_type='transfer', batch_id, +quantity)
→ COMMIT
→
→ Toast "Chuyển kho thành công. Đã tạo X lô mới."
→ Redirect SCR-01 → tồn kho vật tư tăng, tồn kho chi nhánh giảmAudit trail: 1 lô nguồn kho chi nhánh → 1 material_batch kho vật tư (1:1, DEC-D36) Giá + HSD: lấy từ lô nguồn, không phải cost_price chung → audit chính xác
Flow 4: Cập nhật giá vật tư (versioning)
[Admin] SCR-01 > Click dòng vật tư
→ SCR-03 mở (Chi tiết vật tư)
→ Click [Cập nhật giá]
→ SCR-02 mở (pre-fill data cũ)
→ Admin sửa: Giá tham chiếu / Hao hụt / ĐV sử dụng
→ Click [Lưu]
→ Config cũ: SET effective_to = now()
→ INSERT config mới: effective_from = now(), effective_to = NULL
→ Clone usage_units sang config mới (có thể chỉnh)
→ SCR-03 refresh → Lịch sử giá thêm dòng mới
→ Subtask cũ giữ snapshot giá FIFO cũ (BR-03)
→ Giá tham chiếu chỉ ảnh hưởng display — giá thực tế vẫn từ batch FIFOFlow 5: KTV thêm vật tư vào subtask (chọn ĐVT + xem giá)
[KTV/Manager/Admin] TaskDetail > Click "Thêm công việc" hoặc icon sửa subtask
→ TaskCreate popup mở (isChild=true)
→ SCR-04: MaterialForm hiển thị
→ [Auto-fill] nếu chọn subtask definition có vật tư mẫu
→ KTV search thêm vật tư → dropdown product
→ Chọn sản phẩm → hệ thống:
(a) Load material_usage_unit[] → populate dropdown ĐVT
(b) Lấy giá ước tính từ lô FIFO cũ nhất còn hàng
(batch active, remaining_qty > 0, ORDER BY created_at ASC)
(c) Snapshot giá: unit_price (từ batch FIFO), to_stock_factor
→ KTV chọn ĐVT (dropdown), nhập SL
→ Hệ thống auto-tính:
- amount = FORMULA-003 (ước tính, Phase 1 — DEC-D28)
- stock_equivalent = FORMULA-004 (quantity × to_stock_factor)
→ [Manager/Admin] thấy cột Đơn giá + Thành tiền
→ [Staff/KTV] cột Đơn giá + Thành tiền ẩn (DEC-D10)
→ Click [Lưu]
→ INSERT project_task_material[] (unit_price ước tính, amount, usage_unit, ...)
→ Popup đóng → AggregateView refreshTwo-Phase Pricing (DEC-D28): Lưu = giá ước tính. Done = giá thực tế FIFO (xem Flow 6).
Flow 6: Auto-deduct FIFO khi subtask done
[KTV/Manager] Subtask status → done
→ Event trigger: materialAutoDeduct(taskId)
→ FOR each material in subtask:
→ Lock (warehouse, product) — pg_advisory_xact_lock
→ Idempotent check: đã có movement auto_deduct cho material này? → SKIP
→
→ totalNeeded = material.total_stock_equivalent
→ batches = SELECT * FROM material_batch
WHERE warehouse_id AND product_id AND status='active'
AND remaining_qty > 0
AND (expiry_date IS NULL OR expiry_date > CURRENT_DATE)
ORDER BY created_at ASC -- FIFO: lô cũ nhất trước
→
→ remaining = totalNeeded
→ totalCost = 0
→ FOR each batch (FIFO):
→ deductQty = MIN(remaining, batch.remaining_qty)
→ UPDATE material_batch SET remaining_qty -= deductQty
→ IF remaining_qty = 0 → SET status = 'depleted'
→ INSERT material_stock_movement (
source_type='auto_deduct', batch_id,
quantity_change = -deductQty,
batch_remaining_after = batch.remaining_qty - deductQty,
unit_price = batch.unit_price
)
→ totalCost += deductQty × batch.unit_price
→ remaining -= deductQty
→
→ IF remaining > 0:
→ ROLLBACK all → BLOCK (DEC-D23: không cho phép tồn kho âm)
→ Return error "Không đủ tồn kho {product}: cần {totalNeeded}, thiếu {remaining}"
→ Subtask KHÔNG chuyển done
→
→ Phase 2 giá (DEC-D28): UPDATE project_task_material
SET unit_price = totalCost / totalNeeded (bình quân gia quyền nếu FIFO split)
SET amount = ROUND(unit_price × quantity × to_stock_factor) — final, locked
→
→ Check cảnh báo: SUM(remaining_qty) các lô active ≤ min_stock?
→ sendLowStockNotification() (Manager + Admin)
→
→ SCR-01: tồn kho cập nhật realtime
→ SCR-03: lịch sử movement thêm dòng "Auto FIFO"Flow 7: Nhập kho manual / Excel (FALLBACK — DEC-D30)
[Manager/Admin] SCR-01 > Click [Nhập tay]
→ SCR-06 mở
→ ℹ️ "Nhập tay dùng cho chi nhánh chưa có data kho chính chuẩn.
Ưu tiên dùng [★ Chuyển kho] nếu có thể."
→
→ Chọn cách nhập:
(a) Nhập tay:
→ [+ Thêm dòng] → chọn vật tư → nhập SL, giá nhập, HSD, mã lô
→ Mỗi dòng = 1 lô mới (DEC-D25)
(b) Import Excel:
→ Download template → Upload → preview → xác nhận
→
→ Click [Xác nhận nhập]
→ FOR each dòng:
→ Lock (warehouse, product) — pg_advisory_xact_lock
→ INSERT material_batch (status=active, purchase_price, expiry_date, batch_code)
→ INSERT material_stock_movement (source_type='stock_in', batch_id, +quantity)
→
→ SCR-01 refresh → tồn kho tăng
→ SCR-03: danh sách lô thêm dòng mớiFlow 8 cũ (Nhập kho tạo lô) đã gộp vào Flow 7 — cùng logic, tránh trùng lặp.
Flow 8: Kiểm kê kho
[Manager/Admin] SCR-01 > Click [Kiểm kê]
→ SCR-07 mở
→ Hiển thị danh sách tất cả vật tư có tồn kho:
- Tồn hệ thống = SUM(remaining_qty) các lô active
- Input: Tồn thực tế
- Auto-tính: Chênh lệch = thực tế - hệ thống
- Input: Lý do (nếu có chênh lệch)
→ Admin/Manager nhập tồn thực tế
→ Click [Xác nhận kiểm kê]
→ FOR each dòng có chênh lệch != 0:
→ IF chênh lệch < 0 (thực tế < hệ thống): trừ FIFO từ lô cũ nhất
→ IF chênh lệch > 0 (thực tế > hệ thống): cộng vào lô mới nhất active
→ INSERT material_stock_movement (
source_type='adjustment',
batch_id = lô được điều chỉnh,
quantity_change = chênh lệch,
batch_remaining_after = lô.remaining_qty sau điều chỉnh,
note = lý do
)
→ SCR-01 refresh → tồn kho = giá trị thực tếFlow 9: Hủy lô (bao gồm hàng hỏng / hết hạn / thu hồi)
Dùng khi: Lô hỏng khi nhận, lô hết HSD bị auto-lock, lô lỗi cần thu hồi, hoặc hàng đổ vỡ.
[Manager/Admin] SCR-03 > Chọn lô → Click "Hủy lô"
→ [Confirm dialog] "Hủy lô {batch_code}? Còn {remaining_qty} {stock_unit} sẽ bị hủy."
→ Nhập lý do hủy
→ Click "Xác nhận"
→ UPDATE material_batch SET status='disposed', remaining_qty=0
→ INSERT material_stock_movement (
source_type='disposal', batch_id,
quantity_change = -remaining_qty,
batch_remaining_after = 0,
note = lý do hủy
)
→ SCR-03 refresh — lô hiện trạng thái "Đã hủy"Flow 10: Trả lô / Hủy phiếu chuyển kho (v3.0)
Dùng khi: Manager chuyển nhầm (sai SP, sai SL, sai lô) hoặc muốn trả lô chưa dùng về kho chi nhánh. Logic: Lô chưa dùng → cho phép trả. Lô đã dùng → chặn.
[Manager/Admin] SCR-03 hoặc Lịch sử chuyển kho > Chọn phiếu/lô → Click "Trả lô" / "Hủy phiếu"
→ Hệ thống kiểm tra: batch từ phiếu này đã có auto_deduct movements?
→
→ [Trường hợp A] Batch chưa dùng (remaining_qty = initial_qty):
→ [Confirm dialog] "Trả lô {batch_code} về kho chi nhánh? Tồn kho chi nhánh sẽ được hoàn lại."
→ Click "Xác nhận"
→ UPDATE material_batch SET status='disposed', remaining_qty=0
→ INSERT material_stock_movement (source_type='disposal', batch_id)
→ Cộng lại tồn kho chi nhánh (reverse product_supplying)
→ UPDATE inventory_document SET status='inventory_canceled'
→ Toast "Đã trả lô. Tồn kho chi nhánh đã hoàn lại."
→
→ [Trường hợp B] Batch đã dùng (có auto_deduct movements):
→ Block: "Không thể trả: lô đã được sử dụng trong dịch vụ."
→ Gợi ý: "Dùng Kiểm kê (SCR-07) để điều chỉnh nếu cần."B4) Notification Spec
Low Stock Alert
| Field | Value |
|---|---|
| ID | NOTIF-MW-001 |
| Template | [{branch_name}] {product_name} con {balance} {stock_unit}, duoi nguong {min_stock} |
| Template (hiển thị) | [{branch_name}] {product_name} còn {balance} {stock_unit}, dưới ngưỡng {min_stock} |
| Variables | {branch_name}: tên chi nhánh, {product_name}: tên sản phẩm, {balance}: tồn kho hiện tại (format số), {stock_unit}: đơn vị kho (ml/g/miếng), {min_stock}: ngưỡng cảnh báo |
| Kênh | In-app notification |
| Người nhận | Manager của branch + tất cả Admin |
| Trigger | Realtime — khi auto_deduct hoặc adjustment làm SUM(remaining_qty) <= min_stock |
| Dedupe rule | 1 notification / product / warehouse / ngày. Check bảng notification trước khi gửi: WHERE product_id = X AND warehouse_id = Y AND created_at >= today() |
| Ví dụ | [Quận 1] Mask collagen còn 49 miếng, dưới ngưỡng 50. 💡 Gợi ý: chuyển thêm 1 hộp (50 miếng) từ kho chi nhánh. |
| Reorder suggestion (v3.0) | Kèm gợi ý bổ sung: 💡 Gợi ý: chuyển thêm {suggested_qty} {purchase_unit} từ kho chi nhánh. Trong đó suggested_qty = CEIL((min_stock × 2 - current_balance) / source_quantity) — đề xuất bổ sung đến 2× ngưỡng, quy đổi ngược về đơn vị mua. Nếu kho chi nhánh không đủ tồn → "(kho chi nhánh còn {branch_stock} {purchase_unit})" |
Low Stock — Reorder Suggestion Logic (v3.0)
Khi gửi low stock notification:
→ current_balance = SUM(remaining_qty) các lô active
→ target = min_stock × 2 (bổ sung đến 2× ngưỡng — đủ dùng thêm 1 chu kỳ)
→ needed_stock_units = target - current_balance (VD: 100 - 49 = 51 miếng)
→ source_quantity = material_price_config.source_quantity (VD: 1 hộp = 50 miếng)
→ suggested_qty = CEIL(needed_stock_units / source_quantity) (VD: CEIL(51/50) = 2 hộp)
→
→ Kiểm tra tồn kho chi nhánh:
branch_stock = product_sku_stock.quantity_remain (đơn vị mua)
IF branch_stock >= suggested_qty:
→ "💡 Gợi ý: chuyển thêm {suggested_qty} {purchase_unit} từ kho chi nhánh."
ELSE IF branch_stock > 0:
→ "💡 Gợi ý: chuyển thêm {suggested_qty} {purchase_unit} (kho chi nhánh còn {branch_stock})."
ELSE:
→ "⚠️ Kho chi nhánh hết hàng. Liên hệ kho tổng bổ sung."B5) Permission Matrix
| Màn hình | Action | Staff (KTV) | Manager (Xem) | Manager (Sửa) | Admin |
|---|---|---|---|---|---|
| SCR-01 Danh sách kho vật tư | Xem trang | Ẩn menu | Branch mình | Branch mình | Tất cả branch |
| SCR-01 Danh sách kho vật tư | Click [+ Thêm vật tư] | Ẩn | Ẩn | Ẩn | Hiện |
| SCR-01 Danh sách kho vật tư | Click [Nhập kho] | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-01 Danh sách kho vật tư | Click [★ Chuyển kho] | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-01 Danh sách kho vật tư | Click [Kiểm kê] | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-01 Danh sách kho vật tư | Click [Xuất Excel] | Ẩn | Hiện (branch) | Hiện (branch) | Hiện |
| SCR-02 Cấu hình vật tư | Xem chi tiết cấu hình | Ẩn | Hiện (readonly) | Hiện (readonly) | Hiện (editable) |
| SCR-02 Cấu hình vật tư | Thêm/sửa giá, quy đổi | Ẩn | Ẩn | Ẩn | Hiện |
| SCR-02 Cấu hình vật tư | Sửa stock_unit (đã có data) | Ẩn | Ẩn | Ẩn | Ẩn (BR-02 lock) |
| SCR-03 Chi tiết vật tư | Xem chi tiết + lịch sử | Ẩn | Hiện (branch) | Hiện (branch) | Hiện |
| SCR-03 Chi tiết vật tư | Click [Cập nhật giá] | Ẩn | Ẩn | Ẩn | Hiện |
| SCR-03 Chi tiết vật tư | Hủy lô (disposal) | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-03 Chi tiết vật tư | Click [Nhập kho] | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-04 Form subtask — MaterialForm | Xem vật tư | Subtask mình | Tất cả branch | Tất cả branch | Tất cả |
| SCR-04 Form subtask — MaterialForm | Thêm/sửa/xóa vật tư | Subtask mình, status != done/canceled | Tất cả branch, status != done/canceled | Tất cả branch, status != done/canceled | Tất cả, status != done/canceled |
| SCR-04 Form subtask — MaterialForm | Xem cột Đơn giá + Thành tiền | Ẩn (DEC-D10) | Hiện | Hiện | Hiện |
| SCR-05 Aggregate — AggregateView | Xem bảng tổng hợp | Theo quyền task cha | Hiện (branch) | Hiện (branch) | Hiện |
| SCR-05 Aggregate — AggregateView | Xem cột Đơn giá + Thành tiền | Ẩn (DEC-D10) | Hiện | Hiện | Hiện |
| SCR-06 Nhập kho | Nhập tay / Import Excel | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-07 Kiểm kê | Kiểm kê + xác nhận điều chỉnh | Ẩn | Ẩn | Hiện (branch) | Hiện |
| SCR-08 Chuyển kho | Chuyển kho chi nhánh → vật tư | ❌ Ẩn | ✅ Branch mình (tự thực hiện, không duyệt) | ✅ Branch mình (tự thực hiện, không duyệt) | ✅ |
| Sidebar tài chính đơn hàng | Xem chi phí vật tư | Ẩn | Hiện (branch mình) | Hiện (branch mình) | Hiện (RBAC có) |
Ghi chú:
- Staff (KTV): không truy cập module Kho vật tư. Chỉ dùng MaterialForm trong subtask.
- Manager (Xem): xem danh sách, chi tiết, lịch sử — branch mình. Không sửa giá/config.
- Manager (Sửa): nhập kho, kiểm kê — branch mình. Không sửa giá/config.
- Admin: toàn quyền, bao gồm cấu hình giá + quy đổi.
- Không có quyền = ẩn menu/button (không disable). Ref: Diva RBAC Pattern.
B6) State Matrix
| Màn hình | Loading | Empty | Error | No Permission | Partial |
|---|---|---|---|---|---|
| SCR-01 Danh sách kho vật tư | Skeleton table (5 rows), header + filter đã render | "Chưa có vật tư. Nhấn [+ Thêm vật tư] để bắt đầu." + illustration | Toast: "Không thể tải danh sách vật tư. Vui lòng thử lại." + nút Retry | Ẩn menu "Kho vật tư" hoàn toàn (Staff) | Hiển thị rows đã load, infinite scroll tiếp tục |
| SCR-02 Cấu hình vật tư | Skeleton form fields (6 fields) | Form trống, sẵn sàng nhập (khi tạo mới) | Toast: "Không thể lưu cấu hình. Vui lòng kiểm tra lại." + giữ form data | Ẩn nút [+ Thêm vật tư] (non-Admin) | Hiển thị fields đã load, fields lỗi highlight |
| SCR-03 Chi tiết vật tư | Skeleton: info card + 2 bảng (3 rows mỗi bảng) | Tab Lịch sử giá: "Chưa có lịch sử thay đổi giá." / Tab Movement: "Chưa có giao dịch nhập xuất." | Toast: "Không thể tải chi tiết vật tư." + nút Retry | Ẩn menu Kho vật tư (Staff). Manager: ẩn nút [Cập nhật giá] | Hiển thị info card, bảng lịch sử loading riêng |
| SCR-03 BatchList (Danh sách lô) | Skeleton rows (3 dòng) | "Chưa có lô nào. Nhập kho để tạo lô." | Toast: "Không thể tải danh sách lô." + nút Retry | Ẩn (Staff) | Hiển thị partial rows, lô chưa load hiện skeleton |
| SCR-04 Form subtask — MaterialForm | Skeleton rows (3 dòng) trong section VẬT TƯ | Bảng trống + text "Chưa có vật tư. Tìm kiếm để thêm." | Toast: "Không thể tải vật tư." + search disabled | Staff: ẩn section nếu subtask không phải của mình. Call Center: ẩn hoàn toàn | Hiển thị rows đã load, search vẫn hoạt động |
| SCR-04 MaterialForm (readonly) | Skeleton rows (3 dòng) | Bảng trống + text "Không có vật tư" | Toast: "Không thể tải vật tư." | Ẩn section | Hiển thị partial rows |
| SCR-05 Aggregate — AggregateView | Spinner trong section VẬT TƯ DỰ KIẾN | Ẩn section hoàn toàn (không có vật tư dự kiến) | Toast: "Không thể tải tổng hợp vật tư." | Ẩn section (Staff không quyền xem task cha) | Hiển thị rows đã load, tổng tính trên partial data |
| SCR-06 Nhập kho | Spinner toàn form | Form trống: 1 dòng mặc định + [+ Thêm dòng] | Toast: "Không thể lưu phiếu nhập. Vui lòng thử lại." + giữ form data | Ẩn nút [Nhập kho] trên SCR-01 (Staff, Manager Xem) | Hiển thị dòng đã nhập, dòng lỗi highlight |
| SCR-07 Kiểm kê | Skeleton table (load danh sách vật tư + tồn hệ thống) | "Không có vật tư nào trong kho để kiểm kê." | Toast: "Không thể tải dữ liệu kiểm kê." + nút Retry | Ẩn nút [Kiểm kê] trên SCR-01 (Staff, Manager Xem) | Hiển thị vật tư đã load, vật tư chưa load hiện skeleton |
| SCR-08 Chuyển kho | Skeleton table + shimmer trên dropdown SP | Bảng rỗng + "Chọn sản phẩm để bắt đầu chuyển kho" | Toast: "Không thể tải dữ liệu chuyển kho." + nút Retry. Inline: input đỏ khi SL > Tồn lô, dropdown đỏ khi chưa chọn lô | Ẩn nút [★ Chuyển kho] trên SCR-01 (Staff). Manager: chỉ branch mình | Hiển thị dòng đã thêm, dòng lỗi highlight riêng |
B7) Copy Text Dictionary
| # | Key (i18n) | Text (VI) | Màn hình | Ghi chú |
|---|---|---|---|---|
| 1 | material.label.page_title | Kho vật tư | SCR-01 | Page title / breadcrumb |
| 2 | material.label.add_material | Thêm vật tư | SCR-01 | Button thêm |
| 3 | material.label.search_placeholder | Tìm kiếm vật tư... | SCR-01 | Search input placeholder |
| 4 | material.label.branch_select | Chi nhánh | SCR-01 | Label dropdown chi nhánh |
| 5 | material.label.product_name | Vật tư | SCR-01, 03, 04, 05 | Cột tên sản phẩm |
| 6 | material.label.product_sku | Mã | SCR-01, 03, 04, 05 | Cột mã SKU |
| 7 | material.label.stock_unit | ĐV kho | SCR-01, 02 | Cột/label đơn vị kho |
| 8 | material.label.unit_price | Giá/ĐV | SCR-01 | Cột giá per đơn vị kho |
| 9 | material.label.stock_balance | Tồn kho | SCR-01, 03 | Cột tồn kho hiện tại |
| 10 | material.label.status | TT | SCR-01 | Cột trạng thái tồn kho |
| 11 | material.label.import_stock | Nhập kho | SCR-01, 06 | Button nhập kho |
| 12 | material.label.inventory_check | Kiểm kê | SCR-01, 07 | Button kiểm kê |
| 13 | material.label.export_excel | Xuất Excel | SCR-01 | Button export |
| 14 | material.label.config_title | Cấu hình vật tư | SCR-02 | Popup/drawer title |
| 15 | material.label.select_product | Chọn sản phẩm | SCR-02 | Label chọn product |
| 16 | material.label.source_price | Giá nhập | SCR-02 | Label giá mua gốc |
| 17 | material.label.source_quantity | SL/đơn vị mua | SCR-02 | Label SL per đơn vị mua |
| 18 | material.label.wastage_rate | Hao hụt hệ thống | SCR-02 | Label % hao hụt |
| 19 | material.label.min_stock | Ngưỡng cảnh báo | SCR-02 | Label min stock |
| 20 | material.label.calculated_price | Giá/ | SCR-02 | Auto-calculated (readonly) |
| 21 | material.label.usage_unit_section | Đơn vị sử dụng | SCR-02 | Section heading |
| 22 | material.label.usage_unit_name | Tên | SCR-02 | Cột tên ĐV sử dụng |
| 23 | material.label.to_stock_factor | Hệ số (quy đổi ĐV kho) | SCR-02 | Cột hệ số |
| 24 | material.label.usage_unit_price | Giá/đơn vị | SCR-02 | Cột giá auto-tính |
| 25 | material.label.is_discrete | Rời? | SCR-02 | Checkbox đơn vị rời |
| 26 | material.label.is_default | Mặc định | SCR-02 | Radio mặc định |
| 27 | material.label.add_usage_unit | Thêm đơn vị | SCR-02 | Button thêm usage unit |
| 28 | material.label.current_price | Giá hiện tại | SCR-03 | Info card |
| 29 | material.label.effective_from | Áp dụng từ | SCR-03 | Info card |
| 30 | material.label.update_price | Cập nhật giá | SCR-03 | Button cập nhật |
| 31 | material.label.price_history | Lịch sử giá | SCR-03 | Tab/section heading |
| 32 | material.label.movement_history | Lịch sử nhập/xuất | SCR-03 | Tab/section heading |
| 33 | material.label.date_from | Từ ngày | SCR-03 | Cột lịch sử giá |
| 34 | material.label.date_to | Đến ngày | SCR-03 | Cột lịch sử giá |
| 35 | material.label.changed_by | Người sửa | SCR-03 | Cột lịch sử giá |
| 36 | material.label.movement_date | Ngày | SCR-03 | Cột lịch sử movement |
| 37 | material.label.movement_type | Loại | SCR-03 | Cột loại giao dịch |
| 38 | material.label.movement_qty | SL | SCR-03 | Cột số lượng thay đổi |
| 39 | material.label.balance_after | Tồn sau | SCR-03 | Cột tồn kho sau giao dịch |
| 40 | material.label.note | Ghi chú | SCR-03, 06 | Cột ghi chú |
| 41 | material.label.material_section | VẬT TƯ | SCR-04 | Section heading trong form subtask |
| 42 | material.label.usage_unit | ĐVT | SCR-04, 05 | Cột đơn vị sử dụng |
| 43 | material.label.quantity | SL | SCR-04 | Cột số lượng input |
| 44 | material.label.price_per_unit | Đơn giá | SCR-04, 05 | Cột đơn giá (ẩn Staff) |
| 45 | material.label.amount | Thành tiền | SCR-04, 05 | Cột thành tiền (ẩn Staff) |
| 46 | material.label.total_amount | Tổng | SCR-04 | Footer row tổng thành tiền |
| 47 | material.label.total_cost | Tổng chi phí | SCR-05 | Footer row aggregate |
| 48 | material.label.search_add_material | Tìm kiếm thêm vật tư... | SCR-04 | Search placeholder trong form |
| 49 | material.label.aggregate_title | VẬT TƯ DỰ KIẾN | SCR-05 | Section heading aggregate |
| 50 | material.label.aggregate_subtitle | tổng hợp từ {count} công việc con | SCR-05 | Subtitle aggregate |
| 51 | material.label.qty_planned | SL dự kiến | SCR-05 | Cột số lượng aggregate |
| 52 | material.label.import_title | Nhập kho vật tư | SCR-06 | Page/popup title |
| 53 | material.label.import_qty | SL nhập | SCR-06 | Cột số lượng nhập |
| 54 | material.label.add_row | Thêm dòng | SCR-06 | Button thêm dòng nhập |
| 55 | material.label.import_excel | Import Excel | SCR-06 | Button import |
| 56 | material.label.confirm_import | Xác nhận nhập | SCR-06 | Button submit |
| 57 | material.label.inventory_title | Kiểm kê kho vật tư | SCR-07 | Page/popup title |
| 58 | material.label.system_qty | Hệ thống | SCR-07 | Cột tồn hệ thống |
| 59 | material.label.actual_qty | Thực tế | SCR-07 | Cột tồn thực tế (input) |
| 60 | material.label.difference | Chênh lệch | SCR-07 | Cột chênh lệch (auto) |
| 61 | material.label.reason | Lý do | SCR-07 | Cột lý do điều chỉnh |
| 62 | material.label.confirm_inventory | Xác nhận kiểm kê | SCR-07 | Button submit |
| 63 | material.empty.no_material | Chưa có vật tư. Nhấn [+ Thêm vật tư] để bắt đầu. | SCR-01 | Empty state danh sách |
| 64 | material.empty.no_price_history | Chưa có lịch sử thay đổi giá. | SCR-03 | Empty tab lịch sử giá |
| 65 | material.empty.no_movement | Chưa có giao dịch nhập xuất. | SCR-03 | Empty tab movement |
| 66 | material.empty.no_material_form | Chưa có vật tư. Tìm kiếm để thêm. | SCR-04 | Empty state MaterialForm |
| 67 | material.empty.no_inventory | Không có vật tư nào trong kho để kiểm kê. | SCR-07 | Empty state kiểm kê |
| 68 | material.badge.disabled | Ngưng KD | SCR-04 | Badge product disabled |
| 69 | material.badge.no_price | Chưa có giá | SCR-04 | Badge product chưa config |
| 70 | material.badge.stock_ok | Đủ | SCR-01 | Badge tồn kho đủ |
| 71 | material.badge.stock_low | Sắp hết | SCR-01 | Badge tồn kho sắp hết |
| 72 | material.badge.stock_critical | Dưới ngưỡng | SCR-01 | Badge tồn kho dưới min |
| 73 | material.badge.stock_out | Hết hàng | SCR-01 | Badge tồn kho = 0 (tất cả lô depleted/locked/disposed) |
| 74 | material.error.load_failed | Không thể tải danh sách vật tư. Vui lòng thử lại. | SCR-01 | Toast error |
| 75 | material.error.save_failed | Không thể lưu cấu hình. Vui lòng kiểm tra lại. | SCR-02 | Toast error |
| 76 | material.error.import_failed | Không thể lưu phiếu nhập. Vui lòng thử lại. | SCR-06 | Toast error |
| 77 | material.error.inventory_failed | Không thể tải dữ liệu kiểm kê. | SCR-07 | Toast error |
| 78 | material.error.qty_invalid | Số lượng phải lớn hơn 0. | SCR-04, 06 | Inline validation |
| 79 | material.error.qty_must_integer | Đơn vị rời không chấp nhận số thập phân. | SCR-04 | Inline validation (BR-04) |
| 80 | material.error.duplicate_material | Vật tư đã có trong danh sách. | SCR-04 | Toast warning |
| 81 | material.success.config_saved | Đã lưu cấu hình vật tư. | SCR-02 | Toast success |
| 82 | material.success.import_saved | Đã nhập kho thành công. | SCR-06 | Toast success |
| 83 | material.success.inventory_saved | Đã xác nhận kiểm kê thành công. | SCR-07 | Toast success |
| 84 | material.confirm.delete_material | Bạn có chắc chắn muốn xóa vật tư {product_name}? | SCR-04 | Confirm dialog |
| 85 | material.movement_type.manual | Nhập tay | SCR-03 | Label loại giao dịch |
| 86 | material.movement_type.auto_deduct | Auto (trừ khi done) | SCR-03 | Label loại giao dịch |
| 87 | material.movement_type.auto_reverse | Hoàn trả (undo done) | SCR-03 | Label loại giao dịch |
| 88 | material.movement_type.adjustment | Điều chỉnh (kiểm kê) | SCR-03 | Label loại giao dịch |
| 89 | material.movement_type.wastage | Hao hụt / Đổ bỏ | SCR-03 | Label loại giao dịch |
| 90 | material.movement_type.import | Import Excel | SCR-03 | Label loại giao dịch |
| 91 | material.confirm.global_price | Bạn đang cập nhật giá toàn hệ thống (global). Tất cả chi nhánh chưa có giá riêng sẽ áp dụng giá mới. Tiếp tục? | SCR-02 | Confirm dialog khi sửa giá global |
| 92 | material.confirm.batch_import_reverse | Bạn có chắc chắn muốn hoàn trả (reverse) phiếu nhập #{import_id}? Tồn kho sẽ bị trừ lại. | SCR-06 | Confirm dialog reverse batch import |
| 93 | material.label.batch_list | Danh sách lô | SCR-03 | Section heading |
| 94 | material.label.batch_code | Mã lô | SCR-03, SCR-06 | Column header |
| 95 | material.label.expiry_date | HSD | SCR-03, SCR-06 | Column header |
| 96 | material.label.batch_status | Trạng thái | SCR-03 | Column header |
| 97 | material.label.batch_status_active | Active | SCR-03 | Badge |
| 98 | material.label.batch_status_depleted | Đã hết | SCR-03 | Badge |
| 99 | material.label.batch_status_locked | Hết hạn | SCR-03 | Badge |
| 100 | material.label.batch_status_disposed | Đã hủy | SCR-03 | Badge |
| 101 | material.label.dispose_batch | Hủy lô | SCR-03 | Button |
| 102 | material.label.dispose_confirm | Hủy lô {batch_code}? Còn {remaining_qty} {stock_unit} sẽ bị hủy. | SCR-03 | Confirm dialog |
| 103 | material.label.dispose_reason | Lý do hủy | SCR-03 | Input label |
| 104 | material.label.purchase_price | Giá nhập | SCR-06 | Column header |
| 105 | material.label.batch_empty | Chưa có lô nào. Nhập kho để tạo lô. | SCR-03 | Empty state |
| 106 | material.label.expiry_warning | Lô {batch_code} sẽ hết hạn sau {days} ngày | SCR-03 | Warning badge |
| 107 | material.label.insufficient_stock | Không đủ tồn kho. Cần {needed} {stock_unit}, chỉ còn {available}. | SCR-04 | Error toast |
| 108 | material.label.fifo_price_note | Đơn giá lấy từ lô FIFO đầu tiên | SCR-04 | Footnote |
| 109 | material.label.price_estimated | (ước tính) | SCR-04 | Badge bên cạnh đơn giá khi subtask chưa done (DEC-D28 Two-Phase Pricing) |
| 110 | material.label.transfer_title | Chuyển kho vật tư | SCR-08 | Page title |
| 111 | material.label.transfer_from | Từ | SCR-08 | Label kho nguồn |
| 112 | material.label.transfer_to | Đến | SCR-08 | Label kho đích |
| 113 | material.label.transfer_search | Tìm sản phẩm từ kho chi nhánh... | SCR-08 | Search placeholder |
| 114 | material.label.transfer_batch_source | Lô nguồn | SCR-08 | Column header |
| 115 | material.label.transfer_batch_remain | Tồn lô | SCR-08 | Column header |
| 116 | material.label.transfer_qty | SL chuyển | SCR-08 | Column header |
| 117 | material.label.transfer_purchase_unit | ĐV mua | SCR-08 | Column header |
| 118 | material.label.transfer_conversion | → {converted_qty} | SCR-08 | Dòng quy đổi bên dưới SL |
| 119 | material.label.transfer_add_product | Thêm sản phẩm | SCR-08 | Button thêm dòng |
| 120 | material.label.transfer_confirm | Xác nhận chuyển kho | SCR-08 | Button submit |
| 121 | material.label.transfer_cancel_confirm | Bạn có chắc chắn muốn hủy? Dữ liệu chưa lưu sẽ mất. | SCR-08 | Confirm dialog khi nhấn Hủy |
| 122 | material.empty.transfer_no_product | Chọn sản phẩm để bắt đầu chuyển kho | SCR-08 | Empty state bảng |
| 123 | material.error.transfer_insufficient | Tồn lô không đủ: chỉ còn | SCR-08 | Inline validation SL > Tồn lô |
| 124 | material.error.transfer_no_batch | Chọn mã lô hàng cho vật tư! | SCR-08 | Inline validation chưa chọn lô |
| 125 | material.error.transfer_no_conversion | Vui lòng cấu hình quy đổi đơn vị | SCR-08 | Row disabled khi SP chưa config |
| 126 | material.error.transfer_zero_price | Giá nhập = 0, vui lòng kiểm tra | SCR-08 | Warning vàng (cho phép tiếp tục) |
| 127 | material.success.transfer_completed | Chuyển kho thành công. Đã tạo {count} lô mới. | SCR-08 | Toast success |
| 128 | material.movement_type.transfer | Chuyển kho (chi nhánh → vật tư) | SCR-03 | Label loại giao dịch mới |
B8) Analytics Events
| # | Event | Trigger | Data payload | Mục đích |
|---|---|---|---|---|
| 1 | material_config_created | Admin tạo cấu hình vật tư mới | { product_id, stock_unit, source_price, wastage_rate, usage_unit_count } | Track adoption module kho vật tư |
| 2 | material_config_updated | Admin cập nhật giá (version mới) | { product_id, old_price, new_price, price_change_pct } | Track tần suất thay đổi giá |
| 3 | material_added_to_subtask | KTV/Manager thêm vật tư vào subtask | { subtask_id, product_id, usage_unit, quantity, source: "auto_fill" | "search", has_price: true|false } | Track auto-fill vs manual, adoption giá |
| 4 | material_removed_from_subtask | KTV/Manager xóa vật tư khỏi subtask | { subtask_id, product_id } | Track vật tư bị bỏ thường xuyên |
| 5 | material_usage_unit_selected | KTV chọn ĐVT khác default | { product_id, usage_unit_name, is_default } | Track ĐVT nào hay dùng nhất |
| 6 | stock_imported_manual | Manager/Admin nhập kho tay | { warehouse_id, product_count, total_quantity } | Track tần suất nhập kho |
| 7 | stock_imported_excel | Manager/Admin import Excel | { warehouse_id, product_count, row_count } | Track adoption import Excel |
| 8 | stock_inventory_completed | Manager/Admin xác nhận kiểm kê | { warehouse_id, product_count, adjusted_count, total_adjustment } | Track tần suất kiểm kê, mức chênh lệch |
| 9 | stock_auto_deducted | Hệ thống trừ kho (subtask done) | { warehouse_id, product_id, quantity_change, new_balance } | Monitor tồn kho realtime |
| 10 | stock_low_alert_triggered | Tồn kho <= min_stock | { warehouse_id, product_id, balance, min_stock } | Track tần suất cảnh báo |
B9) Tooltip Dictionary
| # | Màn hình | Field/Icon | Tooltip Text | Điều kiện hiện |
|---|---|---|---|---|
| 1 | SCR-01 | Cột "Giá/ĐV" | Giá per đơn vị kho, đã bao gồm hao hụt hệ thống. Tính theo FORMULA-001. | Hover header cột |
| 2 | SCR-01 | Cột "Tồn kho" | Tồn kho hiện tại theo đơn vị kho. Cập nhật realtime khi KTV hoàn thành công việc. | Hover header cột |
| 3 | SCR-01 | Badge "Sắp hết" | Tồn kho đang ở mức cần chú ý (trên ngưỡng nhưng dưới 2x ngưỡng). | Hover badge |
| 4 | SCR-01 | Badge "Dưới ngưỡng" | Tồn kho dưới ngưỡng cảnh báo ({min_stock} {stock_unit}). Cần nhập thêm. | Hover badge |
| 5 | SCR-01 | Badge "Hết hàng" | Tồn kho = 0. Tất cả lô đã hết, hết hạn hoặc đã hủy. Cần nhập kho mới. | Hover badge |
| 6 | SCR-02 | Hao hụt hệ thống (%) | Phần trăm vật tư mất do bay hơi, đáy chai, lắng cặn. Được tính vào đơn giá kho. | Hover label |
| 7 | SCR-02 | Hệ số quy đổi | 1 đơn vị sử dụng = X đơn vị kho. VD: 1 giọt = 0.05 ml. | Hover header cột "Hệ số" |
| 8 | SCR-02 | Checkbox "Rời?" | Đánh dấu nếu đơn vị này là rời rạc (miếng, viên). Số lượng phải nguyên, không nhập 0.5. | Hover checkbox |
| 9 | SCR-02 | Ngưỡng cảnh báo | Khi tồn kho bằng hoặc thấp hơn giá trị này, hệ thống gửi thông báo cho Manager và Admin. | Hover label |
| 10 | SCR-02 | Giá/{stock_unit} (auto) | Tự động tính: Giá nhập / (SL/đơn vị mua x (1 - Hao hụt%)). Không cần nhập. | Luôn hiện (readonly) |
| 11 | SCR-03 | Cột "Tồn sau" | Số dư tồn kho ngay sau giao dịch này. Dùng để kiểm tra dòng tiền tồn kho. | Hover header cột |
| 12 | SCR-03 | Loại "Auto" | Hệ thống tự động trừ kho khi KTV hoàn thành công việc (subtask done). | Hover text "Auto" |
| 13 | SCR-03 | Loại "Hoàn trả" | Hệ thống cộng lại tồn kho khi subtask bị undo done hoặc canceled sau done. | Hover text "Hoàn trả" |
| 14 | SCR-04 | Cột "Đơn giá" | Giá lấy từ lô FIFO cũ nhất còn hàng tại thời điểm thêm vào công việc. Snapshot khi lưu, không thay đổi sau đó. | Hover header cột (Manager/Admin) |
| 15 | SCR-04 | Badge "Chưa có giá" | Admin chưa cấu hình giá cho vật tư này. Chi phí sẽ không được tính. | Hover badge |
| 16 | SCR-04 | Badge "Ngưng KD" | Sản phẩm đã ngưng kinh doanh. Vật tư vẫn được giữ để theo dõi, không cho thêm mới. | Hover badge |
| 17 | SCR-05 | Subtitle "(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. Tổng chi phí = SUM thành tiền. | Luôn hiện (subtitle) |
| 18 | SCR-06 | Button "Import Excel" | Tải về template Excel, điền dữ liệu, upload lại. Hệ thống validate trước khi nhập. | Hover button |
| 19 | SCR-07 | Cột "Chênh lệch" | Tự động tính: Thực tế - Hệ thống. Dương = thừa, Âm = thiếu. | Luôn hiện (auto-calculated) |
| 20 | SCR-07 | Cột "Lý do" | Bắt buộc nhập khi có chênh lệch. VD: Hao hụt, đổ bỏ, sai kiểm đếm lần trước. | Hiện khi chênh lệch != 0 |
| 21 | SCR-03 | Trạng thái lô | Active: đang sử dụng. Depleted: đã hết. Locked: hết hạn sử dụng. Disposed: đã hủy. | Hover badge trạng thái |
| 22 | SCR-03 | HSD | Hạn sử dụng của lô. Lô hết hạn sẽ tự động bị khóa. | Hover cột HSD |
| 23 | SCR-03 | Hủy lô | Hủy lô lỗi/thu hồi. Tồn kho sẽ bị trừ và không thể hoàn lại. | Hover button Hủy lô |
| 24 | SCR-06 | Giá nhập | Giá nhập per đơn vị mua (chai/hộp). Hệ thống tự tính giá per đơn vị kho. | Hover cột Giá nhập |
| 25 | SCR-04 | Đơn giá | Giá lấy từ lô cũ nhất còn hàng (FIFO). Nếu vượt 2 lô, giá = bình quân gia quyền. | Hover cột Đơn giá |
| 26 | SCR-08 | Lô nguồn | Chọn lô hàng từ kho chi nhánh. Giá nhập và HSD lấy từ lô gốc, tạo batch mới trong kho vật tư. | Hover header cột |
| 27 | SCR-08 | SL chuyển | Số lượng đơn vị mua (chai/hộp). Hệ thống tự quy đổi sang đơn vị kho (ml/g/miếng). | Hover header cột |
| 28 | SCR-08 | → Quy đổi | Tự động tính: SL chuyển × SL/đơn vị mua (source_quantity). VD: 2 chai × 500ml = 1,000ml. | Luôn hiện (readonly) |
| 29 | SCR-08 | ★ Chuyển kho | Chuyển vật tư từ kho chi nhánh sang kho vật tư. Không cần duyệt, tạo lô mới tự động. | Hover button trên SCR-01 |
B-Desktop: Wireframes
Wireframe dùng dữ liệu thực tế ngành spa. Không placeholder.
SCR-01: Trang danh sách Kho vật tư
┌─── KHO VẬT TƯ ── [Chi nhánh: Quận 1 ▼] ─────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ [+ Thêm vật tư] │
│ │ 🔍 Tìm kiếm vật tư... │ │
│ └────────────────────────────────┘ │
│ │
│ ┌───┬──────────────────┬───────┬───────┬───────────┬──────────┬──────────────┐│
│ │ │ Vật tư │ Mã │ĐV kho │ Giá/ĐV │ Tồn kho │ Trạng thái ││
│ ├───┼──────────────────┼───────┼───────┼───────────┼──────────┼──────────────┤│
│ │ ▶ │ Serum Laser X │ SP001 │ ml │ 5,000đ │ 798 ml │ ✅ Đủ ││
│ │ ▼ │ Gel làm mát │ SP042 │ g │ 2,000đ │ 800 g │ ✅ Đủ ││
│ │ ├──────────────────┴───────┴───────┴───────────┴──────────┴──────────────┤│
│ │ │ ĐVT sử dụng (giá theo lô FIFO: Lô G01 — 300,000đ/hộp): ││
│ │ │ [tube] 15g = 30,000đ [muỗng] 5g = 10,000đ [g] 1g = 2,000đ ││
│ │ │ ││
│ │ │ Danh sách lô: ││
│ │ │ ► Lô G01 500g 2,000đ/g HSD 06/2027 ← đang xuất (FIFO) ││
│ │ │ Lô G02 300g 2,200đ/g HSD 12/2027 ││
│ │ ├──────────────────┬───────┬───────┬───────────┬──────────┬──────────────┤│
│ │ ▶ │ Mask collagen │ SP018 │ miếng │30,000đ │ 49 │ 🔴 Dưới ngưỡng││
│ │ ▶ │ Bông tẩy trang │ SP023 │ miếng │ 500đ │ 200 │ ✅ Đủ ││
│ │ ▶ │ Tinh dầu massage │ SP055 │ ml │ 1,200đ │ 30 ml │ ⚠️ Sắp hết ││
│ │ ▶ │ Kem chống nắng │ SP067 │ g │ 800đ │ 0 g │ 🔴 Hết hàng ││
│ └───┴──────────────────┴───────┴───────┴───────────┴──────────┴──────────────┘│
│ │
│ Trạng thái: [Tất cả ▼] │
│ │
│ Hiển thị 1-6 / 6 vật tư [◀ 1 2 3 ... ▶] │
│ │
│ [★ Chuyển kho] [Nhập tay] [Kiểm kê] [Xuất Excel] │
│ │
└────────────────────────────────────────────────────────────────────────────────┘Expandable row (QTable expand): Click ▶ để mở rộng dòng, hiện danh sách ĐVT sử dụng + giá per ĐVT. Giá lấy từ lô FIFO hiện tại (batch active cũ nhất). SP chỉ có 1 ĐVT → expand vẫn hiện 1 dòng. Quasar QTable hỗ trợ sẵn expand slot.
SCR-02: Form cấu hình vật tư (popup/drawer)
┌─── CẤU HÌNH VẬT TƯ ──────────────────────────────────────────────────────────┐
│ │
│ Sản phẩm: [🔍 Serum Laser X (SP001) ▼] │
│ │
│ ── Thông tin kho ───────────────────────────────────────── │
│ Đơn vị kho: [ml ▼] │
│ Giá nhập: [2,000,000 ] đ │
│ SL/đơn vị mua: [500 ] │
│ Hao hụt hệ thống: [2 ] % ℹ️ │
│ Ngưỡng cảnh báo: [50 ] ml ℹ️ │
│ → Giá/ml: 4,081.63đ (tự tính) ℹ️ │
│ │
│ ── Đơn vị sử dụng ─────────────────────────────────────── │
│ ┌──────────┬─────────────┬──────────────┬───────┬─────────┐ │
│ │ Tên │ Hệ số │ Giá/đơn vị │ Rời? │ Mặc định │ │
│ │ │ (→ ĐV kho) │ │ ℹ️ │ │ │
│ ├──────────┼─────────────┼──────────────┼───────┼─────────┤ │
│ │ [giọt ] │ [0.05 ]ml│ 204.08đ │ [ ] │ (●) │ │
│ │ [muỗng ] │ [5 ]ml│ 20,408.16đ │ [ ] │ ( ) │ │
│ │ [ml ] │ [1 ]ml│ 4,081.63đ │ [ ] │ ( ) │ │
│ └──────────┴─────────────┴──────────────┴───────┴─────────┘ │
│ [+ Thêm đơn vị] │
│ │
│ [Hủy] [Lưu] │
└────────────────────────────────────────────────────────────────────────────────┘SCR-02: Variant — Edit mode (stock_unit locked)
│ Đơn vị kho: [ml 🔒] ← locked, không sửa được khi đã có usage_unit hoặc dataKhi đã có
material_usage_unithoặcproject_task_materialreference → fieldstock_unithiển thị icon 🔒, readonly. Ref: BR-02, DEC-D06.
SCR-03: Chi tiết vật tư + Lịch sử giá + Lịch sử movement
┌─── SERUM LASER X (SP001) ──────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Giá hiện tại: 4,081.63đ/ml Tồn kho: 4,999.75 ml │ │
│ │ Áp dụng từ: 01/01/2026 Trạng thái: Đủ │ │
│ │ Hao hụt: 2% Ngưỡng: 50 ml │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ [Cập nhật giá] [Nhập kho] │
│ │
│ ── Lịch sử giá ────────────────────────────────────────────────────── │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ Từ ngày │ Đến ngày │ Giá/ml │ Người sửa │ │
│ ├────────────┼────────────┼────────────┼────────────┤ │
│ │ 01/01/2026 │ (hiện tại) │ 4,081.63đ │ Admin A │ │
│ │ 15/06/2025 │ 31/12/2025 │ 3,800.00đ │ Admin A │ │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
│ ── Lịch sử nhập/xuất ──────────────────────────────────────────────── │
│ ┌─────────────┬──────────────────────┬──────────┬──────────┬────────────────┐│
│ │ Ngày │ Loại │ SL │ Tồn sau │ Ghi chú ││
│ ├─────────────┼──────────────────────┼──────────┼──────────┼────────────────┤│
│ │ 26/03 10:30 │ Nhập tay │+5,000 ml │ 5,000 ml │ Kiểm kê đầu kỳ││
│ │ 26/03 14:15 │ Auto (trừ khi done) │ -0.15 ml │4,999.85ml│ Subtask #1234 ││
│ │ 26/03 14:20 │ Auto (trừ khi done) │ -0.10 ml │4,999.75ml│ Subtask #1235 ││
│ │ 26/03 16:00 │ Hoàn trả (undo done) │ +0.10 ml │4,999.85ml│ Subtask #1235 ││
│ └─────────────┴──────────────────────┴──────────┴──────────┴────────────────┘│
│ │
│ Hiển thị 1-4 / 4 giao dịch [Tải thêm] │
│ │
│ ── Danh sách lô ────────────────────────────────────────────────────── │
│ ┌──────┬──────────┬────────┬────────┬─────────┬────────────┐ │
│ │ Lô │ Ngày nhập│ Giá/ĐV │ Tồn │ HSD │ Trạng thái │ │
│ ├──────┼──────────┼────────┼────────┼─────────┼────────────┤ │
│ │ Lô A │01/01/2026│5,000đ │ 498ml │30/06/26 │ ✅ Active │ │
│ │ Lô B │01/02/2026│6,000đ │ 300ml │31/12/26 │ ✅ Active │ │
│ │ Lô C │01/01/2026│5,000đ │ 0ml │30/06/26 │ ⬜ Depleted│ │
│ │ Lô D │15/12/2025│4,500đ │ 100ml │15/03/26 │ 🔒 Locked │ │
│ └──────┴──────────┴────────┴────────┴─────────┴────────────┘ │
│ [Hủy lô D] │
│ │
└────────────────────────────────────────────────────────────────────────────────┘SCR-04: Form subtask — MaterialForm (view Manager/Admin)
┌─── VẬT TƯ ────────────────────────────────────────────────────────────────────┐
│ │
│ ┌───┬──────────────────┬───────┬──────────┬─────┬──────────┬────────┬───┐ │
│ │ # │ Vật tư │ Mã │ ĐVT ▼ │ SL │ Đơn giá │Thành │ │ │
│ │ │ │ │ │ │ │tiền │ │ │
│ ├───┼──────────────────┼───────┼──────────┼─────┼──────────┼────────┼───┤ │
│ │ 1 │ Serum Laser X │ SP001 │ [giọt]▼ │ [3] │ 204đ │ 612đ │ 🗑│ │
│ │ 2 │ Gel làm mát │ SP042 │ [tube] ▼ │ [1] │15,000đ │15,000đ │ 🗑│ │
│ │ 3 │ Mask collagen │ SP018 │ [miếng] │ [1] │30,000đ │30,000đ │ 🗑│ │
│ │ 4 │ Tinh dầu massage │ SP055 │ [ml] ▼ │ [5] │ 1,200đ │ 6,000đ │ 🗑│ │
│ ├───┴──────────────────┴───────┴──────────┴─────┴──────────┼────────┼───┤ │
│ │ Tổng: │51,612đ │ │ │
│ └──────────────────────────────────────────────────────────┴────────┴───┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔍 Tìm kiếm thêm vật tư... │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────────┘Note: Section hao hụt (wastage) mặc định ẩn, Admin bật qua Settings (Ref: DEC-D20).
Two-Phase Pricing (DEC-D28):
- Khi subtask chưa done: cột Đơn giá hiện badge (ước tính) bên cạnh giá. Giá ước tính lấy từ lô FIFO cũ nhất tại thời điểm save.
- Khi subtask done: badge (ước tính) biến mất. Giá là final (FIFO thực tế tại thời điểm deduct, có thể khác ước tính nếu lô thay đổi).
SCR-04: Form subtask — MaterialForm (view Staff/KTV — ẩn cột giá)
┌─── VẬT TƯ ────────────────────────────────────────────────────────────────────┐
│ │
│ ┌───┬──────────────────┬───────┬──────────┬─────┬───┐ │
│ │ # │ Vật tư │ Mã │ ĐVT ▼ │ SL │ │ │
│ ├───┼──────────────────┼───────┼──────────┼─────┼───┤ │
│ │ 1 │ Serum Laser X │ SP001 │ [giọt]▼ │ [3] │ 🗑│ │
│ │ 2 │ Gel làm mát │ SP042 │ [tube] ▼ │ [1] │ 🗑│ │
│ │ 3 │ Mask collagen │ SP018 │ [miếng] │ [1] │ 🗑│ │
│ └───┴──────────────────┴───────┴──────────┴─────┴───┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔍 Tìm kiếm thêm vật tư... │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Cột Đơn giá + Thành tiền: ẩn với Staff (KTV) — Ref: DEC-D10 │
│ │
└────────────────────────────────────────────────────────────────────────────────┘SCR-04: MaterialForm — Readonly (subtask done) — có chi tiết FIFO lô
┌─── VẬT TƯ ────────────────────────────────────────────────────────────────────┐
│ │
│ ┌───┬──────────────────┬───────┬──────────┬─────┬──────────┬────────┬───────┐│
│ │ # │ Vật tư │ Mã │ ĐVT │ SL │ Đơn giá │Thành │ ││
│ │ │ │ │ │ │ (final) │tiền │ ││
│ ├───┼──────────────────┼───────┼──────────┼─────┼──────────┼────────┼───────┤│
│ │ 1 │ Serum Laser X │ SP001 │ giọt │ 40 │ 275đ │11,000đ │[chi tiết]│
│ │ ├──────────────────┴───────┴──────────┴─────┴──────────┴────────┴───────┤│
│ │ │ Chi tiết FIFO (DEC-D28 — giá thực tế sau done): ││
│ │ │ Lô A: 20 giọt (1ml) × 250đ = 5,000đ — lô hết (depleted) ││
│ │ │ Lô B: 20 giọt (1ml) × 300đ = 6,000đ — lô còn 299ml ││
│ │ │ → Bình quân gia quyền: (5,000+6,000)/40 = 275đ/giọt ││
│ │ ├──────────────────┬───────┬──────────┬─────┬──────────┬────────┬───────┤│
│ │ 2 │ Gel làm mát │ SP042 │ tube │ 1 │30,000đ │30,000đ │[chi tiết]│
│ │ ├──────────────────┴───────┴──────────┴─────┴──────────┴────────┴───────┤│
│ │ │ Chi tiết FIFO: ││
│ │ │ Lô G01: 1 tube (15g) × 2,000đ/g = 30,000đ — lô còn 485g ││
│ │ ├──────────────────┬───────┬──────────┬─────┬──────────┬────────┬───────┤│
│ │ 3 │ Mask collagen │ SP018 │ miếng │ 1 │30,000đ │30,000đ │[chi tiết]│
│ ├───┴──────────────────┴───────┴──────────┴─────┴──────────┼────────┤ ││
│ │ Tổng: │71,000đ │ ││
│ └──────────────────────────────────────────────────────────┴────────┘ ││
│ │
│ (Không cho sửa — công việc đã hoàn thành. Đã trừ kho tự động.) │
│ Đơn giá = giá thực tế FIFO (final, locked). Click [chi tiết] xem lô nào. │
│ │
└────────────────────────────────────────────────────────────────────────────────┘[chi tiết] expandable: Click mở rộng dòng, hiện danh sách lô đã xuất + SL + giá per lô. Data từ
material_stock_movement WHERE source_type='auto_deduct' AND source_reference_id = ptm.id. Chỉ hiện khi subtask đã done (Phase 2 — DEC-D28). Khi chưa done: hiện "Giá ước tính từ lô FIFO".
SCR-04: MaterialForm — Product chưa có giá + Product disabled
│ 3 │ Vitamin C serum [Chưa có giá]│ SP099 │ [ml] ▼ │ [2] │ — │ — │ 🗑│
│ 4 │ Serum cũ [Ngưng KD] │ SP010 │ ml │ 2 │ 200đ │ 400đ │ 🗑│
- "Chưa có giá": product chưa có material_price_config. Thêm được, Đơn giá + Thành tiền hiện "—" (DEC-D11).
- "Ngưng KD": product bị disable. Vật tư đã có giữ lại, không cho search thêm mới (EC-11).
SCR-05: Aggregate vật tư task cha (view Manager/Admin)
┌─── VẬT TƯ DỰ KIẾN (tổng hợp từ 3 công việc con) ─────────────────────────────┐
│ │
│ ┌───┬──────────────────┬───────┬───────┬──────────┬──────────┬────────┐ │
│ │ # │ Vật tư │ Mã │ ĐVT │SL dự kiến│ Đơn giá │Thành │ │
│ │ │ │ │ │ │ │tiền │ │
│ ├───┼──────────────────┼───────┼───────┼──────────┼──────────┼────────┤ │
│ │ 1 │ Serum Laser X │ SP001 │ giọt │ 9 │ 204đ │ 1,836đ │ │
│ │ 2 │ Gel làm mát │ SP042 │ tube │ 3 │15,000đ │45,000đ │ │
│ │ 3 │ Mask collagen │ SP018 │ miếng │ 2 │30,000đ │60,000đ │ │
│ │ 4 │ Tinh dầu massage │ SP055 │ ml │ 10 │ 1,200đ │12,000đ │ │
│ ├───┴──────────────────┴───────┴───────┴──────────┴──────────┼────────┤ │
│ │ Tổng chi phí: │118,836đ│ │
│ └────────────────────────────────────────────────────────────┴────────┘ │
│ │
│ Lưu ý: Tổng chi phí = SUM(amount), không phải SUM(qty) × giá │
│ (vì subtask khác nhau có thể lấy giá từ lô FIFO khác nhau) │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
> **Note:** Aggregate group by (product_id, usage_unit). Nếu cùng product khác ĐVT → hiển thị dòng riêng. VD: Serum Laser X dùng cả "giọt" và "ml" ở 2 subtask khác nhau → 2 dòng riêng trong bảng aggregate.SCR-06: Nhập kho manual + Import Excel
┌─── NHẬP KHO VẬT TƯ ───────────────────────────────────────────────────────────┐
│ │
│ Kho: Kho vật tư Quận 1 Ngày: 26/03/2026 │
│ │
│ ┌───┬───────────────┬──────┬──────────┬──────────┬────────┬─────┬───────────┐ │
│ │ # │ Vật tư │ĐV kho│ SL nhập │ Giá nhập │ HSD │Mã lô│ Ghi chú │ │
│ ├───┼───────────────┼──────┼──────────┼──────────┼────────┼─────┼───────────┤ │
│ │ 1 │ Serum Laser X │ ml │ [500 ] │[500,000 ]│[30/06] │[A01]│[NCC giao ]│ │
│ │ 2 │ Mask collagen │miếng │ [50 ] │[1,500,000]│[31/12]│[M01]│[Nhập BS ]│ │
│ │ 3 │ Gel làm mát │ g │ [1,000 ] │[200,000 ]│[ ] │[ ]│[NCC giao ]│ │
│ └───┴───────────────┴──────┴──────────┴──────────┴────────┴─────┴───────────┘ │
│ [+ Thêm dòng] │
│ │
│ Mỗi dòng nhập = 1 lô mới. Giá nhập = giá per đơn vị mua. HSD và mã lô │
│ không bắt buộc. │
│ │
│ ──────────────────────────────────────────────────────────── │
│ Hoặc: [Import Excel] (Tải template Excel) │
│ │
│ [Hủy] [Xác nhận nhập] │
│ │
└────────────────────────────────────────────────────────────────────────────────┘SCR-07: Kiểm kê kho
┌─── KIỂM KÊ KHO VẬT TƯ ───────────────────────────────────────────────────────┐
│ │
│ Kho: Kho vật tư Quận 1 Ngày: 26/03/2026 │
│ │
│ ┌───┬──────────────────┬───────┬──────────┬──────────┬──────────┬───────────┐│
│ │ # │ Vật tư │ĐV kho │ Hệ thống │ Thực tế │ Chênh │ Lý do ││
│ │ │ │ │ │ │ lệch │ ││
│ ├───┼──────────────────┼───────┼──────────┼──────────┼──────────┼───────────┤│
│ │ 1 │ Serum Laser X │ ml │ 4,999.75 │ [4,850 ] │ -149.75 │ [Hao hụt ]││
│ │ 2 │ Gel làm mát │ g │ 800.00 │ [800 ] │ 0.00 │ ││
│ │ 3 │ Mask collagen │ miếng │ 49 │ [48 ] │ -1 │ [Rách khi]││
│ │ │ │ │ │ │ │ [mở gói ]││
│ │ 4 │ Bông tẩy trang │ miếng │ 200 │ [200 ] │ 0 │ ││
│ │ 5 │ Tinh dầu massage │ ml │ 30.00 │ [35 ] │ +5.00 │ [Nhập mà ]││
│ │ │ │ │ │ │ │ [chưa ghi]││
│ │ 6 │ Kem chống nắng │ g │ 0.00 │ [0 ] │ 0.00 │ ││
│ └───┴──────────────────┴───────┴──────────┴──────────┴──────────┴───────────┘│
│ │
│ Tổng dòng có chênh lệch: 3 / 6 │
│ │
│ [Hủy] [Xác nhận kiểm kê] │
│ │
└────────────────────────────────────────────────────────────────────────────────┘SCR-08: Chuyển kho chi nhánh → Kho vật tư (★ NEW v3.0)
Mục đích: Manager chuyển vật tư từ kho chi nhánh sang kho vật tư, chọn cụ thể lô nguồn.
Entry point: SCR-01 → [★ Chuyển kho]
Layout: Full-page form
┌─── CHUYỂN KHO VẬT TƯ ──────────────────────────────────────────┐
│ │
│ Từ: Kho chi nhánh [Quận 1] (readonly) │
│ Đến: Kho vật tư [Quận 1] (readonly, auto) │
│ │
│ 🔍 Tìm sản phẩm từ kho chi nhánh... │
│ │
│ ┌───────────────┬──────┬─────────────┬────────┬──────────┬──────┐│
│ │ Sản phẩm │ SKU │ Lô nguồn ▼ │Tồn lô │ SL chuyển│ĐV mua││
│ ├───────────────┼──────┼─────────────┼────────┼──────────┼──────┤│
│ │ Serum Laser X │SP001 │[A02-NCC Abc]│ 8 chai │ [2 ] │ chai ││
│ │ │ │ HSD 06/2027 │ │ → 1,000ml│ ││
│ │ │ │ Giá: 500,000đ│ │ │ ││
│ │ Gel làm mát │SP042 │[G03-NCC Xyz]│ 5 hộp │ [1 ] │ hộp ││
│ │ │ │ HSD 12/2027 │ │ → 500g │ ││
│ │ │ │ Giá: 300,000đ│ │ │ ││
│ │ Mask collagen │SP018 │[M04-NCC Abc]│ 3 hộp │ [2 ] │ hộp ││
│ │ │ │ HSD 09/2027 │ │→ 100 miếng│ ││
│ │ │ │ Giá: 750,000đ│ │ │ ││
│ └───────────────┴──────┴─────────────┴────────┴──────────┴──────┘│
│ [+ Thêm sản phẩm] [🗑 Xóa dòng] │
│ │
│ ℹ️ Chọn lô nguồn bắt buộc (DEC-D36). Giá + HSD lấy từ lô. │
│ Mỗi lô = 1 batch riêng trong kho vật tư. │
│ Quy đổi tự động: "2 chai × 500ml = 1,000ml" (DEC-D34). │
│ Không cần duyệt (DEC-D31). │
│ │
│ [Hủy] [Xác nhận chuyển kho] │
└───────────────────────────────────────────────────────────────────┘Cột chi tiết:
| Cột | Loại | Mô tả | Validation |
|---|---|---|---|
| Sản phẩm | Dropdown search | Reuse ProductLotNumberSelect. Chỉ hiện SP có tồn > 0 trong kho chi nhánh | Bắt buộc |
| SKU | Text readonly | Auto-fill từ SP đã chọn | — |
| Lô nguồn | Dropdown | Danh sách lô: [Mã lô – NCC], HSD, Giá nhập, Tồn lô. Reuse component hiện tại. Filter: chỉ hiện lô status = 'active' AND remaining_qty > 0 (ẩn locked/disposed). Warning: lô HSD còn ≤ 90 ngày → ⚠️ "Sắp hết hạn (còn X ngày)". Lô quantity_remain < 10% initial → ⚠️ "Tồn lô rất thấp". Placeholder: "Chọn lô nguồn..." | Bắt buộc. "Chọn mã lô hàng cho vật tư!" |
| Tồn lô | Number readonly | quantity_remain của lô đã chọn | — |
| SL chuyển | Number input | Số lượng đơn vị mua cần chuyển | > 0, ≤ Tồn lô. Integer nếu ĐV rời |
| ĐV mua | Text readonly | Đơn vị mua từ product (chai, hộp, tube) | — |
| → Quy đổi | Text readonly | Hiện bên dưới SL: "→ 1,000ml" = SL × source_quantity | Auto-calculate |
Interaction:
| Action | Behavior |
|---|---|
| Chọn SP | Load danh sách lô (chỉ lô active, remaining_qty > 0). Không auto-select lô — dropdown lô hiện placeholder "Chọn lô nguồn...". Fill ĐV mua. SL chuyển input disabled cho đến khi user chọn lô |
| Chọn Lô nguồn | Update Tồn lô, Giá nhập, HSD. Enable SL chuyển input. Reset SL chuyển = blank |
| Nhập SL chuyển | Validate ≤ Tồn lô. Tính quy đổi realtime |
| [+ Thêm SP] | Thêm dòng mới. SP đã chọn → loại khỏi dropdown |
| [🗑 Xóa dòng] | Xóa dòng, SP quay lại dropdown |
| [Xác nhận] | Validate all → tạo phiếu + trừ kho + tạo batch |
| [Hủy] | Confirm dialog → quay về SCR-01 |
States:
| State | Hiển thị |
|---|---|
| Loading | Skeleton table + shimmer trên dropdown SP |
| Empty (chưa thêm SP) | Bảng rỗng + text "Chọn sản phẩm để bắt đầu chuyển kho" |
| Error: SL > Tồn lô | Input đỏ + "Tồn lô không đủ: chỉ còn X" |
| Chưa chọn lô (default sau chọn SP) | Dropdown lô hiện placeholder "Chọn lô nguồn...", SL chuyển disabled. Submit blocked nếu còn dòng chưa chọn lô |
| Error: Chưa chọn lô (submit) | Dropdown đỏ + "Chọn mã lô hàng cho vật tư!" |
| Error: SP chưa config quy đổi | Row disabled + "Vui lòng cấu hình quy đổi đơn vị" + link SCR-02 |
| Error: Giá = 0/NULL | Warning vàng "Giá nhập = 0, vui lòng kiểm tra" (cho phép tiếp tục) |
| Success | Toast "Chuyển kho thành công. Đã tạo X lô mới." → redirect SCR-01 |
| No Permission | Redirect home. Staff: ẩn menu. Manager: chỉ branch mình |
B-Edge Cases
| # | Tình huống | Behavior | SCR | Ref |
|---|---|---|---|---|
| EC-01 | Subtask done → undo | Reverse: tạo movement +quantity, running_balance cộng lại. MaterialForm chuyển về editable. | SCR-04, SCR-03 | DEC-D18 |
| EC-02 | Subtask canceled (chưa done) | Không trừ kho (chưa done = chưa trừ). Materials giữ nguyên trên subtask (readonly). | SCR-04 | — |
| EC-03 | Subtask canceled (đã done) | Reverse movement giống undo done. Tạo movement source_type='auto_reverse'. | SCR-04, SCR-03 | DEC-D18 |
| EC-04 | Không đủ tồn kho khi auto-deduct | Không cho phép tồn kho âm, block khi không đủ. SCR-04: toast error "Không đủ tồn kho. Cần {needed} {stock_unit}, chỉ còn {available}." Subtask không chuyển done. | SCR-01, SCR-04 | DEC-D23 |
| EC-05 | 2 KTV trừ kho cùng sản phẩm cùng lúc | pg_advisory_xact_lock per (warehouse, product) serialize giao dịch. KTV không thấy delay (< 100ms). | SCR-04 | DEC-D15 |
| EC-06 | Product chưa có config (chưa cấu hình giá) | Thêm vào subtask được. Badge "Chưa có giá". Đơn giá = NULL, Thành tiền = "—". Không trừ kho. | SCR-04 | DEC-D11 |
| EC-07 | Đơn vị rời (is_discrete=true) + nhập số thập phân | Validation inline: "Đơn vị rời không chấp nhận số thập phân." Block submit. | SCR-04 | BR-04, DEC-D07 |
| EC-08 | KTV nhập SL = 0 hoặc âm | Validation inline: "Số lượng phải lớn hơn 0." Block submit. | SCR-04, SCR-06 | BR-04 |
| EC-09 | Thêm product đã có trong bảng MaterialForm | Toast warning: "Vật tư đã có trong danh sách." Không thêm duplicate. Focus row đã có. | SCR-04 | — |
| EC-10 | Giá lô thay đổi → subtask cũ | Subtask cũ giữ snapshot giá FIFO tại thời điểm thêm vật tư. Subtask mới lấy giá từ lô FIFO hiện tại. | SCR-04 | BR-03, DEC-D05 |
| EC-11 | Admin đổi stock_unit khi đã có usage_unit reference | Block đổi. stock_unit field readonly. Toast: "Không thể đổi đơn vị kho khi đã có đơn vị sử dụng." | SCR-02 | BR-02, DEC-D06 |
| EC-12 | Kiểm kê: thực tế > hệ thống | Movement source_type='adjustment', quantity_change = +chênh lệch. Tồn kho tăng. | SCR-07 | — |
| EC-13 | Kiểm kê: thực tế < hệ thống | Movement source_type='adjustment', quantity_change = -chênh lệch. Tồn kho giảm. | SCR-07 | — |
| EC-14 | Xóa subtask đã done | Block. Toast: "Không thể xóa công việc đã hoàn thành. Vui lòng undo trước." | SCR-04 | BR-10 |
| EC-15 | Cảnh báo tồn kho dedupe | 1 notification/ngày per product per warehouse. Check trước khi gửi. Ngày sau reset. | SCR-01 | BR-11 |
| EC-16 | Product bị disable (ngưng KD) | Badge "Ngưng KD" trên row. Không cho search thêm mới. Vật tư đã có giữ lại. Tồn kho giữ nguyên. | SCR-01, SCR-04 | EC-11 design |
| EC-17 | Branch chưa có warehouse | Auto-create warehouse khi Admin vào SCR-01 lần đầu. Không cần thao tác tạo thủ công. | SCR-01 | — |
| EC-18 | Import Excel: file rỗng hoặc sai format | Toast error: "File không hợp lệ. Vui lòng dùng template Excel." Không import. | SCR-06 | DEC-D13 |
| EC-19 | Import Excel: product_id không tồn tại | Bỏ qua dòng lỗi. Hiển thị preview với highlight dòng lỗi + lý do. Cho phép submit dòng hợp lệ. | SCR-06 | — |
| EC-20 | Network error khi auto-deduct | Retry 3 lần (exponential backoff). Nếu fail → log error, gửi alert Admin. Subtask vẫn done, kho chưa trừ. | SCR-04 | — |
| EC-21 | Vật tư thay thế (Phase 1.5) | Phase 1 KHÔNG hỗ trợ nút "Thay thế vật tư". Nếu vật tư hết → KTV thêm vật tư khác thủ công. Phase 1.5 sẽ bổ sung flow thay thế tự động (suggest alternative + swap + recalc giá). | SCR-04 | OI-06 |
| EC-22 | Staff view ẩn giá qua GraphQL | Hasura permission cho role Staff KHÔNG include cột unit_price, amount trong select_permissions. Nếu Staff query trực tiếp GraphQL → cột trả về NULL (không lộ giá). FE ẩn cột chỉ là layer 2, Hasura permission là layer 1 (bắt buộc). | SCR-04, SCR-05 | DEC-D10 |
| EC-23 | FIFO split: dùng vượt 2+ lô | Tách thành nhiều giao dịch. Chi phí = tổng cost các lô. | SCR-04 | — |
| EC-24 | Lô hết hạn auto-lock | Cron hàng ngày → status='locked', tạo disposal movement | SCR-03 | — |
| EC-25 | Hủy lô | Confirm dialog + nhập lý do → status='disposed' | SCR-03 | — |
| EC-26 | Nhập cùng giá cùng ngày | Vẫn 2 lô riêng (có thể khác mã lô, HSD) | SCR-06 | — |
| EC-27 | Không đủ tồn kho | Block deduct, toast error, không partial | SCR-04 | — |
| EC-28 | Tất cả lô locked/disposed | Xử lý như không đủ tồn kho | SCR-04 | — |
| EC-29 | Reverse nhưng lô gốc disposed/locked | Cộng lại vào lô gốc, chuyển status active | SCR-03 | — |
Changelog
| Version | Ngày | Thay đổi | Tác giả |
|---|---|---|---|
| 1.0 | 2026-03-26 | Khởi tạo UI Spec: 7 screens, 7 flows, permission matrix, state matrix, 90 copy text entries, 10 analytics events, 20 tooltips, 8 wireframes, 20 edge cases | PO/BA + AI |
| 1.1 | 2026-03-27 | B5: Sửa sidebar tài chính Manager "Hiện (branch mình)" (DEC-D21). SCR-01: thêm filter trạng thái + pagination. SCR-02: variant edit mode stock_unit locked. SCR-04: note wastage section mặc định ẩn (DEC-D20). SCR-05: note aggregate group by (product_id, usage_unit). B7: +2 copy text (confirm global price, batch import reverse). B-Edge Cases: +EC-21 vật tư thay thế Phase 1.5, +EC-22 Staff ẩn giá qua GraphQL. | PO/BA + AI |
| 2.0 | 2026-03-27 | Design v2 — Batch-based FIFO. B1: SCR-03 thêm batch list. B2: +BatchList component. B3: +Flow 8 (nhập kho tạo lô), +Flow 9 (hủy lô). SCR-03 wireframe: +section "Danh sách lô" (4 trạng thái batch). SCR-06 wireframe: +cột giá nhập, HSD, mã lô. B5: +hủy lô permission. B6: +BatchList states. B7: +16 copy text entries (batch, FIFO, disposal). B9: +5 tooltips (batch status, HSD, hủy lô, giá nhập, FIFO). B-Edge Cases: +EC-23~EC-29 (FIFO split, auto-lock, hủy lô, insufficient stock, reverse). Sửa EC-04: không cho phép tồn kho âm → block khi không đủ. Sửa snapshot giá → FIFO theo lô. Badge "Âm kho" → "Hết hàng". | PO/BA + AI |
| 2.1 | 2026-03-27 | Fix issues. B2: Sửa FR references toàn bộ B2 cho khớp PRD (FR-001~021). SCR-04: +note Two-Phase Pricing (DEC-D28) với badge "(ước tính)". B7: +copy text material.label.price_estimated. SCR-07: sửa wireframe bỏ tồn kho -5.00 (DEC-D23). B8: bỏ is_negative khỏi analytics event. | PO/BA + AI |
| 3.0 | 2026-03-27 | Design v3 — Kết nối kho chi nhánh. B1: +SCR-08 TransferForm (chuyển kho chi nhánh → kho vật tư). SCR-01 wireframe: đổi buttons thành [★ Chuyển kho] [Nhập tay] [Kiểm kê] [Xuất Excel]. B5: +SCR-08 permission row (Staff ẩn, Manager branch mình, Admin full). B6: +SCR-08 state matrix (5 states). B7: +19 copy text entries (transfer form, validation, success/error messages). B-Desktop: +SCR-08 wireframe full-page form với bảng chọn lô nguồn, quy đổi ĐV, interaction spec, states. Ref: DEC-D31 (không duyệt), DEC-D34 (quy đổi tự động), DEC-D36 (chọn lô bắt buộc). | PO/BA + AI |