From 43186d3dd80bfcd0ff1f0e9bd886de07fd1d3759 Mon Sep 17 00:00:00 2001 From: freedakgmail Date: Thu, 23 Jul 2026 18:22:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=91=98=E5=B7=A5=E5=BC=B9=E7=AA=97=E5=90=88=E5=90=8C=E5=92=8C?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E6=9C=9F=E6=99=BA=E8=83=BD=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 缺省入职日期和合同开始日期为今天,签订日期默认空(留空表示尚未签订) 2. 签约时长与合同结束日期双向自动计算:改年限→算结束日期,改结束日期→算年限 3. 开始日期变更时自动重算结束日期 4. 试用期长度按劳动合同法第19条校验(3月以下不得约定,1年以下≤1月,3年以下≤2月,3年以上≤6月) 5. 有试用期时试用期工资必填,且不得低于转正工资80%(劳动合同法第20条) 6. 校验不通过时禁止保存并显示错误提示 --- frontend/src/pages/Roster.tsx | 137 +++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/Roster.tsx b/frontend/src/pages/Roster.tsx index 5cdcbae..768bdf5 100644 --- a/frontend/src/pages/Roster.tsx +++ b/frontend/src/pages/Roster.tsx @@ -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 (
@@ -978,28 +1063,58 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
- setForm({ ...form, contractType: e.target.value as any, endDate: e.target.value === 'UNFIXED' ? '' : form.endDate })}>
{form.contractType !== 'UNSIGNED' && (
-
setForm({ ...form, signDate: e.target.value })} />
-
setForm({ ...form, startDate: e.target.value })} />
+
+ + setForm({ ...form, signDate: e.target.value })} /> +
留空表示尚未签订
+
+
handleStartDateChange(e.target.value)} />
{form.contractType === 'FIXED' && ( -
-
setForm({ ...form, endDate: e.target.value })} />
-
setForm({ ...form, probationMonths: parseInt(e.target.value) || 0 })} min={0} max={6} />
-
setForm({ ...form, probationSalary: parseInt(e.target.value) || 0 })} />
-
+ <> +
+
+ + handleContractYearsChange(parseInt(e.target.value) || 0)} min={1} /> +
+
+ + handleEndDateChange(e.target.value)} /> +
+
+
修改签约时长自动计算结束日期,修改结束日期自动计算签约时长
+ )} +
+
+ + setForm({ ...form, probationMonths: parseInt(e.target.value) || 0 })} min={0} max={6} /> + {contractMonths > 0 && ( +
法定上限:{probationMax}个月
+ )} + {probationError &&
{probationError}
} +
+
+ + setForm({ ...form, probationSalary: parseFloat(e.target.value) || 0 })} disabled={form.probationMonths <= 0} /> + {monthlySalaryNum > 0 && form.probationMonths > 0 && ( +
不低于转正工资80%(≥¥{(monthlySalaryNum * 0.8).toFixed(0)})
+ )} + {probationSalaryError &&
{probationSalaryError}
} +
+
)}
- +