From e3d46d9142e0d3e509acca29567c595b015d7e60 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Sat, 15 Aug 2026 15:00:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E9=83=A8=E9=97=A8=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E8=B0=83=E5=8A=A8=EF=BC=8C=E4=BD=BF=E7=94=A8=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E6=9E=B6=E6=9E=84=E9=83=A8=E9=97=A8=E5=92=8C=E8=81=8C?= =?UTF-8?q?=E5=8A=A1=E4=B8=8B=E6=8B=89=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:EmployeeDepartmentRecord 增加 oldPosition/newPosition 字段 - 后端:department-change 接口支持 departmentId 关联 + position 职务变动 - 后端:同步更新 Employee.departmentId 和 Employee.position - 前端:DeptChangeModal 改为从组织架构下拉选择目标部门(树形)和职务 - 前端:选择部门后自动过滤该部门下的职务 - 前端:按钮文案从"调部门"改为"调动" Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- backend/prisma/schema.prisma | 6 ++- backend/src/routes/roster.routes.ts | 31 +++++++++--- frontend/src/pages/Roster.tsx | 4 +- frontend/src/pages/roster/modals.tsx | 75 ++++++++++++++++++++++------ 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 102ce9c..d75e728 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -914,10 +914,12 @@ model EmployeeDepartmentRecord { employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) oldDepartment String // 调整前部门 newDepartment String // 调整后部门 + oldPosition String? // 调整前职务 + newPosition String? // 调整后职务 effectiveMonth String // 生效年月 YYYY-MM endMonth String? // 失效年月 YYYY-MM(null=至今有效) - reason String? // 调部门原因 - changeType String // ONBOARDING=入职, REHIRE=重新入职, TRANSFER=调部门 + reason String? // 调动原因 + changeType String // ONBOARDING=入职, REHIRE=重新入职, TRANSFER=调动 createdBy String createdAt DateTime @default(now()) diff --git a/backend/src/routes/roster.routes.ts b/backend/src/routes/roster.routes.ts index f5a5f3e..db2a0ce 100644 --- a/backend/src/routes/roster.routes.ts +++ b/backend/src/routes/roster.routes.ts @@ -1442,10 +1442,10 @@ router.get('/:id/salary-records', authMiddleware, async (req: AuthRequest, res, } catch (err) { next(err) } }) -// 调部门 +// 调动(部门+职务变动) router.post('/:id/department-change', authMiddleware, async (req: AuthRequest, res, next) => { try { - const { newDepartment, effectiveMonth, reason } = req.body + const { newDepartment, newPosition, departmentId, effectiveMonth, reason } = req.body const employee = await prisma.employee.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) @@ -1454,22 +1454,35 @@ router.post('/:id/department-change', authMiddleware, async (req: AuthRequest, r } const oldDepartment = employee.department + const oldPosition = employee.position || null const effMonth = effectiveMonth || dateToMonth(new Date()) const prevEffMonth = prevMonth(effMonth) + // 校验 departmentId 是否属于当前组织 + let deptName = newDepartment + if (departmentId) { + const dept = await prisma.department.findFirst({ where: { id: departmentId, orgId: req.user!.orgId } }) + if (!dept) { + return res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', message: '目标部门不存在' } }) + } + deptName = dept.name + } + // 关闭之前有效记录 await prisma.employeeDepartmentRecord.updateMany({ where: { employeeId: req.params.id, endMonth: null }, data: { endMonth: prevEffMonth }, }) - // 创建新部门记录 + // 创建新调动记录 const record = await prisma.employeeDepartmentRecord.create({ data: { orgId: req.user!.orgId, employeeId: req.params.id, oldDepartment, - newDepartment, + newDepartment: deptName, + oldPosition, + newPosition: newPosition || null, effectiveMonth: effMonth, endMonth: null, changeType: 'TRANSFER', @@ -1478,13 +1491,17 @@ router.post('/:id/department-change', authMiddleware, async (req: AuthRequest, r }, }) - // 同步 Employee 便捷字段 + // 同步 Employee 字段(department 文本 + departmentId 关联 + position 职务) await prisma.employee.update({ where: { id: req.params.id }, - data: { department: newDepartment }, + data: { + department: deptName, + departmentId: departmentId || null, + position: newPosition || employee.position, + }, }) - await auditLog(req, 'CREATE', 'DEPARTMENT_CHANGE', record.id, { employeeId: req.params.id, oldDepartment, newDepartment }) + await auditLog(req, 'CREATE', 'DEPARTMENT_CHANGE', record.id, { employeeId: req.params.id, oldDepartment, newDepartment: deptName, oldPosition, newPosition }) res.json({ success: true, data: record }) } catch (err) { next(err) } }) diff --git a/frontend/src/pages/Roster.tsx b/frontend/src/pages/Roster.tsx index 788e1e8..97e5439 100644 --- a/frontend/src/pages/Roster.tsx +++ b/frontend/src/pages/Roster.tsx @@ -874,8 +874,8 @@ export default function Roster() { )} - +