コンテンツにスキップ

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 にまとめてはいけない。
  • brandCode
  • consentNoteHtml
  • consentChecklist
  • consentRequired
  • consentTemplateId
  • directShippingEligible は Variant 正本であり、同一 Product 内で値が混在することを許容する。regroup 制約には含めない。
  • name / slug / description / 画像 / CMS説明 / keywords / nameKana は regroup 後の Product 側で手動管理してよい。
  • ただし検索の責務は 商品名が主軸であり、nameKana は漢字商品の読み検索を補う 運用 overridekeywords は商品名や説明に含まれない補助語句を補うための 任意補助フィールド として扱う。
  • ただし 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 へ移してはいけない。
  • consentNoteHtml
  • consentChecklist
  • consentRequired
  • consentTemplateId
  • brandCode は、正本のブランド分類軸としてではなく、brand FacetValue を反映した販売表現上の共有属性・内部連携用ミラーとして 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.specificationnull または空文字(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 を半角カナ流入の受け皿として設計しない。