Skip to content
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
186ecc5
Initial plan
Copilot Feb 21, 2026
6e0fe9a
feat: 設計計画の作成
Copilot Feb 21, 2026
63bd778
feat: 実績連動仕様の設計計画を追加
Copilot Feb 21, 2026
6b10bf6
fix: 設計計画の丸め条件を明確化
Copilot Feb 21, 2026
dae15ed
fix: 丸め仕様の記述を明確化
Copilot Feb 21, 2026
9b7896b
fix: 丸めルールの説明を整理
Copilot Feb 21, 2026
70e3ef3
fix: 丸め規則の説明を補正
Copilot Feb 21, 2026
8a122a6
fix: 設計計画の表記を調整
Copilot Feb 21, 2026
b3b8778
fix: 丸め例の可読性を改善
Copilot Feb 21, 2026
5ac3d0a
fix: 丸め計算式を明示
Copilot Feb 21, 2026
2f3c2cb
fix: 丸め説明と境界例を追加
Copilot Feb 21, 2026
5ec1174
fix: 丸め計算式を簡潔化
Copilot Feb 21, 2026
46df444
fix: 丸め判定の説明を補足
Copilot Feb 21, 2026
5a93c48
fix: 丸め対象の説明を調整
Copilot Feb 21, 2026
107bb13
fix: 丸め方式を明示
Copilot Feb 21, 2026
9af14d7
fix: 丸め境界の説明を補足
Copilot Feb 21, 2026
c0e3af4
fix: 丸め境界の表現を調整
Copilot Feb 21, 2026
368d1d3
fix: 正規化呼び出し設計を追記
Copilot Feb 21, 2026
dc79ce5
fix: 設計計画の表現を統一
Copilot Feb 21, 2026
0f8b829
fix: ガント操作と業務時間帯の設計追記
Copilot Feb 21, 2026
2fba292
fix: 業務時間パラメータの仕様を明確化
Copilot Feb 21, 2026
13a5a9e
fix: 業務時間と正規化順序を補足
Copilot Feb 21, 2026
f991960
fix: 業務時間の解釈を明確化
Copilot Feb 21, 2026
1c741bf
fix: 業務時間と稼働時間の整合を明記
Copilot Feb 21, 2026
40e11b0
fix: 業務時間帯の前提を補足
Copilot Feb 21, 2026
3773a94
fix: 休憩扱いと繰り越し計算を補足
Copilot Feb 21, 2026
d32ccfb
fix: windowHoursの定義を追加
Copilot Feb 21, 2026
accc924
fix: 稼働時間の定義を補強
Copilot Feb 21, 2026
5861147
fix: breakHours算出を簡潔化
Copilot Feb 21, 2026
aa337f1
fix: 休憩と繰り越しの記述を整理
Copilot Feb 21, 2026
d74801a
fix: 繰り越し例の説明を明確化
Copilot Feb 21, 2026
670fc1c
fix: overflow単位の表記を統一
Copilot Feb 21, 2026
661e12f
fix: workHoursPerDay算出とシーケンス図を追加
Copilot Feb 21, 2026
db81d24
Update .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
LevelCapTech Feb 21, 2026
eb290ec
Update .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
LevelCapTech Feb 21, 2026
a4e6fbe
fix: シーケンス図の主体を修正
Copilot Feb 21, 2026
2c21b50
Update .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
LevelCapTech Feb 21, 2026
c72cbd7
Update .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
LevelCapTech Feb 21, 2026
b006a2a
Update .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
LevelCapTech Feb 21, 2026
b87c2e7
fix: 実績工数の命名とテスト観点を整理
Copilot Feb 21, 2026
e324777
fix: 稼働時間算出の式を明記
Copilot Feb 21, 2026
462a6cb
fix: 稼働時間用語の定義を補足
Copilot Feb 21, 2026
d878ad4
fix: 稼働時間の短時間ケースを補足
Copilot Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/copilot/plans/41-actuals-linkage-bar-priority-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Implementation Plan: 実績開始日/実績終了日/実績工数のバー優先連動

## 1. 機能要件 / 非機能要件
- 機能要件:
- SSOT を (ActualStart, ActualEnd) とし、ActualEffortHours は派生値として正規化する。
- ActualEffortHours は既存の `Task.actualEffort`(hours)を指す表現とし、新しい公開APIの追加は行わない。
- 初期表示時に 4 パターンの補完/矛盾解決を実施し、常に矛盾ゼロの表示にする。
- 編集時は 2 項目確定で残り 1 項目を自動更新し、バー優先(start/end)を保持する。
- ActualEffortHours 編集時は ActualStart を固定し、稼働日計算で ActualEnd を算出する。
- 稼働時間/日 (workHoursPerDay) は呼び出し元パラメータで指定可能とし、未指定時は既定の業務時間帯から算出する。
- `windowHours = workdayEndTime - workdayStartTime` の duration。
- `workHoursPerDay` 未指定時は `defaultBreakHours = 1h` として `workHoursPerDay = windowHours - defaultBreakHours`(既定値は 8h)。
- `windowHours <= defaultBreakHours` の場合は `workHoursPerDay = windowHours` とし、`breakHours = 0` とする(業務時間帯が短い場合は休憩を持たない)。
- `effectiveWorkHoursPerDay = min(workHoursPerDay, windowHours)`。
- `breakHours = windowHours - effectiveWorkHoursPerDay`(暗黙の休憩時間)。
- 業務開始/終了時刻 (workdayStartTime/workdayEndTime) を呼び出し元パラメータで指定可能とし、未指定時は 09:00〜18:00 を既定値とする(9h 窓に対して workHoursPerDay 既定 8h を想定し、休憩相当は workHoursPerDay の指定で調整し、breakHours は windowHours と effectiveWorkHoursPerDay の差分として暗黙に決まる)。
- workdayStartTime/workdayEndTime は `"HH:mm"` 形式の文字列で受け取り、タスクの日時と同じローカルタイムゾーンで解釈する(例: `"09:00"`)。
- 期間は [ActualStart, ActualEnd) の半開区間とし、ActualEffortHours は `q = effort / 0.25` に対して `normalized = Math.floor(q + 0.5) * 0.25` を適用する(round-half-up を明示し、0.5 は上方向)。例: 1.12→1.00、1.13→1.25。なお実装では、`effort` を分(または 15 分単位)の整数に変換してからこの丸めを適用するか、`q` 計算時に微小な epsilon(例: `q = effort / 0.25 + Number.EPSILON`)を加えるなど、二進浮動小数誤差に強い丸め方針を用いること。
- 非機能要件:
- 正規化は冪等で、高頻度呼び出しに耐える軽量な計算であること。
- ログ/表示に Secrets/PII を含めない。
- 既存 UI/データ互換を壊さず、既存バー表示と一致すること。

## 2. スコープと変更対象
- 変更ファイル(新規/修正/削除):
- 新規:
- `src/helpers/actuals-helper.ts`(正規化/補完/丸めの専用ユーティリティ)
- 修正:
- `src/types/public-types.ts`(Task に actualStart/actualEnd を追加、正規化オプション workHoursPerDay/workdayStartTime/workdayEndTime を追加)
- `src/helpers/task-helper.ts`(actualStart/actualEnd の入出力サポート)
- `src/components/gantt/gantt.tsx`(tasks 受信時の正規化適用)
- `src/components/task-list/task-list.tsx`(編集確定時に正規化を適用)
- `src/components/task-list/task-list-table.tsx`(実績開始/終了の表示列追加)
- `src/components/task-list/overlay-editor.tsx`(実績開始/終了/工数の編集対応)
- `src/test/task-list-table-editing.test.tsx`(編集時正規化の回帰テスト)
- `src/test/task-model.test.tsx`(actualStart/actualEnd シリアライズ/表示の回帰テスト)
- 影響範囲・互換性リスク:
- Task Table の実績表示、Gantt 実績バー、ロード時の既存データ表示が影響範囲。
- 既存データの ActualEffortHours がバーと矛盾する場合、ロード時に補正される。
- 既存の `actualEffort` フィールドを hours として扱い、`actualEffortHours` は表示上の呼称に留める。
- 外部依存・Secrets の扱い:
- 稼働日カレンダー、1人固定の前提に依存し、1日あたりの稼働時間は workHoursPerDay で指定 (未指定時は `workHoursPerDay = windowHours - defaultBreakHours` で算出する。`windowHours` は workdayStartTime〜workdayEndTime の duration、`defaultBreakHours` は 1h)。
- 業務時間帯は workdayStartTime/workdayEndTime で指定 (未指定時は 09:00〜18:00 を既定値とする)。
- Secrets/PII は扱わない。

## 3. 設計方針
- 責務分離 / データフロー(必要なら Mermaid 1 枚):
- 正規化ロジックは純粋関数として実装し、UI からは `normalizeActuals` を呼び出す。
- 正規化は `recalcEffort`, `deriveEnd`, `deriveStart`, `roundEffortToQuarterHour` に責務分割する。
- 半開区間を前提に稼働日カレンダー計算 API を利用する。
- `normalizeActuals` の引数で `workHoursPerDay` と `calendar`(DisplayOption; 内部的には既存の `CalendarConfig` と同義の稼働日/休日設定)、`workdayStartTime`/`workdayEndTime` を受け取り、Gantt の props から既定値 (8h, 09:00〜18:00) を注入する。
- 呼び出しタイミング:
- 初期表示/再描画: `Gantt` の tasks 受信時に `normalizeActuals` を適用し、正規化後の tasks で `ganttDateRange` と `convertToBarTasks` を生成する。
- 編集確定時: `TaskList` の `commitEditing` で該当タスクに `normalizeActuals` を適用し、正規化済みの差分を `onTaskUpdate` / `onCellCommit` へ渡す(内部的には `TaskList` に `onUpdateTask` として渡される)。
- ガントバー操作: ガントのドラッグ/リサイズで `onDateChange` が発火し、ホスト側で更新した tasks が再投入されたタイミングで `normalizeActuals` を適用する(`onDateChange` の通知は正規化前。ホスト側で同じ正規化を適用してから tasks を更新してもよい)。
- 外部更新: API 再取得や親コンポーネントの更新でも tasks prop の更新で同じ正規化が走る(冪等前提)。

```mermaid
flowchart TD
A[normalizeActuals] --> B{Start+End}
B -->|Yes| C[recalcEffort]
B -->|No| D{Start+Effort}
D -->|Yes| E[deriveEnd -> normalize]
D -->|No| F{End+Effort}
F -->|Yes| G[deriveStart -> normalize]
F -->|No| H[Undetermined]
```

- シーケンス図:

```mermaid
sequenceDiagram
participant User
participant Host
participant Gantt
participant TaskList
participant Normalize as normalizeActuals
participant Calendar

rect rgb(240, 248, 255)
Host->>Gantt: tasks 初期投入
Gantt->>Normalize: 正規化(workHoursPerDay/workdayStartTime等)
Normalize->>Calendar: 稼働日計算
Normalize-->>Gantt: 正規化済み tasks
Gantt-->>Host: 表示更新
end

rect rgb(245, 245, 245)
Host->>TaskList: tasks 再描画
TaskList->>Normalize: 編集確定時の正規化
Normalize->>Calendar: 稼働日計算
Normalize-->>TaskList: 正規化差分
TaskList-->>Host: onUpdateTask/onCellCommit
end

rect rgb(255, 248, 240)
User->>Gantt: ガントバー操作
Gantt-->>Host: onDateChange (正規化前)
Host->>Gantt: 更新済み tasks 再投入
Gantt->>Normalize: 正規化
Normalize-->>Gantt: 正規化済み tasks
end
```

- エッジケース / 例外系 / リトライ方針:
- ActualStart/ActualEnd のパース不能・範囲外・start > end は「欠落」と同等に扱い、初期表示の補完順序に従う。
- ActualEffortHours が負数/NaN は欠落扱いとし補完を試みる。
- ActualEffortHours=0 は start=end を許容し、半開区間のため effort は 0 として扱う。
- 非稼働日跨ぎはカレンダー API に委譲し、加算/差分計算は稼働日のみを対象にする。
- workHoursPerDay が未指定/0 以下/NaN の場合は、既定の算出ルール(`workHoursPerDay = windowHours - defaultBreakHours`、既定値 8h)にフォールバックする。
- workHoursPerDay が業務時間帯の長さ(workdayStartTime/workdayEndTime を時間差に換算した値)を超える場合は、`effectiveWorkHoursPerDay = windowHours` を適用して計算し、設定不整合を警告ログで通知する。
- 正規化は高頻度呼び出しを前提とするため、既存の `src/helpers/calendar-helper.ts` の `warnOnce` と同様に同一内容の警告は 1 回だけ出力する(プロセス内の記憶で抑制し、永続化はしない)。
- workdayStartTime/workdayEndTime が未指定/不正/逆転の場合は既定値 09:00〜18:00 にフォールバックする。
- end 算出/丸めは業務時間帯内で完結させ、丸め後の end が workdayEndTime を超える場合は次稼働日の workdayStartTime に繰り越す。
- 繰り越し手順:
- `overflow = roundedEnd - workdayEndTime`(roundedEnd は datetime、workdayEndTime は同日の時刻に変換し、時間差を分単位の duration として扱う)。
- 次稼働日の `workdayStartTime + overflow` を end とする。
- 例: start 17:45、effort 0.5h=30分 → roundedEnd 18:15、workdayEndTime 18:00 のため overflow 15分、翌稼働日の 09:15 にする。
- ログと観測性(漏洩防止を含む):
- 既存の console.debug / console.warn の構造化ログ方針に合わせる。
- 無効値補完や矛盾補正時は rowId・フィールド名・原因のみをログに出し、値本文は必要最小限にする。

## 4. テスト戦略
- テスト観点(正常 / 例外 / 境界 / 回帰):
- 初期表示 4 パターン: Start+End, Start+Effort, End+Effort, どれも無い。
- 編集時 3 パターン: Start 編集, End 編集, Effort 編集。
- workHoursPerDay の変更: 6h/8h/10h で end 算出が変わることを確認。
- workdayStartTime/workdayEndTime の変更: 08:00〜17:00/09:00〜18:00/10:00〜19:00 で丸め後の end が業務時間内に収束することを確認。
- workHoursPerDay > windowHours のクランプ(`effectiveWorkHoursPerDay = windowHours`)と warnOnce 相当の警告が 1 回だけ出ることを確認(警告は「workHoursPerDay が業務時間帯を超過している」旨と採用値を含む)。
- windowHours <= defaultBreakHours の場合に `workHoursPerDay = windowHours` となり、休憩時間が 0 になることを確認。
- workdayStartTime/workdayEndTime の不正値フォールバックと overflow 繰り越しが次稼働日に反映されることを確認。
- 0.25h 丸めを確認:
- 1.12→1.00、1.13→1.25。
- 1.37→1.25、1.38→1.50。
- 境界条件: effort/0.25 の小数部が 0.5 以上で上方向(1.124→1.00、1.125→1.25)。
- 境界値: 1.125→1.25、1.375→1.50、1.625→1.75、1.875→2.00。
- 境界直前/直後: 1.124→1.00、1.126→1.25、1.374→1.25、1.376→1.50。
- 祝日/非稼働日を含む期間での effort 再計算/ end 算出。
- 無効値(NaN/負数/start>end)入力時の欠落扱い。
- モック / フィクスチャ方針:
- 稼働日カレンダーは既存ユーティリティのモックを用い、固定カレンダーで期待値を確定させる。
- テスト追加の実行コマンド(例: `python -m pytest`):
- `npm run test:unit` (必要に応じて `npm test` で lint/build を含めて実行)

## 5. CI 品質ゲート
- 実行コマンド(format / lint / typecheck / test / security):
- lint/build/test: `npm test` (`test:unit` + `test:lint` + `test:build`)
- security: `npm audit` (既存 CI 運用に合わせて実行)
- 通過基準と失敗時の対応:
- すべてのテストが green であること。失敗時は正規化計算/丸め/稼働日処理を見直す。

## 6. ロールアウト・運用
- ロールバック方法:
- 正規化ロジックを導入したコミットをリバートし、既存表示に戻す。
- 監視・運用上の注意:
- 既存データの effort がロード時に補正される可能性をリリースノートで周知する。

## 7. オープンな課題 / ADR 要否
- 未確定事項:
- なし。
- ADR に残すべき判断:
- なし (本仕様で SSOT/丸め/半開区間が確定済み)。