PRD: Công thức / Bill of Material
| Module | Kho (CORE-06) | PRD ID | PRD-REC-001 |
| Status | In-progress | Owner | Inventory squad |
| Date | 2026-04-15 | Version | v0.1 |
| Packages | @nx/inventory · @nx/sale · @nx/core | URD | REC · MAT |
TL;DR
Cho phép merchant F&B khai báo một sản phẩm tiêu hao những nguyên vật liệu nào dưới dạng công thức (bill of materials) có phiên bản, rồi tự động trừ các nguyên vật liệu đó ngay khi món được chế biến. Khi một kitchen-ticket item tiến triển - hoặc khi payment thành công - công thức đang hiệu lực được bung ra, mỗi nguyên vật liệu bị trừ tại location mặc định của merchant, và một biến động "Used as Material" bất biến được ghi lại. Kết quả: tồn nguyên vật liệu luôn chính xác mà không cần ai thao tác thủ công.
1. Context & Problem
Merchant F&B bán các sản phẩm đã chế biến - món ăn, đồ uống - mà tác động lên tồn không nằm ở thành phẩm mà ở các nguyên vật liệu thô bên dưới. Kho đã theo dõi biến thể và nhận hàng qua đơn mua hàng, nhưng chưa có cách khai báo một sản phẩm tiêu hao nguyên vật liệu nào, cũng như chưa thể trừ các nguyên vật liệu đó khi một món được chế biến. Không có công thức, tồn nguyên vật liệu chỉ có thể chỉnh bằng điều chỉnh thủ công - dễ sai và phá vỡ việc theo dõi chi phí/mức tiêu hao cho ngành F&B mà KICKO nhắm tới.
Hạng mục này giới thiệu Công thức nguyên vật liệu (bill of materials): một công thức có phiên bản gắn vào một biến thể sản phẩm, một tập dòng công thức (nguyên vật liệu + số lượng + đơn vị), và một luồng runtime bung công thức đang hiệu lực và trừ các nguyên vật liệu - được nối từ vòng đời kitchen-ticket và từ luồng payment-success. Nó xây trên danh mục nguyên vật liệu hiện có và các primitive tồn/tracking.
2. Goals & Non-Goals
Goals
- Một công thức có phiên bản gắn vào một biến thể sản phẩm (phiên bản mặc định 1.0), unique theo
(principal, version). - Dòng công thức khai báo một nguyên vật liệu, một số lượng (decimal, 4 chữ số), và một đơn vị; cùng một nguyên vật liệu xuất hiện nhiều nhất một lần mỗi công thức; soft-delete cho phép thêm lại.
- Activate / deactivate sao cho chỉ phiên bản công thức được kích hoạt mới được dùng ở runtime.
- Bung ở runtime: một thay đổi trạng thái kitchen-ticket-item (và luồng payment-success) kích hoạt tra cứu công thức, trừ từng thành phần tại location mặc định, ghi một tracking entry "Used as Material", và phát một event
material.stock-changed. - Nguyên vật liệu là một inventory item type hạng nhất - tạo inventory cho nguyên vật liệu, với
SALE_ORDERđược thêm vàoInventoryTrackingReferenceTypes.
Non-Goals
- BOM nhiều cấp / cụm lắp ráp con - chỉ một cấp.
- Năng suất công thức (yield) và phần trăm hao hụt (scrap).
- Tôn trọng một location bếp không-mặc-định (URD-REC-208) - runtime dùng location mặc định của merchant.
- BOM bung-khi-bán cho bán lẻ ngoài F&B.
3. Success Metrics
| Metric | Mục tiêu / tín hiệu |
|---|---|
| Độ phủ tự động trừ | 100% lần hoàn tất món-chế-biến trừ nguyên vật liệu qua công thức hiệu lực (không điều chỉnh thủ công) |
| Độ chính xác tồn | Tồn nguyên vật liệu sau khi bung = tổng số lượng dòng công thức × số món chế biến |
| Idempotent | Cùng (referenceType, referenceId) phát lại tạo đúng một tracking entry |
| Toàn vẹn phiên bản | Chỉ phiên bản công thức được kích hoạt mới được bung; các phiên bản trước được giữ |
4. Personas & Use Cases
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Owner | Định nghĩa công thức theo từng món, kiểm soát phiên bản nào đang chạy, xem mức tiêu hao nguyên vật liệu |
| Nhân viên bếp / kho | Để nguyên vật liệu được trừ tự động khi món được chế biến |
| System | Bung công thức hiệu lực khi có event kitchen/payment và ghi audit trail |
Core scenarios: owner tạo một công thức cho một biến thể → thêm dòng công thức (nguyên vật liệu + số lượng + đơn vị) → activate → một kitchen-ticket item tiến triển (hoặc payment thành công) → công thức hiệu lực bung ra, nguyên vật liệu bị trừ tại location mặc định, một entry "Used as Material" được ghi, và một event material.stock-changed được phát.
5. User Stories
- Là một owner, tôi muốn gắn một công thức vào một biến thể sản phẩm, để món biết nó tiêu hao những nguyên vật liệu nào.
- Là một owner, tôi muốn thêm dòng công thức với một nguyên vật liệu, số lượng và đơn vị, để mỗi lần trừ nguyên vật liệu đều chính xác.
- Là một owner, tôi muốn activate đúng một phiên bản công thức, để runtime luôn dùng đúng định nghĩa.
- Là một owner, tôi muốn việc sửa một công thức đang hiệu lực tạo ra một phiên bản mới, để các công thức lịch sử được giữ.
- Là nhân viên bếp, tôi muốn nguyên vật liệu tự động trừ khi một món được chế biến, để không bao giờ phải chỉnh tồn nguyên vật liệu thủ công.
- Là system, tôi muốn việc bung là idempotent và no-op an toàn khi không có công thức hay location mặc định, để các lần phát lại và trường hợp biên không làm hỏng tồn.
6. Functional Requirements
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Tạo một công thức có phiên bản gắn vào một biến thể sản phẩm (mặc định 1.0); (principal, version) unique | URD-REC-001..003 |
| FR-2 | Dòng công thức khai báo nguyên vật liệu + số lượng (4dp) + đơn vị; cùng nguyên vật liệu nhiều nhất một lần; soft-delete cho phép thêm lại | URD-REC-004..006 · URD-REC-101..105 |
| FR-3 | Activate / deactivate một công thức; chỉ phiên bản được kích hoạt mới dùng ở runtime | URD-REC-007..008 · URD-REC-011 |
| FR-4 | Sửa một công thức đang hiệu lực tạo phiên bản mới; bản gốc được giữ; phiên bản liệt kê được theo principal | URD-REC-009..010 |
| FR-5 | Một thay đổi trạng thái kitchen-ticket-item kích hoạt tra cứu công thức và chuẩn bị trừ tại location mặc định | URD-REC-201 |
| FR-6 | Mỗi thành phần bị trừ; một tracking entry "Used as Material" được tạo; tồn cập nhật tại location mặc định | URD-REC-202..203 |
| FR-7 | Công thức được kích hoạt của biến thể là cái được bung; một event material.stock-changed được phát sau khi bung | URD-REC-204..205 |
| FR-8 | Không có location mặc định → ghi warning, bỏ qua (không lỗi cứng); không có công thức hiệu lực → no-op âm thầm | URD-REC-206..207 |
| FR-9 | Nguyên vật liệu là một inventory item type hạng nhất với việc tạo inventory cho nguyên vật liệu; hỗ trợ barcode/các scheme định danh | URD-MAT-001..007 · URD-MAT-101..106 |
Toàn văn yêu cầu và tiêu chí nghiệm thu nằm trong URD Kho. PRD này tham chiếu chúng thay vì lặp lại. URD-REC-208 (location bếp không-mặc-định) được loại khỏi phạm vi hạng mục này một cách tường minh.
7. Non-Functional Requirements
| Area | Yêu cầu |
|---|---|
| Data integrity | Mỗi lần trừ nguyên vật liệu và tracking entry "Used as Material" của nó được ghi cùng nhau - không thay đổi tồn mà không có audit entry bất biến tương ứng |
| Idempotency | Việc bung là idempotent theo (referenceType, referenceId); phát lại không tạo biến động trùng |
| Tenancy & authz | Mọi thao tác scope theo merchant (x-merchant-id); mutation công thức được gate bởi permission của inventory |
| Độ chính xác | Số lượng dòng công thức và phép tính tồn dùng float(value, 4) |
| Resilience | Thiếu location mặc định hoặc thiếu công thức hiệu lực suy giảm mượt mà (warn / no-op), không bao giờ lỗi cứng trên luồng kitchen/payment |
| Eventing | Việc bung ở runtime được định tuyến qua một material worker chuyên dụng và phát material.stock-changed |
| i18n | Tên nguyên vật liệu và nhãn hiển thị cho người dùng là song ngữ ({ en, vi }) |
8. UX & Flows
Màn hình chính (trong apps/client): trình soạn công thức trên một biến thể sản phẩm (thêm/sửa dòng công thức, activate/deactivate phiên bản) và danh mục nguyên vật liệu. Việc bung ở runtime không có UI - nó được điều khiển bằng event.
9. Data & Domain
| Entity | Vai trò |
|---|---|
Material | Một nguyên vật liệu thô được theo dõi tách biệt với sản phẩm bán được; có các định danh (SKU/BARCODE/QR) và tên i18n |
MaterialRecipe | Một công thức có phiên bản gắn vào một biến thể sản phẩm; một phiên bản được kích hoạt điều khiển runtime |
MaterialRecipeItem | Một dòng công thức - tham chiếu nguyên vật liệu, số lượng (4dp), đơn vị |
| Inventory item của nguyên vật liệu | Một inventory item hạng nhất cho nguyên vật liệu, để tồn và tracking áp dụng được |
| Movement record | Tracking entry "Used as Material" bất biến được ghi khi trừ |
Chỉ là khái niệm - schema và bất biến đầy đủ trong domain model của inventory.
10. Dependencies & Assumptions
Phụ thuộc vào
- Danh mục nguyên vật liệu (URD-MAT) - dòng công thức tham chiếu các nguyên vật liệu đã tồn tại.
- Mức tồn & audit biến động (URD-STK · URD-TRK) - việc trừ xây trên các primitive tồn và tracking.
- Location kho (URD-LOC) - các thành phần bị trừ tại location mặc định của merchant.
- Event sale / kitchen (
@nx/sale) - thay đổi trạng thái kitchen-ticket-item và luồng payment-success kích hoạt việc bung.
Giả định
- Merchant có một location kho mặc định.
- Nguyên vật liệu đã tồn tại và đã liên kết tới inventory item trước khi một công thức tham chiếu chúng.
- Biến thể sản phẩm mà một công thức gắn vào đã có trong danh mục.
11. Risks & Open Questions
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Việc trừ và tracking entry có thể lệch nhau khi lỗi một phần | Được ghi cùng nhau; không thay đổi tồn mà không có movement record tương ứng |
| Món kitchen không có công thức hiệu lực | No-op âm thầm - đã ghi tài liệu, không phải lỗi |
| Merchant không có location mặc định | Ghi warning, bỏ qua việc bung; không lỗi cứng |
| Location bếp không-mặc-định chưa được tôn trọng | Ngoài phạm vi (URD-REC-208); runtime dùng location mặc định của merchant - giữ REC In-progress |
| Có thể cần BOM nhiều cấp / cụm lắp ráp con về sau | Ngoài phạm vi; chỉ một cấp ở hạng mục này |
12. Release Plan & Launch Criteria
| Aspect | Kế hoạch |
|---|---|
| Phase | P3 (BOM + Advanced) - xem feature catalog của URD |
| Rollout | Merchant F&B; không feature flag |
| Migration | Entity mới (material recipe + recipe item); không backfill |
| Launch criteria | Tạo→activate→event kitchen→trừ nguyên vật liệu + entry "Used as Material" được kiểm chứng end-to-end; phát lại idempotent được kiểm chứng; các luồng no-op/bỏ qua được kiểm chứng |
| Monitoring | Lượng event material.stock-changed, tỷ lệ lỗi/bỏ-qua khi bung, kiểm tra nhất quán tồn-vs-movement của nguyên vật liệu |
13. FAQ
Tại sao trừ nguyên vật liệu thay vì thành phẩm? Tác động lên tồn của F&B nằm ở nguyên liệu, không phải món. Công thức ánh xạ một biến thể tới các nguyên vật liệu nó tiêu hao để đúng tồn được biến động.
Cái gì kích hoạt việc trừ? Một thay đổi trạng thái kitchen-ticket-item, và cả luồng payment-success. Cả hai định tuyến qua một material worker chuyên dụng.
Phiên bản công thức nào được dùng? Chỉ phiên bản được kích hoạt. Sửa một công thức đang hiệu lực tạo phiên bản mới và giữ bản gốc.
Điều gì xảy ra nếu không có công thức hay không có location mặc định? Không có công thức hiệu lực thì no-op âm thầm; không có location mặc định thì ghi warning và bỏ qua - cả hai không chặn luồng kitchen hay payment.
Có hỗ trợ BOM nhiều cấp không? Không - chỉ một cấp ở hạng mục này. Cụm lắp ráp con và yield/scrap của công thức nằm ngoài phạm vi.
References
- URD: Kho - Công thức / BOM · Nguyên vật liệu
- Xây trên: Mức tồn · Audit Biến động · Location Kho
- Module: Kho - tổng quan + traceability
- Developer: @nx/inventory · @nx/sale · domain model