feat(版本): 完善研发计划与预警已读
关键改动: - 增加版本表单、发布校验和调研方向进度规则 - 扩展小宝预警已读状态、风险签名和今日证据 - 补充组长权限、加班查看范围、活动记录与相关测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -120,12 +120,13 @@ export function getWorkspaceDailyReport({
|
||||
...items.map((item) => item.taskId),
|
||||
]);
|
||||
const worklogTaskIds = new Set(items.map((item) => item.taskId));
|
||||
const activityHours = Array.from(new Set(reportActivities.map((activity) => activity.sourceId)))
|
||||
.filter((sourceId) => !worklogTaskIds.has(sourceId))
|
||||
.reduce((sum, sourceId) => {
|
||||
const item = workItemMap.get(sourceId);
|
||||
return sum + calcWorkItemHoursForDate(item, date, now);
|
||||
}, 0);
|
||||
const activityHours = calcActivityHoursForDate(
|
||||
Array.from(new Set(reportActivities.map((activity) => activity.sourceId))),
|
||||
workItemMap,
|
||||
worklogTaskIds,
|
||||
date,
|
||||
now,
|
||||
);
|
||||
|
||||
const needsProgressItems = workItems
|
||||
.filter((item) => shouldRequireProgress(item, date, touchedSourceIds))
|
||||
@@ -297,25 +298,64 @@ function getTestCaseTerminalFallback(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function calcWorkItemHoursForDate(item: WorkItem | undefined, date: string, now: Date): number {
|
||||
function calcActivityHoursForDate(
|
||||
sourceIds: string[],
|
||||
workItemMap: Map<string, WorkItem>,
|
||||
excludedSourceIds: Set<string>,
|
||||
date: string,
|
||||
now: Date,
|
||||
): number {
|
||||
const intervals = sourceIds
|
||||
.filter((sourceId) => !excludedSourceIds.has(sourceId))
|
||||
.map((sourceId) => getWorkItemIntervalForDate(workItemMap.get(sourceId), date, now))
|
||||
.filter(isTimeInterval);
|
||||
|
||||
return calcMergedIntervalHours(intervals);
|
||||
}
|
||||
|
||||
function getWorkItemIntervalForDate(item: WorkItem | undefined, date: string, now: Date): { start: number; end: number } | undefined {
|
||||
const interval = getActualInterval(item, now);
|
||||
if (!interval) return 0;
|
||||
if (!interval) return undefined;
|
||||
|
||||
const day = getLocalDateBounds(date);
|
||||
if (!day) return 0;
|
||||
if (!day) return undefined;
|
||||
|
||||
const start = new Date(interval.start).getTime();
|
||||
const end = new Date(interval.end).getTime();
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return 0;
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return undefined;
|
||||
|
||||
const overlapStart = Math.max(start, day.start.getTime());
|
||||
const overlapEnd = Math.min(end, day.end.getTime());
|
||||
if (overlapEnd <= overlapStart) return 0;
|
||||
if (overlapEnd <= overlapStart) return undefined;
|
||||
|
||||
return calcActualElapsedHours(
|
||||
new Date(overlapStart).toISOString(),
|
||||
new Date(overlapEnd).toISOString(),
|
||||
);
|
||||
return { start: overlapStart, end: overlapEnd };
|
||||
}
|
||||
|
||||
function isTimeInterval(interval: { start: number; end: number } | undefined): interval is { start: number; end: number } {
|
||||
return Boolean(interval);
|
||||
}
|
||||
|
||||
function calcMergedIntervalHours(intervals: Array<{ start: number; end: number }>): number {
|
||||
if (intervals.length === 0) return 0;
|
||||
|
||||
const sorted = [...intervals].sort((a, b) => a.start - b.start);
|
||||
const merged: Array<{ start: number; end: number }> = [];
|
||||
let current = { ...sorted[0] };
|
||||
|
||||
for (const interval of sorted.slice(1)) {
|
||||
if (interval.start <= current.end) {
|
||||
current.end = Math.max(current.end, interval.end);
|
||||
} else {
|
||||
merged.push(current);
|
||||
current = { ...interval };
|
||||
}
|
||||
}
|
||||
merged.push(current);
|
||||
|
||||
return merged.reduce((sum, interval) => sum + calcActualElapsedHours(
|
||||
new Date(interval.start).toISOString(),
|
||||
new Date(interval.end).toISOString(),
|
||||
), 0);
|
||||
}
|
||||
|
||||
function getActualInterval(item: WorkItem | undefined, now: Date): { start: string; end: string } | undefined {
|
||||
|
||||
Reference in New Issue
Block a user