633 lines
22 KiB
TypeScript
633 lines
22 KiB
TypeScript
import dayjs from 'dayjs';
|
||
import { normalizeNullableStringId, normalizeStringId } from './shared';
|
||
|
||
type ProjectStatusCode = Api.Project.ProjectStatusCode;
|
||
type ProjectStatusActionCode = Exclude<Api.Project.ProjectStatusActionCode, 'auto_start'>;
|
||
|
||
type StringIdResponse = string | number;
|
||
|
||
export type ProjectLocalDateValue = string | number[] | null;
|
||
|
||
export type LifecycleActionResponse<ActionCode extends string> = Partial<Api.Project.LifecycleAction<ActionCode>> & {
|
||
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<Api.Project.ProjectExecutionActionCode>[] | null;
|
||
plannedStartDate?: ProjectLocalDateValue;
|
||
plannedEndDate?: ProjectLocalDateValue;
|
||
actualStartDate?: ProjectLocalDateValue;
|
||
actualEndDate?: ProjectLocalDateValue;
|
||
progressRate?: number | null;
|
||
priority?: string | number | null;
|
||
priorityName?: string | null;
|
||
};
|
||
|
||
export type MyExecutionResponse = Omit<
|
||
Api.Project.MyExecutionItem,
|
||
| 'id'
|
||
| 'projectId'
|
||
| 'projectRequirementId'
|
||
| 'priority'
|
||
| 'progressRate'
|
||
| 'plannedStartDate'
|
||
| 'plannedEndDate'
|
||
| 'actualStartDate'
|
||
| 'actualEndDate'
|
||
> & {
|
||
id: StringIdResponse;
|
||
projectId: StringIdResponse;
|
||
projectRequirementId?: StringIdResponse | null;
|
||
priority?: string | number | null;
|
||
progressRate?: number | null;
|
||
plannedStartDate?: ProjectLocalDateValue;
|
||
plannedEndDate?: ProjectLocalDateValue;
|
||
actualStartDate?: ProjectLocalDateValue;
|
||
actualEndDate?: ProjectLocalDateValue;
|
||
};
|
||
|
||
export type MyParticipatedProjectResponse = Omit<Api.Project.MyParticipatedProjectItem, 'id'> & {
|
||
id: StringIdResponse;
|
||
};
|
||
|
||
export type MyOwnedProjectMemberResponse = Omit<Api.Project.MyOwnedProjectMember, 'userId'> & {
|
||
userId: StringIdResponse;
|
||
};
|
||
|
||
export type MyOwnedProjectResponse = Omit<Api.Project.MyOwnedProjectItem, 'id' | 'members'> & {
|
||
id: StringIdResponse;
|
||
members?: MyOwnedProjectMemberResponse[] | null;
|
||
};
|
||
|
||
export type MyTaskResponse = Omit<
|
||
Api.Project.MyTaskItem,
|
||
| 'id'
|
||
| 'projectId'
|
||
| 'executionId'
|
||
| 'priority'
|
||
| 'plannedEndDate'
|
||
| 'progressRate'
|
||
| 'createTime'
|
||
| 'parentTaskId'
|
||
| 'availableActions'
|
||
> & {
|
||
id: StringIdResponse;
|
||
projectId: StringIdResponse;
|
||
executionId?: StringIdResponse | null;
|
||
priority?: string | number | null;
|
||
plannedEndDate?: ProjectLocalDateValue;
|
||
progressRate?: number | string | null;
|
||
createTime?: string | number | null;
|
||
parentTaskId?: StringIdResponse | null;
|
||
availableActions?: LifecycleActionResponse<Api.Project.ProjectTaskActionCode>[] | null;
|
||
};
|
||
|
||
export type TeamLoadDistributionItemResponse = Omit<Api.Project.TeamLoadDistributionItem, 'projectId'> & {
|
||
projectId?: StringIdResponse | null;
|
||
};
|
||
|
||
export type TeamLoadMemberResponse = Omit<Api.Project.TeamLoadMember, 'userId' | 'items'> & {
|
||
userId: StringIdResponse;
|
||
items?: TeamLoadDistributionItemResponse[] | null;
|
||
};
|
||
|
||
export type TeamLoadResponse = {
|
||
members?: TeamLoadMemberResponse[] | null;
|
||
};
|
||
|
||
export type WorklogDistributionItemResponse = Omit<Api.Project.WorklogDistributionItem, 'projectId'> & {
|
||
projectId?: StringIdResponse | null;
|
||
};
|
||
|
||
export type MyWorklogWeekResponse = Omit<Api.Project.MyWorklogWeekResult, 'dailyHours' | 'distribution'> & {
|
||
dailyHours?: number[] | null;
|
||
distribution?: WorklogDistributionItemResponse[] | null;
|
||
};
|
||
|
||
export type TeamWorklogWeekMemberResponse = Omit<Api.Project.TeamWorklogWeekMember, 'userId' | 'items'> & {
|
||
userId: StringIdResponse;
|
||
items?: WorklogDistributionItemResponse[] | null;
|
||
};
|
||
|
||
export type TeamWorklogWeekResponse = Omit<Api.Project.TeamWorklogWeekResult, 'members'> & {
|
||
members?: TeamWorklogWeekMemberResponse[] | null;
|
||
};
|
||
|
||
export type ExecutionAssigneeResponse = Omit<Api.Project.ExecutionAssignee, 'id' | 'executionId' | 'userId'> & {
|
||
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<Api.Project.TaskAssigneeRef, 'id' | 'userId'> & {
|
||
id: StringIdResponse;
|
||
userId: StringIdResponse;
|
||
};
|
||
|
||
/**
|
||
* 后端 attachments 项的兼容形态:历史/当前响应字段名是 `id`,前端类型统一用 `fileId`。
|
||
* normalizeAttachments 负责把两者归一成 `fileId`。
|
||
*/
|
||
type AttachmentItemResponse = Omit<Api.Project.AttachmentItem, 'fileId'> & {
|
||
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<Api.Project.ProjectTaskActionCode>[] | 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<ProjectStatusActionCode, string> = {
|
||
pause: '暂停项目',
|
||
resume: '恢复项目',
|
||
complete: '完成项目',
|
||
cancel: '取消项目',
|
||
reopen: '重新开启',
|
||
archive: '归档项目'
|
||
};
|
||
|
||
const projectLifecycleActionReasonRequiredMap: Record<ProjectStatusActionCode, boolean> = {
|
||
pause: true,
|
||
resume: false,
|
||
complete: true,
|
||
cancel: true,
|
||
reopen: true,
|
||
archive: false
|
||
};
|
||
|
||
const projectLifecycleActionMap: Record<ProjectStatusCode, ProjectStatusActionCode[]> = {
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 后端 LocalDateTime 统一序列化为毫秒时间戳(也可能是数字字符串/格式化字符串),
|
||
* 归一为 'YYYY-MM-DD HH:mm:ss' 供展示与 dayjs 解析。
|
||
*/
|
||
export function normalizeProjectDateTime(value: string | number | null | undefined): string {
|
||
if (value === null || value === undefined || value === '') {
|
||
return '';
|
||
}
|
||
|
||
let parsed: dayjs.Dayjs;
|
||
if (typeof value === 'number') {
|
||
parsed = dayjs(value);
|
||
} else if (/^\d+$/.test(value)) {
|
||
// 字符串形态的毫秒时间戳:dayjs 无法直接解析,先转数值(时间值非 ID,安全整数范围内)
|
||
parsed = dayjs(Number(value));
|
||
} else {
|
||
parsed = dayjs(value);
|
||
}
|
||
|
||
return parsed.isValid() ? parsed.format('YYYY-MM-DD HH:mm:ss') : '';
|
||
}
|
||
|
||
export function normalizeLifecycleActions<ActionCode extends string>(
|
||
actions: LifecycleActionResponse<ActionCode>[] | null | undefined
|
||
): Api.Project.LifecycleAction<ActionCode>[] {
|
||
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);
|
||
}
|
||
|
||
function normalizeProgressRate(value: number | string | null | undefined) {
|
||
if (value === null || value === undefined || value === '') {
|
||
return null;
|
||
}
|
||
|
||
const numeric = typeof value === 'number' ? value : Number(value ?? 0);
|
||
return Number.isFinite(numeric) ? numeric : null;
|
||
}
|
||
|
||
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 normalizeMyExecution(response: MyExecutionResponse): Api.Project.MyExecutionItem {
|
||
return {
|
||
...response,
|
||
id: normalizeStringId(response.id),
|
||
projectId: normalizeStringId(response.projectId),
|
||
statusName: response.statusName ?? null,
|
||
priority: normalizePriority(response.priority),
|
||
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0,
|
||
plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate),
|
||
plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate),
|
||
actualStartDate: normalizeProjectLocalDate(response.actualStartDate),
|
||
actualEndDate: normalizeProjectLocalDate(response.actualEndDate),
|
||
projectRequirementId: normalizeNullableStringId(response.projectRequirementId),
|
||
projectRequirementName: response.projectRequirementName ?? null
|
||
};
|
||
}
|
||
|
||
export function normalizeMyParticipatedProject(
|
||
response: MyParticipatedProjectResponse
|
||
): Api.Project.MyParticipatedProjectItem {
|
||
return {
|
||
...response,
|
||
id: normalizeStringId(response.id),
|
||
code: response.code ?? null,
|
||
statusName: response.statusName ?? null,
|
||
myRole: response.myRole ?? null
|
||
};
|
||
}
|
||
|
||
export function normalizeMyOwnedProject(response: MyOwnedProjectResponse): Api.Project.MyOwnedProjectItem {
|
||
return {
|
||
...response,
|
||
id: normalizeStringId(response.id),
|
||
code: response.code ?? null,
|
||
myRole: response.myRole ?? null,
|
||
plannedEndDate: response.plannedEndDate ?? null,
|
||
members: (response.members ?? []).map(member => ({
|
||
...member,
|
||
userId: normalizeStringId(member.userId),
|
||
userName: member.userName ?? null
|
||
}))
|
||
};
|
||
}
|
||
|
||
export function normalizeMyTask(response: MyTaskResponse): Api.Project.MyTaskItem {
|
||
return {
|
||
...response,
|
||
id: normalizeStringId(response.id),
|
||
projectId: normalizeStringId(response.projectId),
|
||
executionId: normalizeNullableStringId(response.executionId),
|
||
executionName: response.executionName ?? null,
|
||
statusName: response.statusName ?? null,
|
||
priority: normalizePriority(response.priority),
|
||
plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate),
|
||
progressRate: normalizeProgressRate(response.progressRate) ?? 0,
|
||
createTime: normalizeProjectDateTime(response.createTime),
|
||
parentTaskId: normalizeNullableStringId(response.parentTaskId),
|
||
terminal: Boolean(response.terminal),
|
||
allowEdit: Boolean(response.allowEdit),
|
||
availableActions: normalizeLifecycleActions(response.availableActions)
|
||
};
|
||
}
|
||
|
||
function normalizeWorklogDistributionItem(
|
||
response: WorklogDistributionItemResponse | TeamLoadDistributionItemResponse
|
||
): { projectId: string | null; projectName: string | null; kind: 'project' | 'personal' | 'other' } {
|
||
return {
|
||
projectId: normalizeNullableStringId(response.projectId),
|
||
projectName: response.projectName ?? null,
|
||
kind: response.kind
|
||
};
|
||
}
|
||
|
||
export function normalizeTeamLoad(response: TeamLoadResponse): Api.Project.TeamLoadResult {
|
||
return {
|
||
members: (response.members ?? []).map(member => ({
|
||
userId: normalizeStringId(member.userId),
|
||
userNickname: member.userNickname ?? '',
|
||
items: (member.items ?? []).map(item => ({
|
||
...normalizeWorklogDistributionItem(item),
|
||
count: typeof item.count === 'number' ? item.count : 0
|
||
})),
|
||
dueSoonCount: typeof member.dueSoonCount === 'number' ? member.dueSoonCount : 0,
|
||
overdueCount: typeof member.overdueCount === 'number' ? member.overdueCount : 0
|
||
}))
|
||
};
|
||
}
|
||
|
||
export function normalizeMyWorklogWeek(response: MyWorklogWeekResponse): Api.Project.MyWorklogWeekResult {
|
||
return {
|
||
weekStart: response.weekStart ?? '',
|
||
dailyHours: response.dailyHours ?? [0, 0, 0, 0, 0],
|
||
distribution: (response.distribution ?? []).map(item => ({
|
||
...normalizeWorklogDistributionItem(item),
|
||
hours: typeof item.hours === 'number' ? item.hours : 0
|
||
}))
|
||
};
|
||
}
|
||
|
||
export function normalizeTeamWorklogWeek(response: TeamWorklogWeekResponse): Api.Project.TeamWorklogWeekResult {
|
||
return {
|
||
weekStart: response.weekStart ?? '',
|
||
members: (response.members ?? []).map(member => ({
|
||
userId: normalizeStringId(member.userId),
|
||
userNickname: member.userNickname ?? '',
|
||
items: (member.items ?? []).map(item => ({
|
||
...normalizeWorklogDistributionItem(item),
|
||
hours: typeof item.hours === 'number' ? item.hours : 0
|
||
}))
|
||
}))
|
||
};
|
||
}
|
||
|
||
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
|
||
};
|
||
}
|