タスク コア 仕様書

タスク CRUD・カスタムステータス・担当者・ラベル・マイルストーン・依存関係

ステータス: Draft / 作成日: 2026-05-27 PR #1 — 依存: なし(最初に実装)


1. 概要

タスク管理の基盤となるコア機能。すべての後続 PR がこの PR に依存する。

含む機能:

  • タスク CRUD + プロジェクト内連番 ID(KEY-N 形式。例: ENG-42
  • カスタムステータス(プロジェクト単位)
  • 担当者(Primary / Secondary)
  • 優先順位・締切(Soft / Hard)・進捗率
  • 親子タスク・ブロッキング依存関係
  • ラベル(プロジェクト単位 + export / import)
  • マイルストーン

2. データモデル

2.1 project_task_counters

カラム 制約 説明
project_id UUID PK, FK→projects CASCADE  
last_seq INT NOT NULL DEFAULT 0 SELECT ... FOR UPDATE でアトミックにインクリメント

2.2 project_statuses

pub struct Model {
    pub id: Uuid,
    pub project_id: Uuid,
    pub name: String,
    pub color: String,        // hex (#3b82f6)
    pub position: i16,        // 表示順
    pub is_default: bool,     // タスク作成時のデフォルト(プロジェクト内で 1 つのみ)
    pub is_done_state: bool,  // このステータス = 完了とみなす(複数可)
    pub created_at: DateTimeUtc,
}
カラム 制約
id UUID PK
project_id UUID NOT NULL, FK→projects CASCADE
name VARCHAR(100) NOT NULL
color VARCHAR(7) NOT NULL
position SMALLINT NOT NULL
is_default BOOLEAN NOT NULL DEFAULT false
is_done_state BOOLEAN NOT NULL DEFAULT false
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
UNIQUE(project_id, name)

プロジェクト作成時に Backlog(is_default=true)/ In Progress / In Review / Done(is_done_state=true)を自動挿入する。

2.3 tasks

pub struct Model {
    pub id: Uuid,
    pub project_id: Uuid,
    pub seq_id: i32,
    pub title: String,
    pub description: Option<String>,
    pub status_id: Uuid,
    pub priority: TaskPriority,
    pub progress_pct: i16,
    pub parent_task_id: Option<Uuid>,
    pub milestone_id: Option<Uuid>,
    pub soft_deadline: Option<DateTimeUtc>,
    pub hard_deadline: Option<DateTimeUtc>,
    pub estimated_minutes: Option<i32>,
    pub is_archived: bool,
    pub created_by: Uuid,
    pub created_at: DateTimeUtc,
    pub updated_at: DateTimeUtc,
    pub deleted_at: Option<DateTimeUtc>,
}
カラム 制約 説明
id UUID PK  
project_id UUID NOT NULL, FK→projects CASCADE  
seq_id INT NOT NULL プロジェクト内連番。KEY-N 形式で表示(例: ENG-42
title VARCHAR(255) NOT NULL  
description TEXT NULLABLE Markdown
status_id UUID NOT NULL, FK→project_statuses  
priority VARCHAR NOT NULL DEFAULT 'medium'  
progress_pct SMALLINT NOT NULL DEFAULT 0 CHECK (0–100)  
parent_task_id UUID NULLABLE, FK→tasks SET NULL  
milestone_id UUID NULLABLE, FK→milestones SET NULL  
soft_deadline TIMESTAMPTZ NULLABLE  
hard_deadline TIMESTAMPTZ NULLABLE CHECK: soft ≤ hard
estimated_minutes INT NULLABLE CHECK (> 0)  
is_archived BOOLEAN NOT NULL DEFAULT false  
created_by UUID NOT NULL, FK→users  
created_at TIMESTAMPTZ NOT NULL DEFAULT now()  
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()  
deleted_at TIMESTAMPTZ NULLABLE ソフトデリート
UNIQUE(project_id, seq_id)  
CONSTRAINT soft_before_hard  

2.4 task_assignees

カラム 制約
id UUID PK
task_id UUID NOT NULL, FK→tasks CASCADE
user_id UUID NOT NULL, FK→users CASCADE
role VARCHAR NOT NULL DEFAULT 'secondary' (primary / secondary)
assigned_at TIMESTAMPTZ NOT NULL DEFAULT now()
UNIQUE(task_id, user_id)

2.5 task_relations

カラム 制約
id UUID PK
blocker_task_id UUID NOT NULL, FK→tasks CASCADE
blocked_task_id UUID NOT NULL, FK→tasks CASCADE
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
UNIQUE(blocker_task_id, blocked_task_id)
CHECK(blocker_task_id <> blocked_task_id)

循環依存はリレーション追加時にサーバー側 BFS で検出し 409 Conflict

2.6 milestones

カラム 制約
id UUID PK
project_id UUID NOT NULL, FK→projects CASCADE
name VARCHAR(255) NOT NULL
description TEXT NULLABLE
due_date DATE NOT NULL
created_by UUID NOT NULL, FK→users
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()

2.7 labels(既存テーブルに project_id 追加)

ALTER TABLE labels ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE labels ADD CONSTRAINT labels_project_name_unique UNIQUE (project_id, name);

2.8 task_labels

カラム 制約
task_id UUID NOT NULL, FK→tasks CASCADE
label_id UUID NOT NULL, FK→labels CASCADE
PRIMARY KEY(task_id, label_id)

3. マイグレーション

-- プロジェクトキー(GitHub 連携での KEY-N 形式に使用)
ALTER TABLE projects
    ADD COLUMN key VARCHAR(10) NOT NULL DEFAULT '',
    ADD CONSTRAINT projects_key_format CHECK (key ~ '^[A-Z][A-Z0-9]{1,9}$'),
    ADD CONSTRAINT projects_key_tenant_unique UNIQUE (tenant_id, key);

key はテナント内で一意の英大文字識別子(例: ENGBACKWEB)。 プロジェクト作成 API でユーザーが指定するか、プロジェクト名から自動生成する(例: "Backend API" → BACK)。

CREATE TABLE project_task_counters (
    project_id UUID PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
    last_seq INT NOT NULL DEFAULT 0
);

CREATE TABLE project_statuses (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    name VARCHAR(100) NOT NULL,
    color VARCHAR(7) NOT NULL,
    position SMALLINT NOT NULL,
    is_default BOOLEAN NOT NULL DEFAULT false,
    is_done_state BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (project_id, name)
);

CREATE TABLE milestones (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    due_date DATE NOT NULL,
    created_by UUID NOT NULL REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE tasks (
    id UUID PRIMARY KEY,
    project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    seq_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    status_id UUID NOT NULL REFERENCES project_statuses(id),
    priority VARCHAR NOT NULL DEFAULT 'medium',
    progress_pct SMALLINT NOT NULL DEFAULT 0 CHECK (progress_pct BETWEEN 0 AND 100),
    parent_task_id UUID REFERENCES tasks(id) ON DELETE SET NULL,
    milestone_id UUID REFERENCES milestones(id) ON DELETE SET NULL,
    soft_deadline TIMESTAMPTZ,
    hard_deadline TIMESTAMPTZ,
    estimated_minutes INT CHECK (estimated_minutes > 0),
    is_archived BOOLEAN NOT NULL DEFAULT false,
    created_by UUID NOT NULL REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    deleted_at TIMESTAMPTZ,
    UNIQUE (project_id, seq_id),
    CONSTRAINT soft_before_hard CHECK (
        soft_deadline IS NULL OR hard_deadline IS NULL OR soft_deadline <= hard_deadline
    )
);

CREATE TABLE task_assignees (
    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,
    role VARCHAR NOT NULL DEFAULT 'secondary',
    assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (task_id, user_id)
);

CREATE TABLE task_relations (
    id UUID PRIMARY KEY,
    blocker_task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    blocked_task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (blocker_task_id, blocked_task_id),
    CHECK (blocker_task_id <> blocked_task_id)
);

CREATE TABLE task_labels (
    task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
    PRIMARY KEY (task_id, label_id)
);

ALTER TABLE labels ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE labels ADD CONSTRAINT labels_project_name_unique UNIQUE (project_id, name);

CREATE INDEX idx_tasks_project ON tasks(project_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_status ON tasks(status_id);
CREATE INDEX idx_tasks_parent ON tasks(parent_task_id) WHERE parent_task_id IS NOT NULL;

4. API

URL 基底パス: /v1/tenants/{tenant_id}/projects/{project_id}

4.1 タスク

メソッド パス 説明
GET /tasks 一覧(フィルター・ソート・ページング)
POST /tasks 作成
GET /tasks/{id} 取得(UUID または KEY-N、例: ENG-42
PUT /tasks/{id} 更新(全フィールド optional)
DELETE /tasks/{id} ソフトデリート
POST /tasks/{id}/archive アーカイブ
POST /tasks/{id}/unarchive アーカイブ解除

GET /tasks クエリパラメータ

パラメータ 説明
status_id UUID[]?  
priority string[]?  
assignee_id UUID?  
milestone_id UUID?  
parent_task_id UUID? サブタスク一覧
is_archived bool デフォルト false
sort string created_at_desc / priority_asc / deadline_asc
limit / offset u32 デフォルト 50、最大 200

POST /tasks リクエスト

{
  "title": "OAuth 対応を実装する",
  "description": "## 概要\nGitHub Apps を使って…",
  "priority": "high",
  "status_id": "uuid",
  "soft_deadline": "2026-06-01T00:00:00Z",
  "hard_deadline": "2026-06-10T00:00:00Z",
  "estimated_minutes": 180,
  "assignees": [{ "user_id": "uuid", "role": "primary" }],
  "label_ids": ["uuid"],
  "milestone_id": "uuid",
  "parent_task_id": null
}

GET /tasks レスポンス

{
  "tasks": [{
    "id": "uuid", "seq_id": 42,
    "title": "OAuth 対応を実装する",
    "status": { "id": "uuid", "name": "In Progress", "color": "#3b82f6" },
    "priority": "high", "progress_pct": 40,
    "assignees": [{ "user_id": "uuid", "role": "primary" }],
    "soft_deadline": "2026-06-01T00:00:00Z",
    "hard_deadline": "2026-06-10T00:00:00Z",
    "subtask_count": 3,
    "created_at": "2026-05-27T10:00:00Z"
  }],
  "total": 42
}

4.2 担当者

メソッド パス 説明
GET /tasks/{id}/assignees 一覧
POST /tasks/{id}/assignees 追加 { "user_id": "uuid", "role": "primary" }
PUT /tasks/{id}/assignees/{user_id} ロール変更
DELETE /tasks/{id}/assignees/{user_id} 削除

4.3 依存関係

メソッド パス 説明
GET /tasks/{id}/relations 一覧(subtasks / blocks / blocked_by)
POST /tasks/{id}/relations 追加
DELETE /tasks/{id}/relations/{relation_id} 削除

POST リクエスト — どちらのタスクからでも設定可能:

type 意味
"blocks" 現タスクが target をブロック
"blocked_by" 現タスクは target にブロックされている
{ "type": "blocks", "target_task_id": "uuid" }
// 同じ関係を反対側から設定する場合(結果は同一レコード)
{ "type": "blocked_by", "target_task_id": "uuid" }

GET レスポンス:

{
  "subtasks": [{ "id": "uuid", "seq_id": 60, "title": "..." }],
  "blocks":     [{ "id": "uuid", "seq_id": 55, "title": "...", "status": "blocked", "relation_id": "uuid" }],
  "blocked_by": [{ "id": "uuid", "seq_id": 38, "title": "...", "status": "done",    "relation_id": "uuid" }]
}

4.4 カスタムステータス

メソッド パス 説明
GET /statuses 一覧(position 順)
POST /statuses 作成
PUT /statuses/{id} 更新
PUT /statuses/reorder 並び順一括更新 { "ids": ["uuid1", "uuid2"] }
DELETE /statuses/{id} 削除(タスクが存在する場合は migrate_to_status_id 必須)

4.5 マイルストーン

メソッド パス 説明
GET /milestones 一覧
POST /milestones 作成
GET /milestones/{id} 取得(完了率含む)
PUT /milestones/{id} 更新
DELETE /milestones/{id} 削除(タスクの milestone_id は NULL リセット)

GET /milestones/{id} レスポンス:

{
  "id": "uuid", "name": "v2.0 リリース", "due_date": "2026-07-01",
  "progress_pct": 33,
  "task_counts": { "total": 12, "done": 4 }
}

4.6 ラベル

メソッド パス 説明
GET /labels プロジェクトのラベル一覧
POST /labels 作成
PUT /labels/{id} 更新
DELETE /labels/{id} 削除(task_labels も CASCADE)
GET /labels/export JSON エクスポート
POST /labels/import JSON インポート(on_conflict: skip / overwrite

エクスポート形式:

{
  "version": 1,
  "labels": [
    { "name": "bug", "color": "#e11d48", "description": "不具合" }
  ]
}

5. アクセス制御

操作 権限
タスク閲覧 / 作成 / 更新 / アーカイブ プロジェクトメンバー or テナントオーナー
タスク削除(ソフト) 作成者 or テナントオーナー
ステータス / ラベル管理 プロジェクトメンバー or テナントオーナー
マイルストーン管理 プロジェクトメンバー or テナントオーナー

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

ページ

/tenants/{tid}/projects/{pid}/tasks          # 一覧(カンバン / リスト / テーブル切替)
/tenants/{tid}/projects/{pid}/tasks/{id}     # 詳細
/tenants/{tid}/projects/{pid}/milestones     # マイルストーン一覧
/tenants/{tid}/projects/{pid}/labels         # ラベル管理

カンバンボード

カスタムステータスが列になる。ドラッグ&ドロップでステータス変更・列の並び替えが可能。

┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
│ Backlog  │  │In Progress│ │ In Review│  │   Done   │
├──────────┤  ├──────────┤  ├──────────┤  ├──────────┤
│ #42 🔴   │  │ #38 🟡   │  │ #30 🔴   │  │ #25 ✅   │
│ OAuth    │  │ DB 設計   │  │ ログイン  │  │ 環境構築  │
└──────────┘  └──────────┘  └──────────┘  └──────────┘
  [+ ステータス追加]

主要コンポーネント

コンポーネント ファイル
TaskListPage pages/tasks/+Page.vue
KanbanBoard components/tasks/KanbanBoard.vue
TaskDetailPage pages/tasks/[id]/+Page.vue
TaskDetailPanel components/tasks/TaskDetailPanel.vue
AssigneeSelector components/tasks/AssigneeSelector.vue
RelationsGraph components/tasks/RelationsGraph.vue
StatusManager components/tasks/StatusManager.vue
MilestonePage pages/milestones/+Page.vue
LabelManager pages/labels/+Page.vue

7. 決定事項

項目 決定内容
削除方式 ソフトデリート(deleted_at)。物理削除は管理者 API のみ(本 PR 対象外)
ステータス プロジェクト単位のカスタムステータス。ハードコード廃止
連番 ID SELECT FOR UPDATE でアトミック採番。KEY-N 形式で表示(例: ENG-42
ラベルスコープ project_id 追加でプロジェクト単位化