支持企微通讯录缺失手机号(07-26-internal-ops-recovery-baseline)
这个提交包含在:
@@ -219,6 +219,33 @@ describe("users import routes", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("accepts an import without a phone header and persists a missing phone as null", async () => {
|
||||
const { app, employeeCreate } = await buildApp({
|
||||
departments: [{ id: "dept-1", name: "行政部", archived_at: null }],
|
||||
});
|
||||
const multipartRequest = buildMultipartRequest({
|
||||
filename: "users.csv",
|
||||
contentType: "text/csv",
|
||||
content: [
|
||||
"employeeNo,name,email,departmentName,title,roles,hireDate",
|
||||
"E006,NoPhone,[email protected],行政部,员工,employee,2026-07-27",
|
||||
].join("\n"),
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/users/import/apply",
|
||||
...multipartRequest,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(employeeCreate).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ phone: null }),
|
||||
select: expect.any(Object),
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("rejects dry-run uploads with unsupported mime type", async () => {
|
||||
const { app } = await buildApp();
|
||||
const multipartRequest = buildMultipartRequest({
|
||||
|
||||
@@ -62,7 +62,27 @@ async function buildApp({
|
||||
roles: [{ role_code: "employee" }],
|
||||
},
|
||||
);
|
||||
const employeeUpdate = vi.fn();
|
||||
const employeeUpdate = vi.fn(async ({ data }) => ({
|
||||
...(currentEmployee ?? {
|
||||
id: USER_ID,
|
||||
employee_no: "HYD000321",
|
||||
name: "Alice",
|
||||
phone: "13800000000",
|
||||
email: "[email protected]",
|
||||
status: "active",
|
||||
department_id: DEPARTMENT_ID,
|
||||
title: "员工",
|
||||
direct_manager_id: null,
|
||||
hire_date: new Date("2026-05-01T00:00:00.000Z"),
|
||||
archived_at: null,
|
||||
created_at: new Date("2026-05-01T00:00:00.000Z"),
|
||||
updated_at: new Date("2026-05-01T00:00:00.000Z"),
|
||||
roles: [{ role_code: "employee" }],
|
||||
wecom_identity_binding: null,
|
||||
wecom_directory_sync_runs: [],
|
||||
}),
|
||||
...data,
|
||||
}));
|
||||
const employeeRoleCreateMany = vi.fn().mockResolvedValue({ count: 1 });
|
||||
const employeeRoleFindMany = vi.fn().mockResolvedValue(currentRoles.map((role_code) => ({ role_code })));
|
||||
const employeeRoleDeleteMany = vi.fn().mockResolvedValue({ count: currentRoles.length });
|
||||
@@ -139,10 +159,6 @@ async function buildApp({
|
||||
version: 1,
|
||||
}),
|
||||
},
|
||||
wecomDirectorySyncRun: {
|
||||
create: wecomSyncRunCreate,
|
||||
},
|
||||
wecomIdentityBinding: {},
|
||||
$transaction: vi.fn(async (callback) => callback(tx)),
|
||||
});
|
||||
app.decorate("requirePermission", (permissionCode) => async (request) => {
|
||||
@@ -243,6 +259,45 @@ describe("usersRoutes write flows", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("creates a user with a null phone when the field is omitted", async () => {
|
||||
const { app, employeeCreate } = await buildApp();
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/users",
|
||||
payload: {
|
||||
name: "暂缺手机号成员",
|
||||
email: "[email protected]",
|
||||
roles: ["employee"],
|
||||
syncWecomDirectory: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(employeeCreate).toHaveBeenCalledWith(expect.objectContaining({
|
||||
data: expect.objectContaining({ phone: null }),
|
||||
}));
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("allows an administrator to explicitly clear an existing phone to null", async () => {
|
||||
const { app, employeeUpdate } = await buildApp();
|
||||
|
||||
const response = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/api/users/${USER_ID}`,
|
||||
payload: { phone: null },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(employeeUpdate).toHaveBeenCalledWith(expect.objectContaining({
|
||||
data: expect.objectContaining({ phone: null }),
|
||||
}));
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("rejects employeeNo changes on patch", async () => {
|
||||
const { app, employeeFindUnique, employeeUpdate } = await buildApp();
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户