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
+37 -1
View File
@@ -4,6 +4,42 @@ Legend: `[ ]` not started · `[~]` in progress · `[x]` done
Spec: `docs/10-BACKEND-PHASE1.md` (model + rules) · `docs/11-BACKEND-PHASE1.md` (API)
Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** as the code. When ticking `[x]`, append a short note + any deviation.
## Per-variant content size (2026-08-11) — follow-up to the UOM re-model below
The UOM re-model put `ContentQty`/`ContentUnit` on `Item` correctly, but the item **create page
is a variant builder** and collected **one** form-level pair, copying it into every generated
variant. Building "Coca-Cola in 500 ml / 1 L / 250 ml" produced three items all recorded as the
same size — the exact case the builder exists for. The item contract already accepted a
per-item pair, so the whole fix is in how values are captured.
- **`ItemType.IsMeasurable`** (bool, default `false`) — set on Products → Item Types. A flagged
dimension's values are entered as a number + unit; the chip label, the SKU segment, the item
name and the stored content size all derive from that one pair. Unflagged dimensions are
unchanged free text, which is what an apparel `Size` (S/M/L) needs.
- **`UpdateItemTypeRequest.IsMeasurable` is `bool?` and preserved when omitted.** A plain `bool`
binds an absent property as `false`, so the admin screen's name-only PUT would have cleared the
flag on every rename — the same bug class already recorded for `product-config` further down.
- **The `BUILDER_ITEM_TYPES = ["color","size"]` hardcode is gone.** It had one consumer and had
become a live bug: a user-created "Pack Size" would be flagged measurable and then never
appear. Removal is behaviour-preserving on any current database (the seeder seeds exactly those
two names, and the fetch was already `status: Active`) and restores the documented contract
that users add their own types. Every Active item type is now offered; deactivation is the
intended remedy and the admin page already says so.
- **SKU collision fixed before it could bite.** `skuSegment` strips the decimal point and
truncates to 3, so derived labels collided — `1.5L`/`15L``15L`, `500ml`/`500g``500`,
`2.5ml`/`25ml``25M`. Since the create loop is sequential and non-transactional, that would
have failed partway with `SKU_DUPLICATE` after creating some rows. Measurement segments now use
`measureKey`, which mirrors `ItemContent.Normalize` (L/Kg ×1000) and renders the point as `P`.
- **Values dedupe on the normalised size, not the label** — `500 ml` and `0.5 L` read differently
but store identically, and `ItemContent.Normalize` is the server's notion of equality.
- **At most one measurable dimension** per product: unchecked measurable types are disabled once
one is checked, re-checked at submit.
- **The form-level pair survives as a fallback** — correct when the varying dimension isn't size —
and is hidden *and cleared* whenever a measurable dimension is active, so the two can never
disagree. Its validation is skipped in that mode, since its error message would otherwise be
invisible inside the hidden block.
- The item **edit** page is untouched: one item, one size.
## UOM re-model (2026-08-11) — supersedes every "UOM conversion" note below
Per-item UOM conversion is **gone**. Entries further down this file that describe
@@ -60,7 +96,7 @@ What replaced it:
- [x] Warehouse + Bin (`/warehouses`, nested `/warehouses/{id}/bins`, bin code unique per warehouse)
- [x] Item reorder settings (`PUT /items/{id}/reorder` full-replace upsert, warehouse-exists validation)
- [x] Brand master (FR-MD-09) — CRUD + status + ETag; `Item.brandId` nullable FK
- [x] Item Type master (FR-MD-10) — CRUD + status + ETag; **unreferenced by design**, feeds the builder dropdown only
- [x] Item Type master (FR-MD-10) — CRUD + status + ETag; **unreferenced by design**. Feeds the builder's dimension list, and since 2026-08-11 carries `isMeasurable`, which decides whether its values are captured as free text or as a number + unit that becomes each item's content size (see the entry at the top of this file)
- [x] SubCategory (FR-MD-04) — nested list/create under a category, `PUT`/`PATCH status` by id; `Item.subCategoryId` nullable FK, validated to belong to `categoryId`
- [x] Product Configuration (FR-MD-11) — singleton `GET`/`PUT /product-config`; `CONFIG_DISABLED` gating on item writes
- [x] Item **sale price** (FR-MD-01, 2026-07-22) — nullable `Item.SalePrice` (`numeric(18,4)`); on all Item DTOs (list/detail/create/update), validated `>= 0`. **Sales-only** — never enters GRN/FIFO/ledger. `null` ⇒ sell at stock value. Migration `AddItemSalePrice`. See the 2026-07-22 Done entry.