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

716 行
20 KiB
TypeScript

import { apiRequestBlob, apiRequestData, type ApiBlobResponse } from "./http";
export type EmployeeStatus = "active" | "archived";
export type EmployeeEmploymentStatus = "onboarding" | "probation" | "active" | "offboarding" | "inactive";
export type EmployeeContractType = "labor" | "nda" | "non_compete" | "internship";
export type EmployeeContractStatus = "active" | "expired" | "terminated";
export type EmployeeTrainingType = "internal" | "external" | "online";
export type EmployeeTrainingStatus = "planned" | "in_progress" | "completed" | "cancelled";
export type EmployeeCertificateStatus = "valid" | "expired";
export type WecomDirectorySyncOperation = "create" | "update" | "disable";
export type WecomDirectorySyncStatus = "running" | "success" | "failed" | "skipped";
export type WecomDirectoryStateStatus = "unbound" | "bound" | "synced" | "sync_success" | "sync_skipped" | "sync_failed";
export type WecomDirectorySyncRun = {
id: string;
employeeId: string;
externalUserId: string | null;
operation: WecomDirectorySyncOperation;
status: WecomDirectorySyncStatus;
attempts: number;
errorCode: string | null;
errorMessage: string | null;
responseSummary: Record<string, unknown> | null;
triggeredBy: string | null;
createdAt: string | null;
updatedAt: string | null;
};
export type WecomDirectoryState = {
status: WecomDirectoryStateStatus;
externalUserId: string | null;
bound: boolean;
latestSync: WecomDirectorySyncRun | null;
};
export type WecomHrMasterSyncMode = "dry_run" | "apply";
export type WecomHrMasterSyncRunStatus = "running" | "success" | "partial" | "failed" | "skipped";
export type WecomHrMasterSyncItemStatus = "planned" | "success" | "skipped" | "conflict" | "failed";
export type WecomHrMasterSyncItem = {
id: string;
runId: string;
employeeId: string | null;
externalUserId: string;
action: "create" | "update" | "noop" | "skip" | "conflict" | "failed";
status: WecomHrMasterSyncItemStatus;
errorCode: string | null;
errorMessage: string | null;
retryCount: number;
fieldChanges: Record<string, unknown> | null;
sourceSummary: Record<string, unknown> | null;
createdAt: string | null;
updatedAt: string | null;
};
export type WecomHrMasterSyncRun = {
id: string;
mode: WecomHrMasterSyncMode;
status: WecomHrMasterSyncRunStatus;
tokenSource: Record<string, string> | null;
requestedUserids: string[] | null;
limit: number | null;
summary: {
total?: number;
created?: number;
updated?: number;
noop?: number;
skipped?: number;
conflicts?: number;
failed?: number;
planned?: number;
success?: number;
} | null;
errorCode: string | null;
errorMessage: string | null;
triggeredBy: string | null;
startedAt: string | null;
finishedAt: string | null;
createdAt: string | null;
updatedAt: string | null;
itemCount?: number;
items?: WecomHrMasterSyncItem[];
};
export type EmployeeDepartmentSummary = {
id: string;
name: string;
};
export type EmployeeSummary = {
id: string;
employeeNo: string;
name: string;
phone: string;
email: string;
status: EmployeeStatus;
departmentId: string;
department: EmployeeDepartmentSummary | null;
directManagerId: string | null;
directManager: { id: string; employeeNo: string; name: string } | null;
title: string;
employmentStatus: EmployeeEmploymentStatus | null;
hireDate: string | null;
avatarUrl: string | null;
roleCodes: string[];
createdAt: string | null;
updatedAt: string | null;
wecomDirectory?: WecomDirectoryState;
};
export type EmployeeListResponse = {
page: number;
pageSize: number;
total: number;
items: EmployeeSummary[];
};
export type EmployeeLifecycleEvent = {
id: string;
employeeId: string;
eventType: "onboard" | "regularize" | "transfer" | "offboard";
effectiveDate: string | null;
payloadJson: Record<string, unknown> | null;
createdAt: string | null;
createdBy: string | null;
};
export type EmployeeChecklistTask = {
id: string;
employeeId: string;
scene: "onboarding" | "offboarding" | "transfer";
taskType:
| "account_setup"
| "asset_issue"
| "policy_ack"
| "asset_return"
| "access_revoke"
| "expense_clearance"
| "handover_note";
title: string;
status: "pending" | "completed" | "skipped";
dueDate: string | null;
completedAt: string | null;
completedBy: string | null;
payloadJson: Record<string, unknown> | null;
createdAt: string | null;
};
export type EmployeeOrganizationNode = {
id: string;
employeeNo: string;
name: string;
title: string;
status: EmployeeStatus;
avatarUrl: string | null;
department: EmployeeDepartmentSummary | null;
};
export type EmployeeContract = {
id: string;
employeeId: string;
contractNo: string;
contractType: EmployeeContractType;
startDate: string | null;
endDate: string | null;
terminatedDate: string | null;
attachmentPath: string | null;
attachmentUrl: string | null;
status: EmployeeContractStatus;
createdAt: string | null;
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
};
export type EmployeeTraining = {
id: string;
employeeId: string;
name: string;
trainingType: EmployeeTrainingType;
startDate: string | null;
endDate: string | null;
status: EmployeeTrainingStatus;
notes: string | null;
createdAt: string | null;
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
};
export type EmployeeCertificate = {
id: string;
employeeId: string;
name: string;
issuer: string;
obtainedDate: string | null;
expiryDate: string | null;
certificateNo: string | null;
attachmentPath: string | null;
status: EmployeeCertificateStatus;
createdAt: string | null;
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
};
export type OrganizationTreeNode = {
id: string;
employeeNo: string;
name: string;
title: string;
status: EmployeeStatus;
avatarUrl: string | null;
department: EmployeeDepartmentSummary | null;
externalManager: {
id: string;
employeeNo: string;
name: string;
department: EmployeeDepartmentSummary | null;
} | null;
children: OrganizationTreeNode[];
};
export type OrganizationDepartmentSection = {
department: EmployeeDepartmentSummary | null;
roots: OrganizationTreeNode[];
};
export type EmployeeProfile = {
employee: {
id: string;
employeeNo: string;
name: string;
phone: string;
email: string;
status: EmployeeStatus;
employmentStatus: EmployeeEmploymentStatus;
departmentId: string;
title: string;
directManagerId: string | null;
hireDate: string | null;
probationEndDate: string | null;
lastWorkingDate: string | null;
workLocation: string | null;
emergencyContactName: string | null;
emergencyContactPhone: string | null;
notes: string | null;
avatarUrl: string | null;
archivedAt: string | null;
roleCodes: string[];
department: EmployeeDepartmentSummary | null;
directManager: { id: string; employeeNo: string; name: string; avatarUrl: string | null } | null;
wecomDirectory?: WecomDirectoryState;
};
lifecycle: {
events: EmployeeLifecycleEvent[];
checklistTasks: EmployeeChecklistTask[];
blockers: {
openAssetCount: number;
pendingWorkflowTaskCount: number;
canCompleteOffboarding: boolean;
};
};
assignedAssets: Array<{
id: string;
assetNo: string;
name: string;
status: string;
location: string | null;
}>;
organization: {
reportingChain: EmployeeOrganizationNode[];
directReports: EmployeeOrganizationNode[];
};
};
export type FetchEmployeesParams = {
page: number;
pageSize: number;
status?: EmployeeStatus;
departmentId?: string;
q?: string;
title?: string;
managerId?: string;
hireDateFrom?: string;
hireDateTo?: string;
employmentStatus?: EmployeeEmploymentStatus;
};
export type BatchOperationResult = {
summary: {
total: number;
updated: number;
};
employeeIds: string[];
};
export type EmployeeStatsResponse = {
asOfDate: string;
employmentStatusSummary: {
total: number;
items: Array<{
status: EmployeeEmploymentStatus;
count: number;
}>;
};
departmentHeadcount: Array<{
departmentId: string;
departmentName: string;
count: number;
}>;
recentMovements: {
dateFrom: string;
dateTo: string;
hires: number;
departures: number;
};
expiringContracts: {
dateFrom: string;
dateTo: string;
count: number;
};
certificateSummary: {
dateFrom: string;
dateTo: string;
validHolderCount: number;
expiringSoonCount: number;
};
salarySummary: {
currentMonth: {
month: string;
totalNetAmount: number;
averageNetAmount: number;
issuedCount: number;
} | null;
previousMonth: {
month: string;
totalNetAmount: number;
averageNetAmount: number;
issuedCount: number;
} | null;
} | null;
performanceSummary: {
cycle: {
id: string;
name: string;
cycleType: "monthly" | "quarterly" | "semi_annual" | "annual";
status: "draft" | "active" | "completed";
startDate: string | null;
endDate: string | null;
} | null;
averageScore: number | null;
reviewedCount: number;
distribution: Array<{
score: number;
count: number;
}>;
};
};
export type BatchTransferDepartmentPayload = {
employeeIds: string[];
departmentId: string;
};
export type BatchToggleUsersPayload = {
employeeIds: string[];
};
export type BatchSetManagerPayload = {
employeeIds: string[];
managerId: string;
};
export async function fetchEmployees(params: FetchEmployeesParams) {
const search = new URLSearchParams();
search.set("page", String(params.page));
search.set("pageSize", String(params.pageSize));
if (params.status) search.set("status", params.status);
if (params.departmentId) search.set("departmentId", params.departmentId);
if (params.q) search.set("q", params.q);
if (params.title) search.set("title", params.title);
if (params.managerId) search.set("managerId", params.managerId);
if (params.hireDateFrom) search.set("hireDateFrom", params.hireDateFrom);
if (params.hireDateTo) search.set("hireDateTo", params.hireDateTo);
if (params.employmentStatus) search.set("employmentStatus", params.employmentStatus);
return apiRequestData<EmployeeListResponse>(`/api/employees?${search.toString()}`);
}
export async function fetchEmployeeProfile(employeeId: string) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/profile`);
}
export async function uploadEmployeeAvatar(employeeId: string, file: File) {
const formData = new FormData();
formData.append("file", file);
return apiRequestData<{ employeeId: string; avatarUrl: string | null }>(`/api/employees/${employeeId}/avatar`, {
method: "PUT",
body: formData,
});
}
export async function deleteEmployeeAvatar(employeeId: string) {
return apiRequestData<{ employeeId: string; avatarUrl: string | null }>(`/api/employees/${employeeId}/avatar`, {
method: "DELETE",
});
}
export async function patchEmployeeProfile(
employeeId: string,
payload: Partial<{
departmentId: string;
directManagerId: string | null;
title: string;
hireDate: string | null;
probationEndDate: string | null;
lastWorkingDate: string | null;
workLocation: string | null;
emergencyContactName: string | null;
emergencyContactPhone: string | null;
notes: string | null;
}>,
) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/profile`, {
method: "PATCH",
body: JSON.stringify(payload),
});
}
export async function startEmployeeOnboarding(
employeeId: string,
payload: { effectiveDate?: string; probationEndDate?: string | null; notes?: string | null } = {},
) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/onboarding/start`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function regularizeEmployee(
employeeId: string,
payload: { effectiveDate?: string; notes?: string | null } = {},
) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/regularize`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function transferEmployee(
employeeId: string,
payload: {
effectiveDate?: string;
departmentId?: string;
directManagerId?: string | null;
title?: string;
notes?: string | null;
} = {},
) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/transfer`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function startEmployeeOffboarding(
employeeId: string,
payload: { effectiveDate?: string; lastWorkingDate?: string | null; notes?: string | null } = {},
) {
return apiRequestData<EmployeeProfile>(`/api/employees/${employeeId}/offboarding/start`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function fetchEmployeeLifecycle(employeeId: string) {
return apiRequestData<EmployeeProfile["lifecycle"]>(`/api/employees/${employeeId}/lifecycle`);
}
export async function fetchEmployeeWecomDirectorySync(employeeId: string) {
return apiRequestData<{
employeeId: string;
wecomDirectory: WecomDirectoryState;
items: WecomDirectorySyncRun[];
}>(`/api/employees/${employeeId}/wecom-directory-sync`);
}
export async function retryEmployeeWecomDirectorySync(employeeId: string, operation?: WecomDirectorySyncOperation) {
return apiRequestData<{
employeeId: string;
operation: WecomDirectorySyncOperation;
result: {
ok: boolean;
skipped: boolean;
status: WecomDirectorySyncStatus;
code: string;
message: string | null;
run: WecomDirectorySyncRun | null;
} | null;
wecomDirectory: WecomDirectoryState;
}>(`/api/employees/${employeeId}/wecom-directory-sync/retry`, {
method: "POST",
body: JSON.stringify(operation ? { operation } : {}),
});
}
export async function runWecomHrMasterSync(payload: {
mode: WecomHrMasterSyncMode;
userids?: string[];
limit?: number;
}) {
return apiRequestData<WecomHrMasterSyncRun>("/api/employees/wecom-hr-master-sync/runs", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function fetchWecomHrMasterSyncRuns(pageSize = 5) {
return apiRequestData<{ items: WecomHrMasterSyncRun[] }>(
`/api/employees/wecom-hr-master-sync/runs?pageSize=${pageSize}`,
);
}
export async function fetchWecomHrMasterSyncRun(runId: string) {
return apiRequestData<WecomHrMasterSyncRun>(`/api/employees/wecom-hr-master-sync/runs/${runId}`);
}
export async function fetchEmployeeOrganization() {
return apiRequestData<{ items: OrganizationDepartmentSection[] }>("/api/employees/organization");
}
export async function fetchEmployeeStats() {
return apiRequestData<EmployeeStatsResponse>("/api/employees/stats");
}
export async function fetchEmployeeContracts(employeeId: string) {
return apiRequestData<{ items: EmployeeContract[] }>(`/api/employees/${employeeId}/contracts`);
}
export async function createEmployeeContract(
employeeId: string,
payload: {
contractNo: string;
contractType: EmployeeContractType;
startDate: string;
endDate: string;
terminatedDate?: string | null;
attachmentPath?: string | null;
},
) {
return apiRequestData<EmployeeContract>(`/api/employees/${employeeId}/contracts`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function patchEmployeeContract(
employeeId: string,
contractId: string,
payload: Partial<{
contractNo: string;
contractType: EmployeeContractType;
startDate: string;
endDate: string;
terminatedDate: string | null;
attachmentPath: string | null;
}>,
) {
return apiRequestData<EmployeeContract>(`/api/employees/${employeeId}/contracts/${contractId}`, {
method: "PATCH",
body: JSON.stringify(payload),
});
}
export async function uploadEmployeeContractAttachment(employeeId: string, contractId: string, file: File) {
const formData = new FormData();
formData.append("file", file);
return apiRequestData<EmployeeContract>(`/api/employees/${employeeId}/contracts/${contractId}/attachment`, {
method: "PUT",
body: formData,
});
}
export async function downloadEmployeeContractAttachment(attachmentUrl: string): Promise<ApiBlobResponse> {
return apiRequestBlob(attachmentUrl);
}
export async function deleteEmployeeContract(employeeId: string, contractId: string) {
return apiRequestData<{ contractId: string; deleted: boolean }>(`/api/employees/${employeeId}/contracts/${contractId}`, {
method: "DELETE",
});
}
export async function fetchEmployeeTrainings(employeeId: string) {
return apiRequestData<{ items: EmployeeTraining[] }>(`/api/employees/${employeeId}/trainings`);
}
export async function createEmployeeTraining(
employeeId: string,
payload: {
name: string;
trainingType: EmployeeTrainingType;
startDate: string;
endDate?: string | null;
status: EmployeeTrainingStatus;
notes?: string | null;
},
) {
return apiRequestData<EmployeeTraining>(`/api/employees/${employeeId}/trainings`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function patchEmployeeTraining(
employeeId: string,
trainingId: string,
payload: Partial<{
name: string;
trainingType: EmployeeTrainingType;
startDate: string;
endDate: string | null;
status: EmployeeTrainingStatus;
notes: string | null;
}>,
) {
return apiRequestData<EmployeeTraining>(`/api/employees/${employeeId}/trainings/${trainingId}`, {
method: "PATCH",
body: JSON.stringify(payload),
});
}
export async function fetchEmployeeCertificates(employeeId: string) {
return apiRequestData<{ items: EmployeeCertificate[] }>(`/api/employees/${employeeId}/certificates`);
}
export async function createEmployeeCertificate(
employeeId: string,
payload: {
name: string;
issuer: string;
obtainedDate: string;
expiryDate?: string | null;
certificateNo?: string | null;
attachmentPath?: string | null;
},
) {
return apiRequestData<EmployeeCertificate>(`/api/employees/${employeeId}/certificates`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function patchEmployeeCertificate(
employeeId: string,
certificateId: string,
payload: Partial<{
name: string;
issuer: string;
obtainedDate: string;
expiryDate: string | null;
certificateNo: string | null;
attachmentPath: string | null;
}>,
) {
return apiRequestData<EmployeeCertificate>(`/api/employees/${employeeId}/certificates/${certificateId}`, {
method: "PATCH",
body: JSON.stringify(payload),
});
}
export async function completeEmployeeChecklistTask(employeeId: string, taskId: string) {
return apiRequestData<EmployeeProfile["lifecycle"]>(`/api/employees/${employeeId}/checklist-tasks/${taskId}/complete`, {
method: "POST",
body: JSON.stringify({}),
});
}
export async function skipEmployeeChecklistTask(employeeId: string, taskId: string) {
return apiRequestData<EmployeeProfile["lifecycle"]>(`/api/employees/${employeeId}/checklist-tasks/${taskId}/skip`, {
method: "POST",
body: JSON.stringify({}),
});
}
export async function batchTransferEmployeesDepartment(payload: BatchTransferDepartmentPayload) {
return apiRequestData<BatchOperationResult>("/api/users/batch/transfer-department", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function batchEnableUsers(payload: BatchToggleUsersPayload) {
return apiRequestData<BatchOperationResult>("/api/users/batch/enable", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function batchDisableUsers(payload: BatchToggleUsersPayload) {
return apiRequestData<BatchOperationResult>("/api/users/batch/disable", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function batchSetEmployeesManager(payload: BatchSetManagerPayload) {
return apiRequestData<BatchOperationResult>("/api/users/batch/set-manager", {
method: "POST",
body: JSON.stringify(payload),
});
}