2026-03 SMILE商品の Product / Variant 責務分離¶
背景¶
- SMILE の商品マスタは SKU(
商品コード)単位で管理される。 - Vendure では初回取り込み時に
1 Product = 1 Variantを基本形とする一方、運用上は複数 SKU を同一 Product に手動 regroup する。 - そのため、SMILE 1行由来の値と、Storefront の販売表現として共有する値を分離しないと、再インポートや手動 regroup で整合が崩れる。
決定事項¶
| 項目 | 正本 | 理由 |
|---|---|---|
Product.name / slug / description / 画像 / CMS説明 |
Product | 商品ページ導線と販売表現の共有情報だから |
consentNoteHtml / consentChecklist / consentRequired / consentTemplateId / consentIds / consentInstructions |
Product + Variant | Product 共通同意と SKU 固有同意を Variant 優先 / Product フォールバック で解決するため |
keywords / nameKana |
Product | 検索・一覧導線の共有情報だから。nameKana は運用 override、keywords は商品名を補う補助検索語として扱う |
brandCode |
Product | 正本の分類軸は brand FacetValue だが、Product 側にも共有属性のミラー値として保持するため |
categoryCode |
smileRaw |
SMILE の部門コード相当で、販売表現の共有属性ではなく保存専用の参考値として扱うため |
directShippingEligible |
Variant | SKU ごとの配送制約(isDirectShippingOnly と同階層)。Product 側では扱わない |
| バンドル商品の代表画像 | Variant | バンドル商品は Product 共有導線とは別概念で管理し、bundle parent variant 単位で差し替えるため |
sku / price / stock |
Variant | 受注・在庫・価格の主キーだから |
smileListPrice / specification / smileRaw |
Variant | SMILE 1行由来で SKU ごとに差分がありうるため |
isDirectShippingOnly |
Variant | SKU ごとの配送制約だから |
minimumQuantity / purchaseUnit / maxPerOrder |
PurchaseLimitRule | 購入制限は商品共通・顧客別・顧客グループ別を同じ rule で扱うため、ProductVariant custom field には戻さない |
Variant表示名(正式品名 または 商品名 + 規格) |
Variant | SKU ごとの表示差分だから |
regroup 運用ルール¶
- 手動 regroup で同一 Product にまとめてよいのは、Product 側に残す業務属性が同値の SKU 同士に限る。
- 次の項目が SKU 間で異なる場合、同一 Product にまとめてはいけない。
brandCodeconsentNoteHtmlconsentChecklistconsentRequiredconsentTemplateIddirectShippingEligibleは Variant 正本であり、同一 Product 内で値が混在することを許容する。regroup 制約には含めない。name/slug/description/ 画像 / CMS説明 /keywords/nameKanaは regroup 後の Product 側で手動管理してよい。- ただし検索の責務は 商品名が主軸であり、
nameKanaは漢字商品の読み検索を補う 運用 override、keywordsは商品名や説明に含まれない補助語句を補うための 任意補助フィールド として扱う。 - ただし
customFields.setComponentsを持つバンドル親 variant の代表画像は例外とし、ProductVariant.featuredAsset/assetIdsで管理する。 - 同意スコープの優先順位と
ConsentRecord.variantIdの扱いはdocs/specifications/2026-03-consent-scope-product-variant.mdを正本とする。
影響範囲評価¶
- 今回 Variant 側へ移したのは、
specification/smileRawのような SMILE 1行由来で SKU ごとの差分が必ずありうる項目に限定する。 - 上記 2 項目は importer / インポートプレビュー / データ保持の用途が中心で、storefront の商品導線・配送計算・アクセス制御・同意判定の前提にはなっていない。
- 一方、次の項目は 現行実装が Product 側前提のため、安易に Variant へ移してはいけない。
consentNoteHtmlconsentChecklistconsentRequiredconsentTemplateIdbrandCodeは、正本のブランド分類軸としてではなく、brandFacetValue を反映した販売表現上の共有属性・内部連携用ミラーとして Product 側に保持する。- したがって、同一 Product に regroup する SKU は同一
brandCodeを持つことを前提にする。 categoryCodeは SMILE の部門コード相当の参考値として扱い、販売表現や共有制御の正本にはしない。- したがって、新規実装では
Productの共有属性として依存せず、smileRawに保存するだけでプログラムから参照しない。 - 上記の項目を Variant 化する場合は、SMILE importer の変更だけでは不十分であり、storefront / SDK fragment / shipping / access-control / consent service を含む横断改修として扱う。
- 2026-03 の consent 横断改修では、上記
consent*項目をVariant 優先 / Product フォールバックで解決する設計へ切り替える。 directShippingEligibleは SKU ごとの配送制約(isDirectShippingOnlyと同階層)であり、Variant 側を正本とする。- 旧版(〜2026-04)は Product 側に配置して regroup 制約に組み込んでいたが、これは SMILE importer の "1 Product = 1 Variant 初期形" を販売モデルに持ち込んだ誤りであり、同一 Product に複数 SKU が並ぶ運用と整合しないため訂正した。
- Product 側では direct shipping を扱わない。importer / storefront カタログ・カート判定 / shipping policy / regroup helper / WP CMS マッピング / SDK fragment はすべて Variant 値を参照する経路へ統一する。
- したがって、現時点の安全ラインは 「SMILE行由来で SKU 差分が明確な項目と、同意のように SKU 単位判定が必要な項目だけを Variant に寄せる」 までとする。
再インポート時の保護フィールド¶
SMILE 商品マスタ上、以下の項目は備考で「マスター上の値は使わず BtoB 発注システム上で自由に設定しています」と明示されており、Vendure 側(運用ユーザー)を正本とする:
- 商品マスタ「商品名」(SMILE CSV:
商品名/正式品名) - 商品マスタ「規格」(SMILE CSV:
規格)
このため SMILE 商品 CSV 再インポート時の挙動を次のとおりとする:
| フィールド | 初回 create | 再インポート (update) |
|---|---|---|
ProductVariant.translations.name(正式品名 / 商品名 + 規格) |
SMILE 値で seed | 既存値を保持。既存値が空文字 / null のときに限り SMILE 値で補完 |
ProductVariant.customFields.specification |
SMILE 値で seed | 既存値を保持。既存値が空文字 / null のときに限り SMILE 値で補完 |
ProductVariant.customFields.smileRaw / smileListPrice |
SMILE 値で seed | SMILE 値で上書き(業務正本が SMILE 側) |
Product.name / slug / description / 画像 |
初回のみ seed | 触らない(既存運用通り) |
- 補完判定は「既存 variant の現行 translation
name/customFields.specificationがnullまたは空文字(trim 後)」のときだけ SMILE 値で書き込む。それ以外は SMILE 行に値があっても書き込まない。 - この保護は Variant 側にのみ適用する。
Product.nameは元々 update path で触らないため変更なし。 - importer の monkey-patch ではなく、
csv-import-product.processor.tsの update path 自体に保護ロジックを置く。
実装ルール¶
- SMILE 再インポート時は、既存 SKU が見つかった場合に Variant を更新する(ただし上記「再インポート時の保護フィールド」に従う)。
- 親 Product の自動更新は、旧 canonical の
smile-<product-code>または現行 canonical の<product-code>slug を持つ単独 SKU Product に限定する。 - Product slug を補正したときは
product_translationだけで完結させず、search_index_item.slugも同一 migration で同期するか search reindex を伴わせる。 - Vendure Dashboard の商品詳細ページ / 商品バリアント詳細ページには、 「Product の責務」「Variant の責務」案内カードを常設しない。Product / Variant の責務判断そのものはこの仕様書を正本とし、画面上では必要最小限の導線のみを提供する。
Product.customFields.specification/Product.customFields.smileRawは新規実装で使用しない。- SMILE 行由来の新規属性を追加する場合は、特段の理由がない限り Variant 側へ追加する。
nameKanaは SMILE 正本から自動注入する前提にしない。検索品質のために必要な場合だけ、運用ユーザーが Product 側で明示登録する。- SMILE 由来の半角カナや表記揺れの吸収は、まず商品名と
keywordsの検索専用正規化列で扱い、nameKanaを半角カナ流入の受け皿として設計しない。