Gate 1. Narong's mid-V0.3 Telegram: split Products from Product Variants — Products page
becomes template-only; variants need their own surface because some properties are unique
to variants (SKU, barcode, cost). No archive on variants (Narong's hard constraint).
No migration — every column Odoo's view needs already exists on cafe.product_product.
| ✓ | listProductVariantsForAdmin DAO exported with includeArchivedTemplate opt; variant-values assembled via string_agg of attr.name || ': ' || val.value |
| ✓ | updateVariantCells DAO + updateVariantCellsAction exported; action validates priceUsd / standardPriceUsd via existing validatePrice |
| ✓ | /products/variants page + client wired with all V0.4 testids (variants-title, variants-search-input, variants-list, variant-cell-${"$"}{field}-…, variant-values-…, variant-link-…); InlineCell wired for all 3 editable price/SKU fields; FiltersMenu bound to /products/variants basePath; lazy initializer present on inline cells |
| ✓ | Shared FiltersMenu + ProductsTabs extracted to app/(authed)/products/filters-menu.tsx + products-tabs.tsx; products-list-client imports both; no duplicate inline FiltersMenu function lingers |
| ✓ | ProductsTabs renders Products + Variants tabs with hrefs /products + /products/variants and matching testids |
| ✓ | DAO round-trip on local Postgres: created an active template with attribute Size = {S, M} + 2 variants; default list returns both with variantValues assembled like "Size_T: S" / "Size_T: M" in attr-name-alphabetical order. Archived template's variant absent from default list; includeArchivedTemplate:true brings it back. updateVariantCells round-trip persists all 4 fields (defaultCode, barcode, priceUsd, standardPriceUsd). Cross-tenant guard rejects (null return) when tenantId doesn't match — verified by attempting an UPDATE with a zero-UUID tenant and confirming the row's value didn't change. |
| ✓ | Action sanity probe (DAO survives tenant-scope guard on happy path) |