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 (営業担当)
| プロパティ | 型 | 必須 | 説明 |
|---|---|---|---|
| activityType | ActivityType | ✓ | call / meeting / inquiry / email / email_received / other |
| subject | string | ✓ | 件名 (インデックス付き、255 文字以内) |
| description | string | 詳細メモ (2000 文字以内) | |
| activityDate | Date | ✓ | 活動日時 (インデックス付き) |
| duration | number | 所要時間 (分、0 より大きい値) | |
| account | → Account | ✓ | 関連取引先 (インデックス付き) |
| contact | → Contact | 関連担当者 (任意) | |
| deal | → Deal | 関連案件 (任意) | |
| group | string | StaffGroups.slug | |
| salesRep | → User | 活動実施者 | |
| createdBy | → User | 作成者 (読取専用) | |
| updatedBy | → User | 最終更新者 (読取専用) | |
| version | number | 楽観ロック用 |
不変条件 (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 | ✓ | 報告者 (インデックス付き) |
| reportDate | Date | ✓ | 報告対象日 (インデックス付き) |
| dealSummaries | Array | 案件進捗サマリー | |
| activitySummaries | Array | 活動結果サマリー | |
| notes | string | 所感・メモ (textarea) | |
| nextActions | string | 次のアクション (textarea) | |
| createdBy | → User | 作成者 (読取専用) | |
| updatedBy | → User | 最終更新者 (読取専用) | |
| version | number | 楽観ロック用 |
不変条件 (Invariants):
user + reportDateはユニーク制約。同一ユーザーの同日日報は 1 件のみbeforeValidateでcreate時に既存チェック- 重複時エラー:
この日付の日報は既に存在します
userが未指定の場合はreq.userから自動設定 (beforeChange)- 楽観ロック:
versionフィールドで更新競合を防止
アクセス制御:
| 操作 | admin / manager / executive | sales_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/設定/カレンダー連携設定.htmlTransLead は 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 | ✓ | 対象ユーザー |
| provider | CalendarProvider | ✓ | google / outlook |
| isEnabled | boolean | 有効フラグ (デフォルト: false) | |
| syncActivityTypes | ActivityType[] | 同期対象活動タイプ (空 = 全タイプ) | |
| authToken | string | OAuth2 認証トークン (暗号化保存) | |
| lastSyncAt | Date | 最終同期日時 | |
| syncStatus | SyncStatus | connected / error / disconnected | |
| errorMessage | string | 直近の同期エラーメッセージ |
不変条件 (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,メール配信設定.htmlTransLead はメール受信取り込み + メール配信の両方を提供。
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 | ✓ | 対象ユーザー |
| isEnabled | boolean | 有効フラグ (デフォルト: false) | |
| emailAddress | string | ✓ | 取り込み対象メールアドレス |
| protocol | EmailProtocol | ✓ | imap / pop3 |
| host | string | ✓ | メールサーバーホスト名 |
| port | number | ✓ | ポート番号 |
| useSsl | boolean | SSL/TLS 使用 (デフォルト: true) | |
| authCredentials | string | ✓ | 認証情報 (暗号化保存) |
| autoActivityType | ActivityType | 自動設定する活動タイプ (デフォルト: email_received) | |
| lastFetchAt | Date | 最終メール取得日時 | |
| fetchStatus | FetchStatus | connected / error / disconnected | |
| errorMessage | string | 直近のエラーメッセージ |
不変条件 (Invariants):
emailAddressは有効なメール形式portは 1-65535 の範囲authCredentialsは暗号化して保存 (PII)- 各ユーザーが自身の設定のみ管理可能
取り込みフロー:
メール受信 (定期ポーリング)
│
├─ EmailCapture.isEnabled = true?
│ └─ メールサーバーに接続
│ └─ 新着メール取得
│ └─ 送信者メールアドレスで Contact を検索
│ ├─ Contact 見つかった → Activity 自動作成
│ │ (account = Contact.account, contact = Contact)
│ └─ Contact 見つからない → スキップ or 未割当キューに追加
│
└─ EmailCapture.isEnabled = false → 取り込みしない
設計判断: 初期実装はインバウンド取り込みのみ。 メール配信 (アウトバウンド) は将来検討 (ドメイン設定、配信リスト管理が必要)。