Sales CRM 集約: レポート・分析 / Aggregate: Reporting
バウンデッドコンテキスト: Operations (オペレーション)
→ 知識ベース:
knowledge/07-sales-management/performance-management.md(KPI) → 知識ベース:knowledge/06-crm-fundamentals/crm-strategy.md(パイプライン分析)
設計方針
Reporting は 読み取り専用のプロジェクション (Read Model) であり、新しいコレクションは作成しない。 既存の Deal, Activity, Account データを集計・投影する API エンドポイントとして実装する。
┌──────────┐ ┌──────────┐ ┌──────────────────┐
│ Deal │────▶│ │────▶│ PipelineReport │
│ │ │ 集計 │ │ (読取専用投影) │
└──────────┘ │ ロジック │ └──────────────────┘
┌──────────┐ │ │ ┌──────────────────┐
│ Activity │────▶│ │────▶│ ActivitySummary │
│ │ │ │ │ (読取専用投影) │
└──────────┘ └──────────┘ └──────────────────┘
集約ルート (Read Models)
1. PipelineReport (パイプラインレポート)
ステージ別・担当者別のパイプライン集計を提供する読み取り専用プロジェクション。
PipelineReport (読取専用プロジェクション)
├── period: DateRange (集計期間)
├── stageBreakdown[] (ステージ別集計)
│ ├── stage: DealType (lead / inquiry / negotiation)
│ ├── count: number (案件数)
│ ├── totalAmount: number (合計金額)
│ └── weightedAmount: number (加重金額)
├── statusBreakdown[] (ステータス別集計)
│ ├── status: DealStatus (open / won / lost)
│ ├── count: number (案件数)
│ └── totalAmount: number (合計金額)
├── repBreakdown[] (担当者別集計)
│ ├── salesRep: → User (営業担当)
│ ├── count: number (案件数)
│ ├── totalAmount: number (合計金額)
│ └── weightedAmount: number (加重金額)
├── totalDeals: number (全案件数)
├── totalAmount: number (合計金額)
├── totalWeightedAmount: number (加重金額合計)
└── winRate: number (成約率: won / (won + lost))
| プロパティ | 型 | 説明 |
|---|---|---|
| period | DateRange | 集計対象期間 (開始日-終了日) |
| stageBreakdown | Array | DealType 別の案件数・金額集計 |
| statusBreakdown | Array | DealStatus 別の案件数・金額集計 |
| repBreakdown | Array | 営業担当者別の案件数・金額・加重金額 |
| totalDeals | number | 対象期間の全案件数 |
| totalAmount | number | 合計金額 (円) |
| totalWeightedAmount | number | 加重金額合計 (amount x probability / 100) |
| winRate | number | 成約率 (0-100%) |
ルール:
- PipelineReport は永続化しない。リクエスト時に Deal データから動的に集計
weightedAmount=amountxprobability/ 100winRate=won件数/ (won件数+lost件数) x 100。open のみの場合は 0- 期間フィルタ:
expectedCloseDateまたはcreatedAtで絞り込み - グループフィルタ:
group(StaffGroups.slug) で絞り込み可能
API エンドポイント (計画):
GET /api/reports/pipeline— パイプライン集計- クエリパラメータ:
startDate,endDate,group,salesRep
- クエリパラメータ:
2. ActivitySummary (活動サマリー)
活動タイプ別・担当者別の活動集計を提供する読み取り専用プロジェクション。
ActivitySummary (読取専用プロジェクション)
├── period: DateRange (集計期間)
├── typeBreakdown[] (タイプ別集計)
│ ├── activityType: ActivityType (call / meeting / etc.)
│ ├── count: number (件数)
│ └── totalDuration: number (合計時間・分)
├── repBreakdown[] (担当者別集計)
│ ├── salesRep: → User (営業担当)
│ ├── count: number (件数)
│ └── totalDuration: number (合計時間・分)
├── dailyTrend[] (日別推移)
│ ├── date: Date (日付)
│ └── count: number (件数)
├── totalActivities: number (全活動件数)
└── totalDuration: number (全合計時間・分)
| プロパティ | 型 | 説明 |
|---|---|---|
| period | DateRange | 集計対象期間 (開始日-終了日) |
| typeBreakdown | Array | ActivityType 別の件数・合計時間 |
| repBreakdown | Array | 営業担当者別の件数・合計時間 |
| dailyTrend | Array | 日別の活動件数推移 |
| totalActivities | number | 対象期間の全活動件数 |
| totalDuration | number | 全合計所要時間 (分) |
ルール:
- ActivitySummary は永続化しない。リクエスト時に Activity データから動的に集計
- 期間フィルタ:
activityDateで絞り込み - グループフィルタ:
group(StaffGroups.slug) で絞り込み可能 totalDurationはdurationが設定されている活動のみ合算
API エンドポイント (計画):
GET /api/reports/activities— 活動サマリー- クエリパラメータ:
startDate,endDate,group,salesRep,activityType
- クエリパラメータ:
RBAC
レポートのアクセスは以下のロールモデルに従う:
| ロール | パイプラインレポート | 活動サマリー |
|---|---|---|
| admin | 全データ | 全データ |
| manager | 全データ (チーム管理用) | 全データ (チーム管理用) |
| sales_rep | 自分の担当分のみ | 自分の活動のみ |
| executive | 全データ (閲覧専用) | 全データ (閲覧専用) |
レポートの RBAC は、元データ (Deal, Activity) の RBAC スコーピングを集計時に適用する。 → 参照:
iam.md§RBAC データアクセスマトリクス
関連図
┌──────────┐ ┌──────────────────┐
│ Deal │─集計───▶ │ PipelineReport │
│ (案件) │ │ (ステージ×金額× │
│ │ │ 担当者) │
└──────────┘ └──────────────────┘
┌──────────┐ ┌──────────────────┐
│ Activity │─集計───▶ │ ActivitySummary │
│ (活動) │ │ (タイプ×件数× │
│ │ │ 担当者) │
└──────────┘ └──────────────────┘