---
date: 2026-05-21
type: epic
tags: [epic, product, packaging, ban-hang, mes]
epic_id: EPIC-005
title: Add Packaging Material — Vật tư bao bì theo Đơn hàng
status: planning
---

# EPIC-005 — Add Packaging Material (Vật tư bao bì theo Đơn hàng)

## Problem Statement

Mỗi đơn hàng tại Kingston có **cách đóng gói riêng** tùy vào điểm giao, số lượng, và yêu cầu khách hàng — ví dụ: đơn giao xa cần foam góc + màng PE + dây đai; đơn giao gần chỉ cần thùng carton. Hiện tại hệ thống **không có nơi ghi nhận danh sách vật tư bao bì theo từng đơn hàng**, khiến:

- Kỹ thuật viên / kho phải ghi chú bao bì ngoài hệ thống (chat, giấy, file riêng)
- Không có cơ sở để kho chuẩn bị bao bì trước khi thành phẩm ra lò
- Dễ thiếu bao bì khi giao hàng, phải chờ bổ sung → trễ giao

**Người dùng bị ảnh hưởng:** Kỹ thuật viên (người xác định cách đóng gói), Thủ kho (người chuẩn bị vật tư bao bì), Quản lý sản xuất (người lên kế hoạch xuất hàng).

## Business Value

| Lợi ích | Đo lường |
|---------|----------|
| Tập trung thông tin bao bì vào ERP | 100% đơn hàng có ghi nhận danh sách bao bì trước khi giao |
| Kho chủ động chuẩn bị bao bì | Giảm tình trạng thiếu bao bì lúc đóng gói xuống 0 |
| Nền tảng cho tính định mức bao bì tự động | Tech Lead có thể dùng danh sách này để tính chi phí đóng gói theo đơn |

## Target User

| Người dùng | Vai trò | Nhu cầu |
|-----------|---------|---------|
| Kỹ thuật viên | Xác định cách đóng gói | Thêm / sửa / xóa vật tư bao bì vào đơn hàng |
| Thủ kho | Chuẩn bị vật tư | Xem danh sách bao bì cần chuẩn bị cho đơn sắp giao |
| Quản lý sản xuất | Lên kế hoạch xuất | Tra cứu bao bì để phối hợp lịch đóng gói |

## Scope

### In Scope

- Entity mới **OrderPackagingMaterial** — bảng liên kết giữa `Order` và `Material`, có trường `quantity` và `note`; đơn vị tính lấy từ `Material.unitId` (không lưu riêng)
- CRUD OrderPackagingMaterial — **chỉ được thao tác khi Order ở trạng thái `WAIT_FOR_APPROVE`**; các trạng thái khác (TODO, IN_PROGRESS, COMPLETED, REJECTED) là read-only:
  - Thêm vật tư bao bì vào đơn hàng
  - Cập nhật số lượng / ghi chú
  - Xóa một dòng bao bì
- Danh sách Material để chọn là toàn bộ danh mục (không lọc theo typeId) — bất kỳ vật tư nào đều có thể dùng để đóng gói tùy đơn hàng
- API liệt kê danh sách bao bì theo `orderUUID`
- API thêm / cập nhật / xóa một entry bao bì
- Web UI: section "Vật tư bao bì" trong màn hình chi tiết đơn hàng
- Validation: không cho trùng `materialId` trong cùng một đơn hàng

### Out of Scope

- Tự động đề xuất bao bì dựa trên sản phẩm trong đơn (sẽ là epic riêng)
- Tính chi phí bao bì vào tổng chi phí đơn hàng
- Phiếu xuất kho bao bì tự động khi đơn hàng hoàn thành
- Quản lý Material Type trong master-data (đã có sẵn, `typeId` đã được thêm vào `IMaterial`)
- Import từ Excel
- Mobile app (app-kingston)

## User Stories

| ID | Story | Acceptance Criteria (summary) | Priority |
|----|-------|-------------------------------|----------|
| US-01 | Là kỹ thuật viên, tôi muốn thêm vật tư bao bì vào đơn hàng đang chờ duyệt để kho biết cần chuẩn bị gì | Thêm được OrderPackagingMaterial với materialId, quantity; không trùng materialId trong cùng đơn; chỉ khi Order = WAIT_FOR_APPROVE | MUST |
| US-02 | Là kỹ thuật viên, tôi muốn cập nhật số lượng / ghi chú bao bì trước khi đơn được duyệt | Cập nhật thành công, danh sách refresh; chỉ khi Order = WAIT_FOR_APPROVE | MUST |
| US-03 | Là kỹ thuật viên, tôi muốn xóa một dòng bao bì khỏi đơn khi không còn cần | Xóa thành công; bản ghi Material gốc không bị ảnh hưởng | MUST |
| US-04 | Là thủ kho / quản lý sản xuất, tôi muốn xem danh sách bao bì của một đơn hàng để chuẩn bị trước | Hiển thị list với Mã vật tư, Tên, Số lượng, Đơn vị, Ghi chú; empty state rõ ràng | MUST |
| US-05 | Là kỹ thuật viên, tôi không muốn thêm trùng cùng một vật tư bao bì vào đơn | Hệ thống trả lỗi validation khi materialId đã tồn tại trong đơn đó | MUST |

## Acceptance Criteria (chi tiết)

### EPIC-005-AC01 — Thêm vật tư bao bì vào đơn hàng
**Given** kỹ thuật viên đang xem đơn hàng có status `WAIT_FOR_APPROVE`  
**When** nhấn "Thêm vật tư đóng gói", chọn vật tư từ toàn bộ danh mục Material, nhập số lượng (> 0), nhấn Lưu  
**Then** OrderPackagingMaterial mới được tạo và hiển thị trong section "Vật tư bao bì"; đơn vị hiển thị lấy từ `Material.unit`

**Error cases:**
- Số lượng ≤ 0 → lỗi "Số lượng phải lớn hơn 0"
- Không chọn vật tư → lỗi "Vui lòng chọn vật tư bao bì"
- Vật tư đã tồn tại trong đơn → lỗi "Vật tư này đã có trong danh sách bao bì của đơn hàng"

### EPIC-005-AC02 — Cập nhật vật tư bao bì
**Given** đơn hàng có status `WAIT_FOR_APPROVE`, danh sách bao bì đang hiển thị  
**When** nhấn Edit trên một dòng, sửa quantity và/hoặc note, nhấn Lưu  
**Then** dữ liệu được cập nhật, danh sách refresh; `materialId` không thể thay đổi khi edit

### EPIC-005-AC03 — Xóa vật tư bao bì
**Given** đơn hàng có status `WAIT_FOR_APPROVE`, danh sách bao bì đang hiển thị  
**When** nhấn Delete trên một dòng, xác nhận  
**Then** dòng bị xóa; bản ghi `Material` gốc không bị ảnh hưởng

### EPIC-005-AC04 — Xem danh sách vật tư bao bì
**Given** bất kỳ user có quyền xem đơn hàng  
**When** mở màn hình chi tiết đơn hàng, cuộn đến section "Vật tư bao bì"  
**Then** hiển thị danh sách với columns: Mã vật tư, Tên vật tư, Số lượng, Đơn vị, Ghi chú  
**Empty state:** "Đơn hàng này chưa có vật tư bao bì. Nhấn 'Thêm vật tư đóng gói' để bắt đầu." (nút action chỉ hiển thị khi Order = WAIT_FOR_APPROVE và user có quyền write)

### EPIC-005-AC05 — Block write khi Order không ở WAIT_FOR_APPROVE
**Given** đơn hàng có status khác WAIT_FOR_APPROVE (TODO, IN_PROGRESS, COMPLETED, REJECTED)  
**When** user mở section "Vật tư bao bì"  
**Then** danh sách bao bì vẫn hiển thị (read-only); nút "+ Thêm vật tư đóng gói", icon Edit, Delete bị ẩn; API từ chối request write với HTTP 403

## Affected Areas

| Surface | Thay đổi |
|---------|----------|
| **api-kingston** | Entity mới `OrderPackagingMaterial`; use cases CRUD; API endpoints mới dưới `/orders/:uuid/packaging-materials` |
| **web-kingston** | Section "Vật tư bao bì" trong màn hình chi tiết Order (Order detail page) |
| **Database** | Bảng `order_packaging_material` mới (orderUUID, materialId, quantity, note, timestamps) — không có cột unitId riêng |
| **app-kingston** | Không thuộc scope |

## Dependencies

| Phụ thuộc | Trạng thái | Owner | Ghi chú |
|-----------|-----------|-------|---------|
| Order entity (`IOrder` với `uuid` key) | ✅ Done | — | `/orders` endpoint đã có; dùng `uuid` làm FK |
| Material entity + `GET /materials` | ✅ Done | — | Dùng toàn bộ danh mục để chọn vật tư đóng gói; không cần filter typeId |
| Permission framework | ✅ Done | — | Dùng lại pattern phân quyền hiện có |

## Epic Phases

| Phase | Artifact | Status |
|-------|----------|--------|
| Planning (PO) | `EPIC-005.md` | 🟡 In progress |
| PRD | `PRD.md` | ✅ Done |
| Tech Design | `TECH-DESIGN.md`, `IMPL-PLAN-API.md`, `IMPL-PLAN-WEB.md` | ⬜ Chưa bắt đầu |
| Implementation API | `impl-report-api.md` | ⬜ Chưa bắt đầu |
| Implementation Web | `impl-report-web.md` | ⬜ Chưa bắt đầu |

## Risks & Mitigations

| Rủi ro | Mức độ | Mitigation |
|--------|--------|-----------|
| `IOrder` dùng `uuid` thay vì `id` — pattern khác với các entity khác | Thấp | Tech Lead cần lưu ý khi thiết kế `OrderPackagingMaterial` FK và API path |
| Dropdown Material có thể có nhiều items → UX chậm | Thấp | Dùng paginated search API đã có của `/materials?search=...` — không load toàn bộ |

## Ubiquitous Language

| Tiếng Anh | Tiếng Việt | Mô tả |
|-----------|-----------|-------|
| OrderPackagingMaterial | Vật tư đóng gói đơn hàng | Một dòng vật tư dùng để đóng gói gắn với đơn hàng cụ thể |
| Packaging Material | Vật tư đóng gói | Bất kỳ vật tư nào (từ bảng Material) được dùng để đóng gói vận chuyển cho đơn hàng (thùng carton, xốp, màng PE, dây đai...) |

## Links

- [[topics/ban-hang/_index|Ban hàng — Order entity]]
- [[topics/kho-hang/_index|Kho hàng — Vật tư, Material]]
- [[docs/epics/EPIC-004/EPIC-004|EPIC-004 — Structure Material (pattern tương tự)]]
