优化: 添加员工弹窗合同和试用期智能计算
1. 缺省入职日期和合同开始日期为今天,签订日期默认空(留空表示尚未签订) 2. 签约时长与合同结束日期双向自动计算:改年限→算结束日期,改结束日期→算年限 3. 开始日期变更时自动重算结束日期 4. 试用期长度按劳动合同法第19条校验(3月以下不得约定,1年以下≤1月,3年以下≤2月,3年以上≤6月) 5. 有试用期时试用期工资必填,且不得低于转正工资80%(劳动合同法第20条) 6. 校验不通过时禁止保存并显示错误提示
This commit is contained in:
+126
-11
@@ -922,15 +922,96 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
||||
loading: boolean
|
||||
error: any
|
||||
}) {
|
||||
const todayStr = new Date().toISOString().slice(0, 10)
|
||||
const defaultEndDate = (() => {
|
||||
const d = new Date()
|
||||
d.setFullYear(d.getFullYear() + 3)
|
||||
d.setDate(d.getDate() - 1)
|
||||
return d.toISOString().slice(0, 10)
|
||||
})()
|
||||
const [form, setForm] = useState({
|
||||
name: '', department: '', hireDate: '', monthlySalary: '',
|
||||
name: '', department: '', hireDate: todayStr, monthlySalary: '',
|
||||
gender: '男' as '男' | '女', phone: '',
|
||||
isPregnant: false, isInMedicalPeriod: false, isWorkInjured: false,
|
||||
contractType: 'FIXED' as 'FIXED' | 'UNFIXED' | 'UNSIGNED',
|
||||
signDate: '', startDate: '', endDate: '',
|
||||
signDate: '', startDate: todayStr, endDate: defaultEndDate,
|
||||
contractYears: 3, probationMonths: 0, probationSalary: 0,
|
||||
})
|
||||
|
||||
// 计算合同月数
|
||||
const contractMonths = (() => {
|
||||
if (form.contractType !== 'FIXED' || !form.startDate) return 0
|
||||
if (form.endDate) {
|
||||
const start = new Date(form.startDate)
|
||||
const end = new Date(form.endDate)
|
||||
return Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24 * 30.44))
|
||||
}
|
||||
return form.contractYears * 12
|
||||
})()
|
||||
|
||||
// 试用期上限(劳动合同法第19条)
|
||||
const probationMax = (() => {
|
||||
if (contractMonths >= 36) return 6
|
||||
if (contractMonths >= 12) return 2
|
||||
if (contractMonths >= 3) return 1
|
||||
return 0
|
||||
})()
|
||||
|
||||
const probationError = (() => {
|
||||
if (form.probationMonths <= 0) return ''
|
||||
if (contractMonths > 0 && contractMonths < 3) return '合同不足3个月,不得约定试用期'
|
||||
if (form.probationMonths > probationMax) return `合同${contractMonths}个月,试用期最多${probationMax}个月`
|
||||
return ''
|
||||
})()
|
||||
|
||||
const monthlySalaryNum = parseFloat(form.monthlySalary) || 0
|
||||
const probationSalaryError = (() => {
|
||||
if (form.probationMonths <= 0) return ''
|
||||
if (form.probationSalary <= 0) return '有试用期时试用期工资必填'
|
||||
if (monthlySalaryNum > 0 && form.probationSalary < monthlySalaryNum * 0.8) {
|
||||
return `试用期工资不得低于转正工资的80%(最低¥${(monthlySalaryNum * 0.8).toFixed(0)})`
|
||||
}
|
||||
return ''
|
||||
})()
|
||||
|
||||
// 合同结束日期自动计算
|
||||
const handleContractYearsChange = (years: number) => {
|
||||
if (!form.startDate || years <= 0) {
|
||||
setForm({ ...form, contractYears: years, endDate: '' })
|
||||
return
|
||||
}
|
||||
const start = new Date(form.startDate)
|
||||
const end = new Date(start)
|
||||
end.setFullYear(end.getFullYear() + years)
|
||||
end.setDate(end.getDate() - 1)
|
||||
setForm({ ...form, contractYears: years, endDate: end.toISOString().slice(0, 10) })
|
||||
}
|
||||
|
||||
// 合同结束日期变更 → 自动计算签约年限
|
||||
const handleEndDateChange = (endDate: string) => {
|
||||
if (!form.startDate || !endDate) {
|
||||
setForm({ ...form, endDate, contractYears: 0 })
|
||||
return
|
||||
}
|
||||
const start = new Date(form.startDate)
|
||||
const end = new Date(endDate)
|
||||
const months = Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24 * 30.44))
|
||||
setForm({ ...form, endDate, contractYears: Math.max(1, Math.round(months / 12)) })
|
||||
}
|
||||
|
||||
// 开始日期变更 → 重新计算结束日期
|
||||
const handleStartDateChange = (startDate: string) => {
|
||||
if (form.contractType === 'FIXED' && form.contractYears > 0 && startDate) {
|
||||
const start = new Date(startDate)
|
||||
const end = new Date(start)
|
||||
end.setFullYear(end.getFullYear() + form.contractYears)
|
||||
end.setDate(end.getDate() - 1)
|
||||
setForm({ ...form, startDate, endDate: end.toISOString().slice(0, 10) })
|
||||
} else {
|
||||
setForm({ ...form, startDate })
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
const data: any = {
|
||||
name: form.name, department: form.department,
|
||||
@@ -951,6 +1032,10 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
||||
onSubmit(data)
|
||||
}
|
||||
|
||||
const canSubmit = form.name && form.department && form.hireDate && form.monthlySalary
|
||||
&& (form.contractType === 'UNSIGNED' || form.startDate)
|
||||
&& !probationError && !probationSalaryError
|
||||
|
||||
return (
|
||||
<Modal open onClose={onClose} title="添加员工">
|
||||
<div className="space-y-3">
|
||||
@@ -978,28 +1063,58 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
||||
</div>
|
||||
<div className="border-t pt-3">
|
||||
<Label>合同类型</Label>
|
||||
<Select value={form.contractType} onChange={(e) => setForm({ ...form, contractType: e.target.value as any })}>
|
||||
<Select value={form.contractType} onChange={(e) => setForm({ ...form, contractType: e.target.value as any, endDate: e.target.value === 'UNFIXED' ? '' : form.endDate })}>
|
||||
<option value="FIXED">固定期限</option><option value="UNFIXED">无固定期限</option><option value="UNSIGNED">未签合同</option>
|
||||
</Select>
|
||||
</div>
|
||||
{form.contractType !== 'UNSIGNED' && (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div><Label>签订日期</Label><Input type="date" value={form.signDate} onChange={(e) => setForm({ ...form, signDate: e.target.value })} /></div>
|
||||
<div><Label>合同开始日期 *</Label><Input type="date" value={form.startDate} onChange={(e) => setForm({ ...form, startDate: e.target.value })} /></div>
|
||||
<div>
|
||||
<Label>签订日期</Label>
|
||||
<Input type="date" value={form.signDate} onChange={(e) => setForm({ ...form, signDate: e.target.value })} />
|
||||
<div className="text-xs text-gray-400 mt-0.5">留空表示尚未签订</div>
|
||||
</div>
|
||||
<div><Label>合同开始日期 *</Label><Input type="date" value={form.startDate} onChange={(e) => handleStartDateChange(e.target.value)} /></div>
|
||||
</div>
|
||||
{form.contractType === 'FIXED' && (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div><Label>合同结束日期</Label><Input type="date" value={form.endDate} onChange={(e) => setForm({ ...form, endDate: e.target.value })} /></div>
|
||||
<div><Label>试用期(月)</Label><Input type="number" value={form.probationMonths} onChange={(e) => setForm({ ...form, probationMonths: parseInt(e.target.value) || 0 })} min={0} max={6} /></div>
|
||||
<div><Label>试用期工资</Label><Input type="number" value={form.probationSalary} onChange={(e) => setForm({ ...form, probationSalary: parseInt(e.target.value) || 0 })} /></div>
|
||||
</div>
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>签约时长(年)</Label>
|
||||
<Input type="number" value={form.contractYears} onChange={(e) => handleContractYearsChange(parseInt(e.target.value) || 0)} min={1} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>合同结束日期</Label>
|
||||
<Input type="date" value={form.endDate} onChange={(e) => handleEndDateChange(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">修改签约时长自动计算结束日期,修改结束日期自动计算签约时长</div>
|
||||
</>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>试用期(月)</Label>
|
||||
<Input type="number" value={form.probationMonths} onChange={(e) => setForm({ ...form, probationMonths: parseInt(e.target.value) || 0 })} min={0} max={6} />
|
||||
{contractMonths > 0 && (
|
||||
<div className="text-xs text-gray-400 mt-0.5">法定上限:{probationMax}个月</div>
|
||||
)}
|
||||
{probationError && <div className="text-xs text-danger mt-0.5">{probationError}</div>}
|
||||
</div>
|
||||
<div>
|
||||
<Label>试用期工资{form.probationMonths > 0 ? ' *' : ''}</Label>
|
||||
<Input type="number" value={form.probationSalary} onChange={(e) => setForm({ ...form, probationSalary: parseFloat(e.target.value) || 0 })} disabled={form.probationMonths <= 0} />
|
||||
{monthlySalaryNum > 0 && form.probationMonths > 0 && (
|
||||
<div className="text-xs text-gray-400 mt-0.5">不低于转正工资80%(≥¥{(monthlySalaryNum * 0.8).toFixed(0)})</div>
|
||||
)}
|
||||
{probationSalaryError && <div className="text-xs text-danger mt-0.5">{probationSalaryError}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" onClick={onClose}>取消</Button>
|
||||
<Button onClick={handleSubmit} disabled={loading || !form.name || !form.department || !form.hireDate || !form.monthlySalary}>{loading ? '保存中...' : '保存'}</Button>
|
||||
<Button onClick={handleSubmit} disabled={loading || !canSubmit}>{loading ? '保存中...' : '保存'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user