ac94f5f5de
Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
export type DevPlanTaskStatus = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED';
|
|
export type DevPlanVersionStatus = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED';
|
|
|
|
export function computeDurationMinutes(
|
|
devStartedAt: Date | null | undefined,
|
|
devCompletedAt: Date | null | undefined,
|
|
): number | null {
|
|
if (!devStartedAt || !devCompletedAt) return null;
|
|
const ms = devCompletedAt.getTime() - devStartedAt.getTime();
|
|
if (ms < 0) return null;
|
|
return Math.round(ms / 60000);
|
|
}
|
|
|
|
export function versionStatusTimestamps(
|
|
prev: DevPlanVersionStatus,
|
|
next: DevPlanVersionStatus,
|
|
current: {
|
|
devStartedAt?: Date | null;
|
|
devCompletedAt?: Date | null;
|
|
releasedAt?: Date | null;
|
|
},
|
|
): {
|
|
devStartedAt?: Date | null;
|
|
devCompletedAt?: Date | null;
|
|
releasedAt?: Date | null;
|
|
durationMinutes?: number | null;
|
|
} {
|
|
const patch: {
|
|
devStartedAt?: Date | null;
|
|
devCompletedAt?: Date | null;
|
|
releasedAt?: Date | null;
|
|
durationMinutes?: number | null;
|
|
} = {};
|
|
const now = new Date();
|
|
|
|
if (next === 'IN_PROGRESS' && !current.devStartedAt) {
|
|
patch.devStartedAt = now;
|
|
}
|
|
if (next === 'TESTING' && !current.devCompletedAt) {
|
|
patch.devCompletedAt = now;
|
|
}
|
|
if (next === 'RELEASED' && !current.releasedAt) {
|
|
patch.releasedAt = now;
|
|
}
|
|
|
|
const started = patch.devStartedAt ?? current.devStartedAt ?? null;
|
|
const completed = patch.devCompletedAt ?? current.devCompletedAt ?? null;
|
|
const duration = computeDurationMinutes(started, completed);
|
|
if (duration != null) patch.durationMinutes = duration;
|
|
|
|
void prev;
|
|
return patch;
|
|
}
|
|
|
|
export function taskCompletedAtOnStatus(
|
|
prev: DevPlanTaskStatus,
|
|
next: DevPlanTaskStatus,
|
|
currentCompletedAt?: Date | null,
|
|
): Date | null | undefined {
|
|
if (next === 'RELEASED' && !currentCompletedAt) return new Date();
|
|
void prev;
|
|
return undefined;
|
|
}
|
|
|
|
const TASK_STATUS_RANK: Record<DevPlanTaskStatus, number> = {
|
|
TODO: 0,
|
|
IN_PROGRESS: 1,
|
|
DEVELOPED: 2,
|
|
RELEASED: 3,
|
|
};
|
|
|
|
/** 版本状态 → 任务状态:待开始/开发中/测试中/已发布 ↔ 待处理/进行中/已开发/已发布 */
|
|
export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPlanTaskStatus {
|
|
switch (status) {
|
|
case 'PENDING':
|
|
return 'TODO';
|
|
case 'IN_PROGRESS':
|
|
return 'IN_PROGRESS';
|
|
case 'TESTING':
|
|
return 'DEVELOPED';
|
|
case 'RELEASED':
|
|
return 'RELEASED';
|
|
}
|
|
}
|
|
|
|
/** 任务挂多个版本时取进度最高的对应状态 */
|
|
export function resolveTaskStatusFromVersions(
|
|
versionStatuses: DevPlanVersionStatus[],
|
|
): DevPlanTaskStatus | null {
|
|
if (!versionStatuses.length) return null;
|
|
return versionStatuses
|
|
.map(taskStatusFromVersionStatus)
|
|
.reduce((highest, current) =>
|
|
TASK_STATUS_RANK[current] > TASK_STATUS_RANK[highest] ? current : highest,
|
|
);
|
|
}
|