feat: 20260815 系统优化 - 全部31项问题修复(P0×6+P1×14+P2×9+P3×2)
P0紧急修复(6项): - 草稿保存完整恢复所有字段(含socialAvgWage) - 补偿金批次从compensationBreakdown读取 - 违法解除风险确认UI - 合同结束日期前后校验(前后端双保险) P1高优先级(14项): - 离职日期联动社保/公积金截止月(15号规则) - 合规检查+工作交接改为软阻断(生成待办) - 补偿月数(N/N+1/2N/自定义)+计算基数(近12月/合同/自定义) - 解聘并入花名册操作栏(类型选择跳转向导) - 合同续签开始日期自动推导(原合同结束日+1天) - 年龄合规筛查(童工阻断/未成年工/退休警告) - 编辑入职日期后状态联动(待入职↔在职) - 转正移植到花名册操作栏+薪资回写 - 男职工无法选择三期 P2体验优化(9项): - "劳动合同"调整为"用工关系" - 费用结算新增剩余年假折算(300%日工资) - 身份证号全域改为"证件号码"(前后端18个文件) - 手机号查重 - 开具证明+合同续签移植到花名册操作栏 - 批量转正+批量开具证明 - 去掉用工办理模块 P3规划(2项): - 组织架构+审批流(Department/Position/ApprovalFlow/ApprovalInstance) - 客服工作台(Ticket/ChatSession+SUPPORT角色) 新增模型: Department/Position/ApprovalFlow/ApprovalInstance/Ticket/TicketMessage/ChatSession/ChatMessage 新增字段: Employee.departmentId/supervisorId 新增角色: SUPPORT Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { useState, useRef } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { attachmentApi, employeeApi, socialInsuranceApi } from '../../lib/api-services'
|
||||
import { useConfirm } from '../../hooks/useConfirm'
|
||||
import { copyToClipboard } from '../../lib/clipboard'
|
||||
import Card from "../../components/ui/Card"
|
||||
import Button from "../../components/ui/Button"
|
||||
@@ -12,6 +13,7 @@ import { fmt } from "./shared"
|
||||
|
||||
export default function BasicInfo({ profile, employeeId, attachments }: { profile: any; employeeId: string; attachments: any[] }) {
|
||||
const queryClient = useQueryClient()
|
||||
const confirm = useConfirm()
|
||||
const [editing, setEditing] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [fileType, setFileType] = useState<'ID_CARD' | 'BANK_CARD' | 'EDUCATION' | 'CERTIFICATE' | 'CONTRACT' | 'PHOTO' | 'OTHER'>('ID_CARD')
|
||||
@@ -109,11 +111,36 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
enabled: !editing && !!profile.socialInsBase && !!profile.city,
|
||||
})
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
if (form.city !== (profile.city || '') && !form.cityChangeReason.trim()) {
|
||||
toast.error('参保城市变更必须填写变更原因')
|
||||
return
|
||||
}
|
||||
// 入职日期变更联动状态确认
|
||||
const originalHireDate = profile.hireDate?.toString().slice(0, 10) || ''
|
||||
let statusUpdate: string | undefined
|
||||
if (form.hireDate && form.hireDate !== originalHireDate) {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const newHireDate = new Date(form.hireDate)
|
||||
const currentStatus = profile.status || 'ACTIVE'
|
||||
let newStatus = currentStatus
|
||||
if (newHireDate > today) {
|
||||
newStatus = 'PENDING_ONBOARD'
|
||||
} else {
|
||||
newStatus = 'ACTIVE'
|
||||
}
|
||||
if (newStatus !== currentStatus) {
|
||||
const statusLabel = newStatus === 'PENDING_ONBOARD' ? '待入职' : '在职'
|
||||
const currentLabel = currentStatus === 'PENDING_ONBOARD' ? '待入职' : currentStatus === 'ACTIVE' ? '在职' : currentStatus
|
||||
const confirmed = await confirm({
|
||||
title: '入职日期变更确认',
|
||||
message: `入职日期从 ${originalHireDate} 变更为 ${form.hireDate},员工状态将从"${currentLabel}"变更为"${statusLabel}",是否继续?`,
|
||||
})
|
||||
if (!confirmed) return
|
||||
statusUpdate = newStatus
|
||||
}
|
||||
}
|
||||
const data: any = {
|
||||
department: form.department,
|
||||
gender: form.gender,
|
||||
@@ -137,6 +164,7 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
position: form.position || undefined,
|
||||
cityChangeReason: form.city !== profile.city ? form.cityChangeReason || undefined : undefined,
|
||||
}
|
||||
if (statusUpdate) data.status = statusUpdate
|
||||
updateMutation.mutate(data)
|
||||
}
|
||||
|
||||
@@ -147,7 +175,7 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
...(profile.gender === '女'
|
||||
? [{ label: '女性岗位', value: profile.femaleWorkerType === 'CADRE' ? '干部/管理岗' : profile.femaleWorkerType === 'WORKER' ? '工人/操作岗' : '未填写' }]
|
||||
: []),
|
||||
{ label: '身份证号', value: profile.idCardNumber || '未填写' },
|
||||
{ label: '证件号码', value: profile.idCardNumber || '未填写' },
|
||||
{ label: '手机号', value: profile.phone || '未填写' },
|
||||
{ label: '学历', value: profile.education || '未填写' },
|
||||
{ label: '职务/岗位', value: profile.position || '未填写' },
|
||||
@@ -221,13 +249,13 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
<span className="text-gray-500 shrink-0">{f.label}</span>
|
||||
<span className="font-medium text-right truncate ml-2 flex items-center gap-1">
|
||||
{f.value}
|
||||
{f.label === '身份证号' && profile.idCardNumber && (
|
||||
{f.label === '证件号码' && profile.idCardNumber && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-primary transition-colors shrink-0"
|
||||
title="复制身份证号"
|
||||
title="复制证件号码"
|
||||
onClick={() => {
|
||||
copyToClipboard(profile.idCardNumber, '已复制身份证号')
|
||||
copyToClipboard(profile.idCardNumber, '已复制证件号码')
|
||||
}}
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
@@ -317,7 +345,7 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
<div><Label>姓名(不可编辑)</Label><Input value={profile.name} disabled /></div>
|
||||
<div><Label>身份证号(不可编辑)</Label><Input value={profile.idCardNumber || ''} disabled /></div>
|
||||
<div><Label>证件号码(不可编辑)</Label><Input value={profile.idCardNumber || ''} disabled /></div>
|
||||
<div><Label>部门</Label><Input value={form.department} onChange={(e) => setForm({ ...form, department: e.target.value })} /></div>
|
||||
<div><Label>性别</Label><Select value={form.gender} onChange={(e) => setForm({ ...form, gender: e.target.value as '男' | '女' })}><option value="男">男</option><option value="女">女</option></Select></div>
|
||||
{form.gender === '女' && (
|
||||
|
||||
Reference in New Issue
Block a user