|
|
|
@@ -19,6 +19,11 @@ const {
|
|
|
|
|
normalizeOperation,
|
|
|
|
|
syncWecomDirectoryForEmployee,
|
|
|
|
|
} = require("../services/integrations/wecomDirectory");
|
|
|
|
|
const {
|
|
|
|
|
getWecomHrMasterSyncRun,
|
|
|
|
|
listWecomHrMasterSyncRuns,
|
|
|
|
|
runWecomHrMasterSync,
|
|
|
|
|
} = require("../services/integrations/wecomHrMaster");
|
|
|
|
|
const { listOrganizationDepartments } = require("../services/organization");
|
|
|
|
|
const { StorageNotFoundError, openReadStream, parseStoragePath, writeBuffer } = require("../services/storage");
|
|
|
|
|
|
|
|
|
@@ -82,6 +87,16 @@ const WECOM_DIRECTORY_RETRY_SCHEMA = z.object({
|
|
|
|
|
operation: z.enum(["create", "update", "disable"]).optional(),
|
|
|
|
|
}).strict();
|
|
|
|
|
|
|
|
|
|
const WECOM_HR_MASTER_SYNC_RUN_SCHEMA = z.object({
|
|
|
|
|
mode: z.enum(["dry_run", "apply"]).default("dry_run"),
|
|
|
|
|
userids: z.array(z.string().trim().min(1).max(128)).max(200).optional(),
|
|
|
|
|
limit: z.coerce.number().int().min(1).max(200).optional(),
|
|
|
|
|
}).strict();
|
|
|
|
|
|
|
|
|
|
const WECOM_HR_MASTER_SYNC_LIST_QUERY_SCHEMA = z.object({
|
|
|
|
|
pageSize: z.coerce.number().int().min(1).max(50).optional(),
|
|
|
|
|
}).strict();
|
|
|
|
|
|
|
|
|
|
const CONTRACT_CREATE_SCHEMA = z.object({
|
|
|
|
|
contractNo: z.string().trim().min(1).max(100),
|
|
|
|
|
contractType: CONTRACT_TYPE_SCHEMA,
|
|
|
|
@@ -174,6 +189,32 @@ function canUseWecomDirectorySync(fastify) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function canUseWecomHrMasterSync(fastify) {
|
|
|
|
|
return Boolean(
|
|
|
|
|
fastify?.prisma?.wecomHrMasterSyncRun
|
|
|
|
|
&& fastify?.prisma?.wecomHrMasterSyncItem
|
|
|
|
|
&& fastify?.prisma?.wecomIdentityBinding
|
|
|
|
|
&& fastify?.prisma?.integrationBinding
|
|
|
|
|
&& fastify?.prisma?.featureFlag,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureWecomHrMasterSyncEnabled(fastify, request) {
|
|
|
|
|
const gate = await evaluateFeatureFlag({
|
|
|
|
|
prisma: fastify.prisma,
|
|
|
|
|
config: fastify.config,
|
|
|
|
|
flagKey: FEATURE_FLAG_KEYS.wecomHrMasterSync,
|
|
|
|
|
departmentId: request.employee.department_id ?? null,
|
|
|
|
|
strictDepartment: false,
|
|
|
|
|
});
|
|
|
|
|
if (!gate.enabled) {
|
|
|
|
|
throw buildValidationError({ featureFlag: "wecom_hr_master_sync_v1_disabled", reason: gate.reason }, 409);
|
|
|
|
|
}
|
|
|
|
|
if (!canUseWecomHrMasterSync(fastify)) {
|
|
|
|
|
throw buildValidationError({ featureFlag: "wecom_hr_master_sync_v1_unavailable" }, 409);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function trySyncWecomDirectory(fastify, request, options) {
|
|
|
|
|
if (!canUseWecomDirectorySync(fastify)) {
|
|
|
|
|
return null;
|
|
|
|
@@ -808,6 +849,67 @@ async function employeesRoutes(fastify) {
|
|
|
|
|
return sendData(reply, request, stats);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fastify.post("/employees/wecom-hr-master-sync/runs", { preHandler: fastify.authenticate }, async (request, reply) => {
|
|
|
|
|
await ensureFeatureEnabled(fastify, request);
|
|
|
|
|
if (!canManageLifecycle(fastify, request)) {
|
|
|
|
|
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: "system.users" } });
|
|
|
|
|
}
|
|
|
|
|
await ensureWecomHrMasterSyncEnabled(fastify, request);
|
|
|
|
|
const body = parseBody(WECOM_HR_MASTER_SYNC_RUN_SCHEMA, request.body);
|
|
|
|
|
|
|
|
|
|
const run = await runWecomHrMasterSync({
|
|
|
|
|
prisma: fastify.prisma,
|
|
|
|
|
config: fastify.config,
|
|
|
|
|
actorId: request.employee.id,
|
|
|
|
|
mode: body.mode,
|
|
|
|
|
userids: body.userids ?? [],
|
|
|
|
|
limit: body.limit,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fastify.prisma.auditLog.create({
|
|
|
|
|
data: buildAuditLogData(request, {
|
|
|
|
|
actorId: request.employee.id,
|
|
|
|
|
action: body.mode === "apply" ? "WECOM_HR_MASTER_SYNC_APPLY" : "WECOM_HR_MASTER_SYNC_DRY_RUN",
|
|
|
|
|
targetType: "wecom_hr_master_sync_run",
|
|
|
|
|
targetId: run.id,
|
|
|
|
|
extra: {
|
|
|
|
|
mode: run.mode,
|
|
|
|
|
status: run.status,
|
|
|
|
|
summary: run.summary,
|
|
|
|
|
requestedUserids: run.requestedUserids,
|
|
|
|
|
tokenSource: run.tokenSource,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return sendData(reply, request, run);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fastify.get("/employees/wecom-hr-master-sync/runs", { preHandler: fastify.authenticate }, async (request, reply) => {
|
|
|
|
|
await ensureFeatureEnabled(fastify, request);
|
|
|
|
|
if (!canManageLifecycle(fastify, request)) {
|
|
|
|
|
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: "system.users" } });
|
|
|
|
|
}
|
|
|
|
|
await ensureWecomHrMasterSyncEnabled(fastify, request);
|
|
|
|
|
const query = parseBody(WECOM_HR_MASTER_SYNC_LIST_QUERY_SCHEMA, request.query);
|
|
|
|
|
const items = await listWecomHrMasterSyncRuns(fastify.prisma, { pageSize: query.pageSize });
|
|
|
|
|
return sendData(reply, request, { items });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fastify.get("/employees/wecom-hr-master-sync/runs/:id", { preHandler: fastify.authenticate }, async (request, reply) => {
|
|
|
|
|
await ensureFeatureEnabled(fastify, request);
|
|
|
|
|
if (!canManageLifecycle(fastify, request)) {
|
|
|
|
|
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: "system.users" } });
|
|
|
|
|
}
|
|
|
|
|
await ensureWecomHrMasterSyncEnabled(fastify, request);
|
|
|
|
|
const runId = parseId(request.params?.id);
|
|
|
|
|
const run = await getWecomHrMasterSyncRun(fastify.prisma, runId);
|
|
|
|
|
if (!run) {
|
|
|
|
|
throw new AppError({ code: "SYS_404", message: "Not Found", statusCode: 404 });
|
|
|
|
|
}
|
|
|
|
|
return sendData(reply, request, run);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fastify.get("/employees/:id/wecom-directory-sync", { preHandler: fastify.authenticate }, async (request, reply) => {
|
|
|
|
|
await ensureFeatureEnabled(fastify, request);
|
|
|
|
|
if (!canManageLifecycle(fastify, request)) {
|
|
|
|
|