---
date: 2026-05-12
type: epic
tags: [epic, product, structure, mes, ban-hang]
epic_id: EPIC-001
title: Quản lý Kết cấu Sản phẩm (Product Structure)
status: in_progress
---

# EPIC-001 — Quản lý Kết cấu Sản phẩm (Product Structure)

## Problem Statement

Hiện tại, mỗi `Product` chỉ có **một cây component duy nhất** (flat list + tree build theo code). Điều này không phản ánh được thực tế sản xuất nội thất của Kingston:

- Cùng một sản phẩm (ví dụ: bàn làm việc) có thể có **nhiều phiên bản kết cấu khác nhau** — kết cấu gỗ tự nhiên, kết cấu MDF, kết cấu theo đơn đặt riêng
- Mỗi kết cấu gồm các **cụm/chi tiết (component)** khác nhau với vật liệu và thông số khác nhau
- Khi tạo BOM, kỹ thuật viên cần chọn **kết cấu nào** để map sang BOM — hiện tại không có sự phân biệt này, dẫn đến nhầm lẫn và phải ghi chú thủ công ngoài hệ thống

**Người dùng bị ảnh hưởng:** Kỹ thuật viên (người định nghĩa kết cấu), Quản lý sản xuất (người tạo BOM/Work Order), Nhân viên kinh doanh (cần biết sản phẩm có phiên bản nào).

## Business Value

| Lợi ích | Đo lường |
|---------|----------|
| Loại bỏ nhầm lẫn kết cấu khi tạo BOM | Số lần BOM bị tạo sai kết cấu = 0 |
| Hỗ trợ báo giá theo phiên bản kết cấu | Nhân viên KD có thể chọn đúng kết cấu khi báo giá |
| Truy xuất lịch sử: sản phẩm X đã từng có bao nhiêu kết cấu | Audit trail rõ ràng cho QA/kiểm tra |
| Nền tảng cho BOM versioning | Tech Lead có thể link BOM → Structure thay vì BOM → Product |

## Target User

| Người dùng | Vai trò | Nhu cầu |
|-----------|---------|---------|
| Kỹ thuật viên | Tạo/quản lý kết cấu | Định nghĩa từng phiên bản kết cấu, thêm/sửa cụm-chi tiết |
| Quản lý sản xuất | Tạo BOM, Work Order | Chọn đúng kết cấu làm template cho BOM |
| Nhân viên kinh doanh | Báo giá, xử lý đơn hàng | Xem danh sách phiên bản kết cấu của sản phẩm |
| Admin ERP | Quản trị hệ thống | Phân quyền, deactivate kết cấu lỗi thời |

## Scope

### In Scope

- Thêm entity **Structure (Kết cấu)** là lớp trung gian giữa Product và Component
- CRUD Structure: tạo, xem, cập nhật tên/mô tả, deactivate
- Di chuyển Component từ Product → gắn vào Structure
- API liệt kê danh sách Structure của một Product
- API lấy cây component theo Structure
- Web UI: tab "Kết cấu" trong màn hình chi tiết sản phẩm
- Dữ liệu migration: các component hiện có của Product → tự động tạo Structure mặc định

### Out of Scope

- Thay đổi BOM để link sang Structure (sẽ là epic riêng sau)
- Versioning/history của Structure (lưu changelog)
- Import kết cấu từ Excel (có thể mở rộng sau)
- So sánh hai kết cấu với nhau
- Pricing theo kết cấu (scope của Sales module)
- Mobile app (app-kingston) — không thuộc scope EPIC-001

## User Stories

| ID | Story | Acceptance Criteria (summary) | Priority |
|----|-------|-------------------------------|----------|
| US-01 | Là kỹ thuật viên, tôi muốn tạo phiên bản kết cấu mới cho sản phẩm để phân biệt các variant | Tạo được Structure với tên, mô tả; gắn vào Product; status ACTIVE | MUST |
| US-02 | Là kỹ thuật viên, tôi muốn thêm/xóa cụm-chi tiết vào kết cấu để định nghĩa đầy đủ BOM tree | Add/remove component node trong cây của Structure | MUST |
| US-03 | Là kỹ thuật viên, tôi muốn deactivate kết cấu cũ để tránh chọn nhầm | Deactivate Structure; không xóa dữ liệu; BOM đã link vẫn hoạt động | MUST |
| US-04 | Là quản lý sản xuất, tôi muốn xem danh sách kết cấu của sản phẩm để chọn khi tạo BOM | Hiển thị list Structure kèm status; filter ACTIVE; empty state rõ ràng | MUST |
| US-05 | Là quản lý sản xuất, tôi muốn xem cây component của từng kết cấu để kiểm tra trước khi tạo BOM | Hiển thị cây component đầy đủ với vật liệu và thông số | MUST |
| US-06 | Là admin, tôi muốn dữ liệu cũ được tự động migrate để không mất dữ liệu hiện có | Migration script: tạo Structure mặc định cho mỗi Product có component | MUST |

## Acceptance Criteria (chi tiết)

### EPIC-001-AC01 — Tạo Structure
**Given** kỹ thuật viên ở màn hình chi tiết sản phẩm  
**When** nhấn "Thêm kết cấu", nhập tên (bắt buộc, max 100 ký tự), mô tả (tùy chọn), nhấn Lưu  
**Then** Structure mới được tạo với status `ACTIVE`, hiển thị trong tab Kết cấu của sản phẩm

**Error cases:**
- Tên rỗng → lỗi validation "Tên kết cấu là bắt buộc"
- Tên trùng trong cùng Product → lỗi "Tên kết cấu đã tồn tại trong sản phẩm này"

### EPIC-001-AC02 — Thêm component vào Structure
**Given** kỹ thuật viên đang xem cây component của một Structure  
**When** thêm node mới (cụm hoặc chi tiết) vào cây  
**Then** node được thêm đúng vị trí, code tự động cập nhật (ví dụ: "1.2.3"), cây re-render

**Error cases:**
- Tên node rỗng → lỗi validation "Tên cụm/chi tiết là bắt buộc"
- Độ sâu cây vượt 10 levels → lỗi "Cây kết cấu không được sâu quá 10 cấp"

### EPIC-001-AC03 — Deactivate Structure
**Given** kỹ thuật viên deactivate một Structure đang ACTIVE  
**When** xác nhận deactivate  
**Then** Structure chuyển sang `INACTIVE`; không thể chọn khi tạo BOM mới; component vẫn lưu đầy đủ  
**Constraint:** Không deactivate được nếu là Structure duy nhất ACTIVE của Product  
**Error case:** Nếu là Structure ACTIVE duy nhất → lỗi "Phải có ít nhất 1 kết cấu đang hoạt động. Hãy tạo kết cấu mới trước khi deactivate kết cấu này."

### EPIC-001-AC04 — Danh sách Structure
**Given** bất kỳ user có quyền xem sản phẩm  
**When** mở tab Kết cấu trong chi tiết sản phẩm  
**Then** hiển thị danh sách Structure (tên, mô tả, số component, status); filter mặc định = ACTIVE; có thể xem cả INACTIVE

**Empty state:** Nếu Product chưa có Structure nào → hiển thị thông báo "Sản phẩm này chưa có kết cấu nào. Nhấn 'Thêm kết cấu' để bắt đầu." kèm nút action (chỉ hiển thị với kỹ thuật viên)

### EPIC-001-AC05 — Migration data
**Given** hệ thống có Product với component hiện tại (không có Structure)  
**When** chạy migration script  
**Then** mỗi Product được tạo 1 Structure mặc định tên "Kết cấu mặc định", toàn bộ component cũ được gắn vào Structure đó; không mất dữ liệu; idempotent (chạy nhiều lần không tạo duplicate)

**Verification:** Sau migration, row count của `component` không thay đổi; mỗi `component` đều có `structureId` hợp lệ

## Affected Areas

| Module | Surface | Thay đổi |
|--------|---------|---------|
| `api-kingston` | Domain model: Product, Component | Thêm entity Structure; Component FK đổi từ productId → structureId |
| `api-kingston` | Use cases: product/ | Thêm CRUD Structure usecases; sửa get-product-structure |
| `api-kingston` | Controller/DTO | Endpoint mới: `/products/:id/structures` |
| `web-kingston` | Product detail page | Thêm tab "Kết cấu"; component tree viewer per Structure |
| DB (Prisma) | Schema | Bảng mới `product_structure`; alter bảng `component` |

## Dependencies

| Dependency | Status | Owner | Ghi chú |
|-----------|--------|-------|---------|
| Product model hiện tại (api-kingston) | Done | Dev | Cần đọc kỹ ProductTreeModel trước khi thiết kế |
| Component model (api-kingston) | Done | Dev | ComponentTreeBuilder cần refactor để nhận structureId |
| BOM epic (tương lai) | Not started | — | BOM sẽ cần link sang Structure — out of scope EPIC-001 |
| Prisma migration | In scope | Dev | Migration data phải idempotent |
| Phân quyền | Done | Dev | Dùng permission framework hiện có (EPIC-001-AC01 cần role kỹ thuật viên) |

## Non-Functional Requirements

### Performance
- API `GET /products/:id/structures` — response < 300ms với product có ≤ 20 structures
- API `GET /products/:id/structures/:sid/tree` — response < 500ms với cây ≤ 10 levels sâu, ≤ 200 nodes
- Web UI render cây component — hiển thị trong < 1s sau khi API trả về

### Accessibility (Web)
- Tab "Kết cấu" và các nút action phải điều hướng được bằng keyboard (Tab, Enter, Escape)
- Form tạo/sửa Structure: label liên kết với input (`htmlFor`); error message được announce bởi screen reader

### Security / Authorization
- Chỉ user có role **Kỹ thuật viên** hoặc **Admin** mới được tạo/sửa/deactivate Structure
- User có role **Quản lý sản xuất**, **Nhân viên KD**, và các role xem được sản phẩm → chỉ đọc (read-only)
- Không có authorization scope theo workshop trong EPIC-001 (để đơn giản; có thể thêm sau)

---

## Analytics Events

| Event | Trigger | Properties |
|-------|---------|-----------|
| `structure.created` | Kỹ thuật viên tạo Structure mới | `productId`, `structureId`, `userId` |
| `structure.deactivated` | Kỹ thuật viên deactivate Structure | `productId`, `structureId`, `userId` |
| `structure.component_added` | Thêm node vào cây | `structureId`, `nodeType` (cum/chi-tiet), `depth` |
| `structure.viewed` | User mở tab Kết cấu | `productId`, `userRole` |

---

## Rollout Strategy

| Bước | Mô tả |
|------|-------|
| 1. Deploy API + Migration | Chạy migration script (idempotent) trên staging, verify row count |
| 2. Smoke test staging | Kiểm tra CRUD Structure, cây component, deactivate |
| 3. Deploy production | Migration chạy tự động qua `prisma migrate deploy` trong CI/CD |
| 4. Enable Web UI | Tab "Kết cấu" enable sau khi migration confirmed thành công |

**Rollback plan:**  
- Nếu migration fail: revert Prisma migration, deploy lại phiên bản API trước; dữ liệu component giữ nguyên vì `structureId` nullable ban đầu  
- Nếu bug nghiêm trọng sau release: hide tab "Kết cấu" bằng feature flag phía web (không ảnh hưởng API)

---

## Epic Phases

| Phase | Mô tả | Agent |
|-------|-------|-------|
| Planning | PRD chi tiết, user flow, AC đầy đủ | PO |
| Tech Design | Data model, API contract, migration plan | TL |
| Implementation — API | Domain entity + use cases + controller | dev-api |
| Implementation — Web | Component tree UI per Structure | dev-web |
| Testing | Unit + integration + E2E | QA |
| Release | Deployment, migration script chạy, verify | Release |

## Risks & Mitigations

| Risk | Khả năng | Impact | Mitigation |
|------|---------|--------|-----------|
| Migration data sai / mất component | Thấp | Cao | Idempotent migration + backup trước khi chạy; kiểm tra row count trước/sau |
| BOM hiện tại bị ảnh hưởng khi Component đổi FK | Trung bình | Cao | Out of scope EPIC-001; giữ nguyên BOM-Component logic; chỉ thêm structureId optional ban đầu |
| UI phức tạp: tree editor trong tab | Trung bình | Trung bình | Reuse component tree UI hiện có; chỉ thêm Structure switcher |
| Quyền truy cập phân tầng (KTV vs KD vs Admin) | Thấp | Thấp | Dùng permission framework hiện có; define rõ trong PRD |

## Ubiquitous Language

| Tiếng Anh | Tiếng Việt | Mô tả |
|-----------|-----------|-------|
| Structure | Kết cấu | Một phiên bản kết cấu vật lý của sản phẩm |
| Component | Cụm / Chi tiết | Đơn vị trong cây kết cấu (có thể là cụm cha hoặc chi tiết lá) |
| Product Structure Tree | Cây kết cấu | Toàn bộ cây component của một Structure |

## Links

- [[topics/san-xuat/bom|BOM — công thức sản xuất (liên quan)]]
- [[topics/ban-hang/san-pham|Sản phẩm — Product entity hiện tại]]
- [[topics/phan-mem/api-kingston-mes|API Kingston MES]]
