Fab Forward Dev/

DDD ドキュメント

Sales CRM 集約: 活動管理 / Aggregate: Activity Tracking

バウンデッドコンテキスト: Sales Pipeline (営業パイプライン) + Operations (オペレーション)

知識ベース: knowledge/10-people-operations/sales-team-management.md (日報文化) → 知識ベース: knowledge/07-sales-management/performance-management.md (KPI)


集約ルート (Aggregate Roots)

1. Activity (活動)

Activity (集約ルート)
├── activityType: ActivityType  (電話/面談/メール等)
├── subject: string             (件名)
├── description: string         (詳細)
├── activityDate: Date          (活動日時)
├── duration: number            (所要時間・分)
├── account: → Account          (必須参照)
├── contact: → Contact          (任意参照)
├── deal: → Deal                (任意参照)
└── salesRep: → User            (営業担当)
プロパティ必須説明
activityTypeActivityTypecall / meeting / inquiry / email / email_received / other
subjectstring件名 (インデックス付き、255 文字以内)
descriptionstring詳細メモ (2000 文字以内)
activityDateDate活動日時 (インデックス付き)
durationnumber所要時間 (分、0 より大きい値)
account→ Account関連取引先 (インデックス付き)
contact→ Contact関連担当者 (任意)
deal→ Deal関連案件 (任意)
groupstringStaffGroups.slug
salesRep→ User活動実施者
createdBy→ User作成者 (読取専用)
updatedBy→ User最終更新者 (読取専用)
versionnumber楽観ロック用

不変条件 (Invariants):

  • account は必須。活動は必ず取引先に紐付く
  • activityType は ACTIVITY_TYPE_VALUES の値のみ (beforeValidate で検証)
  • subject は空文字不可 (beforeValidate で検証)
  • duration が指定された場合は正の数でなければならない (duration > 0)
  • data が null/undefined の場合は 400 エラー
  • 楽観ロック: バージョン不整合で 409 Conflict

バリデーション (Zod — activitySchema.ts):

{
  activityType: enum(ACTIVITY_TYPE_VALUES),     // 必須
  subject: string().min(1).max(255),            // 必須、1-255文字
  accountId: string().min(1),                   // 必須
  activityDate: string().min(1),                // 必須
  contactId: string().optional(),               // 任意
  dealId: string().optional(),                  // 任意
  duration: number().positive().optional(),     // 任意、正の数
  description: string().max(2000).optional(),   // 任意、2000文字以内
}

2. DailyReport (日報)

DailyReport (集約ルート)
├── user: → User              (報告者、必須)
├── reportDate: Date          (報告日、必須)
├── dealSummaries[]           (案件サマリー配列)
│   ├── deal: → Deal          (案件参照)
│   ├── probability: number   (更新された確度)
│   ├── status: string        (現在のステータス)
│   └── comment: string       (コメント)
├── activitySummaries[]       (活動サマリー配列)
│   ├── activity: → Activity  (活動参照)
│   └── outcome: string       (結果)
├── notes: string             (所感・メモ)
└── nextActions: string       (次のアクション)
プロパティ必須説明
user→ User報告者 (インデックス付き)
reportDateDate報告対象日 (インデックス付き)
dealSummariesArray案件進捗サマリー
activitySummariesArray活動結果サマリー
notesstring所感・メモ (textarea)
nextActionsstring次のアクション (textarea)
createdBy→ User作成者 (読取専用)
updatedBy→ User最終更新者 (読取専用)
versionnumber楽観ロック用

不変条件 (Invariants):

  • user + reportDate はユニーク制約。同一ユーザーの同日日報は 1 件のみ
    • beforeValidatecreate 時に既存チェック
    • 重複時エラー: この日付の日報は既に存在します
  • user が未指定の場合は req.user から自動設定 (beforeChange)
  • 楽観ロック: version フィールドで更新競合を防止

アクセス制御:

操作admin / manager / executivesales_rep
読取全日報閲覧可自分の日報のみ
作成
更新全日報編集可 (admin/manager)自分の日報のみ
削除admin のみ

トランザクション境界

操作同一トランザクション結果整合性 (Eventual)
Activity 作成Activity のみAccount.lastActivityAt, activityCount の更新
Activity 更新Activity のみ
DailyReport 作成DailyReport のみ (dealSummaries, activitySummaries 含む)
DailyReport 更新DailyReport のみ

日報の営業管理上の位置づけ

┌──────────┐     参照      ┌──────────┐
│ Activity │◀──────────────│  Daily   │
│ (個別の  │               │  Report  │
│  営業活動)│               │ (日次    │
└──────────┘               │  集計)   │
      │                    └──────────┘
      │ 更新                     │
      ▼                         ▼
┌──────────┐              ┌──────────┐
│ Account  │              │ Manager  │
│ 活動指標 │              │ Review   │
│ (自動計算)│              │ (確認)   │
└──────────┘              └──────────┘

日報は日本の営業文化における重要な管理ツール:

  • 営業担当 → 1 日の活動を振り返り、次の行動を計画
  • マネージャー → 部下の活動量・案件進捗を確認
  • 経営層 → パイプライン全体の動きを把握

知識ベース参照: knowledge/09-japanese-sales-culture/business-culture.md (日報文化)


カレンダー連携 / Calendar Sync

ベンチマーク: benchmark_translead/設定/カレンダー連携設定.html TransLead は Google Calendar / Outlook との双方向連携を提供。

CalendarSync (カレンダー同期設定)

Activity の外部カレンダーへのアウトバウンド同期を管理する設定エンティティ。

CalendarSync (エンティティ)
├── user: → User                (対象ユーザー)
├── provider: CalendarProvider  (google / outlook)
├── isEnabled: boolean          (有効フラグ)
├── syncActivityTypes[]         (同期対象活動タイプ)
├── authToken: string           (認証トークン — 暗号化保存)
├── lastSyncAt: Date            (最終同期日時)
├── syncStatus: SyncStatus      (connected / error / disconnected)
└── errorMessage: string        (エラーメッセージ)
プロパティ必須説明
user→ User対象ユーザー
providerCalendarProvidergoogle / outlook
isEnabledboolean有効フラグ (デフォルト: false)
syncActivityTypesActivityType[]同期対象活動タイプ (空 = 全タイプ)
authTokenstringOAuth2 認証トークン (暗号化保存)
lastSyncAtDate最終同期日時
syncStatusSyncStatusconnected / error / disconnected
errorMessagestring直近の同期エラーメッセージ

不変条件 (Invariants):

  • user + provider の組み合わせはユニーク
  • isEnabled=true の場合、authToken は必須
  • authToken は暗号化して保存 (PII)
  • 各ユーザーが自身の設定のみ管理可能

同期フロー:

Activity 作成/更新
  │
  ├─ CalendarSync.isEnabled = true?
  │   └─ activityType ∈ syncActivityTypes?
  │       └─ 外部カレンダー API に イベント作成/更新
  │
  └─ CalendarSync.isEnabled = false → 同期しない

設計判断: 初期実装はアウトバウンド同期のみ (Activity → Calendar)。 外部カレンダーからの変更を Activity に反映するインバウンド同期は将来検討。


メール連携 / Email Integration

ベンチマーク: benchmark_translead/設定/メール取り込み設定.html, メール配信設定.html TransLead はメール受信取り込み + メール配信の両方を提供。

EmailCapture (メール取り込み設定)

受信メールを自動的に Activity として登録する設定エンティティ。

EmailCapture (エンティティ)
├── user: → User                (対象ユーザー)
├── isEnabled: boolean          (有効フラグ)
├── emailAddress: string        (取り込み対象メールアドレス)
├── protocol: EmailProtocol     (imap / pop3)
├── host: string                (メールサーバーホスト)
├── port: number                (ポート番号)
├── useSsl: boolean             (SSL/TLS 使用)
├── authCredentials: string     (認証情報 — 暗号化保存)
├── autoActivityType: ActivityType  (自動設定する活動タイプ)
├── lastFetchAt: Date           (最終取得日時)
├── fetchStatus: FetchStatus    (connected / error / disconnected)
└── errorMessage: string        (エラーメッセージ)
プロパティ必須説明
user→ User対象ユーザー
isEnabledboolean有効フラグ (デフォルト: false)
emailAddressstring取り込み対象メールアドレス
protocolEmailProtocolimap / pop3
hoststringメールサーバーホスト名
portnumberポート番号
useSslbooleanSSL/TLS 使用 (デフォルト: true)
authCredentialsstring認証情報 (暗号化保存)
autoActivityTypeActivityType自動設定する活動タイプ (デフォルト: email_received)
lastFetchAtDate最終メール取得日時
fetchStatusFetchStatusconnected / error / disconnected
errorMessagestring直近のエラーメッセージ

不変条件 (Invariants):

  • emailAddress は有効なメール形式
  • port は 1-65535 の範囲
  • authCredentials は暗号化して保存 (PII)
  • 各ユーザーが自身の設定のみ管理可能

取り込みフロー:

メール受信 (定期ポーリング)
  │
  ├─ EmailCapture.isEnabled = true?
  │   └─ メールサーバーに接続
  │       └─ 新着メール取得
  │           └─ 送信者メールアドレスで Contact を検索
  │               ├─ Contact 見つかった → Activity 自動作成
  │               │   (account = Contact.account, contact = Contact)
  │               └─ Contact 見つからない → スキップ or 未割当キューに追加
  │
  └─ EmailCapture.isEnabled = false → 取り込みしない

設計判断: 初期実装はインバウンド取り込みのみ。 メール配信 (アウトバウンド) は将来検討 (ドメイン設定、配信リスト管理が必要)。