feat: P4 dataSource 字段 + overtimePay 修复 + 获取按钮动态化
- schema: PayslipItem 增加 dataSource 字段标记数据来源 - DEFAULT_ITEMS: overtimePay 从 CALCULATED 改为 INPUT(修复被重算覆盖为0的bug) - DEFAULT_ITEMS: performanceSalary/bonus/deduction 标记 dataSource - 迁移逻辑: 已有组织自动补充 dataSource + 修正 overtimePay 类型 - 后端模板 CRUD 支持 dataSource 字段 - 前端模板编辑器增加数据来源下拉选择 - 前端模板表格增加数据来源列 - 前端批次详情'获取'按钮根据模板 dataSource 动态显示
This commit is contained in:
@@ -897,55 +897,66 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
>
|
||||
<Download className="w-4 h-4 mr-1" />下载模板
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => importOvertimeMutation.mutate(undefined)}
|
||||
disabled={importOvertimeMutation.isPending}
|
||||
>
|
||||
<Calculator className="w-4 h-4 mr-1" />
|
||||
{importOvertimeMutation.isPending ? '获取中...' : '获取加班费'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchBonusMutation.mutate(undefined)}
|
||||
disabled={fetchBonusMutation.isPending || isArchived}
|
||||
title="从提成奖金模块按月拉取填充奖金字段"
|
||||
>
|
||||
<DollarSign className="w-4 h-4 mr-1" />
|
||||
{fetchBonusMutation.isPending ? '获取中...' : '获取提成奖金'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchPerformanceMutation.mutate(undefined)}
|
||||
disabled={fetchPerformanceMutation.isPending || isArchived}
|
||||
title="按考核等级系数计算绩效工资(A=1.2/B=1.0/C=0.8/D=0.6),无考核记录按1.0"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4 mr-1" />
|
||||
{fetchPerformanceMutation.isPending ? '获取中...' : '获取绩效工资'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchDisciplinaryMutation.mutate(undefined)}
|
||||
disabled={fetchDisciplinaryMutation.isPending || isArchived}
|
||||
title="按批次月份从违纪记录中汇总扣款金额(action=DEDUCTION)"
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 mr-1" />
|
||||
{fetchDisciplinaryMutation.isPending ? '获取中...' : '获取违纪扣款'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchAttendanceDeductionMutation.mutate(undefined)}
|
||||
disabled={fetchAttendanceDeductionMutation.isPending || isArchived}
|
||||
title="按批次月份从考勤确认中获取扣款金额(需先在考勤确认中填写)"
|
||||
>
|
||||
<CalendarCheck className="w-4 h-4 mr-1" />
|
||||
{fetchAttendanceDeductionMutation.isPending ? '获取中...' : '获取考勤扣款'}
|
||||
</Button>
|
||||
{/* 根据模板 dataSource 动态显示获取按钮 */}
|
||||
{(templateItems || []).some((t: any) => t.dataSource === 'overtime') && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => importOvertimeMutation.mutate(undefined)}
|
||||
disabled={importOvertimeMutation.isPending}
|
||||
>
|
||||
<Calculator className="w-4 h-4 mr-1" />
|
||||
{importOvertimeMutation.isPending ? '获取中...' : '获取加班费'}
|
||||
</Button>
|
||||
)}
|
||||
{(templateItems || []).some((t: any) => t.dataSource === 'bonus') && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchBonusMutation.mutate(undefined)}
|
||||
disabled={fetchBonusMutation.isPending || isArchived}
|
||||
title="从提成奖金模块按月拉取填充奖金字段"
|
||||
>
|
||||
<DollarSign className="w-4 h-4 mr-1" />
|
||||
{fetchBonusMutation.isPending ? '获取中...' : '获取提成奖金'}
|
||||
</Button>
|
||||
)}
|
||||
{(templateItems || []).some((t: any) => t.dataSource === 'performance') && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchPerformanceMutation.mutate(undefined)}
|
||||
disabled={fetchPerformanceMutation.isPending || isArchived}
|
||||
title="按考核等级系数计算绩效工资(A=1.2/B=1.0/C=0.8/D=0.6),无考核记录按1.0"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4 mr-1" />
|
||||
{fetchPerformanceMutation.isPending ? '获取中...' : '获取绩效工资'}
|
||||
</Button>
|
||||
)}
|
||||
{(templateItems || []).some((t: any) => t.dataSource === 'deduction') && (
|
||||
<>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchDisciplinaryMutation.mutate(undefined)}
|
||||
disabled={fetchDisciplinaryMutation.isPending || isArchived}
|
||||
title="按批次月份从违纪记录中汇总扣款金额(action=DEDUCTION)"
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 mr-1" />
|
||||
{fetchDisciplinaryMutation.isPending ? '获取中...' : '获取违纪扣款'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fetchAttendanceDeductionMutation.mutate(undefined)}
|
||||
disabled={fetchAttendanceDeductionMutation.isPending || isArchived}
|
||||
title="按批次月份从考勤确认中获取扣款金额(需先在考勤确认中填写)"
|
||||
>
|
||||
<CalendarCheck className="w-4 h-4 mr-1" />
|
||||
{fetchAttendanceDeductionMutation.isPending ? '获取中...' : '获取考勤扣款'}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
|
||||
@@ -9,6 +9,14 @@ import Card from '../../components/ui/Card'
|
||||
import Button from '../../components/ui/Button'
|
||||
import { Input, Label, Select } from '../../components/ui/Input'
|
||||
import Modal from '../../components/ui/Modal'
|
||||
|
||||
const DATA_SOURCE_LABELS: Record<string, string> = {
|
||||
overtime: '加班记录',
|
||||
performance: '绩效记录',
|
||||
bonus: '奖金记录',
|
||||
deduction: '扣款记录',
|
||||
}
|
||||
|
||||
// 金额格式化:保留两位小数 + 千分位
|
||||
|
||||
// ========== 薪酬模版管理 ==========
|
||||
@@ -24,6 +32,7 @@ export function TemplateManager() {
|
||||
type: 'INPUT' as 'INPUT' | 'CALCULATED',
|
||||
formula: '',
|
||||
calcType: 'FORMULA' as 'FORMULA' | 'SYSTEM',
|
||||
dataSource: '' as string,
|
||||
order: 99,
|
||||
isEditable: true,
|
||||
})
|
||||
@@ -68,7 +77,7 @@ export function TemplateManager() {
|
||||
/** 打开新增表单 */
|
||||
const handleAdd = () => {
|
||||
setEditingItem(null)
|
||||
setForm({ name: '', code: '', type: 'INPUT', formula: '', calcType: 'FORMULA', order: items?.length ? items.length + 1 : 99, isEditable: true })
|
||||
setForm({ name: '', code: '', type: 'INPUT', formula: '', calcType: 'FORMULA', dataSource: '', order: items?.length ? items.length + 1 : 99, isEditable: true })
|
||||
setShowForm(true)
|
||||
}
|
||||
|
||||
@@ -81,6 +90,7 @@ export function TemplateManager() {
|
||||
type: item.type,
|
||||
formula: item.formula || '',
|
||||
calcType: item.calcType || 'FORMULA',
|
||||
dataSource: item.dataSource || '',
|
||||
order: item.order,
|
||||
isEditable: item.isEditable,
|
||||
})
|
||||
@@ -98,6 +108,7 @@ export function TemplateManager() {
|
||||
type: form.type,
|
||||
formula: form.type === 'CALCULATED' ? form.formula.trim() || null : null,
|
||||
calcType: form.type === 'CALCULATED' ? form.calcType : 'FORMULA',
|
||||
dataSource: form.dataSource || null,
|
||||
order: Number(form.order),
|
||||
isEditable: form.isEditable,
|
||||
}
|
||||
@@ -132,6 +143,7 @@ export function TemplateManager() {
|
||||
<th className="py-2 px-2 w-32">字段代码</th>
|
||||
<th className="py-2 px-2 w-20">类型</th>
|
||||
<th className="py-2 px-2 w-24">计算方式</th>
|
||||
<th className="py-2 px-2 w-24">数据来源</th>
|
||||
<th className="py-2 px-2 w-48">计算公式</th>
|
||||
<th className="py-2 px-2 w-16">可编辑</th>
|
||||
<th className="py-2 px-2 text-right w-24">操作</th>
|
||||
@@ -155,6 +167,15 @@ export function TemplateManager() {
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 px-2">
|
||||
{item.dataSource ? (
|
||||
<span className="px-2 py-0.5 rounded text-xs whitespace-nowrap bg-cyan-50 text-cyan-600">
|
||||
{DATA_SOURCE_LABELS[item.dataSource] || item.dataSource}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">手动填写</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 px-2 text-gray-500 font-mono text-xs max-w-48 truncate" title={item.formula || ''}>{item.formula || '—'}</td>
|
||||
<td className="py-2 px-2">
|
||||
<span className={`text-xs ${item.isEditable ? 'text-safe' : 'text-gray-500'}`}>
|
||||
@@ -283,6 +304,22 @@ export function TemplateManager() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{form.type === 'INPUT' && (
|
||||
<div>
|
||||
<Label>数据来源</Label>
|
||||
<Select
|
||||
value={form.dataSource}
|
||||
onChange={(e) => setForm({ ...form, dataSource: e.target.value })}
|
||||
>
|
||||
<option value="">手动填写</option>
|
||||
<option value="overtime">加班记录(从加班系统获取)</option>
|
||||
<option value="performance">绩效记录(从绩效系统获取)</option>
|
||||
<option value="bonus">奖金记录(从奖金系统导入)</option>
|
||||
<option value="deduction">扣款记录(从扣款系统导入)</option>
|
||||
</Select>
|
||||
<p className="text-xs text-gray-500 mt-1">选择外部数据来源后,发薪批次中会显示对应的"获取"按钮</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user