R19/R20: 项目主域进 OA 并打通报销差旅周报待办
这个提交包含在:
@@ -0,0 +1,443 @@
|
||||
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 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 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;
|
||||
travelRequests: number;
|
||||
todoItems: number;
|
||||
weeklyReports: number;
|
||||
contracts: number;
|
||||
deliveries: number;
|
||||
documentRefs: number;
|
||||
};
|
||||
members: ProjectMember[];
|
||||
milestones: ProjectMilestone[];
|
||||
tasks: ProjectTask[];
|
||||
risks: ProjectRisk[];
|
||||
};
|
||||
|
||||
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 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 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 }>(`/api/projects/${projectId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function archiveProject(projectId: string) {
|
||||
return apiRequestData<{ item: { id: string; status: ProjectStatus } }>(`/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 fetchProjectWeeklyReports(projectId: string) {
|
||||
return apiRequestData<{ items: ProjectWeeklyReportLink[] }>(`/api/projects/${projectId}/weekly-reports`);
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户