feat: 电子签署集成到合同管理和离职流程
- 合同管理:选择电子签署时保存后自动创建ESignRecord并关联合同ID - 离职管理:工作交接清单「离职文件签署」步骤增加「发起电子签署」按钮 - 电子签署页面:列表增加来源标识(劳动合同/离职协议) - 合同卡片:电子签署合同显示提示而非手动填写编号/链接
This commit is contained in:
@@ -149,6 +149,12 @@ export default function ESign() {
|
||||
<div className="flex items-center gap-1.5">
|
||||
<FileText className="w-3.5 h-3.5 text-gray-400 shrink-0" />
|
||||
<span className="font-medium truncate max-w-[200px]">{r.documentTitle}</span>
|
||||
{r.contractId && (
|
||||
<span className="px-1.5 py-0.5 rounded text-xs bg-blue-50 text-blue-600 border border-blue-200 shrink-0">劳动合同</span>
|
||||
)}
|
||||
{!r.contractId && r.documentTitle?.includes('离职') && (
|
||||
<span className="px-1.5 py-0.5 rounded text-xs bg-orange-50 text-orange-600 border border-orange-200 shrink-0">离职协议</span>
|
||||
)}
|
||||
</div>
|
||||
{r.remark && <div className="text-xs text-gray-400 mt-0.5">{r.remark}</div>}
|
||||
</td>
|
||||
|
||||
@@ -2,12 +2,12 @@ import { useState, useMemo, useEffect } from 'react'
|
||||
import { usePageSize } from '../hooks/usePageSize'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
import { AlertTriangle, Check, ChevronRight, ChevronLeft, Shield, Info, Calculator, FileText, Printer, Trash2, List, Download, Plus, Edit, Send, CheckCircle, XCircle, Play, Ban, CheckCheck } from 'lucide-react'
|
||||
import { AlertTriangle, Check, ChevronRight, ChevronLeft, Shield, Info, Calculator, FileText, Printer, Trash2, List, Download, Plus, Edit, Send, CheckCircle, XCircle, Play, Ban, CheckCheck, PenTool } from 'lucide-react'
|
||||
import { Stepper } from '../components/ui/Stepper'
|
||||
import { InlineAlert } from '../components/ui/InlineAlert'
|
||||
import PageGuide from '../components/ui/PageGuide'
|
||||
import jsPDF from 'jspdf'
|
||||
import { rosterApi, terminationApi } from '../lib/api-services'
|
||||
import { rosterApi, terminationApi, esignApi } from '../lib/api-services'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
@@ -1549,6 +1549,27 @@ export default function Termination() {
|
||||
placeholder="备注说明(选填)"
|
||||
className="text-xs"
|
||||
/>
|
||||
{item.key === 'docs_signed' && employeeId && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await esignApi.create({
|
||||
employeeId,
|
||||
documentTitle: '离职协议',
|
||||
remark: '离职流程中发起',
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ['esign-records'] })
|
||||
toast.success('离职协议电子签署已发起')
|
||||
} catch {
|
||||
toast.error('发起签署失败')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PenTool className="w-3.5 h-3.5 mr-1" />发起电子签署
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState, useRef } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { employeeApi } from '../../lib/api-services'
|
||||
import { employeeApi, esignApi } from '../../lib/api-services'
|
||||
import { useConfirm } from '../../hooks/useConfirm'
|
||||
import Card from "../../components/ui/Card"
|
||||
import Button from "../../components/ui/Button"
|
||||
import { Input, Label, Select } from "../../components/ui/Input"
|
||||
import { FileText, AlertTriangle, X, Paperclip, Trash2, Info, Download } from "lucide-react"
|
||||
import { FileText, AlertTriangle, X, Paperclip, Trash2, Info, Download, PenTool } from "lucide-react"
|
||||
|
||||
export default function ContractInfo({ employeeId, contracts, hireDate }: { employeeId: string; contracts: any[]; hireDate: string }) {
|
||||
const queryClient = useQueryClient()
|
||||
@@ -17,8 +17,27 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null)
|
||||
|
||||
const addContractMutation = useMutation({
|
||||
mutationFn: (data: any) => employeeApi.addContract({ ...data, employeeId }),
|
||||
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
|
||||
mutationFn: async (data: any) => {
|
||||
const res = await employeeApi.addContract({ ...data, employeeId }) as any
|
||||
const contractId = res?.data?.id
|
||||
if (data.signMethod === 'ELECTRONIC' && contractId) {
|
||||
try {
|
||||
await esignApi.create({
|
||||
contractId,
|
||||
employeeId,
|
||||
documentTitle: `${data.contractType === 'UNFIXED' ? '无固定期限' : '固定期限'}劳动合同`,
|
||||
remark: '合同创建时自动发起',
|
||||
})
|
||||
toast.success('合同已保存,电子签署记录已创建')
|
||||
} catch {
|
||||
toast.success('合同已保存,电子签署记录创建失败(可稍后手动发起)')
|
||||
}
|
||||
} else {
|
||||
toast.success('合同已保存')
|
||||
}
|
||||
return res
|
||||
},
|
||||
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); queryClient.invalidateQueries({ queryKey: ['esign-records'] }); setShowForm(false) },
|
||||
})
|
||||
|
||||
const deleteContractMutation = useMutation({
|
||||
@@ -193,10 +212,12 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
|
||||
</div>
|
||||
)}
|
||||
{form.signMethod === 'ELECTRONIC' && (
|
||||
<>
|
||||
<div><Label>电子合同编号 *</Label><Input value={form.electronicContractNo} onChange={(e) => setForm({ ...form, electronicContractNo: e.target.value })} placeholder="如 E-2026-001" /></div>
|
||||
<div><Label>电子合同链接 *</Label><Input value={form.electronicContractUrl} onChange={(e) => setForm({ ...form, electronicContractUrl: e.target.value })} placeholder="https://..." /></div>
|
||||
</>
|
||||
<div className="md:col-span-2">
|
||||
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs flex items-start gap-2">
|
||||
<PenTool className="w-4 h-4 mt-0.5 shrink-0" />
|
||||
<span>保存合同后将自动创建电子签署记录,对接易签宝后系统会自动生成签署链接并发送给员工。当前为框架预留阶段,签署记录创建后可在「电子签署」页面查看。</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="md:col-span-2 flex gap-2">
|
||||
<Button onClick={() => {
|
||||
@@ -267,17 +288,12 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
|
||||
</div>
|
||||
)}
|
||||
{c.signMethod === 'ELECTRONIC' && (
|
||||
<>
|
||||
{c.electronicContractNo && <div className="flex justify-between"><span className="text-gray-500">电子合同编号</span><span className="font-medium">{c.electronicContractNo}</span></div>}
|
||||
{c.electronicContractUrl && (
|
||||
<div className="flex justify-between md:col-span-3">
|
||||
<span className="text-gray-500">电子合同</span>
|
||||
<a href={c.electronicContractUrl} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline flex items-center gap-1">
|
||||
<FileText className="w-3 h-3" />查看电子合同
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div className="flex justify-between md:col-span-3">
|
||||
<span className="text-gray-500">签署方式</span>
|
||||
<span className="text-primary flex items-center gap-1 text-xs">
|
||||
<PenTool className="w-3 h-3" />电子签署(记录已创建,请在「电子签署」页面查看进度)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user