import { normalizeNullableStringId, normalizeStringId } from './shared'; type ProjectStatusCode = Api.Project.ProjectStatusCode; type ProjectStatusActionCode = Exclude; type StringIdResponse = string | number; export type ProjectLocalDateValue = string | number[] | null; export type LifecycleActionResponse = Partial> & { actionCode: ActionCode; }; export type ProjectExecutionResponse = Omit< Api.Project.ProjectExecution, | 'id' | 'projectId' | 'projectRequirementId' | 'ownerId' | 'availableActions' | 'plannedStartDate' | 'plannedEndDate' | 'actualStartDate' | 'actualEndDate' | 'progressRate' | 'priority' | 'priorityName' > & { id: StringIdResponse; projectId: StringIdResponse; projectRequirementId?: StringIdResponse | null; ownerId: StringIdResponse; availableActions?: LifecycleActionResponse[] | null; plannedStartDate?: ProjectLocalDateValue; plannedEndDate?: ProjectLocalDateValue; actualStartDate?: ProjectLocalDateValue; actualEndDate?: ProjectLocalDateValue; progressRate?: number | null; priority?: string | number | null; priorityName?: string | null; }; export type ExecutionAssigneeResponse = Omit & { id: StringIdResponse; executionId: StringIdResponse; userId: StringIdResponse; }; export type ExecutionAssigneeLogResponse = Omit< Api.Project.ExecutionAssigneeLog, 'id' | 'executionId' | 'userId' | 'operatorUserId' > & { id: StringIdResponse; executionId: StringIdResponse; userId: StringIdResponse; operatorUserId: StringIdResponse; }; type TaskAssigneeRefResponse = Omit & { id: StringIdResponse; userId: StringIdResponse; }; /** * 后端 attachments 项的兼容形态:历史/当前响应字段名是 `id`,前端类型统一用 `fileId`。 * normalizeAttachments 负责把两者归一成 `fileId`。 */ type AttachmentItemResponse = Omit & { fileId?: StringIdResponse; id?: StringIdResponse; }; function normalizeAttachments(list?: AttachmentItemResponse[] | null): Api.Project.AttachmentItem[] | null { if (!list) { return null; } return list.map(item => { const rawId = item.fileId ?? item.id; return { ...item, fileId: rawId === null || rawId === undefined ? '' : String(rawId) }; }); } /** * 5.6 单独接口返的协办人字段(与 5.3 嵌入字段命名口径不一致:返 userNickname 而非 nickname)。 * 经 normalizeTaskAssignee 归一化后对外统一为 Api.Project.TaskAssigneeRef。 */ export type TaskAssigneeFromApiResponse = { id: StringIdResponse; taskId: StringIdResponse; userId: StringIdResponse; userNickname?: string | null; joinedAt?: string | null; }; export type TaskAssigneeLogResponse = Omit< Api.Project.TaskAssigneeLog, 'id' | 'taskId' | 'userId' | 'operatorUserId' > & { id: StringIdResponse; taskId: StringIdResponse; userId: StringIdResponse; operatorUserId: StringIdResponse; }; export type ProjectTaskResponse = Omit< Api.Project.ProjectTask, | 'id' | 'projectId' | 'executionId' | 'parentTaskId' | 'ownerId' | 'executionOwnerId' | 'parentTaskOwnerId' | 'availableActions' | 'plannedStartDate' | 'plannedEndDate' | 'actualStartDate' | 'actualEndDate' | 'progressRate' | 'assignees' | 'attachments' | 'priority' | 'priorityName' > & { id: StringIdResponse; projectId: StringIdResponse; executionId: StringIdResponse; executionName?: string | null; executionStatusCode?: Api.Project.ProjectExecutionStatusCode | null; parentTaskId?: StringIdResponse | null; ownerId: StringIdResponse; executionOwnerId?: StringIdResponse | null; parentTaskOwnerId?: StringIdResponse | null; availableActions?: LifecycleActionResponse[] | null; plannedStartDate?: ProjectLocalDateValue; plannedEndDate?: ProjectLocalDateValue; actualStartDate?: ProjectLocalDateValue; actualEndDate?: ProjectLocalDateValue; progressRate?: number | null; assignees?: TaskAssigneeRefResponse[] | null; attachments?: AttachmentItemResponse[] | null; totalSpentHours?: number | null; priority?: string | number | null; priorityName?: string | null; }; export type TaskWorklogResponse = Omit< Api.Project.TaskWorklog, 'id' | 'taskId' | 'userId' | 'difficulty' | 'attachments' | 'startDate' | 'endDate' > & { id: StringIdResponse; taskId: StringIdResponse; userId: StringIdResponse; difficulty?: string | null; attachments?: AttachmentItemResponse[] | null; startDate?: ProjectLocalDateValue; endDate?: ProjectLocalDateValue; }; export interface ProjectMemberResponse { id: string | number; userId: string | number; userNickname: string; roleId: string | number; roleName: string; roleCode: string; managerFlag: boolean; status: 0 | 1; joinedTime: string; leftTime?: string | null; remark?: string | null; } const projectLifecycleActionNameMap: Record = { pause: '暂停项目', resume: '恢复项目', complete: '完成项目', cancel: '取消项目', reopen: '重新开启', archive: '归档项目' }; const projectLifecycleActionReasonRequiredMap: Record = { pause: true, resume: false, complete: true, cancel: true, reopen: true, archive: false }; const projectLifecycleActionMap: Record = { pending: ['cancel'], active: ['pause', 'complete', 'cancel'], paused: ['resume', 'cancel'], completed: ['reopen', 'archive'], cancelled: [], archived: [] }; export function getProjectLifecycleActions(statusCode: ProjectStatusCode): Api.Project.ProjectLifecycleAction[] { return projectLifecycleActionMap[statusCode].map(actionCode => ({ actionCode, actionName: projectLifecycleActionNameMap[actionCode], needReason: projectLifecycleActionReasonRequiredMap[actionCode] })); } export function normalizeProjectLocalDate(value: ProjectLocalDateValue | undefined) { if (value === null || value === undefined || value === '') { return null; } if (Array.isArray(value)) { const [year, month, day] = value; if (!year || !month || !day) { return null; } return [year, month, day].map(item => String(item).padStart(2, '0')).join('-'); } return String(value); } export function normalizeLifecycleActions( actions: LifecycleActionResponse[] | null | undefined ): Api.Project.LifecycleAction[] { return (actions ?? []).map(action => ({ actionCode: action.actionCode, actionName: action.actionName ?? '', needReason: Boolean(action.needReason) })); } export function normalizeProjectMember(response: ProjectMemberResponse): Api.Project.ProjectMember { return { id: normalizeStringId(response.id), userId: normalizeStringId(response.userId), userNickname: response.userNickname || '', roleId: normalizeStringId(response.roleId), roleName: response.roleName || '', roleCode: response.roleCode || '', managerFlag: Boolean(response.managerFlag), status: response.status, joinedTime: response.joinedTime, leftTime: response.leftTime ?? null, remark: response.remark ?? null }; } function normalizePriority(value: string | number | null | undefined): string { if (value === null || value === undefined || value === '') { return '1'; } return String(value); } export function normalizeProjectExecution(response: ProjectExecutionResponse): Api.Project.ProjectExecution { return { ...response, id: normalizeStringId(response.id), projectId: normalizeStringId(response.projectId), projectRequirementId: normalizeNullableStringId(response.projectRequirementId), projectRequirementName: response.projectRequirementName ?? null, projectRequirementStatusCode: response.projectRequirementStatusCode ?? null, ownerId: normalizeStringId(response.ownerId), ownerNickname: response.ownerNickname ?? null, statusName: response.statusName ?? null, terminal: Boolean(response.terminal), allowEdit: Boolean(response.allowEdit), availableActions: normalizeLifecycleActions(response.availableActions), plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate), plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate), actualStartDate: normalizeProjectLocalDate(response.actualStartDate), actualEndDate: normalizeProjectLocalDate(response.actualEndDate), progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0, priority: normalizePriority(response.priority), priorityName: response.priorityName ?? null, executionDesc: response.executionDesc ?? null, lastStatusReason: response.lastStatusReason ?? null }; } export function normalizeExecutionAssignee(response: ExecutionAssigneeResponse): Api.Project.ExecutionAssignee { return { ...response, id: normalizeStringId(response.id), executionId: normalizeStringId(response.executionId), userId: normalizeStringId(response.userId), userNickname: response.userNickname ?? null, joinedAt: response.joinedAt ?? null, removedAt: response.removedAt ?? null, removedReason: response.removedReason ?? null }; } export function normalizeExecutionAssigneeLog( response: ExecutionAssigneeLogResponse ): Api.Project.ExecutionAssigneeLog { return { ...response, id: normalizeStringId(response.id), executionId: normalizeStringId(response.executionId), userId: normalizeStringId(response.userId), operatorUserId: normalizeStringId(response.operatorUserId), userNicknameSnapshot: response.userNicknameSnapshot ?? null, operatorNicknameSnapshot: response.operatorNicknameSnapshot ?? null, reason: response.reason ?? null }; } export function normalizeProjectTask(response: ProjectTaskResponse): Api.Project.ProjectTask { return { ...response, id: normalizeStringId(response.id), projectId: normalizeStringId(response.projectId), executionId: normalizeStringId(response.executionId), executionName: response.executionName ?? null, executionStatusCode: response.executionStatusCode ?? null, parentTaskId: normalizeNullableStringId(response.parentTaskId), projectRequirementId: normalizeNullableStringId(response.projectRequirementId), projectRequirementName: response.projectRequirementName ?? null, projectRequirementStatusCode: response.projectRequirementStatusCode ?? null, type: response.type ?? '', ownerId: normalizeStringId(response.ownerId), ownerNickname: response.ownerNickname ?? null, executionOwnerId: normalizeNullableStringId(response.executionOwnerId), parentTaskOwnerId: normalizeNullableStringId(response.parentTaskOwnerId), statusName: response.statusName ?? null, terminal: Boolean(response.terminal), allowEdit: Boolean(response.allowEdit), availableActions: normalizeLifecycleActions(response.availableActions), progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0, plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate), plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate), actualStartDate: normalizeProjectLocalDate(response.actualStartDate), actualEndDate: normalizeProjectLocalDate(response.actualEndDate), priority: normalizePriority(response.priority), priorityName: response.priorityName ?? null, taskDesc: response.taskDesc ?? null, lastStatusReason: response.lastStatusReason ?? null, assignees: response.assignees?.map(item => ({ id: normalizeStringId(item.id), userId: normalizeStringId(item.userId), nickname: item.nickname ?? '' })) ?? null, attachments: normalizeAttachments(response.attachments), totalSpentHours: response.totalSpentHours ?? null }; } export function normalizeTaskWorklog(response: TaskWorklogResponse): Api.Project.TaskWorklog { return { ...response, id: normalizeStringId(response.id), taskId: normalizeStringId(response.taskId), userId: normalizeStringId(response.userId), userNickname: response.userNickname ?? null, workContent: response.workContent ?? null, attachments: normalizeAttachments(response.attachments), progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0, // 后端 LocalDate 默认序列化为 [year, month, day] 数组,必须归一为 'YYYY-MM-DD' 字符串供 ElDatePicker 使用 startDate: normalizeProjectLocalDate(response.startDate) ?? '', endDate: normalizeProjectLocalDate(response.endDate) ?? '', // 历史记录或异常缺失时兜底为字典默认档位 "2" difficulty: response.difficulty ?? '2', difficultyName: response.difficultyName ?? null }; } export function normalizeTaskAssignee(response: TaskAssigneeFromApiResponse): Api.Project.TaskAssigneeRef { return { id: normalizeStringId(response.id), userId: normalizeStringId(response.userId), nickname: response.userNickname ?? '', joinedAt: response.joinedAt ?? null }; } export function normalizeTaskAssigneeLog(response: TaskAssigneeLogResponse): Api.Project.TaskAssigneeLog { return { ...response, id: normalizeStringId(response.id), taskId: normalizeStringId(response.taskId), userId: normalizeStringId(response.userId), operatorUserId: normalizeStringId(response.operatorUserId), userNicknameSnapshot: response.userNicknameSnapshot ?? null, operatorNicknameSnapshot: response.operatorNicknameSnapshot ?? null, reason: response.reason ?? null }; }