feat: P2+P3 薪酬模板驱动计算 - SYSTEM注册表 + 前端动态化
P2: 社保/公积金/个税改为 SYSTEM 注册表驱动 - 新增 SystemCalcContext 和 systemCalculators 注册表 - ensureSocialHousingCalculated 缓存社保公积金计算 - calcTaxSystem 提取个税计算为系统函数 - calcBatchEntry 重构为模板拓扑排序调度 P3: 前端批次表格动态化 + 模板编辑器增强 - 后端模板 CRUD 支持 calcType/dependencies 字段 - TemplateTab 表格增加计算方式列 - TemplateTab 编辑表单增加 calcType 选择(公式/系统) - BatchTab editableFields 从模板 isEditable 动态获取
This commit is contained in:
@@ -497,6 +497,13 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
},
|
||||
})
|
||||
|
||||
const { data: templateItems } = useQuery<any[]>({
|
||||
queryKey: ['payslip-template'],
|
||||
queryFn: async () => {
|
||||
return await payrollApi.template()
|
||||
},
|
||||
})
|
||||
|
||||
const updateEntryMutation = useMutation({
|
||||
mutationFn: ({ employeeId, data }: { employeeId: string; data: any }) =>
|
||||
payrollApi.updateBatchEntry(batchId, employeeId, data),
|
||||
@@ -729,10 +736,19 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
}
|
||||
}
|
||||
|
||||
// 可编辑的输入项字段
|
||||
const editableFields = isBonus
|
||||
? ['bonus']
|
||||
: ['baseSalary', 'performanceSalary', 'overtimePay', 'allowance', 'deduction', 'bonus', 'socialEmp', 'housingEmp', 'socialOrg', 'housingOrg']
|
||||
// 可编辑的输入项字段:从模板动态获取,回退到硬编码
|
||||
const editableFields = (() => {
|
||||
if (templateItems && templateItems.length > 0) {
|
||||
const editable = templateItems.filter((i: any) => i.isEditable).map((i: any) => i.code)
|
||||
// 奖金批次只允许编辑 bonus
|
||||
if (isBonus) return ['bonus']
|
||||
// 社保/公积金允许手动覆盖
|
||||
return [...editable, 'socialEmp', 'housingEmp', 'socialOrg', 'housingOrg']
|
||||
}
|
||||
return isBonus
|
||||
? ['bonus']
|
||||
: ['baseSalary', 'performanceSalary', 'overtimePay', 'allowance', 'deduction', 'bonus', 'socialEmp', 'housingEmp', 'socialOrg', 'housingOrg']
|
||||
})()
|
||||
|
||||
// 点击单元格进入编辑
|
||||
const startEdit = (employeeId: string, field: string, currentValue: number) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export function TemplateManager() {
|
||||
code: '',
|
||||
type: 'INPUT' as 'INPUT' | 'CALCULATED',
|
||||
formula: '',
|
||||
calcType: 'FORMULA' as 'FORMULA' | 'SYSTEM',
|
||||
order: 99,
|
||||
isEditable: true,
|
||||
})
|
||||
@@ -67,7 +68,7 @@ export function TemplateManager() {
|
||||
/** 打开新增表单 */
|
||||
const handleAdd = () => {
|
||||
setEditingItem(null)
|
||||
setForm({ name: '', code: '', type: 'INPUT', formula: '', order: items?.length ? items.length + 1 : 99, isEditable: true })
|
||||
setForm({ name: '', code: '', type: 'INPUT', formula: '', calcType: 'FORMULA', order: items?.length ? items.length + 1 : 99, isEditable: true })
|
||||
setShowForm(true)
|
||||
}
|
||||
|
||||
@@ -79,6 +80,7 @@ export function TemplateManager() {
|
||||
code: item.code,
|
||||
type: item.type,
|
||||
formula: item.formula || '',
|
||||
calcType: item.calcType || 'FORMULA',
|
||||
order: item.order,
|
||||
isEditable: item.isEditable,
|
||||
})
|
||||
@@ -91,10 +93,11 @@ export function TemplateManager() {
|
||||
toast.error('名称和字段代码不能为空')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
const payload: any = {
|
||||
name: form.name.trim(),
|
||||
type: form.type,
|
||||
formula: form.type === 'CALCULATED' ? form.formula.trim() || null : null,
|
||||
calcType: form.type === 'CALCULATED' ? form.calcType : 'FORMULA',
|
||||
order: Number(form.order),
|
||||
isEditable: form.isEditable,
|
||||
}
|
||||
@@ -128,6 +131,7 @@ export function TemplateManager() {
|
||||
<th className="py-2 px-2 w-24">名称</th>
|
||||
<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-48">计算公式</th>
|
||||
<th className="py-2 px-2 w-16">可编辑</th>
|
||||
<th className="py-2 px-2 text-right w-24">操作</th>
|
||||
@@ -144,6 +148,13 @@ export function TemplateManager() {
|
||||
{item.type === 'INPUT' ? '输入项' : '计算项'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 px-2">
|
||||
{item.type === 'CALCULATED' && (
|
||||
<span className={`px-2 py-0.5 rounded text-xs whitespace-nowrap ${(item.calcType || 'FORMULA') === 'SYSTEM' ? 'bg-amber-50 text-amber-600' : 'bg-teal-50 text-teal-600'}`}>
|
||||
{(item.calcType || 'FORMULA') === 'SYSTEM' ? '系统计算' : '公式计算'}
|
||||
</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'}`}>
|
||||
@@ -240,15 +251,36 @@ export function TemplateManager() {
|
||||
</div>
|
||||
</div>
|
||||
{form.type === 'CALCULATED' && (
|
||||
<div>
|
||||
<Label>计算公式</Label>
|
||||
<Input
|
||||
type="text"
|
||||
value={form.formula}
|
||||
onChange={(e) => setForm({ ...form, formula: e.target.value })}
|
||||
placeholder="如:baseSalary + overtimePay + allowance - deduction"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">可引用其他字段代码进行加减乘除运算</p>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<Label>计算方式</Label>
|
||||
<Select
|
||||
value={form.calcType}
|
||||
onChange={(e) => setForm({ ...form, calcType: e.target.value as 'FORMULA' | 'SYSTEM' })}
|
||||
disabled={!!editingItem && editingItem.isDefault}
|
||||
>
|
||||
<option value="FORMULA">公式计算(加减乘除表达式)</option>
|
||||
<option value="SYSTEM">系统计算(社保/公积金/个税等)</option>
|
||||
</Select>
|
||||
{editingItem && editingItem.isDefault && (
|
||||
<p className="text-xs text-gray-500 mt-1">预置项的计算方式不可修改</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Label>{form.calcType === 'SYSTEM' ? '系统函数标识' : '计算公式'}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
value={form.formula}
|
||||
onChange={(e) => setForm({ ...form, formula: e.target.value })}
|
||||
placeholder={form.calcType === 'SYSTEM' ? '如:SOCIAL_EMP' : '如:baseSalary + overtimePay + allowance - deduction'}
|
||||
disabled={!!editingItem && editingItem.isDefault && form.calcType === 'SYSTEM'}
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{form.calcType === 'SYSTEM'
|
||||
? '系统计算标识:SOCIAL_EMP(个人社保)、HOUSING_EMP(个人公积金)、TAX(个税)'
|
||||
: '可引用其他字段代码进行加减乘除运算,支持 min()、max()、round() 函数'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user