feat: item master — a SKU's sellable item + category taxonomy (ADR 0022) - #5
Merged
Merged
Conversation
For retail (a grocery sells its inventory as-is), a SKU can now optionally carry a sellable *item* record — name, price, unit, description, image, active/featured flags — alongside its ledger stock, plus a two-level category taxonomy. Additive: a SKU can have stock with no item and vice versa, so a pure-ledger user is unaffected. Keyed by the opaque sku_code with no FK into core (image is a soft media-id ref resolved at render). - Schema: inventory_item + inventory_category (migration 003_items), superseding the "catalog lives in content" docblocks (ADR 0022). - Catalog service: item/category CRUD with the pinned controls — store raw / escape on render, non-negative decimal price, media-id soft ref, field allow-list (no over-posting), category parent-must-exist + two- level depth (cycles impossible), delete blocked while referenced. - MCP: inventory_item_set/get + category_set/get + categories, gated inventory:write/read (deletes stay admin-only — a recorded deferral). - Admin: a Catalog page (item + category management), inventory:write + CSRF, one nonce'd <style> block (admin CSP), mobile-reflow tables. - Guide updated to teach the item master. Tests: item override, raw storage, price/media-id/category validation, content-token-cannot-write, over-posting ignored, two-level depth, delete-block, slug allow-list, admin escaping + CSP-clean + reflow. PHPStan L max, 68 tests, cs-fixer all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Foodmart Slice 1. For retail (a grocery sells its inventory as-is), a SKU can now optionally carry a sellable item record — name, price, unit, description, image, active/featured — alongside its ledger stock, plus a two-level category taxonomy. Additive: a SKU can have stock with no item and vice-versa, so a pure-ledger user is unaffected. Keyed by the opaque
sku_codewith no FK into core (image is a soft media-id ref resolved at render).Evolves — via ADR 0022 — the "catalog lives in content" stance: content collections stay the catalog for editorial items; the item master is for operational stock you sell.
What's here
inventory_item+inventory_category(migration003_items); supersedes the catalog-in-content docblocks.Catalogservice: item/category CRUD with the pinned security controls — store raw / escape on render, non-negative decimal price, media-id soft ref, field allow-list (no over-posting), category parent-must-exist + two-level depth (cycles impossible), delete blocked while referenced.inventory_item_set/get,inventory_category_set/get,inventory_categories— write-gated oninventory:write, reads on:read. (Deletes stay admin-only — a recorded deferral.)inventory:write+ CSRF, one nonce'd<style>block (admin CSP),data-labelreflow tables for mobile.Reviews
Both
nimbus-review-loopandnimbus-security-reviewrun before build. Classification: official plugin, additive — not core. Drift guard passes on the generic-fields constraint (no grocery-specific columns). Security-green: store-raw/escape-on-render contract set now for Slice 2.Verification
PHPStan (max) clean · 68 tests / 187 assertions green · php-cs-fixer clean. Live 375px browser check to follow on deploy (reuses Phase 1/2a responsive primitives).
🤖 Generated with Claude Code