sku and item fix

This commit is contained in:
2026-08-11 21:55:04 +05:30
parent 15ddac178c
commit a7ba3d3e04
15 changed files with 557 additions and 96 deletions
+3 -2
View File
@@ -264,7 +264,7 @@ Costing: FIFO · Multi-warehouse · Single-tenant. Legend: **PK** primary key ·
CATEGORY(category_id PK, name, status) -- top level; no self-nesting
SUBCATEGORY(subcategory_id PK, category_id FK→CATEGORY, name, status)
BRAND(brand_id PK, name, status)
ITEM_TYPE(item_type_id PK, name, status) -- Color, Size, Material — standalone
ITEM_TYPE(item_type_id PK, name, is_measurable, status) -- Color, Size, Material — standalone
UOM(uom_id PK, name)
ITEM(item_id PK, sku, name, category_id FK→CATEGORY, subcategory_id FK→SUBCATEGORY [nullable],
brand_id FK→BRAND [nullable], base_uom_id FK→UOM,
@@ -365,7 +365,8 @@ USER(..., role_id FK→ROLE [nullable]) -- added to the existing USER shadow (s
Note: `USER_ROLE` from the original placeholder sketch was dropped — a user has at most one role (`USER.role_id`), matching AuthHex's own `User.RoleId` being a single scalar FK, not a many-to-many.
## C.9 Modeling notes (load-bearing)
- **Item types are a dropdown, not a relationship.** `ITEM_TYPE` (Color, Size, Material) exists **only** to populate the frontend item-builder's dropdown via `GET /item-types`. Nothing references it and it references nothing — there is no value table and no join to `ITEM`. The builder cross-products the checked types into **one standalone item per combination**; the chosen values (Red, S, M) are encoded by the **client** into the generated SKU (`BL-0002` for one type, `BL-100-0003` for two) and the server only checks that SKU for uniqueness. **The item list is the record of what was built.** This is not a product-variation model: there is no parent-product entity and no variant hierarchy.
- **Item types are a dropdown, not a relationship.** `ITEM_TYPE` (Color, Size, Material) populates the frontend item-builder's dimension list via `GET /item-types`. Nothing references it and it references nothing — there is no value table and no join to `ITEM`. The builder cross-products the checked types into **one standalone item per combination**; the chosen values (Red, S, M) are encoded by the **client** into the generated SKU (`BL-0002` for one type, `BL-100-0003` for two) and the server only checks that SKU for uniqueness. **The item list is the record of what was built.** This is not a product-variation model: there is no parent-product entity and no variant hierarchy.
- *One exception, added 2026-08-11:* the master carries a single semantic the client acts on — **`is_measurable`**. A dimension flagged measurable has its values entered as a number + unit (500 ml, 1 L) instead of free text, and that pair is written to each generated item's `content_qty`/`content_unit` (FR-MD-02). So "exists only to feed a dropdown" is no longer accurate; "is unreferenced by `ITEM`" still is, and the trade-off below is unchanged. The flag is what lets an apparel `Size` (S/M/L) stay plain text while a `Volume` dimension carries units. The server does not read it when writing an item — each item's content pair is validated and normalised on its own.
- *Accepted trade-off (a decision, not an oversight):* the backend cannot answer "list all blue items", cannot filter or report by colour/size, and cannot validate that a SKU's segments correspond to real item types. Renaming an item type (`Color``Colour`) does **not** touch existing SKUs, which keep their old segments — the two are permanently decoupled the moment an item is created. If value-level querying is ever needed, an `ITEM_TYPE_VALUE` table plus a link table can be added additively, but existing SKUs will not be back-fillable without parsing them by hand.
- **Sale price is a per-item scalar, not a variant/price table.** Because each "variant" is its own `ITEM` row (above), the optional selling price lives directly on `ITEM.sale_price` (nullable). `NULL` means "use stock value" — Sales values the item at its FIFO stock cost at sale time (FR-STK-04 / `STOCK_LAYER`); a value is a fixed selling price. It is **Sales-only**: it never participates in GRN, FIFO layering, or the stock ledger, so receipt/costing behaviour is identical whether the item is fixed-priced or not. The create-time "fixed price vs use stock value" choice is a **frontend UX toggle** — the contract is simply the nullable column, and the item builder requires a price on every generated variant when the user picks fixed pricing.
- **Two-level categories.** `CATEGORY` no longer self-nests; `SUBCATEGORY` is the single optional level below it. An item stores both FKs rather than pointing only at the deepest node, so the parent is never inferred or lost. A subcategory cannot be reparented (it would silently invalidate the category of every item referencing it) — deactivate and recreate instead.