支持企微通讯录缺失手机号(07-26-internal-ops-recovery-baseline)

这个提交包含在:
2026-07-27 13:05:27 +08:00
父节点 9875bc37a4
当前提交 2526052e73
共修改 24 个文件,包含 845 行新增和 63 行删除
+19 -14
查看文件
@@ -58,10 +58,15 @@ const normalizedEmailSchema = z
.max(200)
.transform((value) => value.toLowerCase());
const nullablePhoneSchema = z.preprocess(
(value) => (typeof value === "string" && value.trim() === "" ? null : value),
z.string().trim().min(6).max(30).nullable().optional(),
);
const createBodySchema = z.object({
employeeNo: z.string().trim().min(1).max(50).optional(),
name: z.string().min(1).max(100),
phone: z.string().min(6).max(30),
phone: nullablePhoneSchema,
email: normalizedEmailSchema,
departmentId: z.string().uuid().optional(),
title: z.string().min(1).max(100).optional(),
@@ -74,7 +79,7 @@ const createBodySchema = z.object({
const updateBodySchema = z.object({
name: z.string().trim().min(1).max(100).optional(),
phone: z.string().trim().min(6).max(30).optional(),
phone: nullablePhoneSchema,
email: normalizedEmailSchema.optional(),
departmentId: z.string().uuid().optional(),
title: z.string().trim().min(1).max(100).optional(),
@@ -98,8 +103,8 @@ const sensitiveBodySchema = z
})
.strict();
const USER_IMPORT_REQUIRED_HEADERS = ["employeeNo", "name", "phone", "email", "departmentName"];
const USER_IMPORT_OPTIONAL_HEADERS = ["title", "directManagerEmployeeNo", "roles", "hireDate"];
const USER_IMPORT_REQUIRED_HEADERS = ["employeeNo", "name", "email", "departmentName"];
const USER_IMPORT_OPTIONAL_HEADERS = ["phone", "title", "directManagerEmployeeNo", "roles", "hireDate"];
const USER_IMPORT_ALLOWED_EXTENSIONS = new Set([".csv", ".xlsx"]);
const USER_IMPORT_ALLOWED_MIME_TYPES = new Set([
"text/csv",
@@ -773,11 +778,9 @@ function validateImportRowFields(row) {
pushImportRowError(row, "name", "too_long");
}
if (!normalized.phone) {
pushImportRowError(row, "phone", "required");
} else if (normalized.phone.length < 6) {
if (normalized.phone && normalized.phone.length < 6) {
pushImportRowError(row, "phone", "too_short");
} else if (normalized.phone.length > 30) {
} else if (normalized.phone && normalized.phone.length > 30) {
pushImportRowError(row, "phone", "too_long");
}
@@ -834,7 +837,7 @@ function createDepartmentResolutionMap(departments) {
function createEmployeeLookupMaps(employees) {
return {
byEmployeeNo: new Map(employees.map((item) => [item.employee_no, item])),
byPhone: new Map(employees.map((item) => [item.phone, item])),
byPhone: new Map(employees.filter((item) => item.phone).map((item) => [item.phone, item])),
byEmail: new Map(employees.map((item) => [item.email, item])),
};
}
@@ -912,7 +915,9 @@ async function buildUserImportPreview(prisma, rawRows, options = {}) {
pushImportRowError(row, "employeeNo", "duplicate_existing");
}
const phoneConflict = existingLookup.byPhone.get(row.normalized.phone);
const phoneConflict = row.normalized.phone
? existingLookup.byPhone.get(row.normalized.phone)
: null;
if (phoneConflict) {
pushImportRowError(row, "phone", "duplicate_existing");
}
@@ -1012,7 +1017,7 @@ async function createSystemUser(tx, options) {
? { employee_no: options.employeeNo.trim() }
: {}),
name: options.name,
phone: options.phone,
phone: options.phone || null,
email: options.email.trim().toLowerCase(),
department_id: options.departmentId,
title: options.title ?? "员工",
@@ -1573,7 +1578,7 @@ async function usersRoutes(fastify, opts = {}) {
const employee = await createSystemUser(tx, {
employeeNo: row.normalized.employeeNo,
name: row.normalized.name,
phone: row.normalized.phone,
phone: row.normalized.phone || null,
email: row.normalized.email,
departmentId: row.resolvedDepartmentId,
title: row.normalized.title,
@@ -1742,7 +1747,7 @@ async function usersRoutes(fastify, opts = {}) {
if (typeof body.name === "string" && body.name !== current.name) {
changes.name = { before: current.name, after: body.name };
}
if (typeof body.phone === "string" && body.phone !== current.phone) {
if (Object.prototype.hasOwnProperty.call(body, "phone") && (body.phone ?? null) !== current.phone) {
changes.phone = { before: current.phone, after: body.phone };
}
if (typeof body.email === "string" && body.email !== current.email) {
@@ -1768,7 +1773,7 @@ async function usersRoutes(fastify, opts = {}) {
where: { id: userId },
data: {
...(typeof body.name === "string" ? { name: body.name } : {}),
...(typeof body.phone === "string" ? { phone: body.phone } : {}),
...(Object.prototype.hasOwnProperty.call(body, "phone") ? { phone: body.phone ?? null } : {}),
...(typeof body.email === "string" ? { email: body.email } : {}),
...(typeof body.departmentId === "string" ? { department_id: body.departmentId } : {}),
...(typeof body.title === "string" ? { title: body.title } : {}),