# Domain: Catalog Products, SKUs, categories, brands (`modules/product/`, `category/`, `brand/`). ## Shape - `products`: shop-scoped, bilingual name/description, images JSONB array, status draft → published (publish requires ≥1 active SKU with price > 0), optional `freight_template_id` (must belong to the same shop). - `skus`: per-product purchasable units — code, attributes JSONB, `price_minor` + `currency`, guarded `stock`, `weight_grams` (freight). - `categories`: tree with subtree CTE for filtering; seeded reference tree. - `brands`: platform-managed ordered registry, whole-list replacement via `PUT /admin/brands`. ## Public reads `GET /products` (published + active shop only; filters: category subtree, shop, name ILIKE, brand; sort newest/price/sales) and `GET /products/{idOrSlug}`. `sold_count` is computed from paid order items. Merchant reads/writes go through `/shop/products*` with `own_shop` scoping. ## Conventions - Every Product query uses explicit column lists — when adding a product column, update `PRODUCT_COLS` and the literal lists in `service.rs` (search: `category_id, brand_id, slug`). - SKU upsert is keyed on `(product_id, sku_code)`. - Stock is the only concurrently decremented counter today; always the guarded-decrement pattern. ## Key files See `docs/code_index/catalog.md`.