PRD: Đơn mua hàng
| Module | Kho (CORE-06) | PRD ID | PRD-PO-001 |
| Status | Shipped | Owner | Inventory squad |
| Date | 2026-02-24 | Version | v1.0 |
| Packages | @nx/inventory · @nx/core · @nx/finance · apps/client | URD | PO · POI |
TL;DR
Cho phép một merchant ghi nhận những gì họ mua từ một vendor dưới dạng đơn mua hàng (PO), đưa nó qua một vòng đời rõ ràng, và nhận hàng vào tồn với một audit trail có thể kiểm chứng. Việc nhận hàng làm tăng tồn tại một địa điểm, ghi các bản ghi biến động bất biến, và có thể ghi một finance transaction để một lần mua hiện lên trong sổ sách. Kết quả: mọi lần tăng tồn đều được gắn lại với một vendor, một đơn giá, và phân tách discount/thuế - không còn các lần nhập tồn tùy tiện, không truy vết được.
1. Context & Problem
Các merchant nhập hàng bằng cách mua hàng từ vendor, và số hàng đó phải vào tồn với một dấu vết giấy tờ mà chủ có thể audit. Không có tài liệu đơn mua hàng, tồn chỉ có thể được di chuyển qua các phiếu tùy tiện: một lần tăng tồn không thể gắn lại với một vendor, một đơn giá, hay phân tách discount/thuế, và không có trạng thái được kiểm soát nào giữa "đã đặt" và "đã nhận". Điều này khiến việc theo dõi chi phí, đối soát vendor, và báo cáo thuế trở nên không tin cậy - một rào cản lớn cho mảng kế toán HKD/SME mà KICKO nhắm đến.
Increment này xây luồng PO trên nền danh mục vendor sẵn có cùng các primitive tồn/theo dõi của kho, và kết nối việc nhận hàng với lớp finance.
2. Goals & Non-Goals
Goals
- Một tài liệu PO thuộc về một vendor, được tạo trong một thao tác duy nhất (vendor + các dòng mục; vendor bắt buộc).
- Một vòng đời được kiểm soát với việc sửa dòng chỉ cho phép khi còn nháp.
- Nhận hàng làm tăng tồn và ghi các bản ghi biến động bất biến, hỗ trợ nhận đầy đủ và nhận tăng dần.
- Tổng theo dòng và theo PO chính xác với discount/thuế phân biệt rõ thủ công và hệ thống.
- Liên kết finance: một PO đã nhận có thể ghi một finance transaction kèm một payment tùy chọn.
Non-Goals
- Nhập tồn đầu kỳ / nhập tồn di trú - thuộc về Nhập tồn đầu kỳ.
- Định giá tồn đa tiền tệ.
- Một cổng gửi-cho-vendor / phê duyệt bên ngoài.
3. Success Metrics
| Metric | Mục tiêu / tín hiệu |
|---|---|
| Truy vết | 100% lần nhập tồn từ vendor đi qua một PO (không phải phiếu tùy tiện) |
| Độ chính xác nhận hàng | Tồn on-hand sau khi nhận = số lượng đã đặt/đã nhận; không có dòng tồn nào không có bản ghi biến động khớp |
| Độ phủ finance | Số PO đã nhận có ghi một finance transaction (khi có chi phí được ghi nhận) |
| Cycle time | Thời gian trung vị DRAFT → RECEIVED theo từng merchant có xu hướng giảm |
4. Personas & Use Cases
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Chủ | Kiểm soát việc mua hàng, xem chi phí & lịch sử vendor, đối soát với finance |
| Nhân viên kho | Tạo PO, sửa dòng, nhận hàng vào đúng địa điểm |
| Kế toán (qua Finance) | Xem các lần mua được ghi dưới dạng finance transaction |
Các kịch bản cốt lõi: tạo một PO cho một vendor → điều chỉnh dòng khi còn nháp → chuyển sang processing → nhận hàng (đầy đủ hoặc một phần) → tồn tăng kèm audit trail và một lần ghi finance tùy chọn → đóng PO.
5. User Stories
- Là nhân viên kho, tôi muốn tạo một PO với một vendor cùng các dòng mục trong một bước, để đơn được gắn với một nhà cung cấp và một mức giá.
- Là nhân viên kho, tôi muốn sửa các dòng PO khi PO ở trạng thái DRAFT, để tôi có thể chỉnh số lượng và giá trước khi chốt.
- Là nhân viên kho, tôi muốn nhận hàng theo một PO, để tồn tăng tại địa điểm đã chọn kèm một bản ghi biến động.
- Là nhân viên kho, tôi muốn nhận tăng dần hoặc thay thế một số lượng đã nhận, để các đợt giao hàng một phần được xử lý đúng.
- Là một chủ, tôi muốn một PO đã nhận ghi một finance transaction (tùy chọn kèm một payment), để các lần mua phản ánh trong sổ sách.
- Là một chủ, tôi muốn hủy một PO chưa ở trạng thái kết thúc, để các đơn nhầm không làm bẩn tồn hay finance.
6. Functional Requirements
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Tạo một PO thuộc về một vendor (vendor bắt buộc) với các dòng mục trong một aggregate operation | URD-PO-001..003 |
| FR-2 | Vòng đời DRAFT → PROCESSING → RECEIVED → COMPLETED → CLOSED; hủy từ bất kỳ trạng thái chưa kết thúc nào; revert PROCESSING → DRAFT | URD-PO-004..006 |
| FR-3 | Dòng mục chỉ sửa được trong DRAFT; aggregate create/edit (PO + dòng) trong một lời gọi duy nhất | URD-PO-007 · URD-POI-001 |
| FR-4 | Nhận hàng tăng tồn tại địa điểm và ghi các bản ghi biến động bất biến | URD-PO-008..009 |
| FR-5 | Hai chế độ nhận: OVERRIDE (thay thế số lượng đã nhận) và ACCUMULATIVE (cộng dồn vào số lượng đã nhận) | URD-PO-010 |
| FR-6 | Tổng mỗi dòng = price × qty × multiplier + tax − discount; các dòng gộp theo (itemType, itemId, uom); một dòng về 0 sẽ bị soft-delete | URD-POI-002..004 |
| FR-7 | Tổng PO = subtotal − discount + tax; discount/thuế chấp nhận ghi đè thủ công, được đánh dấu thủ công-vs-hệ thống | URD-PO-011 |
| FR-8 | inventoryLocationId tùy chọn; mặc định về địa điểm mặc định của merchant | URD-POI-005 |
| FR-9 | Một PO đã nhận có thể ghi một finance transaction kèm một payment tùy chọn và một finance category mặc định | URD-PO-012 |
| FR-10 | Một số PO duy nhất được gán theo từng merchant | URD-PO-002 |
Toàn văn yêu cầu và tiêu chí chấp nhận nằm trong Inventory URD. PRD này tham chiếu chúng thay vì lặp lại.
7. Non-Functional Requirements
| Lĩnh vực | Yêu cầu |
|---|---|
| Toàn vẹn dữ liệu | Việc tăng tồn và bản ghi biến động của nó được ghi cùng nhau - không có thay đổi tồn nào mà không có một mục audit bất biến khớp |
| Bất biến | Bản ghi biến động chỉ-thêm; việc sửa lỗi diễn ra qua các mục mới, không bao giờ sửa |
| Tenancy & authz | Mọi thao tác được scope theo từng merchant (x-merchant-id); được kiểm soát bởi inventory permission |
| Độ chính xác | Phép toán tiền/số lượng dùng float(value, 4) |
| Nhất quán | Aggregate create/edit và nhận hàng là transactional; lỗi một phần không để lại PO ghi dở |
| i18n | Nhãn/trạng thái hiển thị cho người dùng là song ngữ ({ en, vi }) |
8. UX & Flows
Các màn hình chính (trong apps/client): danh sách PO, tạo PO, sửa PO, control luồng trạng thái, và một xác nhận hủy đơn.
9. Data & Domain
| Entity | Vai trò |
|---|---|
PurchaseOrder | Tài liệu đơn - vendor, status, các tổng, địa điểm, relation financeTransaction tùy chọn |
PurchaseOrderItem | Một dòng - tham chiếu mục (itemType, itemId, uom), qty, price, multiplier, tax, discount, tổng dòng |
| PO config | Thiết lập PO theo từng merchant, seed lúc khởi động |
| Bản ghi biến động | Mục biến động tồn (tracking) bất biến được ghi khi nhận hàng |
Chỉ ở mức khái niệm - toàn bộ schema và bất biến nằm trong inventory domain model.
10. Dependencies & Assumptions
Phụ thuộc vào
- Vendors (URD-VEN) - một PO cần một vendor sẵn có.
- Mức tồn & audit biến động (URD-STK · URD-TRK) - việc nhận hàng xây trên các primitive tồn và tracking.
- Inventory locations (URD-LOC) - tồn vào một địa điểm; phải tồn tại một địa điểm mặc định của merchant.
- Finance (
@nx/finance) - cho việc ghi transaction/payment tùy chọn.
Giả định
- Merchant có ít nhất một vendor và một địa điểm kho mặc định.
- Tồn tại một finance category để phân loại lần ghi mua hàng.
11. Risks & Open Questions
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Việc nhận hàng và ghi tồn có thể lệch nhau khi lỗi một phần | Được ghi transactional; tồn không có mục nào mà không có một bản ghi biến động |
| Không hỗ trợ đa tiền tệ | Ngoài phạm vi; ghi nhận như một ràng buộc cho vendor xuyên biên giới |
| Không có cổng phê duyệt trước khi processing | Chấp nhận cho quy mô SME; xem lại nếu cần phê duyệt cấp doanh nghiệp |
| Revert một PO đã nhận so với finance đã ghi | Mở: định nghĩa đường đảo ngược/bù trừ cho các mục finance |
12. Release Plan & Launch Criteria
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P1 (nền tảng) - xem URD feature catalog |
| Rollout | Tất cả merchant; không feature flag |
| Migration | Không (entity mới; PO config seed lúc khởi động) |
| Tiêu chí phát hành | Tạo→nhận→tồn+biến động được kiểm chứng đầu-cuối; ghi finance được kiểm chứng; các tổng khớp phép toán kỳ vọng |
| Giám sát | Lượng PO theo từng merchant, tỉ lệ lỗi nhận hàng, các kiểm tra nhất quán tồn-vs-biến động |
13. FAQ
Một PO có thể đổi sau khi rời DRAFT không? Không - dòng mục chỉ sửa được trong DRAFT. Dùng revert PROCESSING → DRAFT để chỉnh sửa, hoặc hủy.
Nhận hàng OVERRIDE vs ACCUMULATIVE - khác gì nhau? OVERRIDE thay thế số lượng đã nhận trên dòng; ACCUMULATIVE cộng dồn vào nó (cho các đợt giao đến trong nhiều lần).
Nhận một PO có trả tiền cho vendor không? Không tự động - một PO đã nhận có thể ghi một finance transaction kèm một payment tùy chọn. Ghi nhận một chi phí và trả nó là hai việc riêng.
Nếu không cho địa điểm thì sao? PO mặc định về địa điểm kho mặc định của merchant.
Tôi có thể nhập tồn đầu kỳ qua một PO không? Không - tồn đầu kỳ được xử lý bởi tính năng riêng Nhập tồn đầu kỳ; không có cách lách bằng vendor ảo.
References
- URD: Kho - Đơn mua hàng · Mục đơn mua hàng
- Xây trên: Vendors · Mức tồn · Audit Trail Biến động
- PRD liên quan: Nhập tồn đầu kỳ
- Module: Kho - tổng quan + truy vết
- Lập trình viên: @nx/inventory · domain model