核心变更: - DevTask 字段重构:startDate/dueDate/estimateHours/actualHours 替换为 expectedStartAt/expectedEndAt/actualStartAt/actualEndAt(含时分),预计/实际工时按工作时段(9:00-12:00 + 13:00-18:00)派生计算 - 状态机自动化:到点自动切开发中、未到可手动开干、超期需填延后原因 - 新建 lib/work-hours.ts:calcWorkHours(工作时段过滤)、formatWorkHours(X h(Y 天))、calcTwoMetrics(日历/人力双口径) - 新建 lib/dev-task-transitions.ts:状态切换守卫 - 三个 Tab 顶部统计:开发任务(预/日历/人力)、测试用例 / Bug(日历 / 人力);版本概览总人天投入改双行(日历总耗时 / 人力总投入) - 三个 Tab 加搜索框:300ms debounce + 标题/编号模糊匹配 - 性能优化:requirementIds/categories/requirements/testCases 转 Map/Set 索引、Row 组件 React.memo - 全局耗时显示统一带天数换算:8h(1天)、11h(1.4天) 新增文件: SearchInput.tsx, useDebouncedValue.ts, work-hours.ts, dev-task-transitions.ts, 设计文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
179 lines
6.4 KiB
Markdown
179 lines
6.4 KiB
Markdown
# 版本统计「日历耗时 + 人力投入」双口径设计
|
||
|
||
## Context
|
||
|
||
当前版本详情下的开发任务/测试用例/Bug 三个 Tab 顶部统计,以及概览页的"总人天投入",都用**单条任务独立累加**计算耗时。问题:
|
||
|
||
- 任务 A:09:00–11:00(2h)
|
||
- 任务 B:10:00–12:00(2h)
|
||
|
||
实际只占用了 09:00–12:00 共 3h 工作时段,但当前累加算成 4h。
|
||
|
||
任务越多重叠越严重,"总耗时"会显著虚高,无法回答"这个版本实际走了几天"。
|
||
|
||
**目标**:顶部统计同时展示两个口径——
|
||
|
||
- **日历耗时**:合并区间后的真实工作时段占用(看版本实际走了多久)
|
||
- **人力投入**:单条独立累加(看总人力成本,原口径)
|
||
|
||
## 决策摘要
|
||
|
||
| 项 | 决策 |
|
||
|---|---|
|
||
| 算法位置 | `apps/web/lib/work-hours.ts` 新增 `mergeWorkHours` + `calcTwoMetrics` |
|
||
| 区间提取 | 各模块(dev-task / test-case / bug)暴露 `xxxIntervals(items)` 函数 |
|
||
| 顶部展示 | 三段式:"预 X / 日历 Y / 人力 Z"(开发任务);"日历 Y / 人力 Z"(测试用例 / Bug) |
|
||
| 概览 总人天 | 两行展示:日历总耗时 / 人力总投入(含调研/产品/UI/开发/测试/Bug 全程) |
|
||
| 个人耗时排名 | 不动(按人汇总天然是人力维度) |
|
||
| PlanTab 顶部 | 不动(不在本次范围) |
|
||
| 老聚合函数 | 保留(向后兼容,仍然是 manhours) |
|
||
| Bug 区间 end 优先级 | `closedAt > resolvedAt > 终态 updatedAt > now` |
|
||
|
||
## 1. 合并区间算法
|
||
|
||
`apps/web/lib/work-hours.ts` 新增:
|
||
|
||
```ts
|
||
export interface TimeInterval {
|
||
start: string;
|
||
end: string;
|
||
}
|
||
|
||
// 合并所有重叠区间后,对每段独立调 calcWorkHours 求和
|
||
export function mergeWorkHours(intervals: TimeInterval[]): number;
|
||
|
||
// 双口径汇总
|
||
export function calcTwoMetrics(intervals: TimeInterval[]): {
|
||
calendarHours: number;
|
||
manhours: number;
|
||
};
|
||
```
|
||
|
||
算法:
|
||
|
||
1. 过滤掉非法(`!start || !end`)或 `end <= start` 的区间
|
||
2. 按 `start` 升序排序
|
||
3. 扫描合并:当前与 prev 重叠(`cur.start <= prev.end`)时合并 `prev.end = max(prev.end, cur.end)`,否则把 prev push 出去
|
||
4. 对最终的 N 个不重叠区间,逐个调 `calcWorkHours(start, end)` 求和 → `calendarHours`
|
||
5. `manhours` = 原始 intervals 各自 `calcWorkHours` 之后求和(不合并)
|
||
|
||
精度 0.5h,与现有口径一致。未结束任务以 `now` 作为 end 参与合并。
|
||
|
||
**验证用例**:
|
||
- A(9–11) + B(10–12) → 日历 3h,人力 4h
|
||
- A(9–11) + B(13–15) + C(10–14) → 合并后 9–15 减去午休 12–13 = 5h;人力 6h
|
||
- 跨周末合并:周五 17–周一 10 + 周二 9–11 → 工作时段过滤后两段独立算
|
||
|
||
## 2. 三个模块的 intervals 提取
|
||
|
||
**`apps/web/lib/dev-task.ts`** 新增:
|
||
|
||
```ts
|
||
export function devTaskIntervals(tasks: DevTask[], now?: Date): TimeInterval[];
|
||
// 取 [actualStartAt, actualEndAt ?? now],过滤无 actualStartAt 的
|
||
```
|
||
|
||
**`apps/web/lib/test-case.ts`** 新增:
|
||
|
||
```ts
|
||
export function testCaseIntervals(cases: TestCase[], now?: Date): TimeInterval[];
|
||
// 取 [startedAt, completedAt ?? now],过滤无 startedAt 的
|
||
```
|
||
|
||
**`apps/web/lib/bug.ts`** 新增:
|
||
|
||
```ts
|
||
export function bugIntervals(bugs: Bug[], now?: Date): TimeInterval[];
|
||
// 取 [createdAt, closedAt ?? resolvedAt ?? (status 终态时 updatedAt) ?? now]
|
||
```
|
||
|
||
**`apps/web/lib/version-plan.ts`** 新增(仅供概览总人天用):
|
||
|
||
```ts
|
||
export function planIntervals(plans: VersionPlan[], now?: Date): TimeInterval[];
|
||
// 取 [actualStartAt, completedAt ?? now],过滤无 actualStartAt 的
|
||
```
|
||
|
||
老聚合函数(`aggregateDevTaskHours / aggregateTestCaseActualHours / aggregateBugActualHours`)保留,仍然是 manhours 累加,向后兼容(项目顶部卡片、与我相关页继续用)。
|
||
|
||
## 3. 4 个统计点 UI 调整
|
||
|
||
**(1) `DevTaskTab` 顶部**:
|
||
|
||
```
|
||
12任务 · 预 96h(12天) · 日历 64h(8天) / 人力 84h(10.5天)
|
||
```
|
||
|
||
- 染色:人力 > 预计 红、< 预计 绿、相等灰
|
||
- 日历不染色(中性事实)
|
||
|
||
**(2) `TestCaseTab` 顶部**:
|
||
|
||
```
|
||
40例 · 通过28 · 失败5 · 通过率85% · 日历 32h(4天) / 人力 56h(7天)
|
||
```
|
||
|
||
**(3) `BugTab` 顶部**:
|
||
|
||
```
|
||
Bug 12 个 · 待修复2 · 修复中3 · ... · 解决率75% · 日历 18h / 人力 24h
|
||
```
|
||
|
||
**(4) 概览-总人天投入**(line 778):当前一行 `总人天投入 96h(12人天)` 改为两行:
|
||
|
||
```
|
||
日历总耗时 104h(13天)
|
||
人力总投入 176h(22人天)
|
||
```
|
||
|
||
数据源:plans + devTasks + testCases + bugs 的 intervals 全部合并后调 `calcTwoMetrics`。
|
||
|
||
## 4. 不动的部分
|
||
|
||
- 个人耗时排名(按人累加,本身就是人力维度)
|
||
- 阶段耗时(项目维度)卡片(已经是日历口径,用 `calcCalendar` 算最早开始/最晚结束/天数差)
|
||
- PlanTab(调研/产品方案/UI 设计)顶部统计
|
||
- 项目详情顶部 `HoursStatCard`
|
||
- 与我相关页 DevTask 工时显示
|
||
|
||
## 数据流
|
||
|
||
```
|
||
DevTask + TestCase + Bug + Plan
|
||
│
|
||
├── devTaskIntervals(tasks)
|
||
├── testCaseIntervals(cases)
|
||
├── bugIntervals(bugs)
|
||
└── planIntervals(plans)
|
||
│
|
||
└── work-hours.calcTwoMetrics(intervals)
|
||
│
|
||
├── DevTaskTab 顶部
|
||
├── TestCaseTab 顶部
|
||
├── BugTab 顶部
|
||
└── 概览总人天卡片(4 个 intervals 合并)
|
||
```
|
||
|
||
## 实施清单
|
||
|
||
新增/修改:
|
||
|
||
1. `apps/web/lib/work-hours.ts` — `TimeInterval` + `mergeWorkHours` + `calcTwoMetrics`
|
||
2. `apps/web/lib/dev-task.ts` — `devTaskIntervals`
|
||
3. `apps/web/lib/test-case.ts` — `testCaseIntervals`
|
||
4. `apps/web/lib/bug.ts` — `bugIntervals`
|
||
5. `apps/web/lib/version-plan.ts` — `planIntervals`
|
||
6. `apps/web/components/dev-task/DevTaskTab.tsx` — 三段式
|
||
7. `apps/web/components/test-case/TestCaseTab.tsx` — 加日历/人力两段
|
||
8. `apps/web/components/bug/BugTab.tsx` — 加日历/人力两段
|
||
9. `apps/web/app/versions/[id]/page.tsx` — 概览"总人天"改两行
|
||
|
||
## 验证
|
||
|
||
1. 单条任务 09:00–11:00:日历=2h,人力=2h
|
||
2. 两条 A(9–11) + B(10–12):日历=3h,人力=4h
|
||
3. 三条 A(9–11) + B(13–15) + C(10–14):日历=5h(去午休),人力=6h
|
||
4. 跨周末合并:周五 17–周一 10 与周二 9–11,分两段算工作时段后再相加
|
||
5. `pnpm type-check` + `pnpm build` 0 错误
|
||
6. 浏览器进版本详情:开发/测试/Bug 三个 Tab 顶部三段式正确,概览总人天两行正确
|