Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
import type { Bug } from './bug';
|
|
import type { DevTask } from './dev-task';
|
|
import type { OvertimeRecord } from './overtime';
|
|
import type { Requirement } from './requirement';
|
|
import type { TestCase } from './test-case';
|
|
import type { VersionPlan } from './version-plan';
|
|
|
|
export interface VersionDataScope {
|
|
requirements: Requirement[];
|
|
requirementIds: string[];
|
|
requirementIdSet: Set<string>;
|
|
plans: VersionPlan[];
|
|
plansByType: Record<VersionPlan['type'], VersionPlan[]>;
|
|
devTasks: DevTask[];
|
|
testCases: TestCase[];
|
|
bugs: Bug[];
|
|
overtimeRecords: OvertimeRecord[];
|
|
}
|
|
|
|
interface VersionDataScopeInput {
|
|
versionId: string;
|
|
requirements: Requirement[];
|
|
plans: VersionPlan[];
|
|
devTasks: DevTask[];
|
|
testCases: TestCase[];
|
|
bugs: Bug[];
|
|
overtimeRecords?: OvertimeRecord[];
|
|
}
|
|
|
|
interface VersionDataScopeMapInput extends Omit<VersionDataScopeInput, 'versionId'> {
|
|
versionIds: string[];
|
|
}
|
|
|
|
export function buildVersionDataScope(input: VersionDataScopeInput): VersionDataScope {
|
|
const {
|
|
versionId,
|
|
requirements,
|
|
plans,
|
|
devTasks,
|
|
testCases,
|
|
bugs,
|
|
overtimeRecords = [],
|
|
} = input;
|
|
|
|
const scopedRequirements: Requirement[] = [];
|
|
const requirementIds: string[] = [];
|
|
const requirementIdSet = new Set<string>();
|
|
for (const requirement of requirements) {
|
|
if (requirement.versionId !== versionId) continue;
|
|
scopedRequirements.push(requirement);
|
|
requirementIds.push(requirement.id);
|
|
requirementIdSet.add(requirement.id);
|
|
}
|
|
|
|
const scopedPlans: VersionPlan[] = [];
|
|
const plansByType: Record<VersionPlan['type'], VersionPlan[]> = {
|
|
research: [],
|
|
product: [],
|
|
ui: [],
|
|
};
|
|
for (const plan of plans) {
|
|
if (plan.versionId !== versionId) continue;
|
|
scopedPlans.push(plan);
|
|
plansByType[plan.type].push(plan);
|
|
}
|
|
|
|
return {
|
|
requirements: scopedRequirements,
|
|
requirementIds,
|
|
requirementIdSet,
|
|
plans: scopedPlans,
|
|
plansByType,
|
|
devTasks: devTasks.filter((task) => task.versionId === versionId || (!task.versionId && requirementIdSet.has(task.requirementId))),
|
|
testCases: testCases.filter((testCase) => testCase.versionId === versionId),
|
|
bugs: bugs.filter((bug) => bug.versionId === versionId),
|
|
overtimeRecords: overtimeRecords.filter((record) => record.versionId === versionId),
|
|
};
|
|
}
|
|
|
|
export function hasVersionDataScopeRows(scope: VersionDataScope | null | undefined): scope is VersionDataScope {
|
|
if (!scope) return false;
|
|
return (
|
|
scope.requirements.length > 0 ||
|
|
scope.plans.length > 0 ||
|
|
scope.devTasks.length > 0 ||
|
|
scope.testCases.length > 0 ||
|
|
scope.bugs.length > 0
|
|
);
|
|
}
|
|
|
|
export function selectVersionDataScope(input: {
|
|
storeScope: VersionDataScope | null;
|
|
v22Scope?: VersionDataScope | null;
|
|
}): VersionDataScope | null {
|
|
const { storeScope, v22Scope } = input;
|
|
if (hasVersionDataScopeRows(v22Scope)) {
|
|
const requirements = overlayRowsById(v22Scope.requirements, storeScope?.requirements ?? []);
|
|
const plans = overlayRowsById(v22Scope.plans, storeScope?.plans ?? []);
|
|
return {
|
|
...v22Scope,
|
|
requirements,
|
|
requirementIds: requirements.map((item) => item.id),
|
|
requirementIdSet: new Set(requirements.map((item) => item.id)),
|
|
plans,
|
|
plansByType: groupPlansByType(plans),
|
|
devTasks: overlayRowsById(v22Scope.devTasks, storeScope?.devTasks ?? []),
|
|
testCases: overlayRowsById(v22Scope.testCases, storeScope?.testCases ?? []),
|
|
bugs: overlayRowsById(v22Scope.bugs, storeScope?.bugs ?? []),
|
|
overtimeRecords: storeScope?.overtimeRecords ?? v22Scope.overtimeRecords,
|
|
};
|
|
}
|
|
return storeScope;
|
|
}
|
|
|
|
export function buildVersionDataScopeMap(input: VersionDataScopeMapInput): Record<string, VersionDataScope> {
|
|
const scopes: Record<string, VersionDataScope> = {};
|
|
const targetVersionIds = new Set(input.versionIds);
|
|
for (const versionId of input.versionIds) {
|
|
scopes[versionId] = createEmptyScope();
|
|
}
|
|
|
|
const requirementVersionMap = new Map<string, string>();
|
|
for (const requirement of input.requirements) {
|
|
const versionId = requirement.versionId;
|
|
if (!versionId) continue;
|
|
requirementVersionMap.set(requirement.id, versionId);
|
|
const scope = scopes[versionId];
|
|
if (!scope) continue;
|
|
scope.requirements.push(requirement);
|
|
scope.requirementIds.push(requirement.id);
|
|
scope.requirementIdSet.add(requirement.id);
|
|
}
|
|
|
|
for (const plan of input.plans) {
|
|
const scope = scopes[plan.versionId];
|
|
if (!scope) continue;
|
|
scope.plans.push(plan);
|
|
scope.plansByType[plan.type].push(plan);
|
|
}
|
|
|
|
for (const task of input.devTasks) {
|
|
const versionId = task.versionId || requirementVersionMap.get(task.requirementId);
|
|
if (!versionId || !targetVersionIds.has(versionId)) continue;
|
|
scopes[versionId]?.devTasks.push(task);
|
|
}
|
|
|
|
for (const testCase of input.testCases) {
|
|
scopes[testCase.versionId]?.testCases.push(testCase);
|
|
}
|
|
|
|
for (const bug of input.bugs) {
|
|
scopes[bug.versionId]?.bugs.push(bug);
|
|
}
|
|
|
|
for (const record of input.overtimeRecords ?? []) {
|
|
if (!record.versionId) continue;
|
|
scopes[record.versionId]?.overtimeRecords.push(record);
|
|
}
|
|
|
|
return scopes;
|
|
}
|
|
|
|
function createEmptyScope(): VersionDataScope {
|
|
return {
|
|
requirements: [],
|
|
requirementIds: [],
|
|
requirementIdSet: new Set<string>(),
|
|
plans: [],
|
|
plansByType: {
|
|
research: [],
|
|
product: [],
|
|
ui: [],
|
|
},
|
|
devTasks: [],
|
|
testCases: [],
|
|
bugs: [],
|
|
overtimeRecords: [],
|
|
};
|
|
}
|
|
|
|
function overlayRowsById<T extends { id: string }>(baseRows: T[], overlayRows: T[]): T[] {
|
|
if (baseRows.length === 0) return baseRows;
|
|
const overlayById = new Map(overlayRows.map((item) => [item.id, item]));
|
|
const seen = new Set<string>();
|
|
const merged = baseRows.map((item) => {
|
|
seen.add(item.id);
|
|
return overlayById.get(item.id) ?? item;
|
|
});
|
|
for (const item of overlayRows) {
|
|
if (!seen.has(item.id)) merged.push(item);
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
function groupPlansByType(plans: VersionPlan[]): Record<VersionPlan['type'], VersionPlan[]> {
|
|
return {
|
|
research: plans.filter((plan) => plan.type === 'research'),
|
|
product: plans.filter((plan) => plan.type === 'product'),
|
|
ui: plans.filter((plan) => plan.type === 'ui'),
|
|
};
|
|
}
|