R83 员工号自动生成与单用户开通后端闭环
这个提交包含在:
+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 } : {}),
|
||||
|
||||
在新工单中引用
屏蔽一个用户