Skip to content

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

SCRComponentFile pathLoạiFR reference
SCR-01MaterialWarehouseListsrc/pages/ecommerce/material-warehouse/MaterialWarehouseList.vueNewFR-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-02MaterialPriceConfigFormsrc/pages/ecommerce/material-warehouse/MaterialPriceConfigForm.vueNewFR-004 (config giá), FR-005 (ĐVT), FR-006 (versioning), BR-01, BR-02
SCR-03MaterialDetailsrc/pages/ecommerce/material-warehouse/MaterialDetail.vueNewFR-002 (danh sách), FR-006 (versioning — lịch sử giá), FR-012 (auto-deduct — lịch sử movement), DEC-D02
SCR-03BatchListsrc/pages/ecommerce/material-warehouse/MaterialDetail/BatchList.tsxNewFR-018 (lô), FR-019 (HSD), FR-021 (disposal)
SCR-04MaterialForm (nâng cấp)src/components/project/TaskForm/MaterialForm.vueSửaFR-009 (chọn ĐVT), FR-010 (Two-Phase Pricing), FR-011 (hiển thị giá), DEC-D05, DEC-D10, DEC-D28
SCR-05AggregateView (nâng cấp)src/components/project/TaskDetail/General.vueSửaFR-011 (hiển thị giá/amount aggregate), FORMULA-005
SCR-06StockImportFormsrc/pages/ecommerce/material-warehouse/StockImportForm.vueNewFR-007 (nhập kho manual), FR-008 (import Excel), FR-018 (tạo batch), DEC-D13
SCR-07StockInventoryFormsrc/pages/ecommerce/material-warehouse/StockInventoryForm.vueNewFR-014 (kiểm kê), FR-015 (cảnh báo tồn), EC-12, EC-13
SCR-08TransferFormsrc/pages/ecommerce/material-warehouse/TransferForm.vueNewDEC-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ảm

Audit 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 FIFO

Flow 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 refresh

Two-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ới

Flow 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

FieldValue
IDNOTIF-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ênhIn-app notification
Người nhậnManager của branch + tất cả Admin
TriggerRealtime — khi auto_deduct hoặc adjustment làm SUM(remaining_qty) <= min_stock
Dedupe rule1 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ìnhActionStaff (KTV)Manager (Xem)Manager (Sửa)Admin
SCR-01 Danh sách kho vật tưXem trangẨn menuBranch mìnhBranch mìnhTất cả branch
SCR-01 Danh sách kho vật tưClick [+ Thêm vật tư]ẨnẨnẨnHiện
SCR-01 Danh sách kho vật tưClick [Nhập kho]ẨnẨnHiện (branch)Hiện
SCR-01 Danh sách kho vật tưClick [★ Chuyển kho]ẨnẨnHiện (branch)Hiện
SCR-01 Danh sách kho vật tưClick [Kiểm kê]ẨnẨnHiện (branch)Hiện
SCR-01 Danh sách kho vật tưClick [Xuất Excel]ẨnHiện (branch)Hiện (branch)Hiện
SCR-02 Cấu hình vật tưXem chi tiết cấu hìnhẨnHiệ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ẨnHiệ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ửẨnHiện (branch)Hiện (branch)Hiện
SCR-03 Chi tiết vật tưClick [Cập nhật giá]ẨnẨnẨnHiện
SCR-03 Chi tiết vật tưHủy lô (disposal)ẨnẨnHiện (branch)Hiện
SCR-03 Chi tiết vật tưClick [Nhập kho]ẨnẨnHiện (branch)Hiện
SCR-04 Form subtask — MaterialFormXem vật tưSubtask mìnhTất cả branchTất cả branchTất cả
SCR-04 Form subtask — MaterialFormThêm/sửa/xóa vật tưSubtask mình, status != done/canceledTất cả branch, status != done/canceledTất cả branch, status != done/canceledTất cả, status != done/canceled
SCR-04 Form subtask — MaterialFormXem cột Đơn giá + Thành tiềnẨn (DEC-D10)HiệnHiệnHiện
SCR-05 Aggregate — AggregateViewXem bảng tổng hợpTheo quyền task chaHiện (branch)Hiện (branch)Hiện
SCR-05 Aggregate — AggregateViewXem cột Đơn giá + Thành tiềnẨn (DEC-D10)HiệnHiệnHiện
SCR-06 Nhập khoNhập tay / Import ExcelẨnẨnHiện (branch)Hiện
SCR-07 Kiểm kêKiểm kê + xác nhận điều chỉnhẨnẨnHiện (branch)Hiện
SCR-08 Chuyển khoChuyể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àngXem chi phí vật tưẨnHiệ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ìnhLoadingEmptyErrorNo PermissionPartial
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." + illustrationToast: "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 — MaterialFormSkeleton 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 disabledStaff: ẩn section nếu subtask không phải của mình. Call Center: ẩn hoàn toànHiể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 sectionHiển thị partial rows
SCR-05 Aggregate — AggregateViewSpinner 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 khoSpinner toàn formForm 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 khoSkeleton table + shimmer trên dropdown SPBả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ìnhHiển thị dòng đã thêm, dòng lỗi highlight riêng

B7) Copy Text Dictionary

#Key (i18n)Text (VI)Màn hìnhGhi chú
1material.label.page_titleKho vật tưSCR-01Page title / breadcrumb
2material.label.add_materialThêm vật tưSCR-01Button thêm
3material.label.search_placeholderTìm kiếm vật tư...SCR-01Search input placeholder
4material.label.branch_selectChi nhánhSCR-01Label dropdown chi nhánh
5material.label.product_nameVật tưSCR-01, 03, 04, 05Cột tên sản phẩm
6material.label.product_skuSCR-01, 03, 04, 05Cột mã SKU
7material.label.stock_unitĐV khoSCR-01, 02Cột/label đơn vị kho
8material.label.unit_priceGiá/ĐVSCR-01Cột giá per đơn vị kho
9material.label.stock_balanceTồn khoSCR-01, 03Cột tồn kho hiện tại
10material.label.statusTTSCR-01Cột trạng thái tồn kho
11material.label.import_stockNhập khoSCR-01, 06Button nhập kho
12material.label.inventory_checkKiểm kêSCR-01, 07Button kiểm kê
13material.label.export_excelXuất ExcelSCR-01Button export
14material.label.config_titleCấu hình vật tưSCR-02Popup/drawer title
15material.label.select_productChọn sản phẩmSCR-02Label chọn product
16material.label.source_priceGiá nhậpSCR-02Label giá mua gốc
17material.label.source_quantitySL/đơn vị muaSCR-02Label SL per đơn vị mua
18material.label.wastage_rateHao hụt hệ thốngSCR-02Label % hao hụt
19material.label.min_stockNgưỡng cảnh báoSCR-02Label min stock
20material.label.calculated_priceGiá/SCR-02Auto-calculated (readonly)
21material.label.usage_unit_sectionĐơn vị sử dụngSCR-02Section heading
22material.label.usage_unit_nameTênSCR-02Cột tên ĐV sử dụng
23material.label.to_stock_factorHệ số (quy đổi ĐV kho)SCR-02Cột hệ số
24material.label.usage_unit_priceGiá/đơn vịSCR-02Cột giá auto-tính
25material.label.is_discreteRời?SCR-02Checkbox đơn vị rời
26material.label.is_defaultMặc địnhSCR-02Radio mặc định
27material.label.add_usage_unitThêm đơn vịSCR-02Button thêm usage unit
28material.label.current_priceGiá hiện tạiSCR-03Info card
29material.label.effective_fromÁp dụng từSCR-03Info card
30material.label.update_priceCập nhật giáSCR-03Button cập nhật
31material.label.price_historyLịch sử giáSCR-03Tab/section heading
32material.label.movement_historyLịch sử nhập/xuấtSCR-03Tab/section heading
33material.label.date_fromTừ ngàySCR-03Cột lịch sử giá
34material.label.date_toĐến ngàySCR-03Cột lịch sử giá
35material.label.changed_byNgười sửaSCR-03Cột lịch sử giá
36material.label.movement_dateNgàySCR-03Cột lịch sử movement
37material.label.movement_typeLoạiSCR-03Cột loại giao dịch
38material.label.movement_qtySLSCR-03Cột số lượng thay đổi
39material.label.balance_afterTồn sauSCR-03Cột tồn kho sau giao dịch
40material.label.noteGhi chúSCR-03, 06Cột ghi chú
41material.label.material_sectionVẬT TƯSCR-04Section heading trong form subtask
42material.label.usage_unitĐVTSCR-04, 05Cột đơn vị sử dụng
43material.label.quantitySLSCR-04Cột số lượng input
44material.label.price_per_unitĐơn giáSCR-04, 05Cột đơn giá (ẩn Staff)
45material.label.amountThành tiềnSCR-04, 05Cột thành tiền (ẩn Staff)
46material.label.total_amountTổngSCR-04Footer row tổng thành tiền
47material.label.total_costTổng chi phíSCR-05Footer row aggregate
48material.label.search_add_materialTìm kiếm thêm vật tư...SCR-04Search placeholder trong form
49material.label.aggregate_titleVẬT TƯ DỰ KIẾNSCR-05Section heading aggregate
50material.label.aggregate_subtitletổng hợp từ {count} công việc conSCR-05Subtitle aggregate
51material.label.qty_plannedSL dự kiếnSCR-05Cột số lượng aggregate
52material.label.import_titleNhập kho vật tưSCR-06Page/popup title
53material.label.import_qtySL nhậpSCR-06Cột số lượng nhập
54material.label.add_rowThêm dòngSCR-06Button thêm dòng nhập
55material.label.import_excelImport ExcelSCR-06Button import
56material.label.confirm_importXác nhận nhậpSCR-06Button submit
57material.label.inventory_titleKiểm kê kho vật tưSCR-07Page/popup title
58material.label.system_qtyHệ thốngSCR-07Cột tồn hệ thống
59material.label.actual_qtyThực tếSCR-07Cột tồn thực tế (input)
60material.label.differenceChênh lệchSCR-07Cột chênh lệch (auto)
61material.label.reasonLý doSCR-07Cột lý do điều chỉnh
62material.label.confirm_inventoryXác nhận kiểm kêSCR-07Button submit
63material.empty.no_materialChưa có vật tư. Nhấn [+ Thêm vật tư] để bắt đầu.SCR-01Empty state danh sách
64material.empty.no_price_historyChưa có lịch sử thay đổi giá.SCR-03Empty tab lịch sử giá
65material.empty.no_movementChưa có giao dịch nhập xuất.SCR-03Empty tab movement
66material.empty.no_material_formChưa có vật tư. Tìm kiếm để thêm.SCR-04Empty state MaterialForm
67material.empty.no_inventoryKhông có vật tư nào trong kho để kiểm kê.SCR-07Empty state kiểm kê
68material.badge.disabledNgưng KDSCR-04Badge product disabled
69material.badge.no_priceChưa có giáSCR-04Badge product chưa config
70material.badge.stock_okĐủSCR-01Badge tồn kho đủ
71material.badge.stock_lowSắp hếtSCR-01Badge tồn kho sắp hết
72material.badge.stock_criticalDưới ngưỡngSCR-01Badge tồn kho dưới min
73material.badge.stock_outHết hàngSCR-01Badge tồn kho = 0 (tất cả lô depleted/locked/disposed)
74material.error.load_failedKhông thể tải danh sách vật tư. Vui lòng thử lại.SCR-01Toast error
75material.error.save_failedKhông thể lưu cấu hình. Vui lòng kiểm tra lại.SCR-02Toast error
76material.error.import_failedKhông thể lưu phiếu nhập. Vui lòng thử lại.SCR-06Toast error
77material.error.inventory_failedKhông thể tải dữ liệu kiểm kê.SCR-07Toast error
78material.error.qty_invalidSố lượng phải lớn hơn 0.SCR-04, 06Inline validation
79material.error.qty_must_integerĐơn vị rời không chấp nhận số thập phân.SCR-04Inline validation (BR-04)
80material.error.duplicate_materialVật tư đã có trong danh sách.SCR-04Toast warning
81material.success.config_savedĐã lưu cấu hình vật tư.SCR-02Toast success
82material.success.import_savedĐã nhập kho thành công.SCR-06Toast success
83material.success.inventory_savedĐã xác nhận kiểm kê thành công.SCR-07Toast success
84material.confirm.delete_materialBạn có chắc chắn muốn xóa vật tư {product_name}?SCR-04Confirm dialog
85material.movement_type.manualNhập taySCR-03Label loại giao dịch
86material.movement_type.auto_deductAuto (trừ khi done)SCR-03Label loại giao dịch
87material.movement_type.auto_reverseHoàn trả (undo done)SCR-03Label loại giao dịch
88material.movement_type.adjustmentĐiều chỉnh (kiểm kê)SCR-03Label loại giao dịch
89material.movement_type.wastageHao hụt / Đổ bỏSCR-03Label loại giao dịch
90material.movement_type.importImport ExcelSCR-03Label loại giao dịch
91material.confirm.global_priceBạ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-02Confirm dialog khi sửa giá global
92material.confirm.batch_import_reverseBạ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-06Confirm dialog reverse batch import
93material.label.batch_listDanh sách lôSCR-03Section heading
94material.label.batch_codeMã lôSCR-03, SCR-06Column header
95material.label.expiry_dateHSDSCR-03, SCR-06Column header
96material.label.batch_statusTrạng tháiSCR-03Column header
97material.label.batch_status_activeActiveSCR-03Badge
98material.label.batch_status_depletedĐã hếtSCR-03Badge
99material.label.batch_status_lockedHết hạnSCR-03Badge
100material.label.batch_status_disposedĐã hủySCR-03Badge
101material.label.dispose_batchHủy lôSCR-03Button
102material.label.dispose_confirmHủy lô {batch_code}? Còn {remaining_qty} {stock_unit} sẽ bị hủy.SCR-03Confirm dialog
103material.label.dispose_reasonLý do hủySCR-03Input label
104material.label.purchase_priceGiá nhậpSCR-06Column header
105material.label.batch_emptyChưa có lô nào. Nhập kho để tạo lô.SCR-03Empty state
106material.label.expiry_warningLô {batch_code} sẽ hết hạn sau {days} ngàySCR-03Warning badge
107material.label.insufficient_stockKhông đủ tồn kho. Cần {needed} {stock_unit}, chỉ còn {available}.SCR-04Error toast
108material.label.fifo_price_noteĐơn giá lấy từ lô FIFO đầu tiênSCR-04Footnote
109material.label.price_estimated(ước tính)SCR-04Badge bên cạnh đơn giá khi subtask chưa done (DEC-D28 Two-Phase Pricing)
110material.label.transfer_titleChuyển kho vật tưSCR-08Page title
111material.label.transfer_fromTừSCR-08Label kho nguồn
112material.label.transfer_toĐếnSCR-08Label kho đích
113material.label.transfer_searchTìm sản phẩm từ kho chi nhánh...SCR-08Search placeholder
114material.label.transfer_batch_sourceLô nguồnSCR-08Column header
115material.label.transfer_batch_remainTồn lôSCR-08Column header
116material.label.transfer_qtySL chuyểnSCR-08Column header
117material.label.transfer_purchase_unitĐV muaSCR-08Column header
118material.label.transfer_conversion→ {converted_qty}SCR-08Dòng quy đổi bên dưới SL
119material.label.transfer_add_productThêm sản phẩmSCR-08Button thêm dòng
120material.label.transfer_confirmXác nhận chuyển khoSCR-08Button submit
121material.label.transfer_cancel_confirmBạn có chắc chắn muốn hủy? Dữ liệu chưa lưu sẽ mất.SCR-08Confirm dialog khi nhấn Hủy
122material.empty.transfer_no_productChọn sản phẩm để bắt đầu chuyển khoSCR-08Empty state bảng
123material.error.transfer_insufficientTồn lô không đủ: chỉ cònSCR-08Inline validation SL > Tồn lô
124material.error.transfer_no_batchChọn mã lô hàng cho vật tư!SCR-08Inline validation chưa chọn lô
125material.error.transfer_no_conversionVui lòng cấu hình quy đổi đơn vịSCR-08Row disabled khi SP chưa config
126material.error.transfer_zero_priceGiá nhập = 0, vui lòng kiểm traSCR-08Warning vàng (cho phép tiếp tục)
127material.success.transfer_completedChuyển kho thành công. Đã tạo {count} lô mới.SCR-08Toast success
128material.movement_type.transferChuyển kho (chi nhánh → vật tư)SCR-03Label loại giao dịch mới

B8) Analytics Events

#EventTriggerData payloadMục đích
1material_config_createdAdmin 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ư
2material_config_updatedAdmin 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á
3material_added_to_subtaskKTV/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á
4material_removed_from_subtaskKTV/Manager xóa vật tư khỏi subtask{ subtask_id, product_id }Track vật tư bị bỏ thường xuyên
5material_usage_unit_selectedKTV chọn ĐVT khác default{ product_id, usage_unit_name, is_default }Track ĐVT nào hay dùng nhất
6stock_imported_manualManager/Admin nhập kho tay{ warehouse_id, product_count, total_quantity }Track tần suất nhập kho
7stock_imported_excelManager/Admin import Excel{ warehouse_id, product_count, row_count }Track adoption import Excel
8stock_inventory_completedManager/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
9stock_auto_deductedHệ thống trừ kho (subtask done){ warehouse_id, product_id, quantity_change, new_balance }Monitor tồn kho realtime
10stock_low_alert_triggeredTồ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ìnhField/IconTooltip TextĐiều kiện hiện
1SCR-01Cộ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
2SCR-01Cộ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
3SCR-01Badge "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
4SCR-01Badge "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
5SCR-01Badge "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
6SCR-02Hao 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
7SCR-02Hệ số quy đổi1 đơn vị sử dụng = X đơn vị kho. VD: 1 giọt = 0.05 ml.Hover header cột "Hệ số"
8SCR-02Checkbox "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
9SCR-02Ngưỡng cảnh báoKhi 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
10SCR-02Giá/{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)
11SCR-03Cộ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
12SCR-03Loại "Auto"Hệ thống tự động trừ kho khi KTV hoàn thành công việc (subtask done).Hover text "Auto"
13SCR-03Loạ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ả"
14SCR-04Cộ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)
15SCR-04Badge "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
16SCR-04Badge "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
17SCR-05Subtitle "(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)
18SCR-06Button "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
19SCR-07Cộ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)
20SCR-07Cộ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
21SCR-03Trạ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
22SCR-03HSDHạn sử dụng của lô. Lô hết hạn sẽ tự động bị khóa.Hover cột HSD
23SCR-03Hủ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ô
24SCR-06Giá nhậpGiá 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
25SCR-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á
26SCR-08Lô nguồnChọ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
27SCR-08SL chuyểnSố 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
28SCR-08→ Quy đổiTự động tính: SL chuyển × SL/đơn vị mua (source_quantity). VD: 2 chai × 500ml = 1,000ml.Luôn hiện (readonly)
29SCR-08★ Chuyển khoChuyể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 data

Khi đã có material_usage_unit hoặc project_task_material reference → field stock_unit hiể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ộtLoạiMô tảValidation
Sản phẩmDropdown searchReuse ProductLotNumberSelect. Chỉ hiện SP có tồn > 0 trong kho chi nhánhBắt buộc
SKUText readonlyAuto-fill từ SP đã chọn
Lô nguồnDropdownDanh 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 readonlyquantity_remain của lô đã chọn
SL chuyểnNumber inputSố lượng đơn vị mua cần chuyển> 0, ≤ Tồn lô. Integer nếu ĐV rời
ĐV muaText readonlyĐơn vị mua từ product (chai, hộp, tube)
→ Quy đổiText readonlyHiện bên dưới SL: "→ 1,000ml" = SL × source_quantityAuto-calculate

Interaction:

ActionBehavior
Chọn SPLoad 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ồnUpdate Tồn lô, Giá nhập, HSD. Enable SL chuyển input. Reset SL chuyển = blank
Nhập SL chuyểnValidate ≤ 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:

StateHiển thị
LoadingSkeleton 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 đổiRow disabled + "Vui lòng cấu hình quy đổi đơn vị" + link SCR-02
Error: Giá = 0/NULLWarning vàng "Giá nhập = 0, vui lòng kiểm tra" (cho phép tiếp tục)
SuccessToast "Chuyển kho thành công. Đã tạo X lô mới." → redirect SCR-01
No PermissionRedirect home. Staff: ẩn menu. Manager: chỉ branch mình

B-Edge Cases

#Tình huốngBehaviorSCRRef
EC-01Subtask done → undoReverse: tạo movement +quantity, running_balance cộng lại. MaterialForm chuyển về editable.SCR-04, SCR-03DEC-D18
EC-02Subtask 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-03Subtask canceled (đã done)Reverse movement giống undo done. Tạo movement source_type='auto_reverse'.SCR-04, SCR-03DEC-D18
EC-04Không đủ tồn kho khi auto-deductKhô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-04DEC-D23
EC-052 KTV trừ kho cùng sản phẩm cùng lúcpg_advisory_xact_lock per (warehouse, product) serialize giao dịch. KTV không thấy delay (< 100ms).SCR-04DEC-D15
EC-06Product 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-04DEC-D11
EC-07Đơn vị rời (is_discrete=true) + nhập số thập phânValidation inline: "Đơn vị rời không chấp nhận số thập phân." Block submit.SCR-04BR-04, DEC-D07
EC-08KTV nhập SL = 0 hoặc âmValidation inline: "Số lượng phải lớn hơn 0." Block submit.SCR-04, SCR-06BR-04
EC-09Thêm product đã có trong bảng MaterialFormToast warning: "Vật tư đã có trong danh sách." Không thêm duplicate. Focus row đã có.SCR-04
EC-10Giá 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-04BR-03, DEC-D05
EC-11Admin đổi stock_unit khi đã có usage_unit referenceBlock đổi. stock_unit field readonly. Toast: "Không thể đổi đơn vị kho khi đã có đơn vị sử dụng."SCR-02BR-02, DEC-D06
EC-12Kiểm kê: thực tế > hệ thốngMovement source_type='adjustment', quantity_change = +chênh lệch. Tồn kho tăng.SCR-07
EC-13Kiểm kê: thực tế < hệ thốngMovement source_type='adjustment', quantity_change = -chênh lệch. Tồn kho giảm.SCR-07
EC-14Xóa subtask đã doneBlock. Toast: "Không thể xóa công việc đã hoàn thành. Vui lòng undo trước."SCR-04BR-10
EC-15Cảnh báo tồn kho dedupe1 notification/ngày per product per warehouse. Check trước khi gửi. Ngày sau reset.SCR-01BR-11
EC-16Product 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-04EC-11 design
EC-17Branch chưa có warehouseAuto-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-18Import Excel: file rỗng hoặc sai formatToast error: "File không hợp lệ. Vui lòng dùng template Excel." Không import.SCR-06DEC-D13
EC-19Import Excel: product_id không tồn tạiBỏ 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-20Network error khi auto-deductRetry 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-21Vậ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-04OI-06
EC-22Staff view ẩn giá qua GraphQLHasura 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-05DEC-D10
EC-23FIFO 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-24Lô hết hạn auto-lockCron hàng ngày → status='locked', tạo disposal movementSCR-03
EC-25Hủy lôConfirm dialog + nhập lý do → status='disposed'SCR-03
EC-26Nhập cùng giá cùng ngàyVẫn 2 lô riêng (có thể khác mã lô, HSD)SCR-06
EC-27Không đủ tồn khoBlock deduct, toast error, không partialSCR-04
EC-28Tất cả lô locked/disposedXử lý như không đủ tồn khoSCR-04
EC-29Reverse nhưng lô gốc disposed/lockedCộng lại vào lô gốc, chuyển status activeSCR-03

Changelog

VersionNgàyThay đổiTác giả
1.02026-03-26Khở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 casesPO/BA + AI
1.12026-03-27B5: 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.02026-03-27Design 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.12026-03-27Fix 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.02026-03-27Design 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