881 lines
28 KiB
TypeScript
881 lines
28 KiB
TypeScript
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<T> = Promise<FlatResponseData<any, T>>;
|
|
type StringIdResponse = string | number;
|
|
type PersonalItemLocalDateValue = string | number[] | null;
|
|
type AttachmentItemResponse = Omit<Api.Project.AttachmentItem, 'fileId'> & {
|
|
fileId?: StringIdResponse;
|
|
id?: StringIdResponse;
|
|
};
|
|
type PersonalItemLifecycleActionResponse = Omit<Api.PersonalItem.PersonalItemLifecycleAction, 'needReason'> & {
|
|
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<Api.PersonalItem.PersonalItemPageResult, 'total' | 'list'> & {
|
|
total: number | string;
|
|
list: PersonalItemResponse[];
|
|
};
|
|
type PersonalItemWorklogPageResponse = Api.Project.PageResult<TaskWorklogResponse>;
|
|
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<T>(data: T): PersonalItemResult<T> {
|
|
return Promise.resolve({
|
|
data,
|
|
error: null,
|
|
response: undefined
|
|
} as unknown as FlatResponseData<any, T>);
|
|
}
|
|
|
|
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: '<p>补齐今天会议纪要,沉淀成一页内部记录,便于后续同步。</p>',
|
|
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: '<p>把已经废弃的测试环境、旧文档入口统一清理。</p>',
|
|
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: '<p>为新同事入职说明补一版截图,后续发在群公告。</p>',
|
|
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<Record<Api.PersonalItem.PersonalItemStatusCode, string>> = {
|
|
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<PersonalItemPageResponse>({
|
|
...safeJsonRequestConfig,
|
|
url: query ? `${PERSONAL_ITEM_PREFIX}/page?${query}` : `${PERSONAL_ITEM_PREFIX}/page`,
|
|
method: 'get'
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<PersonalItemPageResponse>, data => ({
|
|
total: normalizePageTotal(data.total),
|
|
list: data.list.map(normalizePersonalItem)
|
|
}));
|
|
}
|
|
|
|
export async function fetchGetPersonalItemDetail(id: string) {
|
|
const result = await request<PersonalItemResponse>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${id}`,
|
|
method: 'get'
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<PersonalItemResponse>, normalizePersonalItem);
|
|
}
|
|
|
|
export async function fetchCreatePersonalItem(data: Api.PersonalItem.SavePersonalItemParams) {
|
|
const result = await request<string | number>({
|
|
...safeJsonRequestConfig,
|
|
url: PERSONAL_ITEM_PREFIX,
|
|
method: 'post',
|
|
data: toPersonalItemSaveRequest(data)
|
|
});
|
|
|
|
const mapped = mapServiceResult(result as ServiceRequestResult<string | number>, 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<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${data.id}`,
|
|
method: 'put',
|
|
data: toPersonalItemSaveRequest(data)
|
|
});
|
|
|
|
const mapped = mapServiceResult(result as ServiceRequestResult<boolean>, 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<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${id}/change-status`,
|
|
method: 'post',
|
|
data: {
|
|
actionCode: data.actionCode,
|
|
reason: data.reason ?? undefined
|
|
}
|
|
});
|
|
|
|
const mapped = mapServiceResult(result as ServiceRequestResult<boolean>, 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<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/delete`,
|
|
method: 'delete',
|
|
params: { id }
|
|
});
|
|
|
|
const mapped = mapServiceResult(result as ServiceRequestResult<boolean>, 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<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: query ? `${PERSONAL_ITEM_PREFIX}/delete-list?${query}` : `${PERSONAL_ITEM_PREFIX}/delete-list`,
|
|
method: 'delete'
|
|
});
|
|
|
|
const mapped = mapServiceResult(result as ServiceRequestResult<boolean>, value => Boolean(value));
|
|
|
|
if (!mapped.error && mapped.data) {
|
|
removeItemsByIds(payload.ids);
|
|
}
|
|
|
|
return mapped;
|
|
}
|
|
|
|
export async function fetchGetPersonalItemExecutionOptions() {
|
|
const result = await request<PersonalItemExecutionOptionResponse[]>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/owner/all-execution`,
|
|
method: 'get'
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<PersonalItemExecutionOptionResponse[]>, data =>
|
|
data.map(normalizePersonalItemExecutionOption)
|
|
);
|
|
}
|
|
|
|
export async function fetchBindPersonalItemsToExecution(payload: Api.PersonalItem.BindPersonalItemExecutionParams) {
|
|
const query = createBindExecutionQuery(payload);
|
|
const result = await request<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: query ? `${PERSONAL_ITEM_PREFIX}/relate-execution?${query}` : `${PERSONAL_ITEM_PREFIX}/relate-execution`,
|
|
method: 'post'
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<boolean>, value => Boolean(value));
|
|
}
|
|
|
|
export function fetchStartPersonalItem(id: string): PersonalItemResult<boolean> {
|
|
return fetchChangePersonalItemStatus(id, { actionCode: 'start' }) as PersonalItemResult<boolean>;
|
|
}
|
|
|
|
export function fetchCompletePersonalItem(id: string): PersonalItemResult<boolean> {
|
|
return fetchChangePersonalItemStatus(id, { actionCode: 'complete' }) as PersonalItemResult<boolean>;
|
|
}
|
|
|
|
export function fetchReopenPersonalItem(id: string): PersonalItemResult<boolean> {
|
|
return fetchChangePersonalItemStatus(id, { actionCode: 'reopen' }) as PersonalItemResult<boolean>;
|
|
}
|
|
|
|
export async function fetchGetPersonalItemWorklogPage(
|
|
taskId: string,
|
|
params: Api.PersonalItem.PersonalItemWorklogSearchParams = {}
|
|
) {
|
|
const result = await request<PersonalItemWorklogPageResponse>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs`,
|
|
method: 'get',
|
|
params
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<PersonalItemWorklogPageResponse>, data => ({
|
|
...data,
|
|
list: data.list.map(normalizeTaskWorklog)
|
|
}));
|
|
}
|
|
|
|
export async function fetchCreatePersonalItemWorklog(
|
|
taskId: string,
|
|
data: Api.PersonalItem.SavePersonalItemWorklogParams
|
|
) {
|
|
const result = await request<string | number>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs`,
|
|
method: 'post',
|
|
data: toPersonalItemWorklogSaveRequest(data)
|
|
});
|
|
|
|
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
|
|
}
|
|
|
|
export function fetchUpdatePersonalItemWorklog(
|
|
taskId: string,
|
|
payload: { worklogId: string; data: Api.PersonalItem.SavePersonalItemWorklogParams }
|
|
): PersonalItemResult<boolean> {
|
|
return request<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs/${payload.worklogId}`,
|
|
method: 'put',
|
|
data: toPersonalItemWorklogSaveRequest(payload.data)
|
|
});
|
|
}
|
|
|
|
export function fetchDeletePersonalItemWorklog(taskId: string, worklogId: string): PersonalItemResult<boolean> {
|
|
return request<boolean>({
|
|
...safeJsonRequestConfig,
|
|
url: `${PERSONAL_ITEM_PREFIX}/${taskId}/worklogs/${worklogId}`,
|
|
method: 'delete'
|
|
});
|
|
}
|