feat: 调部门改为调动,使用组织架构部门和职务下拉选择
- 后端: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>
This commit is contained in:
@@ -874,8 +874,8 @@ export default function Roster() {
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
title="调部门"
|
||||
aria-label={`为${e.name}调整部门`}
|
||||
title="调动"
|
||||
aria-label={`为${e.name}调动`}
|
||||
className="rounded-md p-1.5 text-gray-500 transition hover:bg-primary/10 hover:text-primary"
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation()
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
import { rosterApi, socialInsuranceApi, employeeApi } from '../../lib/api-services'
|
||||
import api from '../../lib/api'
|
||||
import Button from "../../components/ui/Button"
|
||||
import { Input, Label, Select } from "../../components/ui/Input"
|
||||
import Modal from "../../components/ui/Modal"
|
||||
@@ -83,23 +84,55 @@ export function DeptChangeModal({ employee, onClose, onSubmit, loading, error }:
|
||||
}) {
|
||||
const todayStr = new Date().toISOString().slice(0, 10)
|
||||
const [form, setForm] = useState({
|
||||
newDepartment: employee.department || '',
|
||||
departmentId: employee.departmentId || '',
|
||||
newPosition: employee.position || '',
|
||||
effectiveDate: todayStr,
|
||||
reason: '',
|
||||
})
|
||||
|
||||
// 拉取组织架构部门和职务列表
|
||||
const { data: departments = [] } = useQuery({
|
||||
queryKey: ['departments'],
|
||||
queryFn: () => api.get('/departments').then(r => r.data),
|
||||
})
|
||||
const { data: positions = [] } = useQuery({
|
||||
queryKey: ['positions'],
|
||||
queryFn: () => api.get('/positions').then(r => r.data),
|
||||
})
|
||||
|
||||
// 构建部门树形下拉选项(带层级缩进)
|
||||
const deptOptions: { id: string; label: string; level: number }[] = []
|
||||
const buildDeptOptions = (items: any[], parentId: string | null, level: number) => {
|
||||
items.filter(d => d.parentId === parentId).sort((a, b) => a.sortOrder - b.sortOrder).forEach(d => {
|
||||
deptOptions.push({ id: d.id, label: d.name, level })
|
||||
buildDeptOptions(items, d.id, level + 1)
|
||||
})
|
||||
}
|
||||
buildDeptOptions(departments, null, 0)
|
||||
|
||||
// 选中新部门后,过滤该部门下的职务(如果职务有 departmentId 关联)
|
||||
const filteredPositions = form.departmentId
|
||||
? positions.filter((p: any) => !p.departmentId || p.departmentId === form.departmentId)
|
||||
: positions
|
||||
|
||||
const selectedDept = departments.find((d: any) => d.id === form.departmentId)
|
||||
const hasChange = (form.departmentId && form.departmentId !== employee.departmentId) ||
|
||||
(form.newPosition && form.newPosition !== employee.position)
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit({
|
||||
newDepartment: form.newDepartment,
|
||||
effectiveDate: new Date(form.effectiveDate).toISOString(),
|
||||
departmentId: form.departmentId || undefined,
|
||||
newDepartment: selectedDept?.name || employee.department,
|
||||
newPosition: form.newPosition || undefined,
|
||||
effectiveMonth: form.effectiveDate.slice(0, 7),
|
||||
reason: form.reason || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const canSubmit = form.newDepartment && form.effectiveDate && form.newDepartment !== employee.department
|
||||
const canSubmit = form.effectiveDate && hasChange
|
||||
|
||||
return (
|
||||
<Modal open onClose={onClose} title={`调部门 - ${employee.name}`}>
|
||||
<Modal open onClose={onClose} title={`调动 - ${employee.name}`}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
@@ -107,23 +140,37 @@ export function DeptChangeModal({ employee, onClose, onSubmit, loading, error }:
|
||||
<div className="text-xs text-gray-600 py-1.5">{employee.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>当前部门</Label>
|
||||
<div className="text-xs text-gray-600 py-1.5">{employee.department}</div>
|
||||
<Label>当前部门 / 职务</Label>
|
||||
<div className="text-xs text-gray-600 py-1.5">{employee.department} / {employee.position || '—'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>新部门 *</Label>
|
||||
<Input value={form.newDepartment} onChange={(e) => setForm({ ...form, newDepartment: e.target.value })} placeholder="如:市场部" />
|
||||
<Label>目标部门 *</Label>
|
||||
<Select value={form.departmentId} onChange={(e) => setForm({ ...form, departmentId: e.target.value })}>
|
||||
<option value="">请选择部门</option>
|
||||
{deptOptions.map(d => (
|
||||
<option key={d.id} value={d.id}>{' '.repeat(d.level)}{d.label}</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>生效日期 *</Label>
|
||||
<Input type="date" value={form.effectiveDate} onChange={(e) => setForm({ ...form, effectiveDate: e.target.value })} />
|
||||
<Label>新职务</Label>
|
||||
<Select value={form.newPosition} onChange={(e) => setForm({ ...form, newPosition: e.target.value })}>
|
||||
<option value="">保持不变</option>
|
||||
{filteredPositions.map((p: any) => (
|
||||
<option key={p.id} value={p.name}>{p.name}{p.level ? `(${p.level})` : ''}</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>调部门原因(选填)</Label>
|
||||
<Input value={form.reason} onChange={(e) => setForm({ ...form, reason: e.target.value })} placeholder="如:组织架构调整" />
|
||||
<Label>生效日期 *</Label>
|
||||
<Input type="date" value={form.effectiveDate} onChange={(e) => setForm({ ...form, effectiveDate: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>调动原因(选填)</Label>
|
||||
<Input value={form.reason} onChange={(e) => setForm({ ...form, reason: e.target.value })} placeholder="如:组织架构调整、岗位轮换" />
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-xs text-danger">
|
||||
@@ -132,7 +179,7 @@ export function DeptChangeModal({ employee, onClose, onSubmit, loading, error }:
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" onClick={onClose}>取消</Button>
|
||||
<Button onClick={handleSubmit} disabled={loading || !canSubmit}>{loading ? '保存中...' : '确认调部门'}</Button>
|
||||
<Button onClick={handleSubmit} disabled={loading || !canSubmit}>{loading ? '保存中...' : '确认调动'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user