文件
hyb-oa/client/src/api/projects.ts
T

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),
});
}