Bỏ qua để đến nội dung

Bước 1 — Thêm mới mặt hàng

Cuối Bước 1 bạn sẽ:

  1. 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ị.
  2. 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.
  3. 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.
  4. 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ó.

CáchKhi nào dùngThời gianLưu vào catalog?
Thêm nhanh từ POSBạn đang test quầy — chỉ một hoặc hai mặt hàng.30 giâyKhô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útCó (tạo dòng catalog)
Nhập ExcelBạn đã có danh mục hiện tại hoặc file master.5–15 phútCó (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 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.

Đâ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ườngBắt buộc?Ví dụ
TênCó“Sản phẩm test”
GiáCó10000
Đơn vịCócái / kg / gói
(tuỳ chọn) Danh mụcKhôngchưa phân loại
(tuỳ chọn) Mã vạchKhôngSKU-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 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 đủ.

TrườngKiểuBắt buộcGhi chú
NametextCó1–127 ký tự. Đây là tên hiển thị tại quầy và trên menu online.
PricenumberCó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 variantscheckboxKhôngKhi tick, modal hiện bộ chọn theo hàng (master attribute + tag-style values editor).
(mỗi hàng) Select attributedropdownBắt buộc khi bật variantsLiệ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 valuestag inputBắ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.

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):

  1. Nhấn + Add another — hàng mới hiện ra với select attribute rỗng.
  2. Chọn attribute thứ hai. Dropdown loại trừ mọi attribute đã dùng ở hàng khác.
  3. 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.

Nhấn Next sẽ chạy createVendorProduct({ name, price, attributes, choice_options }):

  1. Sản phẩm được lưu với status = 1 (active) và type = RETAIL mặc định.
  2. Cache danh sách được invalidate để dòng mới hiện ra.
  3. Router điều hướng đến vendorCatalogItemEditPath(created.id) — tức là /dashboard/items/:id/edit — trang chỉnh sửa đầy đủ.
  4. 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 edit mặt hàng 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.

Từ trên xuống dưới:

  1. 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ở DuplicateProductModal clone dòng với hậu tố " (copy)" và mint lại SKU/barcode nếu có.
  2. 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.
  3. 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)
  4. Footer sticky — Cancel (về trang detail hoặc list) và Save changes (Button với type="submit"). Footer có border-t trê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).

  • 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 + zodResolver chung (product-form.schema.ts); các trường bắt buộc được đánh dấu đỏ * trong label.

Accordion đầu tiên. Nó mirror thứ tự trường của Clover item.html và được triển khai bởi ProductFormDetailsSection.

Details accordion Accordion Details — Pin on POS, Name, Alternative name, Price, Price type, Wholesale, Cost, Unit, rồi khối Online ordering (ảnh + mô tả).

TrườngTá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.

TrườngBắt buộcGhi chú
NameCóTên hiển thị trên hoá đơn, lưới POS và menu online. Tối đa 127 ký tự.
Alternative nameKhôngTê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ườngBắt buộcGhi 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ôngMộ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.

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).

TrườngBắt buộcGhi chú
Cost (cost)KhôngSố 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 itemKhôngKhi 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”.

TrườngBắt buộcGhi chú
Unit (item_unit_id)Bắt buộc khi price_type = Per unit, các trường hợp khác tuỳ chọnMap 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.

Nửa dưới của accordion Details là khối Online ordering, mirror layout Clover.

TrườngBắt buộcGhi chú
Item imageKhôngẢnh bìa. Chọn từ vendor media gallery (VendorGalleryImagePickerModal); hỗ trợ một ảnh bìa và album nhiều ảnh.
DescriptionKhôngRich-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.

Triển khai bởi ProductFormTaxesAndFeesSection. Một accordion, ba khái niệm.

Taxes and fees accordion 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.

Mô hình dữ liệu hỗ trợ hai đầu vào thuế độc lập trên mỗi sản phẩm:

  1. 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ý.
  2. 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ùng tax_value inline (với tax_type = percent hoặc amount).
  • 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.

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_id và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ó.

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ặc Fixed 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.

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).

Triển khai bởi ProductFormCategoriesSection. Mirror cùng pattern với các accordion gán khác (Taxes, Modifier groups, Stores).

Categories accordion Accordion Categories. Mỗi category đã gán có số thứ tự (#1 là primary category).

Categories được dùng cho ba việc trong phần còn lại của hệ thống:

  1. POS filter — thu ngân có thể thu hẹp lưới sản phẩm theo category.
  2. Nhóm menu online — trang menu nhóm sản phẩm theo primary category.
  3. Báo cáo bán hàng — doanh thu và đơn vị bán được cuộn theo category trong Sales Report.

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).

Đâ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.

Item variants accordion 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.

Đâ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
- Large

Nế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.

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
AttributeTên dễ đọc (vd “Size”). Fallback về id nếu lookup chưa hydrate.
OptionsDanh 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ưới VENDOR_ROUTES.items.attributes và 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.

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ộtEditable in cell?Ghi chú
NameCó (drawer)Tên hiển thị; mặc định là tổ hợp giá trị option (vd “Red / Large”).
BarcodeCó (drawer)Mã vạch quét được. Để trống để tự sinh EAN-13 khi lưu.
CostCó (drawer)COGS theo variant. Fallback về cost cấp sản phẩm nếu trống.
PriceCó (drawer)Giá bán lẻ theo variant. Fallback về price cấp sản phẩm nếu trống.
WholesaleCó (drawer)Wholesale price theo variant. Fallback về wholesale price cấp sản phẩm nếu trống.
In stockCó (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ườngMap vàoGhi chú
Variant namevariation.nameMặc định là tổ hợp option. Editable.
SKUvariation.skuStock-keeping unit. Dùng cho tồn kho và catalog nhà cung cấp.
PLUvariation.pluPrice-look-up code (một số cân và POS peripheral dùng).
Barcodevariation.barcodeQuét được. Dùng Random để tự sinh EAN-13 (pass Luhn checksum).
Costvariation.costCOGS theo variant.
Pricevariation.priceGiá bán lẻ theo variant.
Wholesalevariation.wholesale_priceWholesale price theo variant.
Stockvariation.stockQuick-edit chỉ cho default warehouse.
Low stock thresholdvariation.low_stock_thresholdGhi đè theo variant. Fallback về giá trị cấp sản phẩm.
Maximum cart quantityvariation.max_cart_qtyGiới hạn theo variant. Fallback về giá trị cấp sản phẩm.
Availablevariation.is_availableKhi 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.

  • 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.

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:

  1. 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).
  2. 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 variations cục bộ để bảng Variants cập nhật trước khi bạn lưu. Đây là trải nghiệm “live preview”.
  3. Khi đổi option của attribute — option editor nội tuyến trong phần Attributes cập nhật choice_options mà 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.

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.

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.

Stores & inventory accordion 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.

Hành độngTác dụng
+ Assign storesMở 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.

Nửa dưới của accordion là strip tab store + bảng tồn per-warehouse:

  1. 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.
  2. 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
  3. 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 Total tô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.

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.

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 promotionTác dụngEmpty state
Line discountMộ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 quantityCap 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 promotionsMua 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.

Other settings accordion Accordion Other settings. Trên: conversion units (pack → each). Dưới: suppliers.

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 packBao nhiêu đơn vị cơ sở trong một pack (vd 1 bao = 50 kg).
POS defaultCó phải unit mặc định trong Wholesale workspace (và default thứ cấp trong Retail) hay không.
Import defaultCó phải unit mặc định để nhập tồn từ purchase lot hay không.
UnpackCó 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.

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.

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 typeHiển thị ở đâuGiá dùng
RETAIL (mặc định)POS workspace (/point-of-sale/sale), menu onlineprice (retail)
WHOLESALEWholesale POS workspace (/dashboard/orders/create-wholesale), B2B portalwholesale_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ỉ (xem VENDOR_PRODUCT_DEFAULT_TYPES trong packages/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.

Nếu bạn đã có danh sách mặt hàng (CSV/Excel), dùng template nhập:

  1. 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.
  2. Điền các cột: name | sku | barcode | category | unit | price | stock | status.
  3. 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.

Danh mục mặt hàng Categories — danh sách master các category bạn có thể gán cho sản phẩm.

Đơn vị tính Đơn vị tính (each, kg, pack…).

Nhà cung cấp Nhà cung cấp — dùng khi nhập hàng ở Bước 2.

Khu vực conRouteTác dụng
Categories/dashboard/items/categoriesDanh sách master các category (drag-reorder, primary flag).
Modifier groups/dashboard/items/modifier-groupsNhóm add-on / option (vd “Toppings” với Pepperoni / Mushroom).
Units/dashboard/items/unitsDanh sách master các unit. Picker Unit của form sản phẩm đọc từ đây.
Taxes and fees/dashboard/items/taxesStore tax profile tái sử dụng. Accordion Taxes and fees của form đọc từ đây.
Attributes/dashboard/items/attributesDanh sách master các attribute (Size, Color, …) dùng để sinh variants.
Tags/dashboard/items/tagsTag tuỳ do dùng để lọc và báo cáo.
Brands/dashboard/items/brandsDanh sách brand. Dùng trong drawer filter Products.
Pricing rules/dashboard/items/pricing-rulesThay đổ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-listsPrice list nhiều tier theo kênh.
Price approvals/dashboard/items/price-approvalsHàng đợi review cho thay đổi giá vượt ngưỡng phê duyệt.
Addons/dashboard/items/addonsCatalog add-on POS.
Gallery/dashboard/items/galleryVendor media gallery — chọn ảnh cho bìa và album sản phẩm.
Suppliers/dashboard/items/suppliersMaster 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-importTạo hàng loạt sản phẩm từ file catalog của nhà cung cấp.
  • Í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.

→ Bước 2 — Nhập hàng (Lots) để đưa tồn kho thật lên kệ: bước 2.