コンテンツにスキップ

述語ベース CustomerGroup(auto group)

  • 作成日: 2026-05-21
  • ステータス: 仕様確定(実装は段階リリース)
  • 関連仕様: 2026-03-collection-facet-boundary.md(同期方式の先行事例), 2026-03-smile-customer-import-upsert-rules.md, 2026-05-commercial-rule-facet-targeting.md

背景

SMILE から取り込まれた顧客属性(customerStatus, salesRateClassCode, billingCustomerType 他)は Customer の custom field として独立保持されており、Vendure の CustomerGroup には連動していない。商品側はすでに Collection × Facet の宣言的メンバシップ(保存時の on-save 同期)が稼働しているが、顧客側は同等の仕組みを持たず、SMILE 属性更新のたびに CustomerGroup の手動メンテが必要だった。

本仕様では、CustomerGroup述語(predicate)で自動的にメンバを決める auto group として運用可能にし、既存の手動 CustomerGroup と並存させる。

ゴール

  • Customer の custom field を条件式にして、CustomerGroup メンバを自動で確定する。
  • Customer 保存・SMILE import を契機に 物理的にメンバを add/remove する(評価時動的解決ではない)。
  • 既存の手動 CustomerGroup 運用は変更しない(並存)。
  • 商流ルール / 表示制御などの下流コードはメンバ生成方法を意識しない。

非ゴール

  • 任意 JavaScript 表現での述語評価。
  • OR / NOT 演算子(初版は AND のみ。OR は別 auto group への分割で表現する)。
  • Customer 側の facet 概念の新設(Vendure 標準で Customer は facetable ではない)。

データモデル

CustomerGroup custom field

field 必須 用途
isAuto boolean yes true の場合 auto group。default false。一度 true に設定すると手動メンバ編集が禁止される
predicate text no JSON 文字列の述語。isAuto=true のときのみ必須。上限 8KB

実装位置: apps/vendure-server/src/config/custom-fields.ts(既存ファイルに CustomerGroup ブロックを追加)。

既存スキーマへの影響

  • Customer, CommercialRule には変更なし。
  • マイグレーションは Vendure custom field の自動マイグレーションに従う(customer_group テーブルへの 2 列追加)。

述語スキーマ

{
  "version": 1,
  "all": [
    { "field": "customerStatus", "op": "in", "values": ["special", "vip"] },
    { "field": "salesRateClassCode", "op": "equals", "values": ["A01"] },
  ],
}

op

op 意味 values の扱い
equals 等価。複数 values 指定時は OR にせず無効 単一要素必須
in いずれか一致 1 件以上
not_in いずれにも一致しない 1 件以上
exists 値あり (NOT NULL かつ空文字でない) 空配列を強制
not_exists 値なし 空配列を強制

field の制約(allowlist)

  • 参照可能な field は 明示 allowlist で制限し、packages/plugins/src/system-integration/smile/smile-field-guard.ts 同等の仕組みを customer-group-predicate.allowlist.ts として新設する。
  • 初版 allowlist:
  • customerStatus
  • salesRateClassCode
  • billingCustomerType
  • collectionMethod
  • directShippingEnabled
  • 名称解決が必要な表示は code を元に SMILE master / enum / 請求先区分マスタ経由で行う (derivative の salesRateClassName / billingCustomerTypeName / collectionMethodLabel / shippingCompany / salesRateClass は廃止済み)
  • 機密性が高い field(パスワード関連、メール、住所等)は allowlist に入れない。

バリデーション

  • Admin API 保存時に JSON schema + allowlist チェックを実施し、失敗時は ErrorResult を返す。
  • values の型は対象 field の type 定義(customer-custom-fields.ts)と一致すること。

同期サービス

配置

  • 新規: packages/plugins/src/standard-extensions/admin-extensions/customer-group-auto-sync.service.ts
  • 述語 → SQL コンパイラを分離: customer-group-predicate.compiler.ts(純粋関数 / 単体テスト対象)

責務と契約

トリガー 動作
CustomerEvent (created / updated) 対象 customer を全 auto group に対して再評価し差分 add/remove
CustomerGroupEvent (predicate 変更時) 対象 group を bulk recompute(全 customer 走査)
Admin mutation recomputeAutoCustomerGroup(id) 同 bulk recompute を明示的に再実行

Bulk recompute の SQL パターン

collection-save-sync.service.ts の TypeORM CTE 差分パターンを踏襲する。概念:

WITH target AS (
  SELECT id FROM customer WHERE <predicate sql>
),
current AS (
  SELECT customerId FROM customer_group_customers_customer WHERE customerGroupId = :id
)
-- INSERT (target - current), DELETE (current - target)

Fail-soft 方針

  • 同期処理中の例外は Sentry に送信するが、CustomerEvent のハンドラから例外を投げない(Customer 保存自体は成功させる)。
  • これにより、auto group の同期失敗が pricing 計算や注文確定をブロックしない。

競合制御

  • auto group 同士は独立評価。Customer が複数の auto group の述語に一致した場合は 全ての該当 group にメンバとして登録される(重複所属を許容)。
  • auto group と手動 group も独立。Customer は両方のグループに同時所属できる。
  • 評価結果は述語コンパイラが純関数のため評価順序に依存しない。

Admin API

種別 名称 振る舞い
Mutation addCustomersToCustomerGroup 対象が isAuto=true の場合 ForbiddenError(auto group is read-only)
Mutation removeCustomersFromCustomerGroup 同上
Mutation recomputeAutoCustomerGroup(id: ID!): Job! bulk recompute を明示実行(CLI / Admin から)
Query previewAutoCustomerGroupMatches(predicate: JSON!): Int! 保存前 dry-run。当該述語にマッチする customer 件数を返す
権限 UpdateCustomerGroup predicate の編集 / recompute / preview すべてこの権限

React Dashboard

  • auto group 詳細画面に predicate editor を追加(field 選択 + 演算子 + 値)。
  • predicate editor 上で previewAutoCustomerGroupMatches を自動呼び出しし、影響件数を表示する。保存時は件数表示があることを必須にする(誤定義防止)。
  • 手動メンバ追加 UI は isAuto=true で非表示。
  • 一覧画面で auto / manual のバッジを表示。

SMILE import との連携

  • packages/plugins/src/system-integration/smile/services/smile-customer-row.parser.ts 経由の Customer upsert は最終的に CustomerService.update を呼び CustomerEvent を発火するため、auto sync が自動追随する。
  • SMILE 側コード変更は不要
  • bulk import 直後の整合確認用に、CLI / Admin から recomputeAutoCustomerGroup を全 auto group に対して順次叩く運用手順を補助スクリプトとして提供(実装フェーズ 6 の範疇)。

監査 / 運用ガード

  • predicate 変更は CustomerGroupEvent 経由で既存の audit log に diff を残す。
  • predicate field allowlist は customer-group-predicate.allowlist.ts に集約し、追加時はレビューとテスト必須。
  • 保存前 previewAutoCustomerGroupMatches は UI 上必須化(誤って広範囲の顧客を auto group に含めるリスク低減)。
  • 同期エラーは Sentry に issue_kind: operational_failure で送信。
  • 想定 auto group 件数: 初期は 10 件以下。これを超えるようなら同期コストを再評価し、job queue 化を検討する。

関連ドキュメントへの追記方針

下記 3 本に、auto group の概要を 3〜5 行で追記し本仕様へリンクする:

  • docs/03-implementation/vendure-plugins/customer-management.md
  • docs/03-implementation/vendure-plugins/customer-visibility.md
  • docs/03-implementation/vendure-plugins/pricing-system.md

追記の趣旨: 「auto group は述語で自動決定され、商流ルールや表示制御からは通常の CustomerGroup ID として参照可能」。

実装フェーズ

  1. Predicate schema + compiler + unit test(field allowlist 含む)
  2. CustomerGroupisAuto / predicate custom field 登録 + Admin 保存時 validator
  3. CustomerGroupAutoSyncService + CustomerEvent / CustomerGroupEvent 購読
  4. Admin resolver の ForbiddenError ガード + recomputeAutoCustomerGroup / previewAutoCustomerGroupMatches mutation/query
  5. React Dashboard predicate editor + dry-run プレビュー UI
  6. SMILE bulk import 後の整合 e2e + 補助スクリプト
  7. ドキュメント整合反映(auto/manual の運用差分)

テスト戦略

Unit

  • customer-group-predicate.compiler.spec.ts で各 op の SQL 生成 / 評価関数を網羅。
  • customer-group-predicate.validator.spec.ts で allowlist 違反 / 型不一致 / values 制約違反を検証。

Integration (apps/vendure-server e2e)

  • Customer create / update → 該当 auto group のメンバ反映
  • predicate 変更 → bulk recompute でメンバが差分更新
  • auto group への addCustomersToCustomerGroup が 403
  • SMILE import 経由 update でもメンバが追従
  • 同期エラー発生時に Customer 保存が成功し、Sentry にイベントが届く

回帰

  • 既存の手動 CustomerGroup 操作と CustomerGroup 参照に影響がないこと。

Critical Files(実装時参照)

  • apps/vendure-server/src/config/custom-fields.ts
  • apps/vendure-server/src/config/custom-fields/customer-custom-fields.ts
  • packages/plugins/src/standard-extensions/admin-extensions/collection-save-sync.service.ts
  • packages/plugins/src/standard-extensions/admin-extensions/admin-extensions.plugin.ts
  • packages/plugins/src/system-integration/smile/services/smile-customer-row.parser.ts
  • packages/plugins/src/rule-engine/visibility/entities.ts

リスク

リスク 対処
誤定義による広範囲影響 保存前 previewAutoCustomerGroupMatches の件数表示を UI で必須化
機密フィールド参照 predicate field allowlist で強制
同期失敗が pricing をブロック fail-soft(Customer 保存自体は成功、Sentry 通知)
想定件数増 同期コストの再評価 → job queue 化 / OR・NOT 演算子の追加を別フェーズで検討