キャンペーン/価格ルール編集 UI の簡素化設計¶
| 項目 | 内容 |
|---|---|
| ステータス | ドラフト(合意待ち) |
| 種別 | 追加合意・補足仕様(React Dashboard UX 改善) |
| 対象画面 | React Dashboard /commercial-rules/$id(キャンペーン詳細 / 価格ルール詳細) |
| 関連 | 2026-03-dashboard-commercial-rule-price-flow.md / 2026-05-campaign-target-product-selection.md / キャンペーン-価格制御責務分離.md / 要件 §3.6.1(6 タイプ) |
| 作成 | 2026-06-08 |
1. 背景・目的¶
/commercial-rules/$id のルール編集画面が「運用者が読み切れないほど複雑」という課題がある。本書は 何が複雑さの原因か を特定し、既存ルールエンジン・既存ルール・検品 fixture を壊さずに 認知負荷を下げる UI 改善方針を定義する。実装着手はこの設計合意後とする。
非目標: ルールエンジン(packages/domain/rules・packages/plugins/src/rule-engine/commercial)のデータモデル変更。本書はあくまで Dashboard の入力 UX 層 の改善に限定する。
2. 現状の構造¶
汎用ルールエンジンの内部構造(conditions ツリー + tiers[] + 6 種 action + ネストしたギフトグループ)を、フォームへ 1:1 でフラットに展開 している。
主要ファイル:
| 役割 | ファイル |
|---|---|
| フォーム束ね | packages/plugins/src/standard-extensions/admin-extensions/dashboard/campaigns/CampaignForm.tsx |
| 基本情報 | CampaignBasicSettingsSection.tsx |
| 対象商品・よりどり | CampaignTargetingSection.tsx |
| 適用条件 | CampaignConditionBuilderSection.tsx |
| ティア/アクション | CampaignTierBuilderSection.tsx(約 1,200 行) |
| 非対応警告 | CampaignEditorNotice.tsx |
| state ⇔ JSON 変換・複雑度判定 | campaign-form-mapper.ts |
3. 問題点(診断)¶
3.1 「条件」を入力する場所が 3 つに分散¶
数量・金額の達成条件を入れる場所が 3 系統あり、運用者は使い分けを理解しないと設定できない。
- よりどり条件(
selection.totalQuantity/selectionGroups)—CampaignTargetingSection.tsx:92 - 適用条件(
matched-quantity/cart-quantity/matched-subtotal/cart-subtotalなど)—CampaignConditionBuilderSection.tsx - ティア発動条件(対象商品数量・カート数量・対象商品小計・カート小計の上下限 = 8 入力)—
CampaignTierBuilderSection.tsx:908
「適用条件の対象商品数量」と「ティア発動条件の対象商品数量」が別物として両方存在し、これが最大の混乱源。
3.2 エンジン内部語の露出¶
運用者が知る必要のない実装概念がラベルに出ている。
セレクションID/グループID/アイテムID/ダミー商品番号- 「storefront 選択データと照合します」「保存時に
conditions.targetsへ自動反映します」 - 「JSON 編集は使わず」(
CampaignEditorNotice.tsx:18)
3.3 すべてのルールに最大構成を見せている¶
単純な掛率・単価固定ルールでも、6 種アクション・ネストしたギフトグループ・8 入力のしきい値が常時展開される。例として現在問題提起の発端となった verif-type5-multicategory-gift は 検品用 fixture(最も複雑なタイプ 5) であり、日常運用のルールではない。
3.4 要件のタイプ体系(型 1〜6)が UI に存在しない¶
要件 §3.6.1 には明確な 6 archetype がある。
| 型 | 名称 | 要旨 |
|---|---|---|
| 1 | 金額ベース選択ギフト | 規定金額達成で複数候補から選択 |
| 2 | 数量ベース価格割引 | 規定個数で段階割引(10 個 10% 等) |
| 3 | 数量ベース無償ギフト | 規定個数で無償ギフト(変動 + 固定) |
| 4 | 複合キャンペーン | 価格割引 + 無償ギフト同時 |
| 5 | マルチカテゴリ選択 | カテゴリ横断(A・B・C 各 1 個)で特典 |
| 6 | ハイブリッド | 1〜5 の段階的組合せ |
実運用のキャンペーンはほぼこの 6 型に収まるのに、UI は型を意識せず汎用エディタで毎回手組みさせている。ここがテンプレ化の最大レバー。
3.5 非対応エスケープハッチの二分化¶
hasUnsupportedConfig(campaign-form-mapper.ts:235-253)は、保存後 JSON と builder 再構築 JSON の不一致で「UI で扱えない」を検出し、保存をブロックする。複雑なルールは seed/JSON でしか作れず、UI が実質読み取り専用になる。この二分化自体が複雑さの症状。
4. 改善方針¶
4.1 採用案: テンプレ層追加(案 B)+ 既存エディタ整理(案 A)の段階適用¶
| 案 | 内容 | 評価 |
|---|---|---|
| A | 既存エディタの整理(条件集約・用語統一・折りたたみ・内部 ID 自動採番) | 低リスク・効果中。第 1 段で実施 |
| B | 型 1〜6 のテンプレ/ウィザード層を上に追加。内部で既存 JSON 生成、エンジン無改修 | 効果大・後方互換。第 2 段の主軸 |
| C | データモデル再設計(3 条件系統合・型をエンティティ化) | 影響大・受託コスト過大。今回非採用 |
方針: 汎用エディタは残したまま、その上に型選択 → 必要欄だけの簡易フォームという「テンプレ層」を被せる。汎用エディタは「上級者向け」「テンプレ非対応ルールの編集」として温存し、hasUnsupportedConfig の逃げ道も維持する。
5. UI 設計¶
5.1 作成フロー(テンプレ層)¶
[キャンペーン作成]
↓ 型を選ぶ(カード選択: 型1〜6 + 「価格ルール(掛率/単価)」 + 「汎用(上級者)」)
↓
[型ごとの簡易フォーム] ← 必要な欄だけ表示
↓ 保存
内部で conditions/tiers JSON を生成 → 既存 createCommercialRule mutation
型ごとに見せる入力欄(最小セット)の指針:
| 型 | 主な入力欄 | 生成される内部構造(概略) |
|---|---|---|
| 1 金額選択ギフト | 対象範囲 / 最低購入金額 / 選択候補・最大選択数 | cart-subtotal>=X + tier に select_gift_items |
| 2 数量価格割引 | 対象商品 / 数量しきい値の段階 / 割引率 | 複数 tier(matched-quantity>=N)+ multiply_unit_price |
| 3 数量無償ギフト | 対象商品 / 数量しきい値 / ギフト商品・数量 | tier(matched-quantity>=N)+ add_gift_items |
| 4 複合 | 型 2 + 型 3 の合成(割引 tier と ギフト tier) | 複数 tier・複数 action |
| 5 マルチカテゴリ | カテゴリ別グループ(各必要数量)/ 特典 | selection.groups + tier action |
| 6 ハイブリッド | 段階定義(数量 → 割引、数量 → ギフト、カテゴリ条件) | 多段 tier・cumulative tierMode |
5.2 既存エディタの整理(案 A 詳細)¶
- 条件入力の集約: 「適用条件」と「ティア発動条件」の重複を解消。原則 ルール全体の前提条件 を 1 か所に集約し、段階のしきい値 は tier に一本化する(運用ガイドで使い分けを明文化)。
- 用語の言い換え:
セレクションID→自動採番で非表示、対象商品小計→「対象商品の金額(税抜)」等、業務語へ統一。「JSON」「storefront 照合」等の実装語は補足ツールチップへ退避。 - 内部 ID 自動採番:
selectionId/groupId/itemIdは UI から隠し、保存時に自動生成。 - 折りたたみ: 未使用セクション(よりどり・選択式ギフト等)はデフォルト折りたたみ。
5.3 価格ルール(kind=pricing)の扱い¶
掛率・単価固定など kind=pricing は本来ティア/ギフト機構の大半が不要。テンプレ層で「価格ルール」を独立カードにし、tiers の単一 tier に price action だけを持つ最小フォームを出す。
6. 後方互換・移行¶
- データ無改修: 既存
commercial_rule_entityのconditions/tiersJSON はそのまま。テンプレ層は読み込み時に「どの型に当てはまるか」を推定し、当てはまればテンプレ簡易表示、当てはまらなければ従来の汎用エディタへフォールバック。 - 検品 fixture:
verif-type5-*等は汎用エディタで従来どおり編集可能。hasUnsupportedConfig判定ロジック(campaign-form-mapper.ts)は変更しない。 - 段階リリース:
- 第 1 段(案 A): 既存エディタの用語・集約・折りたたみ・ID 自動採番。挙動互換、見た目のみ改善。
- 第 2 段(案 B): 作成フローに型選択カード + 型 1〜3 の簡易フォーム(最頻出)。
- 第 3 段: 型 4〜6 簡易フォーム + 読み込み時の型推定によるテンプレ表示。
7. 検証方針¶
- 各型の簡易フォーム → 生成 JSON が、対応する既存 seed/fixture の JSON と意味的に一致することを単体テストで固定(
campaign-form-mapper周辺)。 - 既存ルールの読み込み → 保存で JSON が変化しないこと(round-trip)を回帰テスト化。
- Dashboard コンポーネントの見た目は Playwright CT で locator 単位に固定(AGENTS.md の CT 正本ルールに従う)。
8. 未決事項¶
- 第 1 段の「条件集約」で、
適用条件とティア発動条件を統合する際の既存ルール挙動を完全互換に保てるか(要 round-trip 検証)。一本化が互換を崩す場合は、第 1 段では用語・折りたたみのみに留め、統合は別途検討。 - 型推定(読み込み時にどの archetype か判定)の精度と、推定外れ時の UX(汎用エディタへ素直に落とす)。
- テンプレ層を作成フローのみに置くか、編集画面でも型ビューを出すか。
kind=pricingをテンプレ層の独立カードにするか、価格ルールは別画面に分離するか。
合意後、第 1 段から着手する。