579 行
16 KiB
TypeScript
579 行
16 KiB
TypeScript
import { apiRequestData } from "./http";
|
|
|
|
export type ProjectStatus = "draft" | "active" | "paused" | "completed" | "archived";
|
|
export type ProjectRole = "owner" | "manager" | "member" | "observer";
|
|
export type ProjectMilestoneStatus = "pending" | "in_progress" | "done" | "delayed" | "cancelled";
|
|
export type ProjectTaskStatus = "todo" | "doing" | "blocked" | "done" | "cancelled";
|
|
export type ProjectTaskPriority = "low" | "normal" | "high" | "urgent";
|
|
export type ProjectRiskLevel = "low" | "medium" | "high";
|
|
export type ProjectDeliveryRecordType = "kickoff" | "onsite" | "stage_acceptance" | "final_acceptance" | "handover";
|
|
export type ProjectCommunicationChannel = "wechat" | "phone" | "meeting" | "wecom" | "email" | "onsite" | "other";
|
|
export type ProjectClosureChecklistStatus = "pending" | "done" | "waived";
|
|
export type ConfidentialityLevel = "public" | "internal" | "confidential" | "restricted";
|
|
export type DocumentStorageType = "oa_link" | "nas" | "internal_share" | "offline_archive";
|
|
|
|
export type EmployeeBasic = {
|
|
id: string;
|
|
employeeNo: string;
|
|
name: string;
|
|
};
|
|
|
|
export type ProjectListItem = {
|
|
id: string;
|
|
projectNo: string;
|
|
name: string;
|
|
clientName: string;
|
|
clientAlias: string | null;
|
|
status: ProjectStatus;
|
|
startsAt: string | null;
|
|
targetEndAt: string | null;
|
|
actualEndAt: string | null;
|
|
summary: string | null;
|
|
isConfidential: boolean;
|
|
owner: EmployeeBasic | null;
|
|
manager: EmployeeBasic | null;
|
|
counts: {
|
|
members: number;
|
|
milestones: number;
|
|
tasks: number;
|
|
risks: number;
|
|
overdueMilestones: number;
|
|
highPriorityTasks: number;
|
|
openRisks: number;
|
|
};
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type ProjectMember = {
|
|
id: string;
|
|
employeeId: string;
|
|
role: ProjectRole;
|
|
joinedAt: string | null;
|
|
leftAt: string | null;
|
|
employee: EmployeeBasic | null;
|
|
};
|
|
|
|
export type ProjectMilestone = {
|
|
id: string;
|
|
title: string;
|
|
description: string | null;
|
|
status: ProjectMilestoneStatus;
|
|
dueDate: string | null;
|
|
completedAt: string | null;
|
|
ownerEmployeeId: string | null;
|
|
sortOrder: number;
|
|
};
|
|
|
|
export type ProjectTask = {
|
|
id: string;
|
|
title: string;
|
|
description: string | null;
|
|
status: ProjectTaskStatus;
|
|
priority: ProjectTaskPriority;
|
|
assigneeEmployeeId: string | null;
|
|
dueDate: string | null;
|
|
blockedReason: string | null;
|
|
createdBy: string;
|
|
assignee: EmployeeBasic | null;
|
|
creator: EmployeeBasic | null;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type ProjectRisk = {
|
|
id: string;
|
|
title: string;
|
|
detail: string;
|
|
level: ProjectRiskLevel;
|
|
status: string;
|
|
ownerEmployeeId: string | null;
|
|
owner: EmployeeBasic | null;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type ProjectCommunicationLog = {
|
|
id: string;
|
|
happenedAt: string | null;
|
|
channel: ProjectCommunicationChannel;
|
|
externalContactName: string | null;
|
|
externalOrgName: string | null;
|
|
summary: string;
|
|
nextAction: string | null;
|
|
nextFollowUpDate: string | null;
|
|
createdBy: string;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
canEdit: boolean;
|
|
creator: EmployeeBasic | null;
|
|
};
|
|
|
|
export type ProjectClosureChecklistItem = {
|
|
id: string;
|
|
itemKey: string;
|
|
title: string;
|
|
status: ProjectClosureChecklistStatus;
|
|
note: string | null;
|
|
sortOrder: number;
|
|
completedAt: string | null;
|
|
completedBy: string | null;
|
|
canEdit: boolean;
|
|
completer: EmployeeBasic | null;
|
|
};
|
|
|
|
export type ProjectDetail = {
|
|
id: string;
|
|
projectNo: string;
|
|
name: string;
|
|
clientName: string;
|
|
clientAlias: string | null;
|
|
ownerEmployeeId: string;
|
|
managerEmployeeId: string | null;
|
|
status: ProjectStatus;
|
|
startsAt: string | null;
|
|
targetEndAt: string | null;
|
|
actualEndAt: string | null;
|
|
summary: string | null;
|
|
isConfidential: boolean;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
owner: EmployeeBasic | null;
|
|
manager: EmployeeBasic | null;
|
|
counts: {
|
|
members: number;
|
|
milestones: number;
|
|
tasks: number;
|
|
risks: number;
|
|
reimbursements: number;
|
|
purchaseRequests: number;
|
|
travelRequests: number;
|
|
todoItems: number;
|
|
weeklyReports: number;
|
|
communicationLogs: number;
|
|
closureChecklistItems: number;
|
|
contracts: number;
|
|
deliveries: number;
|
|
documentRefs: number;
|
|
};
|
|
members: ProjectMember[];
|
|
milestones: ProjectMilestone[];
|
|
tasks: ProjectTask[];
|
|
risks: ProjectRisk[];
|
|
communicationSummary: {
|
|
totalCount: number;
|
|
latestHappenedAt: string | null;
|
|
followUpDueTodayCount: number;
|
|
overdueFollowUpCount: number;
|
|
};
|
|
closureSummary: {
|
|
totalCount: number;
|
|
pendingCount: number;
|
|
waivedCount: number;
|
|
warned: boolean;
|
|
facts: {
|
|
openRiskCount: number;
|
|
openReimbursementCount: number;
|
|
openTravelCount: number;
|
|
deliveryRecordCount: number;
|
|
contractCount: number;
|
|
confidentialDocRefCount: number;
|
|
};
|
|
};
|
|
capabilities: {
|
|
communicationLogEnabled: boolean;
|
|
closureChecklistEnabled: boolean;
|
|
};
|
|
};
|
|
|
|
export type ProjectExpenseSummary = {
|
|
totalAmount: string;
|
|
items: Array<{
|
|
id: string;
|
|
formNumber: string;
|
|
status: string;
|
|
totalAmount: string;
|
|
reimbursementDate: string | null;
|
|
updatedAt: string | null;
|
|
}>;
|
|
};
|
|
|
|
export type ProjectTravelSummary = {
|
|
totalAmount: string;
|
|
items: Array<{
|
|
id: string;
|
|
requestNo: string;
|
|
status: string;
|
|
estimatedAmount: string;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
updatedAt: string | null;
|
|
}>;
|
|
};
|
|
|
|
export type ProjectPurchaseSummary = {
|
|
totalAmount: string;
|
|
items: Array<{
|
|
id: string;
|
|
requestNo: string;
|
|
title: string;
|
|
status: string;
|
|
totalAmount: string;
|
|
updatedAt: string | null;
|
|
}>;
|
|
};
|
|
|
|
export type ProjectWeeklyReportLink = {
|
|
id: string;
|
|
weeklyReportId: string;
|
|
weekStart: string | null;
|
|
status: "draft" | "submitted";
|
|
employee: EmployeeBasic | null;
|
|
progressText: string | null;
|
|
riskText: string | null;
|
|
nextStepText: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type ProjectDeliveryRecord = {
|
|
id: string;
|
|
title: string;
|
|
recordType: ProjectDeliveryRecordType;
|
|
summary: string;
|
|
occurredAt: string | null;
|
|
owner: EmployeeBasic | null;
|
|
createdAt: string | null;
|
|
};
|
|
|
|
export type ConfidentialDocumentRef = {
|
|
id: string;
|
|
title: string;
|
|
documentNo: string | null;
|
|
confidentialityLevel: ConfidentialityLevel;
|
|
storageType: DocumentStorageType;
|
|
storagePath: string;
|
|
offlineArchiveNo: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
function toQueryString(query: Record<string, string | number | boolean | undefined>) {
|
|
const params = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(query)) {
|
|
if (value == null || value === "") {
|
|
continue;
|
|
}
|
|
params.set(key, typeof value === "boolean" ? (value ? "1" : "0") : String(value));
|
|
}
|
|
const encoded = params.toString();
|
|
return encoded ? `?${encoded}` : "";
|
|
}
|
|
|
|
export async function fetchProjects(params: {
|
|
status?: ProjectStatus;
|
|
mine?: boolean;
|
|
q?: string;
|
|
} = {}) {
|
|
return apiRequestData<{ items: ProjectListItem[] }>(`/api/projects${toQueryString(params)}`);
|
|
}
|
|
|
|
export async function createProject(payload: {
|
|
projectNo?: string;
|
|
name: string;
|
|
clientName: string;
|
|
clientAlias?: string | null;
|
|
ownerEmployeeId?: string;
|
|
managerEmployeeId?: string | null;
|
|
status?: ProjectStatus;
|
|
startsAt?: string | null;
|
|
targetEndAt?: string | null;
|
|
actualEndAt?: string | null;
|
|
summary?: string | null;
|
|
isConfidential?: boolean;
|
|
}) {
|
|
return apiRequestData<{ item: ProjectDetail }>("/api/projects", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProject(projectId: string) {
|
|
return apiRequestData<{ item: ProjectDetail }>(`/api/projects/${projectId}`);
|
|
}
|
|
|
|
export type ProjectClosureWarning = {
|
|
pendingCount: number;
|
|
message: string;
|
|
} | null;
|
|
|
|
export async function patchProject(
|
|
projectId: string,
|
|
payload: Partial<{
|
|
projectNo: string;
|
|
name: string;
|
|
clientName: string;
|
|
clientAlias: string | null;
|
|
ownerEmployeeId: string;
|
|
managerEmployeeId: string | null;
|
|
status: ProjectStatus;
|
|
startsAt: string | null;
|
|
targetEndAt: string | null;
|
|
actualEndAt: string | null;
|
|
summary: string | null;
|
|
isConfidential: boolean;
|
|
}>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectDetail; closureWarning: ProjectClosureWarning }>(`/api/projects/${projectId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function archiveProject(projectId: string) {
|
|
return apiRequestData<{ item: ProjectDetail; closureWarning: ProjectClosureWarning }>(`/api/projects/${projectId}/archive`, {
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectMembers(projectId: string) {
|
|
return apiRequestData<{ items: ProjectMember[] }>(`/api/projects/${projectId}/members`);
|
|
}
|
|
|
|
export async function createProjectMember(projectId: string, payload: { employeeId: string; role?: ProjectRole }) {
|
|
return apiRequestData<{ item: ProjectMember }>(`/api/projects/${projectId}/members`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function patchProjectMember(
|
|
projectId: string,
|
|
memberId: string,
|
|
payload: Partial<{ role: ProjectRole; leftAt: string | null }>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectMember }>(`/api/projects/${projectId}/members/${memberId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectMilestones(projectId: string) {
|
|
return apiRequestData<{ items: ProjectMilestone[] }>(`/api/projects/${projectId}/milestones`);
|
|
}
|
|
|
|
export async function createProjectMilestone(
|
|
projectId: string,
|
|
payload: {
|
|
title: string;
|
|
description?: string | null;
|
|
status?: ProjectMilestoneStatus;
|
|
dueDate?: string | null;
|
|
completedAt?: string | null;
|
|
ownerEmployeeId?: string | null;
|
|
sortOrder?: number;
|
|
},
|
|
) {
|
|
return apiRequestData<{ item: ProjectMilestone }>(`/api/projects/${projectId}/milestones`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function patchProjectMilestone(
|
|
projectId: string,
|
|
milestoneId: string,
|
|
payload: Partial<{
|
|
title: string;
|
|
description: string | null;
|
|
status: ProjectMilestoneStatus;
|
|
dueDate: string | null;
|
|
completedAt: string | null;
|
|
ownerEmployeeId: string | null;
|
|
sortOrder: number;
|
|
}>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectMilestone }>(`/api/projects/${projectId}/milestones/${milestoneId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectTasks(projectId: string) {
|
|
return apiRequestData<{ items: ProjectTask[] }>(`/api/projects/${projectId}/tasks`);
|
|
}
|
|
|
|
export async function createProjectTask(
|
|
projectId: string,
|
|
payload: {
|
|
title: string;
|
|
description?: string | null;
|
|
status?: ProjectTaskStatus;
|
|
priority?: ProjectTaskPriority;
|
|
assigneeEmployeeId?: string | null;
|
|
dueDate?: string | null;
|
|
blockedReason?: string | null;
|
|
},
|
|
) {
|
|
return apiRequestData<{ item: ProjectTask }>(`/api/projects/${projectId}/tasks`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function patchProjectTask(
|
|
projectId: string,
|
|
taskId: string,
|
|
payload: Partial<{
|
|
title: string;
|
|
description: string | null;
|
|
status: ProjectTaskStatus;
|
|
priority: ProjectTaskPriority;
|
|
assigneeEmployeeId: string | null;
|
|
dueDate: string | null;
|
|
blockedReason: string | null;
|
|
}>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectTask }>(`/api/projects/${projectId}/tasks/${taskId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectRisks(projectId: string) {
|
|
return apiRequestData<{ items: ProjectRisk[] }>(`/api/projects/${projectId}/risks`);
|
|
}
|
|
|
|
export async function createProjectRisk(
|
|
projectId: string,
|
|
payload: { title: string; detail: string; level?: ProjectRiskLevel; status?: string; ownerEmployeeId?: string | null },
|
|
) {
|
|
return apiRequestData<{ item: ProjectRisk }>(`/api/projects/${projectId}/risks`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function patchProjectRisk(
|
|
projectId: string,
|
|
riskId: string,
|
|
payload: Partial<{ title: string; detail: string; level: ProjectRiskLevel; status: string; ownerEmployeeId: string | null }>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectRisk }>(`/api/projects/${projectId}/risks/${riskId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectExpensesSummary(projectId: string) {
|
|
return apiRequestData<ProjectExpenseSummary>(`/api/projects/${projectId}/expenses/summary`);
|
|
}
|
|
|
|
export async function fetchProjectTravelSummary(projectId: string) {
|
|
return apiRequestData<ProjectTravelSummary>(`/api/projects/${projectId}/travel/summary`);
|
|
}
|
|
|
|
export async function fetchProjectPurchaseSummary(projectId: string) {
|
|
return apiRequestData<ProjectPurchaseSummary>(`/api/projects/${projectId}/purchase-requests/summary`);
|
|
}
|
|
|
|
export async function fetchProjectWeeklyReports(projectId: string) {
|
|
return apiRequestData<{ items: ProjectWeeklyReportLink[] }>(`/api/projects/${projectId}/weekly-reports`);
|
|
}
|
|
|
|
export async function fetchProjectCommunicationLogs(projectId: string) {
|
|
return apiRequestData<{ items: ProjectCommunicationLog[] }>(`/api/projects/${projectId}/communication-logs`);
|
|
}
|
|
|
|
export async function createProjectCommunicationLog(
|
|
projectId: string,
|
|
payload: {
|
|
happenedAt: string;
|
|
channel: ProjectCommunicationChannel;
|
|
externalContactName?: string | null;
|
|
externalOrgName?: string | null;
|
|
summary: string;
|
|
nextAction?: string | null;
|
|
nextFollowUpDate?: string | null;
|
|
},
|
|
) {
|
|
return apiRequestData<{ item: ProjectCommunicationLog }>(`/api/projects/${projectId}/communication-logs`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function patchProjectCommunicationLog(
|
|
projectId: string,
|
|
logId: string,
|
|
payload: Partial<{
|
|
happenedAt: string;
|
|
channel: ProjectCommunicationChannel;
|
|
externalContactName: string | null;
|
|
externalOrgName: string | null;
|
|
summary: string;
|
|
nextAction: string | null;
|
|
nextFollowUpDate: string | null;
|
|
}>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectCommunicationLog }>(`/api/projects/${projectId}/communication-logs/${logId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectClosureChecklist(projectId: string) {
|
|
return apiRequestData<{ items: ProjectClosureChecklistItem[] }>(`/api/projects/${projectId}/closure-checklist`);
|
|
}
|
|
|
|
export async function patchProjectClosureChecklistItem(
|
|
projectId: string,
|
|
itemId: string,
|
|
payload: Partial<{ status: ProjectClosureChecklistStatus; note: string | null }>,
|
|
) {
|
|
return apiRequestData<{ item: ProjectClosureChecklistItem }>(`/api/projects/${projectId}/closure-checklist/${itemId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectDeliveries(projectId: string) {
|
|
return apiRequestData<{ items: ProjectDeliveryRecord[] }>(`/api/projects/${projectId}/deliveries`);
|
|
}
|
|
|
|
export async function createProjectDelivery(
|
|
projectId: string,
|
|
payload: {
|
|
title: string;
|
|
recordType: ProjectDeliveryRecordType;
|
|
summary: string;
|
|
occurredAt: string;
|
|
ownerEmployeeId?: string;
|
|
},
|
|
) {
|
|
return apiRequestData<{ item: ProjectDeliveryRecord }>(`/api/projects/${projectId}/deliveries`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectDocumentRefs(projectId: string) {
|
|
return apiRequestData<{ items: ConfidentialDocumentRef[] }>(`/api/projects/${projectId}/document-refs`);
|
|
}
|
|
|
|
export async function createConfidentialDocumentRef(payload: {
|
|
projectId?: string | null;
|
|
contractRegistryId?: string | null;
|
|
title: string;
|
|
documentNo?: string | null;
|
|
confidentialityLevel?: ConfidentialityLevel;
|
|
storageType: DocumentStorageType;
|
|
storagePath: string;
|
|
offlineArchiveNo?: string | null;
|
|
ownerEmployeeId?: string;
|
|
accessRuleJson?: Record<string, unknown> | null;
|
|
}) {
|
|
return apiRequestData<{ item: ConfidentialDocumentRef & { owner?: EmployeeBasic | null; project?: { id: string; projectNo: string; name: string } | null; contractRegistry?: { id: string; contractNo: string } | null } }>("/api/confidential-doc-refs", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|