import dayjs from 'dayjs'; import type { ConfigType } from 'dayjs'; import type { FlatResponseData } from '@sa/axios'; import { WEB_SERVICE_PREFIX } from '@/constants/service'; import { request } from '../request'; import { type ProjectExecutionResponse, type TaskWorklogResponse, normalizeProjectLocalDate, normalizeTaskWorklog } from './project-shared'; import { type ServiceRequestResult, mapServiceResult, normalizeStringId, safeJsonRequestConfig } from './shared'; type PersonalItemRecord = Api.PersonalItem.PersonalItem; type PersonalItemWorklogRecord = Api.Project.TaskWorklog; type PersonalItemResult = Promise>; type StringIdResponse = string | number; type PersonalItemLocalDateValue = string | number[] | null; type AttachmentItemResponse = Omit & { fileId?: StringIdResponse; id?: StringIdResponse; }; type PersonalItemLifecycleActionResponse = Omit & { needReason?: boolean | number | string | null; }; type PersonalItemResponse = Omit< Api.PersonalItem.PersonalItem, | 'id' | 'ownerId' | 'terminal' | 'allowEdit' | 'availableActions' | 'plannedStartDate' | 'plannedEndDate' | 'actualStartDate' | 'actualEndDate' | 'attachments' | 'totalSpentHours' > & { id: StringIdResponse; ownerId: StringIdResponse; terminal?: boolean | number | string | null; allowEdit?: boolean | number | string | null; availableActions?: PersonalItemLifecycleActionResponse[] | null; plannedStartDate?: PersonalItemLocalDateValue; plannedEndDate?: PersonalItemLocalDateValue; actualStartDate?: PersonalItemLocalDateValue; actualEndDate?: PersonalItemLocalDateValue; attachments?: AttachmentItemResponse[] | null; progressRate?: number | null; totalSpentHours?: number | string | null; }; type PersonalItemPageResponse = Omit & { total: number | string; list: PersonalItemResponse[]; }; type PersonalItemWorklogPageResponse = Api.Project.PageResult; type PersonalItemExecutionOptionResponse = ProjectExecutionResponse & { projectName?: string | null; }; type PersonalItemSaveRequest = { executionId?: string; taskTitle: string; type: string; progressRate?: number; plannedStartDate?: string; plannedEndDate?: string; taskDesc?: string; attachments?: Array<{ id?: string; url: string; name: string; size?: number; contentType?: string; }>; }; type PersonalItemWorklogSaveRequest = { startDate: string; endDate: string; durationHours: number; progressRate: number; workContent?: string; attachments?: Array<{ id?: string; url: string; name: string; size?: number; contentType?: string; }>; difficulty: string; }; const PERSONAL_ITEM_PREFIX = `${WEB_SERVICE_PREFIX}/project/personal-items`; const CURRENT_USER_ID = 'current-user'; const CURRENT_USER_NAME = '当前用户'; const personalItems: PersonalItemRecord[] = createSeedItems(); const personalItemWorklogs: PersonalItemWorklogRecord[] = createSeedWorklogs(); const executionOptions: Api.PersonalItem.PersonalItemExecutionOption[] = createExecutionOptions(); function createSuccessResult(data: T): PersonalItemResult { return Promise.resolve({ data, error: null, response: undefined } as unknown as FlatResponseData); } function normalizePageTotal(total: number | string) { const value = Number(total); return Number.isFinite(value) ? Math.max(0, value) : 0; } 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) }; }); } function normalizeBooleanFlag(value: boolean | number | string | null | undefined) { if (typeof value === 'boolean') { return value; } if (typeof value === 'number') { return value === 1; } if (typeof value === 'string') { const normalized = value.trim().toLowerCase(); if (!normalized || normalized === '0' || normalized === 'false' || normalized === 'n') { return false; } return true; } return false; } function normalizeLifecycleActions( actions?: PersonalItemLifecycleActionResponse[] | null ): Api.PersonalItem.PersonalItemLifecycleAction[] { return (actions ?? []).map(action => ({ actionCode: action.actionCode, actionName: action.actionName ?? '', needReason: normalizeBooleanFlag(action.needReason) })); } function normalizePersonalItem(response: PersonalItemResponse): Api.PersonalItem.PersonalItem { return { id: normalizeStringId(response.id), taskTitle: response.taskTitle ?? '', type: response.type ?? '', ownerId: normalizeStringId(response.ownerId), statusCode: response.statusCode, terminal: normalizeBooleanFlag(response.terminal), allowEdit: normalizeBooleanFlag(response.allowEdit), availableActions: normalizeLifecycleActions(response.availableActions), progressRate: typeof response.progressRate === 'number' ? response.progressRate : Number(response.progressRate ?? 0), totalSpentHours: (() => { if (typeof response.totalSpentHours === 'number') { return response.totalSpentHours; } if (response.totalSpentHours === null || response.totalSpentHours === undefined) { return null; } return Number(response.totalSpentHours); })(), plannedStartDate: normalizeProjectLocalDate(response.plannedStartDate), plannedEndDate: normalizeProjectLocalDate(response.plannedEndDate), actualStartDate: normalizeProjectLocalDate(response.actualStartDate), actualEndDate: normalizeProjectLocalDate(response.actualEndDate), taskDesc: response.taskDesc ?? null, lastStatusReason: response.lastStatusReason ?? null, attachments: normalizeAttachments(response.attachments), creator: response.creator ?? '', createTime: response.createTime ?? '', updater: response.updater ?? '', updateTime: response.updateTime ?? '', deleted: Boolean(response.deleted), ownerName: response.ownerName ?? null, ownerNickname: response.ownerNickname ?? null, statusName: response.statusName ?? null }; } function normalizePersonalItemExecutionOption( response: PersonalItemExecutionOptionResponse ): Api.PersonalItem.PersonalItemExecutionOption { return { executionId: normalizeStringId(response.id), executionName: response.executionName ?? '', projectId: normalizeStringId(response.projectId), projectName: response.projectName ?? null }; } function toPersonalItemSaveRequest(data: Api.PersonalItem.SavePersonalItemParams): PersonalItemSaveRequest { return { executionId: data.executionId ?? undefined, taskTitle: data.taskTitle.trim(), type: data.type, progressRate: typeof data.progressRate === 'number' ? data.progressRate : undefined, plannedStartDate: data.plannedStartDate ?? undefined, plannedEndDate: data.plannedEndDate ?? undefined, taskDesc: data.taskDesc ?? undefined, attachments: data.attachments?.map(item => ({ id: item.fileId || undefined, url: item.url, name: item.name, size: item.size, contentType: item.contentType })) ?? undefined }; } function toPersonalItemWorklogSaveRequest( data: Api.PersonalItem.SavePersonalItemWorklogParams ): PersonalItemWorklogSaveRequest { return { startDate: data.startDate, endDate: data.endDate, durationHours: Number(data.durationHours.toFixed(1)), progressRate: Number(data.progressRate.toFixed(2)), workContent: data.workContent ?? undefined, attachments: data.attachments?.map(item => ({ id: item.fileId || undefined, url: item.url, name: item.name, size: item.size, contentType: item.contentType })) ?? undefined, difficulty: data.difficulty }; } function createPersonalItemPageQuery(params: Api.PersonalItem.PersonalItemSearchParams = {}) { const query = new URLSearchParams(); query.append('pageNo', String(params.pageNo ?? 1)); query.append('pageSize', String(params.pageSize ?? 10)); if (params.keyword) { query.append('keyword', params.keyword); } if (params.ownerId) { query.append('ownerId', params.ownerId); } if (params.statusCode) { query.append('statusCode', params.statusCode); } params.updateTime?.forEach(item => { if (item) { query.append('updateTime', item); } }); return query.toString(); } function createIdsQuery(ids: string[]) { const query = new URLSearchParams(); ids.forEach(id => { if (id) { query.append('ids', id); } }); return query.toString(); } function createBindExecutionQuery(payload: Api.PersonalItem.BindPersonalItemExecutionParams) { const query = new URLSearchParams(); payload.ids.forEach(id => { if (id) { query.append('itemIds', id); } }); query.append('executionId', payload.executionId); return query.toString(); } function cloneAttachment(item: Api.Project.AttachmentItem): Api.Project.AttachmentItem { return { ...item }; } function cloneItem(item: PersonalItemRecord): PersonalItemRecord { return { ...item, attachments: item.attachments?.map(cloneAttachment) ?? null }; } function cloneWorklog(item: PersonalItemWorklogRecord): PersonalItemWorklogRecord { return { ...item, attachments: item.attachments?.map(cloneAttachment) ?? null }; } function normalizeDateTime(value?: ConfigType | null) { const target = value ? dayjs(value) : dayjs(); return target.isValid() ? target.format('YYYY-MM-DD HH:mm:ss') : dayjs().format('YYYY-MM-DD HH:mm:ss'); } function normalizeDate(value?: ConfigType | null) { if (!value) { return null; } const target = dayjs(value); return target.isValid() ? target.format('YYYY-MM-DD') : null; } function createSeedItems(): PersonalItemRecord[] { const now = dayjs(); return [ { id: 'personal-item-1', taskTitle: '整理供应商沟通纪要', type: 'daily', ownerId: CURRENT_USER_ID, statusCode: 'active', progressRate: 45, plannedStartDate: normalizeDate(now.subtract(3, 'day')), plannedEndDate: normalizeDate(now.add(2, 'day')), actualStartDate: normalizeDate(now.subtract(2, 'day')), actualEndDate: null, taskDesc: '

补齐今天会议纪要,沉淀成一页内部记录,便于后续同步。

', lastStatusReason: null, attachments: null, creator: CURRENT_USER_NAME, createTime: normalizeDateTime(now.subtract(3, 'day').hour(9).minute(20).second(0)), updater: CURRENT_USER_NAME, updateTime: normalizeDateTime(now.subtract(2, 'hour')), deleted: false, ownerName: CURRENT_USER_NAME, statusName: '进行中' }, { id: 'personal-item-2', taskTitle: '清理浏览器收藏夹里的项目入口', type: 'daily', ownerId: CURRENT_USER_ID, statusCode: 'pending', progressRate: 0, plannedStartDate: normalizeDate(now.add(1, 'day')), plannedEndDate: normalizeDate(now.add(4, 'day')), actualStartDate: null, actualEndDate: null, taskDesc: '

把已经废弃的测试环境、旧文档入口统一清理。

', lastStatusReason: null, attachments: null, creator: CURRENT_USER_NAME, createTime: normalizeDateTime(now.subtract(2, 'day').hour(14).minute(10).second(0)), updater: CURRENT_USER_NAME, updateTime: normalizeDateTime(now.subtract(5, 'hour')), deleted: false, ownerName: CURRENT_USER_NAME, statusName: '待处理' }, { id: 'personal-item-3', taskTitle: '补充账号开通说明截图', type: 'support', ownerId: CURRENT_USER_ID, statusCode: 'completed', progressRate: 100, plannedStartDate: normalizeDate(now.subtract(5, 'day')), plannedEndDate: normalizeDate(now.subtract(2, 'day')), actualStartDate: normalizeDate(now.subtract(5, 'day')), actualEndDate: normalizeDate(now.subtract(1, 'day')), taskDesc: '

为新同事入职说明补一版截图,后续发在群公告。

', lastStatusReason: '已完成并同步团队', attachments: null, creator: CURRENT_USER_NAME, createTime: normalizeDateTime(now.subtract(5, 'day').hour(11).minute(0).second(0)), updater: CURRENT_USER_NAME, updateTime: normalizeDateTime(now.subtract(1, 'day').hour(18).minute(30).second(0)), deleted: false, ownerName: CURRENT_USER_NAME, statusName: '已完成' } ]; } function createSeedWorklogs(): PersonalItemWorklogRecord[] { const now = dayjs(); return [ { id: 'worklog-1', taskId: 'personal-item-1', userId: CURRENT_USER_ID, userNickname: CURRENT_USER_NAME, startDate: normalizeDate(now.subtract(2, 'day'))!, endDate: normalizeDate(now.subtract(2, 'day'))!, durationHours: 2.5, progressRate: 30, difficulty: '2', workContent: '整理会议录音和重点结论,先输出初版纪要。', attachments: null, createTime: normalizeDateTime(now.subtract(2, 'day').hour(19)), updateTime: normalizeDateTime(now.subtract(2, 'day').hour(19)) }, { id: 'worklog-2', taskId: 'personal-item-1', userId: CURRENT_USER_ID, userNickname: CURRENT_USER_NAME, startDate: normalizeDate(now.subtract(1, 'day'))!, endDate: normalizeDate(now.subtract(1, 'day'))!, durationHours: 1.5, progressRate: 45, difficulty: '2', workContent: '补全供应商待确认项并整理后续跟进人。', attachments: null, createTime: normalizeDateTime(now.subtract(1, 'day').hour(18)), updateTime: normalizeDateTime(now.subtract(1, 'day').hour(18)) }, { id: 'worklog-3', taskId: 'personal-item-3', userId: CURRENT_USER_ID, userNickname: CURRENT_USER_NAME, startDate: normalizeDate(now.subtract(5, 'day'))!, endDate: normalizeDate(now.subtract(5, 'day'))!, durationHours: 1, progressRate: 60, difficulty: '1', workContent: '补拍账号开通流程截图。', attachments: null, createTime: normalizeDateTime(now.subtract(5, 'day').hour(15)), updateTime: normalizeDateTime(now.subtract(5, 'day').hour(15)) }, { id: 'worklog-4', taskId: 'personal-item-3', userId: CURRENT_USER_ID, userNickname: CURRENT_USER_NAME, startDate: normalizeDate(now.subtract(1, 'day'))!, endDate: normalizeDate(now.subtract(1, 'day'))!, durationHours: 0.5, progressRate: 100, difficulty: '1', workContent: '校对文案并发到群公告。', attachments: null, createTime: normalizeDateTime(now.subtract(1, 'day').hour(18).minute(20)), updateTime: normalizeDateTime(now.subtract(1, 'day').hour(18).minute(20)) } ]; } function createExecutionOptions(): Api.PersonalItem.PersonalItemExecutionOption[] { return [ { executionId: 'execution-1001', executionName: '2026Q2 运营提效', projectId: 'project-1001', projectName: '运营中台优化' }, { executionId: 'execution-1002', executionName: '2026Q2 用户支持专项', projectId: 'project-1002', projectName: '基础平台升级' }, { executionId: 'execution-1003', executionName: '2026Q3 数据治理', projectId: 'project-1003', projectName: '数据资产规范化' } ]; } function findItemIndex(id: string) { return personalItems.findIndex(item => item.id === id); } function getItemOrThrow(id: string) { const item = personalItems.find(current => current.id === id && !current.deleted); if (!item) { throw new Error(`personal item not found: ${id}`); } return item; } function sortItems(list: PersonalItemRecord[]) { return [...list].sort((left, right) => dayjs(right.updateTime).valueOf() - dayjs(left.updateTime).valueOf()); } function sortWorklogs(list: PersonalItemWorklogRecord[]) { return [...list].sort((left, right) => { const endDiff = dayjs(right.endDate).valueOf() - dayjs(left.endDate).valueOf(); if (endDiff !== 0) { return endDiff; } return dayjs(right.updateTime).valueOf() - dayjs(left.updateTime).valueOf(); }); } function getPersonalItemStatusName(statusCode: Api.PersonalItem.PersonalItemStatusCode) { const statusNameMap: Partial> = { pending: '待处理', active: '进行中', completed: '已完成' }; return statusNameMap[statusCode] || statusCode; } function removeItemsByIds(ids: string[]) { const idSet = new Set(ids); for (let i = personalItems.length - 1; i >= 0; i -= 1) { if (idSet.has(personalItems[i].id)) { personalItems.splice(i, 1); } } for (let i = personalItemWorklogs.length - 1; i >= 0; i -= 1) { if (idSet.has(personalItemWorklogs[i].taskId)) { personalItemWorklogs.splice(i, 1); } } } function sumWorklogHours(logs: PersonalItemWorklogRecord[]) { return logs.reduce((sum, log) => sum + (log.durationHours ?? 0), 0); } function syncItemFromWorklogs(itemId: string) { const item = getItemOrThrow(itemId); const logs = sortWorklogs(personalItemWorklogs.filter(log => log.taskId === itemId)); item.statusName = getPersonalItemStatusName(item.statusCode); item.totalSpentHours = sumWorklogHours(logs); if (logs.length === 0) { if (item.statusCode !== 'completed') { item.progressRate = 0; item.actualStartDate = null; item.actualEndDate = null; } return; } const latestLog = logs[0]; const chronologicalLogs = [...logs].sort( (left, right) => dayjs(left.startDate).valueOf() - dayjs(right.startDate).valueOf() ); item.progressRate = latestLog.progressRate ?? item.progressRate; item.actualStartDate = chronologicalLogs[0]?.startDate ?? item.actualStartDate; item.actualEndDate = latestLog.endDate ?? item.actualEndDate; item.updateTime = latestLog.updateTime; item.updater = CURRENT_USER_NAME; if (item.statusCode === 'pending') { item.statusCode = 'active'; item.statusName = getPersonalItemStatusName(item.statusCode); } } function applySaveFields(target: PersonalItemRecord, payload: Api.PersonalItem.SavePersonalItemParams) { target.taskTitle = payload.taskTitle.trim(); target.type = payload.type; target.ownerId = payload.ownerId || target.ownerId; target.ownerName = CURRENT_USER_NAME; target.plannedStartDate = payload.plannedStartDate; target.plannedEndDate = payload.plannedEndDate; target.taskDesc = payload.taskDesc ?? null; target.attachments = payload.attachments?.map(cloneAttachment) ?? null; target.updater = CURRENT_USER_NAME; target.updateTime = normalizeDateTime(); } function filterWorklogs(taskId: string, params?: Api.PersonalItem.PersonalItemWorklogSearchParams) { return sortWorklogs( personalItemWorklogs.filter(item => { if (item.taskId !== taskId) { return false; } if (params?.userId && item.userId !== params.userId) { return false; } if (params?.startDate && dayjs(item.endDate).isBefore(dayjs(params.startDate), 'day')) { return false; } if (params?.endDate && dayjs(item.startDate).isAfter(dayjs(params.endDate), 'day')) { return false; } return true; }) ); } export async function fetchGetPersonalItemPage(params: Api.PersonalItem.PersonalItemSearchParams = {}) { const query = createPersonalItemPageQuery(params); const result = await request({ ...safeJsonRequestConfig, url: query ? `${PERSONAL_ITEM_PREFIX}/page?${query}` : `${PERSONAL_ITEM_PREFIX}/page`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, data => ({ total: normalizePageTotal(data.total), list: data.list.map(normalizePersonalItem) })); } export async function fetchGetPersonalItemDetail(id: string) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${id}`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, normalizePersonalItem); } export async function fetchCreatePersonalItem(data: Api.PersonalItem.SavePersonalItemParams) { const result = await request({ ...safeJsonRequestConfig, url: PERSONAL_ITEM_PREFIX, method: 'post', data: toPersonalItemSaveRequest(data) }); const mapped = mapServiceResult(result as ServiceRequestResult, normalizeStringId); if (!mapped.error && mapped.data) { const now = normalizeDateTime(); const createdItem: PersonalItemRecord = { id: mapped.data, taskTitle: data.taskTitle.trim(), type: data.type, ownerId: data.ownerId || CURRENT_USER_ID, statusCode: 'pending', progressRate: typeof data.progressRate === 'number' ? data.progressRate : 0, plannedStartDate: data.plannedStartDate, plannedEndDate: data.plannedEndDate, actualStartDate: null, actualEndDate: null, taskDesc: data.taskDesc ?? null, lastStatusReason: null, attachments: data.attachments?.map(cloneAttachment) ?? null, creator: CURRENT_USER_NAME, createTime: now, updater: CURRENT_USER_NAME, updateTime: now, deleted: false, ownerName: CURRENT_USER_NAME, statusName: getPersonalItemStatusName('pending') }; personalItems.unshift(createdItem); } return mapped; } export async function fetchUpdatePersonalItem(data: Api.PersonalItem.UpdatePersonalItemParams) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${data.id}`, method: 'put', data: toPersonalItemSaveRequest(data) }); const mapped = mapServiceResult(result as ServiceRequestResult, value => Boolean(value)); if (!mapped.error && mapped.data) { const targetIndex = findItemIndex(data.id); if (targetIndex >= 0) { applySaveFields(personalItems[targetIndex], data); } } return mapped; } export async function fetchChangePersonalItemStatus(id: string, data: Api.PersonalItem.ChangePersonalItemStatusParams) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${id}/change-status`, method: 'post', data: { actionCode: data.actionCode, reason: data.reason ?? undefined } }); const mapped = mapServiceResult(result as ServiceRequestResult, value => Boolean(value)); if (!mapped.error && mapped.data) { const target = personalItems.find(item => item.id === id); if (target) { target.lastStatusReason = data.reason ?? null; target.updater = CURRENT_USER_NAME; target.updateTime = normalizeDateTime(); if (data.actionCode === 'start') { target.statusCode = 'active'; target.statusName = getPersonalItemStatusName('active'); target.actualStartDate ??= normalizeDate(dayjs()); target.actualEndDate = null; } else if (data.actionCode === 'complete') { target.statusCode = 'completed'; target.statusName = getPersonalItemStatusName('completed'); target.progressRate = 100; target.actualStartDate ??= normalizeDate(dayjs()); target.actualEndDate = normalizeDate(dayjs()); } else if (data.actionCode === 'reopen') { target.statusCode = 'active'; target.statusName = getPersonalItemStatusName('active'); target.actualStartDate ??= normalizeDate(dayjs()); target.actualEndDate = null; } } } return mapped; } export async function fetchDeletePersonalItem(id: string) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/delete`, method: 'delete', params: { id } }); const mapped = mapServiceResult(result as ServiceRequestResult, value => Boolean(value)); if (!mapped.error && mapped.data) { removeItemsByIds([id]); } return mapped; } export async function fetchBatchDeletePersonalItems(payload: Api.PersonalItem.BatchDeletePersonalItemParams) { const query = createIdsQuery(payload.ids); const result = await request({ ...safeJsonRequestConfig, url: query ? `${PERSONAL_ITEM_PREFIX}/delete-list?${query}` : `${PERSONAL_ITEM_PREFIX}/delete-list`, method: 'delete' }); const mapped = mapServiceResult(result as ServiceRequestResult, value => Boolean(value)); if (!mapped.error && mapped.data) { removeItemsByIds(payload.ids); } return mapped; } export async function fetchGetPersonalItemExecutionOptions() { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/owner/all-execution`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, data => data.map(normalizePersonalItemExecutionOption) ); } export async function fetchBindPersonalItemsToExecution(payload: Api.PersonalItem.BindPersonalItemExecutionParams) { const query = createBindExecutionQuery(payload); const result = await request({ ...safeJsonRequestConfig, url: query ? `${PERSONAL_ITEM_PREFIX}/relate-execution?${query}` : `${PERSONAL_ITEM_PREFIX}/relate-execution`, method: 'post' }); return mapServiceResult(result as ServiceRequestResult, value => Boolean(value)); } export function fetchStartPersonalItem(id: string): PersonalItemResult { return fetchChangePersonalItemStatus(id, { actionCode: 'start' }) as PersonalItemResult; } export function fetchCompletePersonalItem(id: string): PersonalItemResult { return fetchChangePersonalItemStatus(id, { actionCode: 'complete' }) as PersonalItemResult; } export function fetchReopenPersonalItem(id: string): PersonalItemResult { return fetchChangePersonalItemStatus(id, { actionCode: 'reopen' }) as PersonalItemResult; } export async function fetchGetPersonalItemWorklogPage( taskId: string, params: Api.PersonalItem.PersonalItemWorklogSearchParams = {} ) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs`, method: 'get', params }); return mapServiceResult(result as ServiceRequestResult, data => ({ ...data, list: data.list.map(normalizeTaskWorklog) })); } export async function fetchCreatePersonalItemWorklog( taskId: string, data: Api.PersonalItem.SavePersonalItemWorklogParams ) { const result = await request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs`, method: 'post', data: toPersonalItemWorklogSaveRequest(data) }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } export function fetchUpdatePersonalItemWorklog( taskId: string, payload: { worklogId: string; data: Api.PersonalItem.SavePersonalItemWorklogParams } ): PersonalItemResult { return request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs/${payload.worklogId}`, method: 'put', data: toPersonalItemWorklogSaveRequest(payload.data) }); } export function fetchDeletePersonalItemWorklog(taskId: string, worklogId: string): PersonalItemResult { return request({ ...safeJsonRequestConfig, url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs/${worklogId}`, method: 'delete' }); }