コンテンツにスキップ

顧客管理プラグイン (Customer Management Plugin)

概要

Ritsubiの複雑な顧客管理要件に対応するVendureカスタムプラグイン。動的な顧客ステータス管理と商品アクセス制御を実現します。

親子アカウント(請求先コード / 得意先コード)の実装状況

  • 親子判定は billingCustomerCode(請求先コード)を基準に行い、customerCode はSMILEで発番された得意先コードとして扱う。親(請求先)の場合は billingCustomerCodecustomerCode と同一値になることを前提とする。Customer.customFields の customerCode / billingCustomerCode / roleType(HEAD/BRANCH/STAFF) / branchType(本店/支店/通常)で表現する。
  • Order.customFields に billingCustomerCode を追加し、請求単位を注文に保持できる。
  • Shop API は CustomerExtensionsPlugincustomerHierarchy を提供し、ログイン中の顧客を起点に親子情報を返す。
  • Vendure Dashboard には請求先コードの手動紐付け・編集UIを設けない。請求先コードはSMILEを正本とし、修正はSMILE側で実施する。
  • マイグレーション 20251217093000_replace_account_with_billing_code.ts で Account エンティティ方式から billingCustomerCode 方式へ移行済み。

互換性レンジ

  • Vendure: 3.5.x(apps/vendure-server は ^3.5.2 を使用)
  • Node.js: >=24.7.0(apps/vendure-server/package.json の engines に準拠)

パッケージ情報

  • パッケージ名: @ritsubi/customer-management-plugin
  • パス: /packages/plugins/customer-management
  • エントリーポイント: src/index.ts

実装機能

1. 顧客ステータス管理

顧客ステータスは CustomerStatusEntity として DB テーブルに管理される動的エンティティです。ステータス数は固定ではなく、CustomerStatusService.initializeDefaultStatuses() で初期値が設定されます。CUSTOMER_STATUS@ritsubi/domain)に定義された運用定数が主なステータスコードです。

顧客への割り当ては assignStatusToCustomer / removeStatusFromCustomer / bulkUpdateCustomerStatuses で行います。

2. 親子アカウント(顧客階層)

親子判定は billingCustomerCode(請求先コード)を基準に行い、customerCode はSMILEで発番された得意先コードとして扱います。CustomerHierarchyService が階層解決ロジックを提供し、Shop API の customerHierarchy クエリとして公開されます。

3. 顧客認証補助

CustomerCredentialService / CustomerPasswordAdminService が管理者によるパスワード設定・更新をサポートします。

技術仕様

プラグイン設定オプション

export interface CustomerManagementPluginOptions {
  /**
   * 階層的顧客ステータス管理を有効にする
   */
  enableStatusHierarchy?: boolean;

  /**
   * カテゴリアクセス制御を有効にする
   */
  enableCategoryAccess?: boolean;

  /**
   * 管理画面での一括ステータス更新を有効にする
   */
  enableBulkStatusUpdate?: boolean;

  /**
   * ステータス変更履歴を記録する
   */
  enableStatusHistory?: boolean;
}

プラグイン初期化

import { CustomerManagementPlugin } from "@ritsubi/customer-management-plugin";

// Vendure設定
export const config: VendureConfig = {
  plugins: [
    CustomerManagementPlugin.init({
      enableStatusHierarchy: true,
      enableCategoryAccess: true,
      enableBulkStatusUpdate: true,
      enableStatusHistory: true,
    }),
  ],
};

サービス

CustomerStatusService

顧客ステータスの管理と検証を行うコアサービス。

主要メソッド:

  • getCustomerStatuses(ctx, customerId): 顧客のステータス一覧を取得
  • getCustomerPrimaryStatus(ctx, customerId): 顧客のプライマリステータスを取得
  • updateCustomerStatus(ctx, id, input): ステータス情報を更新
  • assignStatusToCustomer(ctx, customerId, statusCode): ステータスを割り当て
  • removeStatusFromCustomer(ctx, customerId, statusCode): ステータスを解除
  • bulkUpdateCustomerStatuses(ctx, customerIds, statusCode): 複数顧客への一括適用

対応する要件

要件定義書との対応

  • 3.1.1 顧客ステータス管理: CustomerStatusEntity による動的ステータス管理と階層的権限管理
  • 3.1.2 商品カテゴリ体系・アクセス制御: 顧客ステータスに応じたカタログ制御

使用例

Storefront での顧客階層取得

import { useQuery } from "@tanstack/react-query";
import { graphql } from "@/__generated__/gql";
import { vendureQueryClient } from "@/lib/vendure-fetch-client";

const GET_CUSTOMER_HIERARCHY = graphql(`
  query GetCustomerHierarchy {
    customerHierarchy {
      parent {
        id
        firstName
        lastName
        customFields {
          customerCode
          billingCustomerCode
          roleType
          branchType
        }
      }
      self {
        id
        firstName
        lastName
        customFields {
          customerCode
          billingCustomerCode
          roleType
          branchType
        }
      }
      children {
        id
        firstName
        lastName
      }
    }
  }
`);

const { data } = useQuery({
  queryKey: ["customerHierarchy"],
  queryFn: () => vendureQueryClient(GET_CUSTOMER_HIERARCHY, {}),
});

データモデル

カスタムフィールド

このプラグインが利用する主要な Customer カスタムフィールド(customer-custom-fields.ts で定義):

  • Customer.customFields.customerCode - SMILE 得意先コード
  • Customer.customFields.billingCustomerCode - 請求先(親)得意先コード
  • Customer.customFields.roleType - HEAD / BRANCH / STAFF
  • Customer.customFields.branchType - 本店 / 支店 / 通常
  • Customer.customFields.directShippingBlocked - true の場合は直送出荷を禁止(opt-out フラグ)
  • Customer.customFields.allowedShippingModes - 許可された出荷モード一覧

開発ガイド

ローカル開発

# プラグインディレクトリへ移動
cd packages/plugins/customer-management

# 依存関係インストール
pnpm install

# ビルド
pnpm build

# テスト実行
pnpm test

型定義

// CustomerStatusEntity(DBテーブル管理、動的)
export interface CustomerStatus {
  code: string;
  name: string;
  accessLevel: number;
  canViewPrices: boolean;
  canPurchase: boolean;
}

セキュリティ考慮事項

  1. アクセス制御の徹底: すべてのカテゴリアクセスはバックエンドで検証
  2. ステータス変更権限: ステータス変更は管理者権限が必要
  3. 履歴の改ざん防止: ステータス変更履歴は追記のみ可能
  4. GraphQL認証: すべてのクエリ・ミューテーションで認証を必須化

パフォーマンス最適化

  • キャッシング: 顧客ステータスとカテゴリアクセス情報をRedisにキャッシュ
  • バッチ処理: 一括ステータス更新はトランザクションで処理
  • 遅延ロード: カテゴリ一覧は必要に応じて動的にロード

トラブルシューティング

よくある問題

問題: 顧客がカテゴリにアクセスできない

  • 原因: ステータスの権限レベルが不足
  • 解決: 管理画面で適切なステータスに更新

問題: ステータス変更が反映されない

  • 原因: キャッシュのTTLが長すぎる
  • 解決: キャッシュをクリアまたはTTLを調整

CustomerGroup の auto / manual 並存

CustomerGroup には手動でメンバを管理する従来運用に加え、auto group(述語ベース)が利用できる。auto group は CustomerGroup.customFields.isAuto = true を持ち、predicate JSON で指定した条件式(Customer custom field の allowlist + equals|in|not_in|exists|not_exists)により、Customer 保存時に物理メンバが自動更新される。SMILE import 経由の更新も CustomerEvent を通じて同じ同期パスを通る。auto group は手動メンバ編集が禁止され、表示制御や商流ルールからは通常の CustomerGroup ID として参照可能。詳細は docs/specifications/2026-05-predicate-based-customer-group.md

関連ドキュメント

今後の拡張予定

  • ステータス自動昇格機能(購入実績ベース)
  • カテゴリアクセス申請ワークフロー
  • ステータス期限管理(年間契約など)
  • 詳細な権限カスタマイズUI