作業時間追跡 仕様書

タイマー・手動ログ・工数サマリー

ステータス: Draft / 作成日: 2026-05-27 PR #2 — 依存: コア


1. データモデル

1.1 time_logs

pub struct Model {
    pub id: Uuid,
    pub task_id: Uuid,
    pub user_id: Uuid,
    pub logged_minutes: i32,   // 分単位で保持(表示時に h/m 変換)
    pub logged_at: NaiveDate,  // 作業日
    pub note: Option<String>,
    pub created_at: DateTimeUtc,
}
カラム 制約
id UUID PK
task_id UUID NOT NULL, FK→tasks CASCADE
user_id UUID NOT NULL, FK→users CASCADE
logged_minutes INT NOT NULL CHECK (> 0)
logged_at DATE NOT NULL
note TEXT NULLABLE
created_at TIMESTAMPTZ NOT NULL DEFAULT now()

1.2 task_timers(アクティブタイマー)

カラム 制約 説明
task_id UUID NOT NULL, FK→tasks CASCADE  
user_id UUID NOT NULL, FK→users CASCADE  
started_at TIMESTAMPTZ NOT NULL DEFAULT now()  
PRIMARY KEY(task_id, user_id) 同一ユーザーが同タスクに同時起動 1 つのみ

タイマー Stop 時に (now() - started_at)logged_minutes として time_logs に INSERT し、task_timers レコードを削除する。


2. マイグレーション

CREATE TABLE time_logs (
    id UUID PRIMARY KEY,
    task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    logged_minutes INT NOT NULL CHECK (logged_minutes > 0),
    logged_at DATE NOT NULL,
    note TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE task_timers (
    task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (task_id, user_id)
);

CREATE INDEX idx_time_logs_task ON time_logs(task_id);
CREATE INDEX idx_time_logs_user_date ON time_logs(user_id, logged_at);

3. API

メソッド パス 説明
GET /tasks/{id}/time-logs ログ一覧
POST /tasks/{id}/time-logs 手動ログ追加
PUT /tasks/{id}/time-logs/{log_id} ログ編集(自分のログのみ)
DELETE /tasks/{id}/time-logs/{log_id} 削除(自分 or テナントオーナー)
POST /tasks/{id}/timer/start タイマー開始
POST /tasks/{id}/timer/stop タイマー停止 → ログ生成
GET /tasks/{id}/timer/status 現在のタイマー状態
GET /tasks/{id}/time-logs/summary 工数サマリー

POST /tasks/{id}/time-logs

{
  "logged_minutes": 90,
  "logged_at": "2026-05-27",
  "note": "設計レビュー対応"
}

POST /tasks/{id}/timer/stop レスポンス

{
  "id": "uuid",
  "logged_minutes": 47,
  "logged_at": "2026-05-27",
  "note": null
}

GET /tasks/{id}/time-logs/summary

{
  "estimated_minutes": 180,
  "actual_minutes": 90,
  "remaining_minutes": 90,
  "is_over": false,
  "by_user": [
    { "user_id": "uuid", "name": "田中", "minutes": 60 },
    { "user_id": "uuid", "name": "鈴木", "minutes": 30 }
  ]
}

GET /tasks/{id}/timer/status

{
  "is_running": true,
  "started_at": "2026-05-27T10:30:00Z",
  "elapsed_minutes": 25
}

4. フロントエンド(Phase B)

タイマーウィジェット(タスク詳細右ペイン)

見積  3h
実績  1h30m

[▶ 開始]  または  [⏹ 00:25:14  停止]
  • 起動中はリアルタイム経過時間を表示(polling or SSE)
  • 停止時に任意のメモを入力してからログ保存できる

コンポーネント

コンポーネント ファイル
TimerWidget components/tasks/TimerWidget.vue
TimeLogList components/tasks/TimeLogList.vue
TimeLogForm components/tasks/TimeLogForm.vue