import { api } from './api'; import type { Bug, BugSeverity, BugStatus } from './bug'; import type { DevTask, DevTaskStatus, Reference } from './dev-task'; import type { Priority } from './derive'; import type { Requirement, RequirementStatus, SourceType } from './requirement'; import type { TestCase, TestCaseStatus } from './test-case'; import type { VersionDataScope } from './version-data-scope'; import type { PlanTask, VersionPlan, VersionPlanLog, VersionPlanRequirementCoverage, } from './version-plan'; type V22DateValue = string | Date | null | undefined; interface V22VersionRow { id: string; productId: string; projectId?: string | null; name: string; releaseDate?: V22DateValue; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22RequirementRow { id: string; productId: string; projectId?: string | null; versionId?: string | null; code: string; title: string; description?: string | null; status?: string | null; priority?: number | string | null; type?: string | null; sourceType?: string | null; sourceTarget?: string | null; platform?: string | null; creatorId?: string | null; creatorName?: string | null; creator?: { id?: string | null; name?: string | null } | null; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22VersionPlanRow { id: string; versionId: string; type: string; title: string; status?: string | null; ownerId?: string | null; expectedStartAt?: V22DateValue; expectedEndAt?: V22DateValue; actualStartAt?: V22DateValue; completedAt?: V22DateValue; resultUrl?: string | null; requirementCoverage?: unknown; logs?: unknown; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22DevTaskRow { id: string; versionId: string; requirementId?: string | null; categoryId?: string | null; code: string; title: string; description?: string | null; status?: string | null; priority?: number | string | null; assigneeId?: string | null; creatorId?: string | null; isBlocked?: boolean | null; blockReason?: string | null; expectedStartAt?: V22DateValue; expectedEndAt?: V22DateValue; startDate?: V22DateValue; completedAt?: V22DateValue; estimateHours?: number | null; aiEstimateHours?: number | null; references?: unknown; aiDraft?: boolean | null; aiDraftAt?: V22DateValue; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22TestCaseRow { id: string; versionId: string; requirementId?: string | null; categoryId?: string | null; code: string; title: string; description?: string | null; status?: string | null; roundNo?: number | null; priority?: number | string | null; assigneeId?: string | null; creatorId?: string | null; plannedTestAt?: V22DateValue; plannedEndAt?: V22DateValue; startedAt?: V22DateValue; completedAt?: V22DateValue; estimateHours?: number | null; aiEstimateHours?: number | null; references?: unknown; aiDraft?: boolean | null; aiDraftAt?: V22DateValue; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22BugRow { id: string; versionId: string; testCaseId?: string | null; code: string; title: string; description?: string | null; status?: string | null; severity?: string | null; priority?: number | string | null; assigneeId?: string | null; reporterId?: string | null; plannedFixAt?: V22DateValue; resolvedAt?: V22DateValue; closedAt?: V22DateValue; resolution?: string | null; createdAt?: V22DateValue; updatedAt?: V22DateValue; } interface V22VersionDetailResponse { version: V22VersionRow; requirements: V22RequirementRow[]; versionPlans: V22VersionPlanRow[]; devTasks: V22DevTaskRow[]; testCases: V22TestCaseRow[]; bugs: V22BugRow[]; } export interface V22VersionDetailData { version: V22VersionRow; scope: VersionDataScope; } export interface V22RequirementsQuery { productId: string; projectId?: string; versionId?: string; status?: string; priority?: string; type?: string; q?: string; sort?: string; cursor?: string; limit?: number; } export interface V22RequirementsPage { items: Requirement[]; nextCursor?: string; } export interface V22WorkspaceData { versionPlans: VersionPlan[]; devTasks: DevTask[]; testCases: TestCase[]; bugs: Bug[]; } export interface V22XiaobaoWarningQuery { userId?: string; manager?: boolean; } export interface V22XiaobaoWarningSummary { versionId: string; riskLevel: string; riskScore: number; confidence: number; forecastReleaseDate?: string; riskSignature: string; summary: unknown; dirty?: boolean; recomputedAt?: string; updatedAt?: string; } export async function loadV22VersionDetailData(versionId: string): Promise { const response = await api.get( `/v2.2/versions/${encodeURIComponent(versionId)}/detail-data`, ); return { version: response.version, scope: buildScope(response), }; } export async function loadV22RequirementsPage(query: V22RequirementsQuery): Promise { const response = await api.get<{ items: V22RequirementRow[]; nextCursor?: string }>( `/v2.2/requirements?${queryString(query)}`, ); return { items: response.items.map(mapRequirement), nextCursor: response.nextCursor, }; } export async function loadV22WorkspaceData(userId: string): Promise { const response = await api.get<{ versionPlans: V22VersionPlanRow[]; devTasks: V22DevTaskRow[]; testCases: V22TestCaseRow[]; bugs: V22BugRow[]; }>(`/v2.2/workspace?${queryString({ userId })}`); return { versionPlans: response.versionPlans.map(mapVersionPlan), devTasks: response.devTasks.map(mapDevTask), testCases: response.testCases.map(mapTestCase), bugs: response.bugs.map(mapBug), }; } export async function loadV22XiaobaoWarnings( query: V22XiaobaoWarningQuery, ): Promise { const response = await api.get & { forecastReleaseDate?: V22DateValue; recomputedAt?: V22DateValue; updatedAt?: V22DateValue; }>>(`/v2.2/xiaobao-warning?${queryString({ userId: query.userId, manager: query.manager ? 'true' : undefined, })}`); return response.map((row) => ({ ...row, forecastReleaseDate: optionalIso(row.forecastReleaseDate), recomputedAt: optionalIso(row.recomputedAt), updatedAt: optionalIso(row.updatedAt), })); } function buildScope(response: V22VersionDetailResponse): VersionDataScope { const requirements = response.requirements.map(mapRequirement); const requirementIds = requirements.map((requirement) => requirement.id); const requirementIdSet = new Set(requirementIds); const plans = response.versionPlans.map(mapVersionPlan); const plansByType: VersionDataScope['plansByType'] = { research: [], product: [], ui: [] }; for (const plan of plans) { plansByType[plan.type].push(plan); } return { requirements, requirementIds, requirementIdSet, plans, plansByType, devTasks: response.devTasks.map(mapDevTask), testCases: response.testCases.map(mapTestCase), bugs: response.bugs.map(mapBug), overtimeRecords: [], }; } function mapRequirement(row: V22RequirementRow): Requirement { return { id: row.id, code: row.code, title: row.title, description: row.description ?? '', productId: row.productId, projectId: row.projectId ?? '', versionId: row.versionId ?? undefined, sourceType: toSourceType(row.sourceType), sourceTarget: row.sourceTarget ?? '', platforms: splitCsv(row.platform), typeId: row.type ?? '', status: toRequirementStatus(row.status), priority: toPriority(row.priority), effort: 'M', creator: row.creator?.name ?? row.creatorName ?? row.creatorId ?? '', createdAt: requiredIso(row.createdAt), }; } function mapVersionPlan(row: V22VersionPlanRow): VersionPlan { const coverage = asArray(row.requirementCoverage); return { id: row.id, versionId: row.versionId, type: toPlanType(row.type), title: row.title, owner: row.ownerId ?? '', startTime: requiredIso(row.expectedStartAt), endTime: requiredIso(row.expectedEndAt), status: toPlanStatus(row.status), tasks: [], completedRequirementIds: coverage .filter((item) => item?.status === 'completed') .map((item) => item.requirementId) .filter(Boolean), linkedRequirementIds: coverage.map((item) => item?.requirementId).filter(Boolean), requirementCoverage: coverage, logs: asArray(row.logs), resultUrl: row.resultUrl ?? undefined, actualStartAt: optionalIso(row.actualStartAt), createdAt: requiredIso(row.createdAt), completedAt: optionalIso(row.completedAt), addedBy: row.ownerId ?? '', }; } function mapDevTask(row: V22DevTaskRow): DevTask { return { id: row.id, taskNo: row.code, versionId: row.versionId, requirementId: row.requirementId ?? '', title: row.title, description: row.description ?? '', categoryId: row.categoryId ?? '', assigneeId: row.assigneeId ?? '', priority: toPriority(row.priority), expectedStartAt: requiredIso(row.expectedStartAt), expectedEndAt: requiredIso(row.expectedEndAt), estimateHours: row.estimateHours ?? undefined, aiEstimateHours: row.aiEstimateHours ?? undefined, actualStartAt: optionalIso(row.startDate), actualEndAt: optionalIso(row.completedAt), status: toDevTaskStatus(row.status), isBlocked: row.isBlocked ?? false, blockReason: row.blockReason ?? undefined, references: asArray(row.references), aiDraft: row.aiDraft ?? false, aiDraftAt: optionalIso(row.aiDraftAt), createdBy: row.creatorId ?? '', createdAt: requiredIso(row.createdAt), updatedAt: requiredIso(row.updatedAt), }; } function mapTestCase(row: V22TestCaseRow): TestCase { return { id: row.id, caseNo: row.code, versionId: row.versionId, requirementId: row.requirementId ?? undefined, roundNo: row.roundNo ?? 1, title: row.title, description: row.description ?? '', categoryId: row.categoryId ?? '', priority: toPriority(row.priority), assigneeId: row.assigneeId ?? undefined, status: toTestCaseStatus(row.status), estimateHours: row.estimateHours ?? undefined, aiEstimateHours: row.aiEstimateHours ?? undefined, plannedTestAt: optionalIso(row.plannedTestAt), plannedEndAt: optionalIso(row.plannedEndAt), startedAt: optionalIso(row.startedAt), completedAt: optionalIso(row.completedAt), references: asArray(row.references), aiDraft: row.aiDraft ?? false, aiDraftAt: optionalIso(row.aiDraftAt), createdBy: row.creatorId ?? '', createdAt: requiredIso(row.createdAt), updatedAt: requiredIso(row.updatedAt), }; } function mapBug(row: V22BugRow): Bug { return { id: row.id, bugNo: row.code, versionId: row.versionId, testCaseId: row.testCaseId ?? '', title: row.title, description: row.description ?? '', severity: toBugSeverity(row.severity), priority: toPriority(row.priority), reportedBy: row.reporterId ?? '', assigneeId: row.assigneeId ?? '', status: toBugStatus(row.status), resolvedAt: optionalIso(row.resolvedAt), closedAt: optionalIso(row.closedAt), resolution: row.resolution ?? undefined, plannedFixAt: optionalIso(row.plannedFixAt), createdAt: requiredIso(row.createdAt), updatedAt: requiredIso(row.updatedAt), }; } function queryString(values: T): string { const params = new URLSearchParams(); Object.entries(values).forEach(([key, value]) => { if (value === undefined || value === '') return; params.set(key, String(value)); }); return params.toString(); } function requiredIso(value: V22DateValue): string { return optionalIso(value) ?? ''; } function optionalIso(value: V22DateValue): string | undefined { if (!value) return undefined; if (value instanceof Date) return value.toISOString(); const trimmed = value.trim(); if (!trimmed) return undefined; if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return `${trimmed}T00:00:00.000Z`; const parsed = new Date(trimmed); if (Number.isFinite(parsed.getTime())) return parsed.toISOString(); return trimmed; } function splitCsv(value?: string | null): string[] { if (!value) return []; return value.split(',').map((item) => item.trim()).filter(Boolean); } function asArray(value: unknown): T[] { return Array.isArray(value) ? value as T[] : []; } function toPriority(value: number | string | null | undefined): Priority { if (typeof value === 'string') { const normalized = value.trim().toUpperCase(); if (/^P[0-4]$/.test(normalized)) return normalized as Priority; const parsed = Number(normalized); if (Number.isFinite(parsed)) return toPriority(parsed); } const rank = typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : 2; return `P${Math.max(0, Math.min(4, rank))}` as Priority; } function toSourceType(value?: string | null): SourceType { const allowed: SourceType[] = ['customer', 'internal', 'operation', 'aftersale', 'market', 'competitor', 'management']; return allowed.includes(value as SourceType) ? value as SourceType : 'internal'; } function toRequirementStatus(value?: string | null): RequirementStatus { const allowed: RequirementStatus[] = ['pending_review', 'adopted', 'rejected', 'planned', 'developing', 'testing', 'released', 'closed']; return allowed.includes(value as RequirementStatus) ? value as RequirementStatus : 'pending_review'; } function toPlanType(value: string): VersionPlan['type'] { return value === 'research' || value === 'ui' ? value : 'product'; } function toPlanStatus(value?: string | null): VersionPlan['status'] { if (value === 'in_progress' || value === 'completed') return value; return 'pending'; } function toDevTaskStatus(value?: string | null): DevTaskStatus { if (value === 'in_progress' || value === 'testing' || value === 'submitted') return value; return 'todo'; } function toTestCaseStatus(value?: string | null): TestCaseStatus { if (value === 'running' || value === 'passed' || value === 'failed' || value === 'blocked') return value; return 'pending'; } function toBugStatus(value?: string | null): BugStatus { if (value === 'fixing' || value === 'fixed' || value === 'verifying' || value === 'closed' || value === 'rejected') { return value; } return 'open'; } function toBugSeverity(value?: string | null): BugSeverity { if (value === 'critical' || value === 'major' || value === 'trivial') return value; return 'minor'; }