# 機能設計書 43-タイムトラッキング

## 概要

本ドキュメントは、GitLabにおけるタイムトラッキング（作業時間記録）機能の設計仕様を定義する。タイムトラッキングは、イシューやマージリクエストに対する作業時間の見積もりと実績を記録し、プロジェクトの工数管理を支援する機能である。

### 本機能の処理概要

**業務上の目的・背景**：ソフトウェア開発プロジェクトでは、作業工数の見積もりと実績の把握が重要である。タイムトラッキング機能は、各タスク（イシュー/マージリクエスト）に対する見積もり時間（time_estimate）と実際に費やした時間（time_spent）を記録することで、プロジェクトマネージャーが工数を正確に把握し、将来の計画立案に活用できるようにする。また、チームメンバーの作業負荷の可視化や、クライアントへの請求根拠としても利用される。

**機能の利用シーン**：
- プロジェクトマネージャーがタスクの見積もり時間を設定する場面
- 開発者が作業完了後に実作業時間を記録する場面
- チームリーダーがメンバーの作業時間を集計・分析する場面
- スプリント終了時に予実対比を行う場面
- タイムシート作成やクライアント請求の根拠として利用する場面

**主要な処理内容**：
1. 見積もり時間の設定（/estimate コマンドまたはAPI）
2. 作業時間の追加（/spend コマンドまたはAPI）
3. 作業時間のリセット（/remove_time_spent コマンド）
4. 見積もり時間のリセット（/remove_estimate コマンド）
5. タイムログ（作業時間記録）の一覧表示
6. タイムログの削除
7. 合計作業時間・見積もり時間の表示

**関連システム・外部連携**：
- クイックアクション（/spend、/estimate）によるスラッシュコマンド連携
- GraphQL APIによるタイムログ操作
- Webhook連携（時間更新時のイベント通知）

**権限による制御**：
- 見積もり/作業時間の設定：イシュー/MRの編集権限（Reporter以上）
- タイムログの削除：自分が作成したタイムログ、またはMaintainer以上の権限

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 49 | 課題詳細 | 主機能 | 作業時間の記録 |
| 311 | タイムログ一覧 | 主機能 | タイムログ一覧表示 |

## 機能種別

CRUD操作 / 計算処理 / データ集計

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| time_spent | Integer/String | Yes | 作業時間（秒数または"1h 30m"形式） | 0以外、合計が1年を超えないこと |
| spent_at | DateTime | No | 作業を行った日時 | 未来日時は不可 |
| summary | String | No | 作業内容の要約 | 最大255文字 |
| time_estimate | Integer/String | No | 見積もり時間 | 0以上 |

### 入力データソース

- クイックアクション（/spend 1h、/estimate 3h）
- 画面入力（タイムトラッキングウィジェット）
- REST API / GraphQL API

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | タイムログID |
| time_spent | Integer | 作業時間（秒） |
| spent_at | DateTime | 作業日時 |
| summary | String | 作業内容の要約 |
| user_id | Integer | 記録したユーザーのID |
| issue_id | Integer | 関連イシューID（イシューの場合） |
| merge_request_id | Integer | 関連MRのID（MRの場合） |
| created_at | DateTime | 作成日時 |
| total_time_spent | Integer | 合計作業時間（秒） |
| time_estimate | Integer | 見積もり時間（秒） |
| human_time_spent | String | 人間が読める形式の合計作業時間 |
| human_time_estimate | String | 人間が読める形式の見積もり時間 |

### 出力先

- 画面表示（イシュー/MR詳細のタイムトラッキングウィジェット）
- JSON/GraphQL レスポンス
- システムノート

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ クイックアクション解析またはAPI受信
2. 認可チェック
   └─ create_timelog / admin_timelog 権限を検証
3. バリデーション
   ├─ spent_atが未来でないことを確認
   ├─ time_spentが0でないことを確認
   └─ 合計時間が1年を超えないことを確認
4. タイムログ作成/更新
   └─ timelogs テーブルへのレコード操作
5. イシュー/MRの更新
   └─ touch（updated_at更新）
6. システムノート作成
   └─ 時間の追加/削除を記録
7. Webhook実行
   └─ 'update' イベントの発行
8. レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{入力種別}
    B -->|クイックアクション| C[/spend or /estimate 解析]
    B -->|API| D[パラメータ取得]

    C --> E{操作種別}
    D --> E

    E -->|時間追加| F[CreateService実行]
    E -->|時間リセット| G[reset_spent_time]
    E -->|見積もり設定| H[time_estimate更新]

    F --> I{バリデーション}
    I -->|成功| J[Timelog作成]
    I -->|未来日時| K[エラー: 未来日時]
    I -->|ゼロ時間| L[エラー: ゼロ不可]
    I -->|上限超過| M[エラー: 1年超過]

    J --> N[SystemNote作成]
    N --> O[Webhook発行]
    O --> P[終了]

    G --> Q[負の時間でTimelog作成]
    Q --> N

    H --> R[Issue/MR更新]
    R --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-43-001 | 最大合計時間 | 1つのイシュー/MRの合計作業時間は1年（31,557,600秒）を超えられない | 時間追加時 |
| BR-43-002 | 未来日時禁止 | spent_atは現在日時以前でなければならない | 時間追加時 |
| BR-43-003 | ゼロ時間禁止 | time_spentに0を指定することはできない | 時間追加時 |
| BR-43-004 | 減算上限 | 減算する時間は現在の合計時間を超えられない | 時間減算時 |
| BR-43-005 | 見積もり非負 | time_estimateは0以上でなければならない | 見積もり設定時 |
| BR-43-006 | 排他所属 | Timelogはissueまたはmerge_requestのいずれか一方にのみ関連付けられる | 作成時 |

### 計算ロジック

**時間形式の変換**：
- 入力形式："1mo 2w 3d 4h 5m" のようなヒューマンリーダブル形式
- 内部形式：秒数（Integer）
- 変換ルール：
  - 1mo（月） = 4週間 = 160時間（work_hours_per_week設定依存）
  - 1w（週） = 40時間（デフォルト）
  - 1d（日） = 8時間（デフォルト）
  - 1h（時間） = 60分
  - 1m（分） = 60秒

**合計時間の算出**：
- total_time_spent = SUM(timelogs.time_spent) ただし上限・下限を適用
- 下限：-31,557,600秒
- 上限：31,557,600秒

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 時間追加 | timelogs | INSERT | 新規タイムログレコードの挿入 |
| 時間追加 | issues/merge_requests | UPDATE | updated_at の更新（touch） |
| 時間追加 | notes | INSERT | システムノートの追加 |
| 時間リセット | timelogs | INSERT | 負の時間でタイムログ挿入 |
| 時間削除 | timelogs | DELETE | タイムログレコードの削除 |
| 見積もり設定 | issues/merge_requests | UPDATE | time_estimate カラムの更新 |

### テーブル別操作詳細

#### timelogs

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | time_spent | 秒数（正または負） | 必須 |
| INSERT | spent_at | 作業日時 | デフォルト：現在日時 |
| INSERT | summary | 作業内容の要約 | 最大255文字 |
| INSERT | user_id | 記録ユーザーのID | 必須 |
| INSERT | issue_id | 関連イシューID | issue/MRいずれか必須 |
| INSERT | merge_request_id | 関連MRのID | issue/MRいずれか必須 |
| INSERT | project_id | プロジェクトID | 自動設定 |
| INSERT | namespace_id | 名前空間ID | 自動設定 |
| DELETE | id | 削除対象のタイムログID | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | イシュー/MRが存在しない、または権限がない | 対象の存在と権限を確認 |
| 404 | Validation Error | spent_atが未来日時 | 過去または現在の日時を指定 |
| 404 | Validation Error | time_spentが0 | 0以外の時間を指定 |
| 422 | Validation Error | 合計時間が1年を超える | 追加時間を調整 |
| 422 | Validation Error | 減算時間が合計を超える | 減算時間を調整 |
| 400 | Server Error | タイムログの削除に失敗 | 再試行 |

### リトライ仕様

- 特別なリトライ処理は実装されていない

## トランザクション仕様

- タイムログ作成・削除は単一レコード操作のため暗黙のトランザクション
- 時間リセットは負の時間でタイムログを作成（論理削除ではなく追加）

## パフォーマンス要件

- タイムログ追加：500ms以内
- 合計時間計算：キャッシュ（strong_memoize）を使用、リロード時にクリア

## セキュリティ考慮事項

- **認証・認可**：すべての操作で認証必須、権限チェック実施
- **自分のタイムログ削除**：作成者は自分のタイムログを削除可能
- **他者のタイムログ削除**：Maintainer以上の権限が必要

## 備考

- 時間形式のパースはGitlab::TimeTrackingFormatterで実行
- グローバルタイムトラッキングレポート機能はフィーチャーフラグで制御（global_time_tracking_report）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

タイムログのデータモデルとタイムトラッカブルconcernを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | timelog.rb | `app/models/timelog.rb` | Timelogモデルの定義、バリデーション、関連を理解 |
| 1-2 | time_trackable.rb | `app/models/concerns/time_trackable.rb` | Issue/MRに含まれるタイムトラッキングconcern |

**読解のコツ**:
- `MAX_TOTAL_TIME_SPENT = 31557600`（6行目）で最大合計時間を定義
- `validates_with ExactlyOnePresentValidator, fields: [:issue, :merge_request]`（17行目）でissue/MRの排他チェック
- `check_total_time_spent_is_within_range`（71-76行目）で合計時間の範囲チェック
- `time_trackable.rb`の`spend_time`メソッド（45-61行目）がタイムログ作成のエントリーポイント

#### Step 2: サービス層を理解する

ビジネスロジックを担当するサービスクラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | create_service.rb | `app/services/timelogs/create_service.rb` | タイムログ作成ロジック |
| 2-2 | delete_service.rb | `app/services/timelogs/delete_service.rb` | タイムログ削除ロジック |

**主要処理フロー**:
- **create_service.rb 16-53行目**: execute - 権限チェック、バリデーション、タイムログ作成、システムノート、Webhook
- **create_service.rb 24行目**: 未来日時チェック `spent_at.future?`
- **create_service.rb 25行目**: ゼロ時間チェック `time_spent == 0`
- **create_service.rb 47行目**: SystemNoteService.created_timelog でシステムノート作成
- **delete_service.rb 13-30行目**: execute - 権限チェック、削除、システムノート

#### Step 3: ファインダーを理解する

タイムログの検索ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | timelogs_finder.rb | `app/finders/timelogs/timelogs_finder.rb` | 検索条件の組み立てとフィルタリング |

**主要処理フロー**:
- **12-20行目**: execute - 各フィルタを順次適用
- **24-33行目**: by_time - 開始日時・終了日時でフィルタ
- **35-40行目**: by_user - ユーザーでフィルタ
- **42-49行目**: by_group - グループでフィルタ

#### Step 4: 時間形式の変換を理解する

時間文字列のパースロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | time_tracking_formatter.rb | `lib/gitlab/time_tracking_formatter.rb` | 時間文字列の解析と出力 |

### プログラム呼び出し階層図

```
クイックアクション /spend 1h
    │
    ├─ QuickActions::InterpretService
    │      └─ spend_time メソッド解析
    │
    └─ Issue#spend_time= (TimeTrackable concern)
           │
           ├─ add_or_subtract_spent_time
           │      └─ timelogs.new(time_spent: ...)
           │
           └─ issue.save
                  └─ Timelog レコード保存

API / GraphQL
    │
    └─ Timelogs::CreateService#execute
           │
           ├─ can?(:create_timelog, issuable) チェック
           ├─ spent_at.future? チェック
           ├─ time_spent == 0 チェック
           │
           ├─ Timelog.new(...).save
           │
           ├─ SystemNoteService.created_timelog
           │
           └─ issuable_base_service.execute_hooks
                  └─ Webhook 'update' イベント
```

### データフロー図

```
[入力]                      [処理]                         [出力]

/spend 1h 30m ─────────▶ TimeTrackingFormatter ────────▶ 5400 (秒)
                               │
                               ▼
                        CreateService
                               │
                        ┌──────┴──────┐
                        ▼             ▼
                   timelogs      notes
                   テーブル       テーブル
                        │
                        ▼
                   Issue/MR
                   (updated_at更新)
                        │
                        ▼
                   Webhook通知 ──────────────────────▶ 外部システム
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| timelog.rb | `app/models/timelog.rb` | モデル | タイムログのデータモデル |
| time_trackable.rb | `app/models/concerns/time_trackable.rb` | Concern | タイムトラッキング共通処理 |
| create_service.rb | `app/services/timelogs/create_service.rb` | サービス | タイムログ作成ロジック |
| delete_service.rb | `app/services/timelogs/delete_service.rb` | サービス | タイムログ削除ロジック |
| timelogs_finder.rb | `app/finders/timelogs/timelogs_finder.rb` | ファインダー | タイムログ検索 |
| timelogs_controller.rb | `app/controllers/time_tracking/timelogs_controller.rb` | コントローラー | グローバルタイムログ表示 |
| timelog_policy.rb | `app/policies/timelog_policy.rb` | ポリシー | 権限定義 |
| timelog_type.rb | `app/graphql/types/timelog_type.rb` | GraphQL | GraphQL型定義 |
| time_tracking_formatter.rb | `lib/gitlab/time_tracking_formatter.rb` | ライブラリ | 時間形式の変換 |
| timelog_category.rb | `app/models/time_tracking/timelog_category.rb` | モデル | タイムログカテゴリ |
