R83 员工号自动生成与单用户开通后端闭环

这个提交包含在:
2026-05-05 00:05:49 +08:00
父节点 184373c07e
当前提交 8f08243882
共修改 4 个文件,包含 300 行新增和 21 行删除
+32 -20
查看文件
@@ -43,7 +43,7 @@ const listPaginationSchema = z.object({
});
const createBodySchema = z.object({
employeeNo: z.string().min(1).max(50),
employeeNo: z.string().trim().min(1).max(50).optional(),
name: z.string().min(1).max(100),
phone: z.string().min(6).max(30),
email: z.string().email().max(200),
@@ -52,11 +52,10 @@ const createBodySchema = z.object({
directManagerId: z.string().uuid().nullable().optional(),
hireDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
roles: z.array(roleCodeSchema).min(1).optional(),
password: z.string().min(1),
password: z.string().trim().min(1).optional(),
});
const updateBodySchema = z.object({
employeeNo: z.string().trim().min(1).max(50).optional(),
name: z.string().trim().min(1).max(100).optional(),
phone: z.string().trim().min(6).max(30).optional(),
email: z.string().trim().email().max(200).optional(),
@@ -147,14 +146,16 @@ function parseCreateBody(body) {
});
}
const errors = validatePassword(parsed.data.password);
if (errors.length) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { password: errors },
});
if (typeof parsed.data.password === "string" && parsed.data.password.length > 0) {
const errors = validatePassword(parsed.data.password);
if (errors.length) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { password: errors },
});
}
}
return {
@@ -164,6 +165,14 @@ function parseCreateBody(body) {
}
function parseUpdateBody(body) {
if (body && typeof body === "object" && Object.prototype.hasOwnProperty.call(body, "employeeNo")) {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 409,
details: { employeeNo: "immutable" },
});
}
const parsed = updateBodySchema.safeParse(body ?? {});
if (!parsed.success) {
throw new AppError({
@@ -872,7 +881,9 @@ async function createSystemUser(tx, options) {
const employee = await tx.employee.create({
data: {
employee_no: options.employeeNo,
...(typeof options.employeeNo === "string" && options.employeeNo.trim()
? { employee_no: options.employeeNo.trim() }
: {}),
name: options.name,
phone: options.phone,
email: options.email,
@@ -1050,6 +1061,7 @@ async function usersRoutes(fastify, opts = {}) {
const body = parseCreateBody(request.body);
const hireDate = body.hireDate ? new Date(`${body.hireDate}T00:00:00.000Z`) : new Date();
const departmentId = body.departmentId ?? request.employee.department_id;
const oneTimePassword = body.password ?? generateOneTimePassword();
await ensureDepartmentUsable(fastify.prisma, departmentId);
@@ -1072,7 +1084,7 @@ async function usersRoutes(fastify, opts = {}) {
title: body.title ?? "员工",
directManagerId: body.directManagerId ?? null,
hireDate,
password: body.password,
password: oneTimePassword,
roles: body.roles,
actorId: request.employee.id,
now,
@@ -1093,7 +1105,10 @@ async function usersRoutes(fastify, opts = {}) {
}),
});
return employee;
return {
employee,
oneTimePassword,
};
});
} catch (error) {
if (error?.code === "P2002") {
@@ -1108,12 +1123,13 @@ async function usersRoutes(fastify, opts = {}) {
}
const roles = await fastify.prisma.employeeRole.findMany({
where: { employee_id: created.id },
where: { employee_id: created.employee.id },
select: { role_code: true },
});
return sendData(reply, request, {
...mapEmployeeSummary({ ...created, roles }),
...mapEmployeeSummary({ ...created.employee, roles }),
oneTimePassword: created.oneTimePassword,
});
},
);
@@ -1515,9 +1531,6 @@ async function usersRoutes(fastify, opts = {}) {
}
const changes = {};
if (typeof body.employeeNo === "string" && body.employeeNo !== current.employee_no) {
changes.employeeNo = { before: current.employee_no, after: body.employeeNo };
}
if (typeof body.name === "string" && body.name !== current.name) {
changes.name = { before: current.name, after: body.name };
}
@@ -1546,7 +1559,6 @@ async function usersRoutes(fastify, opts = {}) {
const employee = await tx.employee.update({
where: { id: userId },
data: {
...(typeof body.employeeNo === "string" ? { employee_no: body.employeeNo } : {}),
...(typeof body.name === "string" ? { name: body.name } : {}),
...(typeof body.phone === "string" ? { phone: body.phone } : {}),
...(typeof body.email === "string" ? { email: body.email } : {}),