| Name | TextColumn::searchable(isIndividual)->url()->sortable() | string (material / variant) | derived display: '{name} / {variant code | supplier tag}' — keep name and variant code as distinct stored fields | variants.id |
| Variant code / SKU | TextColumn::searchable(isIndividual) | string, nullable | stored per variant, optional (empty on "BBeast Men's Candy Sachet"); can differ from Name suffix (Belgian Dark Cocoa → CBADCP22PLARO91B) | — |
| Item type | TextColumn::badge() + SelectFilter | enum, mandatory | stored: items.item_type — raw_material | packaging | finished_good | consumable. Master classification axis, independent of category; rendered as the gray Type badge | — |
| Category group | TextColumn (parent) + SelectFilter | FK, admin-locked | stored: items.category_group_id — 5 admin-locked parents (Actives & Botanicals, Sweeteners, Flavor & Acids, Functional aids, Base powders); shown as the small group label under the category badge | category_groups.id |
| Category | TextColumn::badge() + SelectFilter (grouped by category group) | FK, admin-locked | stored: items.category_id — admin-locked child of a category group (~22 clean values). Clean names only: the units-style alias map strips the legacy "(China)" prefix on import ("(China) Actives" → category Actives + origin_country China) and folds the known typos (fruit → Fruit Powder, sweet → Sweetener, "OIl Powder" → Oil Powder) | categories.id |
| Origin country | TextColumn + SelectFilter | enum / FK, admin-locked | stored: items.origin_country — authoritative country of origin (NPRA); the value each recipe/BOM Origin column inherits per ingredient. Seeded from the stripped "(China)" prefix, else supplier country (China, Malaysia, Italy, India, Indonesia, Belgium …) | countries.id |
| Customer-supplied | IconColumn / badge + Toggle | bool | stored: items.customer_supplied — customer-paid packaging (RM0 cost to Cyto); shown only on packaging items as the teal "Customer-supplied" badge | — |
| Default supplier | TextColumn::placeholder('—') | string, nullable | stored FK — unpopulated across the snapshot despite supplier names embedded in Name strings | suppliers.id |
| Default purchase price | TextColumn::money('MYR') | money MYR | stored default unit cost; defaults to 0 MYR when unset (every visible row 0) | — |
| Default sales price | TextColumn::money('MYR')->placeholder('') | money MYR, nullable | stored, optional — populated only on materials also sold directly (2 of 18 sample rows) | — |
| Active / Archive tabs | Tabs (lifecycle filter); Archive view not captured | bool flag | stored: items.archived_at | — |
| Bulk actions | BulkAction: Bulk edit / Archive / Delete (+ Print, Export header actions) | action | Katana toolbar icon cluster (print, edit, export/archive, trash) | — |