Fab Forward Dev/

DDD ドキュメント

Sales CRM 集約: 認証・権限・企業設定 / Aggregate: IAM & Configuration

バウンデッドコンテキスト: IAM (認証・権限) + Configuration (設定)

知識ベース: knowledge/10-people-operations/sales-team-management.md


集約ルート (Aggregate Roots)

1. User (ユーザー)

User (集約ルート — 認証エンティティ)
├── username: string           (一意識別子)
├── full_name: string          (表示名)
├── email: string              (Payload Auth)
├── role: UserRole             (admin/manager/sales_rep/executive)
├── status: enum               (active/inactive)
├── group: string              (StaffGroups.slug)
├── must_change_password: bool (初回パスワード変更フラグ)
├── password_changed_at: Date  (最終パスワード変更日時)
└── last_login: Date           (最終ログイン日時)
プロパティ必須説明
usernamestring一意、[a-z0-9_]+ パターン
full_namestringユーザーの表示名
emailstringPayload 認証用 (Auth collection)
roleUserRoleadmin / manager / sales_rep / executive
statusselectactive / inactive (デフォルト: active)
groupstringStaffGroups.slug
notesstring管理者用メモ
must_change_passwordboolean初回ログイン時パスワード変更強制
password_changed_atDate最終パスワード変更日時 (読取専用)
last_loginDate最終ログイン日時 (読取専用)
createdBy→ User作成者 (読取専用)
updatedBy→ User最終更新者 (読取専用)

不変条件 (Invariants):

  • username はシステム全体で一意 (beforeChange で重複チェック)
  • username は小文字英数字 + アンダースコアのみ (/^[a-z0-9_]+$/)
  • アカウントロック: 3 回連続ログイン失敗で 15 分間ロック
  • 新規ユーザーは must_change_password = true で作成
  • トークン有効期限: 2 時間

アクセス制御:

操作adminmanager / executivesales_rep
読取全ユーザー全ユーザー自分のみ
作成
更新全ユーザー自分のみ
削除

2. StaffGroup (部署グループ)

StaffGroup (集約ルート)
├── name: string       (表示名)
├── slug: string       (一意識別子)
└── sortOrder: number  (表示順)
プロパティ必須説明
namestringグループ表示名 (例: 営業本部)
slugstring一意識別子 (例: eigyo-honbu)、インデックス付き
sortOrdernumber表示順序 (デフォルト: 0、小さい値が先)

不変条件 (Invariants):

  • slug はシステム全体で一意
  • admin のみ作成・更新・削除可能
  • 読取は全ユーザーに公開

参照元: User.group, Account.group, Contact.group, Deal.group, Activity.group, File.group → 全エンティティが StaffGroups.slug を参照してグループ所属を管理


3. CompanySettings (企業設定)

CompanySettings (集約ルート — シングルトン)
├── company_name: string        (企業名、必須)
├── postal_code: string         (XXX-XXXX)
├── prefecture: string          (都道府県)
├── city: string                (市区町村)
├── street_address: string      (番地・建物名)
├── logo_url: string            (ロゴ画像 URL)
├── business_hours_start: string (HH:MM)
├── business_hours_end: string   (HH:MM)
└── fiscal_year_start_month: number (1-12)
プロパティ必須説明
company_namestring企業名 (最大 100 文字)
postal_codestring郵便番号 (/^\d{3}-\d{4}$/)
prefecturestring都道府県
citystring市区町村
street_addressstring番地・建物名
logo_urlstringロゴ画像 URL (読取専用)
logo_file_keystringロゴファイルストレージキー (読取専用)
business_hours_startstring営業開始時刻 (`/^([01]\d
business_hours_endstring営業終了時刻 (同上)
fiscal_year_start_monthnumber会計年度開始月 (1-12)
createdBy→ User作成者 (読取専用)
updatedBy→ User最終更新者 (読取専用)
versionnumber楽観ロック用

不変条件 (Invariants):

  • シングルトンパターン: レコードは 1 件のみ。2 件目の作成は beforeChange で禁止
  • postal_codeXXX-XXXX 形式のみ
  • business_hours_start < business_hours_end (営業開始は終了より前)
  • business_hours_*HH:MM 形式 (00:0023:59)
  • fiscal_year_start_month は 1-12 の整数
  • admin のみ作成・更新・削除可能
  • 読取は全ユーザーに公開
  • 楽観ロック: バージョン不整合で 409 Conflict

ロールベースアクセス制御 (RBAC) モデル

┌───────────────────────────────────────────────────────┐
│                   User Roles                          │
├───────────┬───────────┬─────────────┬────────────────┤
│   admin   │  manager  │  sales_rep  │   executive    │
├───────────┼───────────┼─────────────┼────────────────┤
│ 全権限    │ 全レポート│ 自分の担当  │ 全レポート     │
│           │ 閲覧+編集 │ 分のみ      │ 閲覧のみ       │
│ ユーザー  │           │             │                │
│ 管理      │ 日報確認  │ 日報提出    │ 日報閲覧       │
│           │           │             │                │
│ 設定変更  │           │             │                │
└───────────┴───────────┴─────────────┴────────────────┘
  • admin: 全操作可能。ユーザー管理、企業設定、カスタムフィールド管理
  • manager: 全データ閲覧可。日報レビュー。配下メンバーの活動確認
  • sales_rep: 自分が担当する取引先・案件・活動・日報のみ操作可能
  • executive: 経営層。全データ閲覧可。編集権限なし (日報閲覧含む)

RBAC データアクセスマトリクス (per-collection × per-role)

各コレクションの CRUD 操作をロール別に定義する。 all = 全レコード、own = salesRep/user が自分のレコードのみ、 = 不可。 スコーピング条件: salesRep: { equals: user.id } または user: { equals: user.id }

営業データコレクション

コレクション操作adminmanagersales_repexecutive
accountsreadallallown (salesRep)all
create
updateallallown (salesRep)
delete
contactsreadallallown (salesRep)all
create
updateallallown (salesRep)
delete
dealsreadallallown (salesRep)all
create
updateallallown (salesRep)
delete
activitiesreadallallown (salesRep)all
create
updateallallown (salesRep)
delete
daily-reportsreadallallown (user)all
create
updateallallown (user)
delete
filesreadallallown (salesRep)all
create
updateallallown (salesRep)
delete

管理コレクション

コレクション操作adminmanagersales_repexecutive
usersreadallallown (id)all
create
updateallown (id)own (id)
delete
staff_groupsreadallallallall
create
update
delete
company_settingsreadallallallall
create
update
delete
custom_field_configsread
create
update
delete

スコーピングパターン

営業データコレクション (accounts, contacts, deals, activities, files) の sales_rep スコーピングは以下の共通パターンに従う:

// access.read for sales_rep
({ req: { user } }) => {
  if (user.role === 'sales_rep') {
    return { salesRep: { equals: user.id } };
  }
  return true; // admin, manager, executive → all
}

参考: daily-reportsuser フィールドでこのパターンを使用。 営業データ用には salesRep フィールドでの同等実装が必要。