feat: 实现20260730优化方案全部功能

- AI文件审查:.docx上传提取文本,支持多种文档类型
- 用工办理工作流:WorkProcess页面+后端API,支持入职/续签/终止等流程
- 企业自建文本库:Templates页面Tab切换,企业模板CRUD+渲染+下载Word
- 考勤发布:Attendance发布/取消发布按钮,员工端MyAttendance页面
- 工资条发布:Money发布/定时发送按钮+弹窗,portal端publishStatus过滤
- 合同到期弹窗:Dashboard合同到期预警可点击打开弹窗,支持续签/终止操作
- Prisma schema新增WorkProcess/EnterpriseTemplate/AttendancePublish模型
- 前后端编译验证全部通过
This commit is contained in:
freedakgmail
2026-07-30 10:21:22 +08:00
parent 38b8849332
commit 42e0c650a4
24 changed files with 3639 additions and 35 deletions
+74
View File
@@ -547,6 +547,27 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '取消归档失败'),
})
const publishPayslipMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/publish`),
onSuccess: (res: any) => {
toast.success(`已发布 ${res.data?.published || 0} 条工资条`)
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
},
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '发布失败'),
})
const [showScheduleModal, setShowScheduleModal] = useState(false)
const [scheduleDate, setScheduleDate] = useState('')
const schedulePayslipMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/schedule`, { scheduledAt: scheduleDate }),
onSuccess: (res: any) => {
toast.success(`已设定定时发送 ${res.data?.scheduled || 0} 条工资条`)
setShowScheduleModal(false)
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
},
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '设定失败'),
})
const importOvertimeMutation = useMutation({
mutationFn: () => api.post(`/payroll/overtime/import-to-batch/${batchId}`),
onSuccess: (res: any) => {
@@ -890,6 +911,24 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
>
{unarchiveMutation.isPending ? '取消中...' : '取消归档'}
</Button>
<Button
size="sm"
onClick={async () => {
if (await confirm({ title: '发布工资条', message: `确认发布 ${batch.month} 月工资条?发布后员工可在员工端查看。`, variant: 'primary' })) {
publishPayslipMutation.mutate()
}
}}
disabled={publishPayslipMutation.isPending}
>
{publishPayslipMutation.isPending ? '发布中...' : '发布工资条'}
</Button>
<Button
variant="secondary"
size="sm"
onClick={() => setShowScheduleModal(true)}
>
<Clock className="w-4 h-4 mr-1" />
</Button>
</div>
)}
</div>
@@ -1042,6 +1081,41 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
</table>
</div>
</Card>
{/* 定时发送弹窗 */}
{showScheduleModal && (
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50 p-4" onClick={() => setShowScheduleModal(false)}>
<Card className="max-w-md w-full" >
<div onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium"></h3>
<button onClick={() => setShowScheduleModal(false)} className="text-gray-400 hover:text-gray-600"><X className="w-4 h-4" /></button>
</div>
<p className="text-xs text-gray-500 mb-3"></p>
<div className="space-y-3">
<div>
<Label></Label>
<Input
type="datetime-local"
value={scheduleDate}
onChange={(e) => setScheduleDate(e.target.value)}
/>
</div>
<Button
size="sm"
onClick={() => {
if (!scheduleDate) { toast.error('请选择发送时间'); return }
schedulePayslipMutation.mutate()
}}
disabled={schedulePayslipMutation.isPending}
>
{schedulePayslipMutation.isPending ? '设定中...' : '确认定时发送'}
</Button>
</div>
</div>
</Card>
</div>
)}
</div>
)
}