Bước 1 — Thêm mới mặt hàng
I. Mục tiêu
Phần tiêu đề “I. Mục tiêu”Cuối Bước 1 bạn sẽ:
- Nhìn thấy mặt hàng của mình trong danh mục POS trước khi kết thúc ngày — thu ngân tại quầy có thể quét mã vạch hoặc chọn từ lưới sản phẩm và nhận đúng giá + tồn kho + thuế hiển thị.
- Mở được trang chỉnh sửa đầy đủ cho mọi sản phẩm và tự tin đi qua từng mục accordion: Details, Taxes and fees, Categories, Item variants, Stores & inventory, Promotions, Other settings.
- Giải thích được sự khác nhau giữa retail price và wholesale price và kênh nào áp dụng giá nào.
- Tạo được variants (Size, Color, …) từ master attribute và hiểu ma trận variant mà hệ thống tự sinh ra.
Nếu chỉ có thời gian đọc một phần, nhảy thẳng đến Mục VI — Trang chỉnh sửa đầy đủ. Đó là trọng tâm của Bước 1; mọi phần khác hoặc là đường vào trang đó, hoặc là khu vực con hỗ trợ nó.
II. Ba cách thêm mặt hàng
Phần tiêu đề “II. Ba cách thêm mặt hàng”| Cách | Khi nào dùng | Thời gian | Lưu vào catalog? |
|---|---|---|---|
| Thêm nhanh từ POS | Bạn đang test quầy — chỉ một hoặc hai mặt hàng. | 30 giây | Không (chỉ sống trên đơn) |
| Modal Add item details (kiểu Clover, trên trang Items list) | Bạn bán ~30 SKU và muốn kiểm soát từng cái. | 1–2 phút | Có (tạo dòng catalog) |
| Nhập Excel | Bạn đã có danh mục hiện tại hoặc file master. | 5–15 phút | Có (hàng loạt) |
Đọc thêm: Hàng hóa & danh mục — tài liệu tham chiếu chính cho toàn bộ khu vực Mặt hàng.
Phần còn lại của bài viết tập trung vào modal Add item details + trang chỉnh sửa đầy đủ đi sau nó — vì đó là đường đi cho bạn mọi khả năng của catalog. Hai đường còn lại là shortcut tiện lợi cho cao thể tích hoặc chỉ counter test.
III. Danh sách mặt hàng — trang chủ catalog
Phần tiêu đề “III. Danh sách mặt hàng — trang chủ catalog”Trước khi tạo bất cứ gì, xác định trang chủ catalog. Từ thanh bên trái mở Mặt hàng → Danh sách mặt hàng — route là VENDOR_ROUTES.items.list (/dashboard/items/list).
Danh sách mặt hàng — bảng điều khiển chính cho toàn bộ catalog. Nút ”+ Add item” nằm ở góc trên bên phải của header trang (nút primary màu xanh, biểu tượng +), ngay cạnh menu overflow (Export / Import / Print). Hàng filter phía trên bảng giữ search, Columns, Filters và trigger + Add item. Lọc trạng thái (Active / Draft / Archived / In stock / Low stock / Out of stock) nằm trong drawer Filters.
Bạn có thể làm gì từ trang này mà không cần vào form edit:
- Lọc theo trạng thái, danh mục, brand, trạng thái tồn kho, khoảng giá (drawer).
- Tìm kiếm theo tên, SKU, mã vạch (có debounce).
- Chọn nhiều dòng (tối đa 200 mỗi batch — xem Quy tắc phân trang dashboard) và xoá hàng loạt hoặc đổi trạng thái hàng loạt từ thanh toolbar chọn.
- Export Excel (overflow menu) — tải XLSX có gắn ngày của tập filter hiện tại.
- Import Excel (overflow menu) — xem Mục XV.
- Mở trang edit bằng cách nhấn vào một dòng (detail) rồi nút Edit, hoặc trực tiếp từ overflow menu của dòng.
IV. Thêm nhanh từ POS (test quầy)
Phần tiêu đề “IV. Thêm nhanh từ POS (test quầy)”Đây là luồng chỉ tại quầy — sản phẩm được tạo ra tại chỗ cho một đơn duy nhất và không được lưu vào catalog chính trừ khi bạn nhấn Lưu vào danh mục ở cuối.
Trong POS Workspace (route VENDOR_ROUTES.pointOfSale.sale, /point-of-sale/sale), nhấn Thêm nhanh (hoặc + cạnh giỏ hàng) → điền:
| Trường | Bắt buộc? | Ví dụ |
|---|---|---|
| Tên | Có | “Sản phẩm test” |
| Giá | Có | 10000 |
| Đơn vị | Có | cái / kg / gói |
| (tuỳ chọn) Danh mục | Không | chưa phân loại |
| (tuỳ chọn) Mã vạch | Không | SKU-001 |
Mặt hàng chỉ tồn tại trong đơn bán này. Nó không được thêm vào danh mục trừ khi bạn nhấn Lưu vào danh mục ở cuối đơn.
Dùng khi bạn đang stress-test quầy trước khi master catalog thật sẵn sàng. Đây cũng là cách nhanh để test xem một mã vạch lạ sẽ chạy ra sao khi quét tại quầy.
V. Modal Add item details — tạo nhanh kiểu Clover
Phần tiêu đề “V. Modal Add item details — tạo nhanh kiểu Clover”Nhấn + Add item ở góc trên bên phải của header trang Items list. Hệ thống mở modal “Add item details” (AddItemDetailsModal) — dialog full-screen kiểu Clover. Nó thu thập tối thiểu dữ liệu cần để tạo sản phẩm, sau đó điều hướng bạn thẳng vào trang chỉnh sửa đầy đủ để tiếp tục điền.
Modal “Add item details” — các trường bắt buộc là Name và Price. Tick “Add items with variants” để seed tối đa N trục biến thể từ master attribute. Nhấn “Next” để tạo sản phẩm và mở trang chỉnh sửa đầy đủ.
V.1 Modal hỏi gì
Phần tiêu đề “V.1 Modal hỏi gì”| Trường | Kiểu | Bắt buộc | Ghi chú |
|---|---|---|---|
| Name | text | Có | 1–127 ký tự. Đây là tên hiển thị tại quầy và trên menu online. |
| Price | number | Có | Số thập phân dương. Map vào cột price retail. Wholesale price không set ở đây — set sau trong trang edit. |
| Add items with variants | checkbox | Không | Khi tick, modal hiện bộ chọn theo hàng (master attribute + tag-style values editor). |
| (mỗi hàng) Select attribute | dropdown | Bắt buộc khi bật variants | Liệt kê tất cả master attribute đã định nghĩa trong Settings → Attributes. Attribute đã chọn ở hàng khác sẽ bị lọc ra để mỗi hàng ứng với một trục duy nhất. |
| (mỗi hàng) Option values | tag input | Bắt buộc khi bật variants | Điền sẵn từ preset values của attribute (Size → Small / Medium / Large). Nhấn Enter để thêm tag tuỳ dịch. |
Quan trọng: modal không cho phép set unit, category, taxes, supplier, hay stock ở bước này. Bản đầu tiên cố tình tối giản — đưa bạn vào trang chỉnh sửa đầy đủ nhanh nhất có thể. Mọi thứ còn lại (taxes, ma trận variants, stores, …) điền ở trang edit sau khi dòng đã tồn tại.
V.2 Thêm biến thể ngay từ modal
Phần tiêu đề “V.2 Thêm biến thể ngay từ modal”Bật “Add items with variants” sẽ tự seed hàng đầu tiên với master attribute đầu và preset values của nó. Để thêm trục thứ hai (vd Size + Color):
- Nhấn + Add another — hàng mới hiện ra với select attribute rỗng.
- Chọn attribute thứ hai. Dropdown loại trừ mọi attribute đã dùng ở hàng khác.
- Hoặc nhận preset values, hoặc tự gõ giá trị mới trong tag input (nhấn Enter để chốt mỗi tag).
Ma trận chưa được materialise trên màn hình này — modal chỉ ghi nhận các trục (attributes + choice_options). Các dòng variant đầy đủ (một dòng cho mỗi tổ hợp) được trang edit sinh ra khi bạn lưu — xem Mục X.6.
Nếu bạn chưa có master attribute nào, modal hiển thị empty state “No attributes yet” với nút Create attribute mở Attribute modal ngay trong luồng.
V.3 Sau khi nhấn Next
Phần tiêu đề “V.3 Sau khi nhấn Next”Nhấn Next sẽ chạy createVendorProduct({ name, price, attributes, choice_options }):
- Sản phẩm được lưu với
status = 1(active) vàtype = RETAILmặc định. - Cache danh sách được invalidate để dòng mới hiện ra.
- Router điều hướng đến
vendorCatalogItemEditPath(created.id)— tức là/dashboard/items/:id/edit— trang chỉnh sửa đầy đủ. - Toast xác nhận tạo thành công.
Từ đây bạn đang ở trang chỉnh sửa đầy đủ, và các mục tiếp theo sẽ dẫn bạn qua từng accordion.
VI. Trang chỉnh sửa đầy đủ (trọng tâm Bước 1)
Phần tiêu đề “VI. Trang chỉnh sửa đầy đủ (trọng tâm Bước 1)”Trang chỉnh sửa đầy đủ được render bởi apps/vendor-web/src/app/dashboard/items/product-form-page.tsx và mount tại /dashboard/items/:id/edit (helper: vendorCatalogItemEditPath(id)).
Trang chỉnh sửa đầy đủ — workhorse của catalog. Nó là một form dài chia thành các mục accordion. Mặc định mọi mục đều mở, nên toàn bộ hệ thống card cuộn vào tầm nhìn. Footer sticky giữ Cancel và Save changes; header trang giữ nút back, breadcrumb, Duplicate và overflow menu với Delete.
VI.1 Cấu trúc trang
Phần tiêu đề “VI.1 Cấu trúc trang”Từ trên xuống dưới:
- Header trang — nút back về items list, tên sản phẩm (live bind theo form), overflow menu với Duplicate và Delete. Delete mở
DeleteProductModal; Duplicate mởDuplicateProductModalclone dòng với hậu tố" (copy)"và mint lại SKU/barcode nếu có. - Banner trạng thái (chỉ khi sản phẩm đã archived) — callout vàng báo mặt hàng đang ẩn khỏi quầy và menu online. Bạn có thể khôi phục từ toggle Status trong mục Details.
- Các mục accordion (theo thứ tự):
- Details — mở mặc định
- Taxes and fees — mở mặc định
- Categories — mở mặc định
- Item variants — mở mặc định
- Stores & inventory — mở mặc định
- Promotions & discounts — mở mặc định
- Other settings — mở mặc định (chứa conversion units + suppliers)
- Footer sticky — Cancel (về trang detail hoặc list) và Save changes (
Buttonvớitype="submit"). Footer cóborder-ttrên body nên luôn nằm dưới accordion cuối.
Mỗi mục được render bởi một component riêng dưới apps/vendor-web/src/components/dashboard/products/, tất cả wire vào cùng context React Hook Form. Có nghĩa là một trường bạn đổi ở mục này sẽ hiển thị ngay ở mục khác (vd: variant mới trong Item variants được phản ánh trong ma trận Stores & inventory ngay khi bạn cuộn xuống).
VI.2 Thanh lưu & banner trạng thái
Phần tiêu đề “VI.2 Thanh lưu & banner trạng thái”- Nút Save là nơi duy nhất thực sự gọi API (
updateVendorProduct). Mọi accordion chỉ là sửa local cho đến khi bạn nhấn save. - Cancel quay về trang detail nếu sản phẩm đã tồn tại (
vendorCatalogItemDetailPath(id)) hoặc về items list nếu bạn mở trang từ “create” mà chưa lưu. - Tóm tắt lỗi validation hiện phía trên nút save khi có ít nhất một kiểm tra Zod thất bại — các trường vi phạm cũng highlight đỏ.
- Trang được wire vào
useForm+zodResolverchung (product-form.schema.ts); các trường bắt buộc được đánh dấu đỏ*trong label.
VII. Mục: Details
Phần tiêu đề “VII. Mục: Details”Accordion đầu tiên. Nó mirror thứ tự trường của Clover item.html và được triển khai bởi ProductFormDetailsSection.
Accordion Details — Pin on POS, Name, Alternative name, Price, Price type, Wholesale, Cost, Unit, rồi khối Online ordering (ảnh + mô tả).
VII.1 Khối On device — Pin on POS
Phần tiêu đề “VII.1 Khối On device — Pin on POS”| Trường | Tác dụng |
|---|---|
Toggle Pin on POS (is_pos_pinned) | Khi bật, sản phẩm được coi là SKU “nổi bật” và được ưu tiên trên hàng chọn nhanh của thu ngân trong POS workspace. Khi tắt, sản phẩm vẫn hiện trong catalog nhưng không nằm trên hàng nổi bật. |
“Show on POS” đã được đổi tên thành Pin on POS trong refactor gần đây — chúng map cùng cột backend
is_pos_pinned.
VII.2 Name, Alternative name
Phần tiêu đề “VII.2 Name, Alternative name”| Trường | Bắt buộc | Ghi chú |
|---|---|---|
| Name | Có | Tên hiển thị trên hoá đơn, lưới POS và menu online. Tối đa 127 ký tự. |
| Alternative name | Không | Tên dự phòng dùng trong một số hoá đơn và báo cáo khi tên chính bị thiếu hoặc quá dài. Không ảnh hưởng tìm kiếm hay lọc. |
VII.3 Price & Price type — Fixed / Variable / Per unit
Phần tiêu đề “VII.3 Price & Price type — Fixed / Variable / Per unit”| Trường | Bắt buộc | Ghi chú |
|---|---|---|
Price (price) | Có | Giá bán lẻ theo tiền tệ của cửa hàng. Hiển thị dùng ký hiệu tiền tệ từ VENDOR_CATALOG_CURRENCY_CODE. |
Price type (price_type) | Không | Một trong Fixed (mặc định), Variable, Per unit. |
Hành vi theo từng price type:
- Fixed — thu ngân thấy một mức giá; nhấn dòng trên lưới POS sẽ thêm sản phẩm ở giá đó. Dùng cho >95% sản phẩm.
- Variable — thu ngân được nhắc nhập giá tại quầy (tiền tip, dịch vụng giá tuỳ ý). Trường Price trở thành giá gợi ý.
- Per unit — thu ngân được nhắc nhập số lượng tại quầy (trái cây cân, vải cắt theo mét). Trường Unit trở thành bắt buộc ở mức form — xem Mục VII.6.
VII.4 Wholesale price
Phần tiêu đề “VII.4 Wholesale price”Một trường riêng nằm cạnh Price. Wholesale price chỉ được áp dụng khi sản phẩm được bán qua kênh Wholesale — tức là orders.type = WHOLESALE. Ở kênh Retail (POS mặc định và menu online), price thường được dùng.
Sản phẩm có thể được bán ở cả hai kênh mà không cần nhân đôi dòng. Trường “Catalog types” của form điều khiển điều này — xem Mục XIV.
Nếu bạn chỉ bán lẻ, để Wholesale trống. Nếu bán sỉ và muốn danh sách giá khác nhau theo kênh, hệ thống cũng hỗ trợ Price lists per-channel tại /dashboard/items/price-lists (nâng cao — ngoài phạm vi Bước 1).
VII.5 Cost & Non-revenue item
Phần tiêu đề “VII.5 Cost & Non-revenue item”| Trường | Bắt buộc | Ghi chú |
|---|---|---|
Cost (cost) | Không | Số liệu giá vốn hàng bán (COGS) dùng cho biên lợi nhuận gộp trong báo cáo tài chính. |
| Toggle Non-revenue item | Không | Khi bật, sản phẩm bị loại khỏi tổng doanh thu trong báo cáo (vd: sample tặng kèm, tiền đặt cọc vỏ chai). |
RBAC: trường cost được gate bởi
vendorCanViewProductCost. Nếu role của bạn không có quyền xem cost, cả khối Cost bị ẩn, và mục Taxes and fees hiển thị gợi ý “Tax fields respect your cost-view permission”.
VII.6 Unit
Phần tiêu đề “VII.6 Unit”| Trường | Bắt buộc | Ghi chú |
|---|---|---|
Unit (item_unit_id) | Bắt buộc khi price_type = Per unit, các trường hợp khác tuỳ chọn | Map vào bảng master units. Dùng cho kg, m, giờ, gói, … Select hiển thị các unit đã định nghĩa dưới VENDOR_ROUTES.items.units. |
Nút + nhỏ cạnh trường unit mở Unit modal để bạn định nghĩa unit mới mà không cần rời form.
VII.7 Khối Online ordering — ảnh & mô tả
Phần tiêu đề “VII.7 Khối Online ordering — ảnh & mô tả”Nửa dưới của accordion Details là khối Online ordering, mirror layout Clover.
| Trường | Bắt buộc | Ghi chú |
|---|---|---|
| Item image | Không | Ảnh bìa. Chọn từ vendor media gallery (VendorGalleryImagePickerModal); hỗ trợ một ảnh bìa và album nhiều ảnh. |
| Description | Không | Rich-text editor (ProductFormRichTextEditor) — hỗ trợ định dạng, liên kết, danh sách. Được sanitize qua sanitizeRichHtml trước khi lưu. Hiển thị trên menu online và (tuỳ cấu hình) trên hoá đơn. |
Đã bỏ theo refactor gần đây (để guide khớp với UI hiện tại): Item color, Age-restricted và Online name. Các cột này có thể vẫn còn trong database để tương thích ngược, nhưng không còn render.
VIII. Mục: Taxes and fees
Phần tiêu đề “VIII. Mục: Taxes and fees”Triển khai bởi ProductFormTaxesAndFeesSection. Một accordion, ba khái niệm.
Accordion Taxes and fees. Empty state bên trái; sản phẩm đã cấu hình bên phải với custom inline tax và link tới cài đặt store tax. Toggle “Tax exempt” là hàng đầu.
VIII.1 Cách tính thuế tại quầy
Phần tiêu đề “VIII.1 Cách tính thuế tại quầy”Mô hình dữ liệu hỗ trợ hai đầu vào thuế độc lập trên mỗi sản phẩm:
- Một store tax profile (
store_tax_id) — quy tắc thuế tái sử dụng được định nghĩa dưới Settings → Taxes and fees. Store tax biết rate và type của nó. Xem Mục XVI để biết nơi quản lý. - Một inline custom tax (
tax_type+tax_value) — override per-item dùng khi sản phẩm cần rate đặc biệt không thuộc bảng store tax toàn cục.
Tại quầy, engine resolve thuế hiệu lực cho sản phẩm như sau:
- Nếu
is_tax_exempt = true→ không áp thuế (và mục render dòng “Tax exempt — no tax applied”). - Ngược lại nếu
store_tax_idđã set → dùng rate của profile đó. - Ngược lại nếu
tax_value > 0→ dùngtax_valueinline (vớitax_type=percenthoặcamount). - Ngược lại → không áp thuế.
Accordion luôn hiển thị cái thực sự sẽ được áp dụng. Đó là lý do body chỉ là một bảng nhỏ — không phải panel cài đặt.
VIII.2 Gán store tax profile
Phần tiêu đề “VIII.2 Gán store tax profile”Nhấn Assign taxes and fees để mở AssignTaxesAndFeesDrawer:
- Drawer liệt kê mọi store tax đã định nghĩa dưới
VENDOR_ROUTES.items.taxes(/dashboard/items/taxes). - Chọn một profile sẽ ghi
store_tax_idvào form; các trường custom tax inline không bị xoá, nhưng dòng được resolve là profile. - Link “Go to Settings” trong drawer nhảy đến store tax manager để bạn tạo profile mới nếu cái cần chưa có.
VIII.3 Custom inline tax
Phần tiêu đề “VIII.3 Custom inline tax”Dùng khi cần một mức thuế một-lần cho một sản phẩm đơn lẻ. Drawer hiển thị:
- Tax type —
Percent(vd 5 = 5%) hoặcFixed amount(vd 1000 = 1000₫ phẳng). - Tax value — con số. Validation bác bỏ số âm.
- Add — ghi inline tax vào form; bảng giờ hiển thị dòng custom.
Anti-pattern: dùng store tax profile cho bất cứ thứ gì ảnh hưởng tới nhiều hơn một sản phẩm. Trường inline cố tình giới hạn một tax cho mỗi sản phẩm — không có chỗ để “xếp chồng” các inline tax.
VIII.4 Tax exempt
Phần tiêu đề “VIII.4 Tax exempt”Một toggle Tax exempt đơn lẻ trong body. Khi bật:
- Dòng tax resolve trong bảng bị thay bằng dòng “Tax exempt”.
- Store tax và inline custom tax không bị xoá (chúng sẽ áp dụng lại nếu bạn tắt exempt).
IX. Mục: Categories
Phần tiêu đề “IX. Mục: Categories”Triển khai bởi ProductFormCategoriesSection. Mirror cùng pattern với các accordion gán khác (Taxes, Modifier groups, Stores).
Accordion Categories. Mỗi category đã gán có số thứ tự (#1 là primary category).
IX.1 Tại sao categories quan trọng
Phần tiêu đề “IX.1 Tại sao categories quan trọng”Categories được dùng cho ba việc trong phần còn lại của hệ thống:
- POS filter — thu ngân có thể thu hẹp lưới sản phẩm theo category.
- Nhóm menu online — trang menu nhóm sản phẩm theo primary category.
- Báo cáo bán hàng — doanh thu và đơn vị bán được cuộn theo category trong Sales Report.
IX.2 Quy tắc primary category
Phần tiêu đề “IX.2 Quy tắc primary category”Category đầu tiên trong danh sách là primary category. Đó là category hiển thị trong POS filter, dùng để nhóm menu online và báo cáo về trong breakdown theo category của Sales Report.
Danh sách giữ nguyên thứ tự bạn chọn. Để sắp xếp lại, hãy gỡ rồi chọn lại theo thứ tự mong muốn — hoặc dùng Edit trong thanh toolbar chọn.
Categories được quản lý dưới VENDOR_ROUTES.items.categories (/dashboard/items/categories).
X. Mục: Item variants — đi sâu
Phần tiêu đề “X. Mục: Item variants — đi sâu”Đây là accordion quan trọng nhất cho bất kỳ cửa hàng nào bán “cùng một sản phẩm ở các size / màu / dung tích khác nhau” — vd áo thun size S/M/L/XL, đồ uống 330 ml / 500 ml / 1 L. Triển khai bởi ProductFormItemVariantsSection và là mục phức tạp nhất trong form.
Accordion Item variants — phần Attributes (trên) và phần Variants (dưới). Empty state hiển thị là sản phẩm mới tạo chưa có attribute.
X.1 Attributes vs Variants — mô hình tư duy
Phần tiêu đề “X.1 Attributes vs Variants — mô hình tư duy”Đây là khái niệm hay bắt người mới nhất, nên cần sơ đồ rõ ràng.
Master attribute → "Size" (id: ATTR_SIZE) Preset values → ["Small", "Medium", "Large"]
Choice options on item → choice_options: [ { id: ATTR_SIZE, name: "Size", values: ["Small", "Medium", "Large"] } ]
Generated variant matrix → 3 dòng: - Small - Medium - LargeNếu bạn thêm một attribute thứ hai — vd Color với ["Red", "Blue"] — ma trận trở thành tích Descartes 3 × 2 = 6 variants (Small/Red, Small/Blue, Medium/Red, …). Mỗi tổ hợp là một dòng bán được riêng với SKU, mã vạch, giá và tồn kho riêng.
X.2 Phần Attributes
Phần tiêu đề “X.2 Phần Attributes”Nửa trên của accordion. Liệt kê các attribute hiện đang gắn với sản phẩm này.
| Cột | Ý nghĩa |
|---|---|
| Attribute | Tên dễ đọc (vd “Size”). Fallback về id nếu lookup chưa hydrate. |
| Options | Danh sách giá trị phân tách bằng dấu phẩy đang active cho item này. Nhấn Edit trong dòng để mở option editor nội tuyến (thêm/gỡ giá trị; thay đổi feed vào choice_options). |
Hành động thanh toolbar:
- + Add attribute (góc trên phải, xanh) — mở
AssignAttributesDrawer. Drawer liệt kê mọi master attribute đã định nghĩa dướiVENDOR_ROUTES.items.attributesvà cho phép chọn nhiều. - Edit (toolbar chọn) — mở lại drawer ở chế độ “edit” để bạn thêm/gỡ attribute.
- Remove (toolbar chọn) — gỡ các attribute đã chọn. Gỡ một attribute sẽ sinh lại ma trận variant — xem Mục X.6.
X.3 Phần Variants
Phần tiêu đề “X.3 Phần Variants”Nửa dưới của accordion. Liệt kê các dòng variant đã materialise (một dòng cho mỗi tổ hợp giá trị của các attribute active).
| Cột | Editable in cell? | Ghi chú |
|---|---|---|
| Name | Có (drawer) | Tên hiển thị; mặc định là tổ hợp giá trị option (vd “Red / Large”). |
| Barcode | Có (drawer) | Mã vạch quét được. Để trống để tự sinh EAN-13 khi lưu. |
| Cost | Có (drawer) | COGS theo variant. Fallback về cost cấp sản phẩm nếu trống. |
| Price | Có (drawer) | Giá bán lẻ theo variant. Fallback về price cấp sản phẩm nếu trống. |
| Wholesale | Có (drawer) | Wholesale price theo variant. Fallback về wholesale price cấp sản phẩm nếu trống. |
| In stock | Có (drawer) | Số lượng on-hand của variant này tại default warehouse. Xem Mục XI.3 để biết nó tương tác với ma trận per-warehouse thế nào. |
Search (“Search items”) và phân trang (25 / 50 / 100 mỗi trang) nằm phía trên bảng.
X.4 Edit variant drawer — từng ô nghĩa là gì
Phần tiêu đề “X.4 Edit variant drawer — từng ô nghĩa là gì”Nhấn vào bất kỳ dòng variant nào (hoặc chọn dòng + Edit) để mở EditVariantDrawer. Drawer phơi bày mọi trường per-variant ghi đè giá trị mặc định cấp sản phẩm:
| Trường | Map vào | Ghi chú |
|---|---|---|
| Variant name | variation.name | Mặc định là tổ hợp option. Editable. |
| SKU | variation.sku | Stock-keeping unit. Dùng cho tồn kho và catalog nhà cung cấp. |
| PLU | variation.plu | Price-look-up code (một số cân và POS peripheral dùng). |
| Barcode | variation.barcode | Quét được. Dùng Random để tự sinh EAN-13 (pass Luhn checksum). |
| Cost | variation.cost | COGS theo variant. |
| Price | variation.price | Giá bán lẻ theo variant. |
| Wholesale | variation.wholesale_price | Wholesale price theo variant. |
| Stock | variation.stock | Quick-edit chỉ cho default warehouse. |
| Low stock threshold | variation.low_stock_threshold | Ghi đè theo variant. Fallback về giá trị cấp sản phẩm. |
| Maximum cart quantity | variation.max_cart_qty | Giới hạn theo variant. Fallback về giá trị cấp sản phẩm. |
| Available | variation.is_available | Khi tắt, variant bị ẩn khỏi POS và online mà không cần disable sản phẩm cha. |
Nhấn Save trong drawer để ghi các override per-variant. Dòng variant trong accordion phản ánh giá trị mới ngay lập tức.
X.5 Xoá variants
Phần tiêu đề “X.5 Xoá variants”- Theo dòng — biểu tượng thùng rác trong dòng mở
DeleteVariantConfirmModal. - Hàng loạt — chọn nhiều dòng + Delete trong thanh toolbar chọn. Modal batch hỏi “Delete {{count}} variants?”.
Xoá variant là phá huỷ — nó gỡ SKU, barcode, tồn per warehouse và mọi lịch sử của variant đó. Dùng toggle Available = off trong drawer cho “soft delete” nếu bạn có thể cần lại variant sau.
X.6 Ma trận variant sinh ra như thế nào
Phần tiêu đề “X.6 Ma trận variant sinh ra như thế nào”Ma trận được derive từ attributes + choice_options trên sản phẩm. Việc sinh ma trận xảy ra ở ba thời điểm:
- Khi lưu (phía server) — backend mở rộng tích chéo giá trị của mọi attribute active thành
variations. Các dòng variant đã lưu trước đó nhưng không còn xuất hiện trong ma trận (vì một attribute bị gỡ hoặc một giá trị bị xoá) sẽ bị xoá; các tổ hợp mới được chèn với các trường per-variant để null (vì vậy chúng thừa kế từ sản phẩm). - Khi thêm/gỡ attribute trong editor — khi bạn nhấn Add attribute hoặc Remove trong toolbar attribute, form gọi một helper tính lại
variationscục bộ để bảng Variants cập nhật trước khi bạn lưu. Đây là trải nghiệm “live preview”. - Khi đổi option của attribute — option editor nội tuyến trong phần Attributes cập nhật
choice_optionsmà không chạy lại ma trận ngay (để tránh re-render sâu đắt). Ma trận được dựng lại khi lưu hoặc khi người dùng nhấn Build variants nếu nó được expose.
Helper dùng chung thực hiện phép mở rộng Descartes là buildCatalogVariantBlueprints + buildUnitAxisSegments trong packages/shared. Mỗi dòng variant sinh ra cũng được cấp:
- Một stable key derive từ tổ hợp option (
normalizeCatalogVariantKeyForForm) để React render ổn định qua reorder. - Một PLU nếu sản phẩm dùng custom EAN-13 (luồng scan per-unit) — xem
pos-per-unit-barcode-scan.util.ts.
X.7 Variant bulk price rules (Power-user)
Phần tiêu đề “X.7 Variant bulk price rules (Power-user)”Cho cửa hàng cần áp dụng cùng một phần trăm giảm giá cho mọi variant cùng lúc (vd giảm giá mùa), mục Other settings phơi bày công cụ bulk-price (applyBulkPricesToCatalogVariations). Nó nhận một patch và ghi cho mọi variant trong một lần lưu. Xem Mục XIII để biết điểm vào.
XI. Mục: Stores & inventory
Phần tiêu đề “XI. Mục: Stores & inventory”Triển khai bởi ProductFormStoresAndInventorySection. Accordion chia hai nửa: gán store ở trên, ma trận tồn kho per-warehouse ở dưới.
Accordion Stores & inventory. Nửa trên liệt kê các store sản phẩm được gán vào; nửa dưới là ma trận tồn kho per-warehouse cho tab store hiện đang chọn.
XI.1 Gán store
Phần tiêu đề “XI.1 Gán store”| Hành động | Tác dụng |
|---|---|
| + Assign stores | Mở drawer liệt kê mọi store trong tenant. Chọn nhiều store mà sản phẩm này nên tồn tại. |
| Edit (toolbar chọn) | Mở lại drawer để bạn thêm/gỡ store. |
| Remove (toolbar chọn) | Gỡ store đã chọn. Tồn kho per-warehouse hiện có của store bị gỡ được giữ nguyên (không xoá) để gán lại sau giữ nguyên lịch sử. |
Nếu sản phẩm được tạo qua modal Add item details, nó được tự động gán vào store active đầu tiên trong tenant. Bạn có thể override ở đây.
XI.2 Ma trận tồn kho theo warehouse
Phần tiêu đề “XI.2 Ma trận tồn kho theo warehouse”Nửa dưới của accordion là strip tab store + bảng tồn per-warehouse:
- Tab store — một tab cho mỗi store được gán vào sản phẩm. Nhấn một tab để chuyển ma trận warehouse bên dưới.
- Dòng warehouse — một dòng cho mỗi warehouse liên kết với store đang chọn. Mỗi dòng có:
- Tên warehouse (read-only)
- Stock qty (editable) — số đơn vị on-hand tại warehouse đó
- Low stock threshold (editable) — ngưỡng cảnh báo tồn kho thấp
- Total — tổng tồn kho của mọi warehouse cho store hiện tại, hiển thị ở footer bảng
RBAC: dòng
Totaltôn trọng quyền xem cost giống như trường Cost trong Details. User không có cost-view thấy cùng số nhưng ở summary gọn hơn.
XI.3 Stock theo sản phẩm vs theo variant
Phần tiêu đề “XI.3 Stock theo sản phẩm vs theo variant”Có hai chế độ tồn kho, và form chọn một dựa trên việc sản phẩm có variant hay không:
- Product-level (không có variant) — mỗi dòng warehouse map trực tiếp vào sản phẩm. Accordion hiển thị một cell stock qty editable mỗi warehouse.
- Variant-level (có variant) — mỗi dòng warehouse được tách theo variant. Accordion hiển thị ma trận với một cột cho mỗi variant và một dòng cho mỗi warehouse; mỗi cell là on-hand của variant đó tại warehouse đó. Header ma trận liệt kê tên mọi variant; footer dòng tính tổng theo warehouse.
Nếu bạn chuyển sản phẩm hiện có từ “không có variant” sang “có variant”, tồn kho per-warehouse hiện tại được di trú vào default variant (variant đầu tiên trong ma trận) — hãy review và phân phối lại bằng tay nếu cần.
XII. Mục: Promotions & discounts
Phần tiêu đề “XII. Mục: Promotions & discounts”Triển khai bởi ProductFormPromotionsSection. Accordion phơi bày ba loại promotion độc lập — tất cả tuỳ chọn, tất cả cộng dồn.
| Loại promotion | Tác dụng | Empty state |
|---|---|---|
| Line discount | Một % hoặc số tiền cố định giảm trên đơn giá tại quầy (vd luôn giảm 10%, hoặc giảm 5 000₫ vào thứ Sáu). | “No line discount configured” |
| Maximum cart quantity | Cap cứng số đơn vị một giỏ có thể chứa. Hữu ích cho rule “tối đa 2 mỗi khách”. | “No purchase limit” |
| Quantity & bundle promotions | Mua 2 tặng 1; 3 giá 2; mua 2 X, giảm 50% Y; v.v. Có thể chồng nhiều promotion. | “No quantity promotions configured” |
Nhấn Edit promotions để mở drawer Promotions. Drawer là form có hướng dẫn — một tab cho mỗi loại promotion — và số đang active hiển thị trong badge xanh ở header accordion.
Promotion cấu hình ở đây là per-product. Promotion toàn cửa hàng (vd “giảm 10% toàn bộ tháng Tám”) được quản lý dưới khu vực Marketing — xem
/dashboard/discounts.
XIII. Mục: Other settings — conversion units & suppliers
Phần tiêu đề “XIII. Mục: Other settings — conversion units & suppliers”Triển khai bởi ProductFormOtherSubsectionHeading + sub-component conversion units và suppliers. Đây là mục chứa các khái niệm không vừa layout tham chiếu Clover.
Accordion Other settings. Trên: conversion units (pack → each). Dưới: suppliers.
XIII.1 Conversion units (pack → each)
Phần tiêu đề “XIII.1 Conversion units (pack → each)”Nhiều sản phẩm được bán ở nhiều đơn vị cùng lúc. Bao gạo có thể bán theo bao cho khách lẻ và theo kg cho khách sỉ. Bảng conversion unit cho phép bạn khai báo các quan hệ đó.
| Cột | Ý nghĩa |
|---|---|
| Unit | Đơn vị thứ cấp (vd “Bag”). |
| Units per pack | Bao nhiêu đơn vị cơ sở trong một pack (vd 1 bao = 50 kg). |
| POS default | Có phải unit mặc định trong Wholesale workspace (và default thứ cấp trong Retail) hay không. |
| Import default | Có phải unit mặc định để nhập tồn từ purchase lot hay không. |
| Unpack | Có cho phép thu ngân bẻ một pack tại quầy (bán 1 kg từ bao 50 kg) hay không. |
Để thêm dòng conversion, nhấn + Add unit cạnh heading mục và chọn một unit từ danh sách. Form điền sẵn units per pack = 1; điều chỉnh nếu cần. Các dòng conversion được lưu dưới dạng variations bổ sung trên sản phẩm với flag đặc biệt kind = conversion — chúng tham gia đếm tồn nhưng không hiện trong accordion Item variants thường.
Anti-pattern: không dùng conversion units để đại diện variant thật (Size / Color). Hai hệ thống độc lập — conversion units về số lượng, variants về bản sắc.
XIII.2 Suppliers & mã catalog nhà cung cấp
Phần tiêu đề “XIII.2 Suppliers & mã catalog nhà cung cấp”Nửa dưới của accordion Other settings. Mỗi dòng liên kết một nhà cung cấp với sản phẩm này và ghi lại supplier catalog number — SKU mà nhà cung cấp dùng nội bộ. Điều này thiết yếu cho:
- Đặt hàng lại (purchase order dùng supplier catalog number).
- Đối chiếu hoá đơn từ nhà cung cấp về catalog của bạn.
- Theo dõi cost-per-supplier khi cùng một sản phẩm có nhiều nhà cung cấp.
Nhấn + Add supplier để chọn từ danh sách nhà cung cấp. Drawer hiển thị tên mỗi nhà cung cấp và (nếu có) thông tin liên hệ.
Suppliers được quản lý dưới
VENDOR_ROUTES.items.suppliers(/dashboard/items/suppliers). Để bật luồng Smart import (tự động tạo sản phẩm từ file catalog của nhà cung cấp), yêu cầu nhà cung cấp upload catalog của họ tại/dashboard/items/suppliers/smart-import.
XIV. Retail vs Wholesale — nhìn nhanh
Phần tiêu đề “XIV. Retail vs Wholesale — nhìn nhanh”Form sản phẩm phơi bày một picker type (Catalog type) duy nhất điều khiển sản phẩm được bán trên kênh nào:
| Catalog type | Hiển thị ở đâu | Giá dùng |
|---|---|---|
| RETAIL (mặc định) | POS workspace (/point-of-sale/sale), menu online | price (retail) |
| WHOLESALE | Wholesale POS workspace (/dashboard/orders/create-wholesale), B2B portal | wholesale_price |
Form cho phép bạn bật cả hai type trên cùng một sản phẩm — trong trường hợp đó cùng dòng sản phẩm hiện ở cả retail và wholesale workspace, và giá đúng được chọn tự động theo kênh.
Mặc định cho sản phẩm mới là
[RETAIL]chỉ (xemVENDOR_PRODUCT_DEFAULT_TYPEStrongpackages/shared). Nếu bạn bán sỉ, nhớ bật thêm catalog type WHOLESALE cho mỗi sản phẩm bạn muốn expose sang kênh đó — hệ thống không tự mirror.
XV. Nhập Excel (nhiều mặt hàng)
Phần tiêu đề “XV. Nhập Excel (nhiều mặt hàng)”Nếu bạn đã có danh sách mặt hàng (CSV/Excel), dùng template nhập:
- Mặt hàng → Danh sách mặt hàng → menu Import (góc trên bên phải) → tải Excel template.
- Điền các cột:
name | sku | barcode | category | unit | price | stock | status. - Upload file → xem trước các dòng → xác nhận.
Giới hạn: API chấp nhận tối đa 200 dòng mỗi lần upload. Chia file lớn thành từng khối 200 dòng. Vendor web enforce cùng cap trên thanh thao tác batch (xem Quy tắc phân trang dashboard).
Endpoint là POST /api/v1/vendor/products/import-excel — xem API catalog → “Vendor products”.
XVI. Các danh mục con trong khu vực Mặt hàng
Phần tiêu đề “XVI. Các danh mục con trong khu vực Mặt hàng”Nhóm Items trong thanh bên có nhiều hơn chỉ danh sách. Mỗi khu vực con là một trang kiểu Settings mà form sản phẩm link vào.
Categories — danh sách master các category bạn có thể gán cho sản phẩm.
Đơn vị tính (each, kg, pack…).
Nhà cung cấp — dùng khi nhập hàng ở Bước 2.
| Khu vực con | Route | Tác dụng |
|---|---|---|
| Categories | /dashboard/items/categories | Danh sách master các category (drag-reorder, primary flag). |
| Modifier groups | /dashboard/items/modifier-groups | Nhóm add-on / option (vd “Toppings” với Pepperoni / Mushroom). |
| Units | /dashboard/items/units | Danh sách master các unit. Picker Unit của form sản phẩm đọc từ đây. |
| Taxes and fees | /dashboard/items/taxes | Store tax profile tái sử dụng. Accordion Taxes and fees của form đọc từ đây. |
| Attributes | /dashboard/items/attributes | Danh sách master các attribute (Size, Color, …) dùng để sinh variants. |
| Tags | /dashboard/items/tags | Tag tuỳ do dùng để lọc và báo cáo. |
| Brands | /dashboard/items/brands | Danh sách brand. Dùng trong drawer filter Products. |
| Pricing rules | /dashboard/items/pricing-rules | Thay đổi giá hàng loạt theo quy tắc (vd giảm 10% toàn bộ một category). |
| Price lists | /dashboard/items/price-lists | Price list nhiều tier theo kênh. |
| Price approvals | /dashboard/items/price-approvals | Hàng đợi review cho thay đổi giá vượt ngưỡng phê duyệt. |
| Addons | /dashboard/items/addons | Catalog add-on POS. |
| Gallery | /dashboard/items/gallery | Vendor media gallery — chọn ảnh cho bìa và album sản phẩm. |
| Suppliers | /dashboard/items/suppliers | Master data nhà cung cấp (tên, liên hệ, điều khoản thanh toán, file catalog). |
| Suppliers smart import | /dashboard/items/suppliers/smart-import | Tạo hàng loạt sản phẩm từ file catalog của nhà cung cấp. |
XVII. Checklist trước khi chuyển bước
Phần tiêu đề “XVII. Checklist trước khi chuyển bước”- Ít nhất một mặt hàng hiển thị trong danh mục POS.
- Giá bán khớp với giá bạn đã nhập.
- Mã vạch (nếu có) quét đúng tại quầy.
- Tồn kho lớn hơn 0 (hoặc theo dõi tồn kho đang tắt).
- Mặt hàng đang ở trạng thái Active (không phải Draft).
- Bạn đã mở trang edit đầy đủ và nhận ra mọi mục accordion.
- (Nếu bán sỉ) catalog type Wholesale đã bật và Wholesale price đã được set.
- (Nếu bán variant) ít nhất một attribute đã gắn và ma trận variant đã sinh và review.
- (Nếu bán ở nhiều store) sản phẩm đã được gán vào mọi store liên quan, và tồn per-warehouse đã set.
XVIII. Bước tiếp theo
Phần tiêu đề “XVIII. Bước tiếp theo”→ Bước 2 — Nhập hàng (Lots) để đưa tồn kho thật lên kệ: bước 2.
XIX. Bài viết liên quan
Phần tiêu đề “XIX. Bài viết liên quan”- Hàng hóa & danh mục — tham chiếu đầy đủ cho Items, Categories, Units, Attributes, Tags, Suppliers.
- POS Workspace tour — cách mặt hàng hiển thị tại quầy.
- Wholesale pricing — hướng dẫn sâu hơn về price list retail vs wholesale.
- API catalog — Vendor products