Files
ftb-project-management/apps/web/lib/derive.ts
Script Generator 121114af6a fix(数据): 支持大体积 AppData 并修正默认状态
关键改动:

- 后端显式注册 10mb JSON/urlencoded body parser,支持较大的 AppData 写入

- 前端版本派生数据缺失状态时默认 planned,而不是 released

- 增加请求体限制和版本默认状态测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-02 19:19:43 +08:00

122 lines
3.4 KiB
TypeScript

import { VersionStatus } from './version-status';
import type { Stage, Role } from './stage';
export interface VersionMember {
role: Role;
name: string;
}
export interface RoleProgress {
role: Role;
percent: number;
daysSpent: number;
}
export type Priority = 'P0' | 'P1' | 'P2' | 'P3' | 'P4';
export interface VersionLinks {
research?: string;
prototype?: string;
ui?: string;
}
interface ProductOverviewLike {
id: string;
name: string;
projects: { id: string; name: string; description: string; createdAt: string; status?: string }[];
versions: {
id: string; name: string; status?: string; releaseDate: string | null; createdAt: string;
currentStage?: Stage; startDate?: string | null; expectedReleaseDate?: string | null;
members?: VersionMember[]; progress?: RoleProgress[];
priority?: Priority; links?: VersionLinks;
}[];
}
export interface ProjectWithContext {
id: string;
name: string;
description: string;
createdAt: string;
status?: string;
productId: string;
productName: string;
versions: VersionWithContext[];
}
export interface VersionWithContext {
id: string;
name: string;
status: VersionStatus;
releaseDate: string | null;
createdAt: string;
productId: string;
productName: string;
projectId: string;
projectName: string;
projectStatus?: string;
currentStage?: Stage;
startDate?: string | null;
expectedReleaseDate?: string | null;
members?: VersionMember[];
progress?: RoleProgress[];
priority?: Priority;
links?: VersionLinks;
}
export function flattenProjects(overview: ProductOverviewLike[]): ProjectWithContext[] {
const result: ProjectWithContext[] = [];
for (const product of overview) {
for (const project of product.projects) {
const versions = product.versions
.filter((v) => v.name.toLowerCase().startsWith(project.name.toLowerCase()))
.map((v) => ({
...v,
status: (v.status || 'planned') as VersionStatus,
productId: product.id,
productName: product.name,
projectId: project.id,
projectName: project.name,
projectStatus: project.status,
}));
result.push({
...project,
productId: product.id,
productName: product.name,
versions,
});
}
}
return result;
}
export function flattenVersions(overview: ProductOverviewLike[]): VersionWithContext[] {
const result: VersionWithContext[] = [];
for (const product of overview) {
for (const version of product.versions) {
const project = product.projects.find((p) =>
version.name.toLowerCase().startsWith(p.name.toLowerCase()),
);
result.push({
...version,
status: (version.status || 'planned') as VersionStatus,
productId: product.id,
productName: product.name,
projectId: project?.id || '',
projectStatus: project?.status,
projectName: project?.name || '未关联',
});
}
}
return result;
}
export function getProjectDetail(overview: ProductOverviewLike[], projectId: string): ProjectWithContext | null {
const projects = flattenProjects(overview);
return projects.find((p) => p.id === projectId) || null;
}
export function getVersionDetail(overview: ProductOverviewLike[], versionId: string): VersionWithContext | null {
const versions = flattenVersions(overview);
return versions.find((v) => v.id === versionId) || null;
}