九鸿:薪酬核算表按客户工资表重排字段,员工档案补标准项
- 薪酬核算列表/预览按客户工资表列顺序展示(姓名、入职日期、岗位、标准出勤天数…扣款合计、税后工资)
- 预览弹窗可手工调整 浮动绩效、7 项加项、3 项扣款,实时重算 应付工资/加项合计/扣款合计/税后工资
- 员工档案新增 标准工资/岗位工资/加班工资标准/安全环保奖标准/绩效标准 表单与详情展示
- 接口类型补齐新字段
Co-Authored-By: Claude Code <noreply@anthropic.com>
| | |
| | | sickLeaveBalance?: number; |
| | | salaryStructureId?: number; |
| | | baseSalary?: number; |
| | | /** 标准工资 */ |
| | | standardSalary?: number; |
| | | /** 岗位工资 */ |
| | | postSalary?: number; |
| | | /** 加班工资标准(元/小时) */ |
| | | overtimeHourlyRate?: number; |
| | | /** 安全环保奖标准 */ |
| | | safetyAwardStandard?: number; |
| | | /** 绩效标准 */ |
| | | performanceStandard?: number; |
| | | roleIds?: number[]; |
| | | roleNames?: string[]; |
| | | bankName?: string; |
| | |
| | | userName?: string; |
| | | deptId?: number; |
| | | deptName?: string; |
| | | /** 入职日期 */ |
| | | hireDate?: string; |
| | | /** 岗位名称 */ |
| | | postName?: string; |
| | | // ===== 九泓工资表:标准项(取自员工档案) ===== |
| | | /** 标准工资 */ |
| | | standardSalary?: number; |
| | | /** 岗位工资 */ |
| | | postSalary?: number; |
| | | /** 加班工资标准(元/小时) */ |
| | | overtimeHourlyRate?: number; |
| | | /** 安全环保奖标准 */ |
| | | safetyAwardStandard?: number; |
| | | /** 绩效标准 */ |
| | | performanceStandard?: number; |
| | | // ===== 九泓工资表:出勤与计薪 ===== |
| | | /** 计薪出勤(天) */ |
| | | paidAttendanceDays?: number; |
| | | /** 标准出勤时长(小时) */ |
| | | standardWorkHours?: number; |
| | | /** 法定假日时长(小时) */ |
| | | legalHolidayHours?: number; |
| | | /** 标准出勤工资 */ |
| | | standardAttendancePay?: number; |
| | | /** 法定假工资 */ |
| | | legalHolidayPay?: number; |
| | | /** 安全环保奖 */ |
| | | safetyAward?: number; |
| | | /** 浮动绩效工资 */ |
| | | floatingPerformancePay?: number; |
| | | /** 应付工资 */ |
| | | payableSalary?: number; |
| | | // ===== 九泓工资表:加项 / 扣项 ===== |
| | | /** 工龄工资 */ |
| | | seniorityPay?: number; |
| | | /** 夜班补助 */ |
| | | nightShiftAllowance?: number; |
| | | /** 学历补贴 */ |
| | | educationAllowance?: number; |
| | | /** 体检 */ |
| | | healthCheckAllowance?: number; |
| | | /** 优秀员工奖 */ |
| | | excellentEmployeeAward?: number; |
| | | /** 其他补助 */ |
| | | otherSubsidy?: number; |
| | | /** 部门绩效 */ |
| | | deptPerformance?: number; |
| | | /** 加项合计 */ |
| | | additionTotal?: number; |
| | | /** 漏打卡 */ |
| | | missedPunchDeduction?: number; |
| | | /** 迟到早退 */ |
| | | lateEarlyDeduction?: number; |
| | | /** 扣款合计 */ |
| | | deductionTotal?: number; |
| | | // ===== 兼容旧字段 ===== |
| | | baseSalary?: number; |
| | | performanceSalary?: number; |
| | | overtimePay?: number; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'postSalary', |
| | | label: '岗位工资', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请输入岗位工资', |
| | | min: 0, |
| | | precision: 2, |
| | | style: { width: '100%' }, |
| | | }, |
| | | help: '与基本工资一起构成工资表中的标准工资', |
| | | }, |
| | | { |
| | | fieldName: 'standardSalary', |
| | | label: '标准工资', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '不填则自动按「基本工资 + 岗位工资」计算', |
| | | min: 0, |
| | | precision: 2, |
| | | style: { width: '100%' }, |
| | | }, |
| | | help: '工资表中按计薪出勤折算的基数;留空时取基本工资 + 岗位工资', |
| | | }, |
| | | { |
| | | fieldName: 'overtimeHourlyRate', |
| | | label: '加班工资标准', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请输入每小时加班单价(元/小时)', |
| | | min: 0, |
| | | precision: 2, |
| | | style: { width: '100%' }, |
| | | }, |
| | | help: '元/小时;留空时按「基本工资 / 21.75 / 8」折算', |
| | | }, |
| | | { |
| | | fieldName: 'safetyAwardStandard', |
| | | label: '安全环保奖标准', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请输入安全环保奖标准', |
| | | min: 0, |
| | | precision: 2, |
| | | style: { width: '100%' }, |
| | | }, |
| | | help: '按计薪出勤比例折算后计入应付工资', |
| | | }, |
| | | { |
| | | fieldName: 'performanceStandard', |
| | | label: '绩效标准', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请输入绩效标准', |
| | | min: 0, |
| | | precision: 2, |
| | | style: { width: '100%' }, |
| | | }, |
| | | help: '按计薪出勤比例折算后计入「浮动绩效工资」', |
| | | }, |
| | | { |
| | | fieldName: 'bankName', |
| | | label: '开户银行', |
| | | component: 'Input', |
| | |
| | | <DescriptionsItem label="转正日期">{{ employee.regularDate || '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="离职日期">{{ employee.leaveDate || '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="基本工资">{{ employee.baseSalary ? `¥${employee.baseSalary.toFixed(2)}` : '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="岗位工资">{{ employee.postSalary ? `¥${employee.postSalary.toFixed(2)}` : '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="标准工资"> |
| | | {{ employee.standardSalary ? `¥${employee.standardSalary.toFixed(2)}` : (employee.baseSalary || employee.postSalary ? `¥${((employee.baseSalary || 0) + (employee.postSalary || 0)).toFixed(2)}(基本+岗位自动合成)` : '-') }} |
| | | </DescriptionsItem> |
| | | <DescriptionsItem label="加班工资标准">{{ employee.overtimeHourlyRate ? `¥${employee.overtimeHourlyRate.toFixed(2)}/小时` : '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="安全环保奖标准">{{ employee.safetyAwardStandard ? `¥${employee.safetyAwardStandard.toFixed(2)}` : '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="绩效标准">{{ employee.performanceStandard ? `¥${employee.performanceStandard.toFixed(2)}` : '-' }}</DescriptionsItem> |
| | | <DescriptionsItem label="用户角色" :span="2"> |
| | | {{ Array.isArray(employee.roleNames) ? employee.roleNames.join('、') : employee.roleNames || '-' }} |
| | | </DescriptionsItem> |
| | |
| | | import type { TableColumnsType } from 'ant-design-vue'; |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { HrmSalaryCalculationApi } from '#/api/hrm/salary/calculation'; |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** 列表的字段 */ |
| | | /** 列表的字段(按九泓工资表列顺序) */ |
| | | export function useGridColumns(): VxeTableGridOptions<HrmSalaryCalculationApi.SalaryCalculation>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { field: 'no', title: '核算单号', minWidth: 140 }, |
| | | { field: 'period', title: '核算周期', width: 100 }, |
| | | { field: 'userName', title: '员工姓名', minWidth: 100 }, |
| | | { field: 'deptName', title: '部门', minWidth: 120 }, |
| | | { type: 'checkbox', width: 50, fixed: 'left' }, |
| | | { field: 'no', title: '核算单号', width: 150, fixed: 'left' }, |
| | | { field: 'period', title: '核算周期', width: 100, fixed: 'left' }, |
| | | { field: 'userName', title: '姓名', width: 90, fixed: 'left' }, |
| | | { field: 'hireDate', title: '入职日期', width: 110 }, |
| | | { field: 'postName', title: '岗位', width: 110 }, |
| | | { field: 'deptName', title: '部门', width: 120 }, |
| | | { field: 'workDays', title: '标准出勤天数', width: 110 }, |
| | | { field: 'paidAttendanceDays', title: '计薪出勤', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'standardSalary', title: '标准工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'baseSalary', title: '基本工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'performanceSalary', title: '绩效工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'overtimePay', title: '加班费', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'mealAllowance', title: '餐补', width: 80, formatter: 'formatAmount2' }, |
| | | { field: 'transportAllowance', title: '交通补贴', width: 80, formatter: 'formatAmount2' }, |
| | | { field: 'otherAllowance', title: '其他补贴', width: 80, formatter: 'formatAmount2' }, |
| | | { field: 'socialSecurityDeduction', title: '社保扣款', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'housingFundDeduction', title: '公积金扣款', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'taxDeduction', title: '个税扣款', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'otherDeduction', title: '其他扣款', width: 80, formatter: 'formatAmount2' }, |
| | | { field: 'leaveDeduction', title: '请假扣款', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'actualSalary', title: '实发工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'workDays', title: '出勤天数', width: 80 }, |
| | | { field: 'overtimeHours', title: '加班时长(h)', width: 100 }, |
| | | { field: 'totalWorkHours', title: '排班工时(h)', width: 100 }, |
| | | { field: 'postSalary', title: '岗位工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'overtimeHourlyRate', title: '加班工资(标准)', width: 120, formatter: 'formatAmount2' }, |
| | | { field: 'safetyAwardStandard', title: '安全环保奖(标准)', width: 130, formatter: 'formatAmount2' }, |
| | | { field: 'performanceStandard', title: '绩效(标准)', width: 110, formatter: 'formatAmount2' }, |
| | | { field: 'standardWorkHours', title: '标准出勤时长', width: 120, formatter: 'formatAmount2' }, |
| | | { field: 'overtimeHours', title: '加班时长', width: 100 }, |
| | | { field: 'legalHolidayHours', title: '法定假日时长', width: 120 }, |
| | | { field: 'standardAttendancePay', title: '标准出勤工资', width: 120, formatter: 'formatAmount2' }, |
| | | { field: 'overtimePay', title: '加班工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'legalHolidayPay', title: '法定假工资', width: 110, formatter: 'formatAmount2' }, |
| | | { field: 'safetyAward', title: '安全环保奖', width: 110, formatter: 'formatAmount2' }, |
| | | { field: 'floatingPerformancePay', title: '浮动绩效工资', width: 120, formatter: 'formatAmount2' }, |
| | | { field: 'payableSalary', title: '应付工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'seniorityPay', title: '工龄工资', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'nightShiftAllowance', title: '夜班补助', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'educationAllowance', title: '学历补贴', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'healthCheckAllowance', title: '体检', width: 90, formatter: 'formatAmount2' }, |
| | | { field: 'excellentEmployeeAward', title: '优秀员工奖', width: 110, formatter: 'formatAmount2' }, |
| | | { field: 'otherSubsidy', title: '其他补助', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'deptPerformance', title: '部门绩效', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'additionTotal', title: '加项合计', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'missedPunchDeduction', title: '漏打卡', width: 90, formatter: 'formatAmount2' }, |
| | | { field: 'lateEarlyDeduction', title: '迟到早退', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'otherDeduction', title: '其他扣款', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'deductionTotal', title: '扣款合计', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'socialSecurityDeduction', title: '社保', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'housingFundDeduction', title: '公积金', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'taxDeduction', title: '个税', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'actualSalary', title: '税后工资', width: 110, formatter: 'formatAmount2', fixed: 'right' }, |
| | | { |
| | | field: 'status', |
| | | title: '状态', |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** 预览表格的列定义(用于Ant Design Table) */ |
| | | export function usePreviewColumns() { |
| | | /** 预览表格里允许手工调整的字段(其余均为自动计算) */ |
| | | export const PREVIEW_EDITABLE_FIELDS = [ |
| | | 'floatingPerformancePay', |
| | | 'seniorityPay', |
| | | 'nightShiftAllowance', |
| | | 'educationAllowance', |
| | | 'healthCheckAllowance', |
| | | 'excellentEmployeeAward', |
| | | 'otherSubsidy', |
| | | 'deptPerformance', |
| | | 'missedPunchDeduction', |
| | | 'lateEarlyDeduction', |
| | | 'otherDeduction', |
| | | ] as const; |
| | | |
| | | /** 加项字段(汇总为「加项合计」) */ |
| | | export const PREVIEW_ADDITION_FIELDS = [ |
| | | 'seniorityPay', |
| | | 'nightShiftAllowance', |
| | | 'educationAllowance', |
| | | 'healthCheckAllowance', |
| | | 'excellentEmployeeAward', |
| | | 'otherSubsidy', |
| | | 'deptPerformance', |
| | | ] as const; |
| | | |
| | | /** 扣款字段(汇总为「扣款合计」) */ |
| | | export const PREVIEW_DEDUCTION_FIELDS = [ |
| | | 'missedPunchDeduction', |
| | | 'lateEarlyDeduction', |
| | | 'otherDeduction', |
| | | ] as const; |
| | | |
| | | /** 预览表格的列定义(用于Ant Design Table,按九泓工资表列顺序) */ |
| | | export function usePreviewColumns(): TableColumnsType { |
| | | return [ |
| | | { title: '员工姓名', dataIndex: 'userName', width: 100, fixed: 'left' }, |
| | | { title: '姓名', dataIndex: 'userName', width: 90, fixed: 'left' }, |
| | | { title: '入职日期', dataIndex: 'hireDate', width: 110 }, |
| | | { title: '岗位', dataIndex: 'postName', width: 110 }, |
| | | { title: '部门', dataIndex: 'deptName', width: 120 }, |
| | | { title: '基本工资', dataIndex: 'baseSalary', width: 110 }, |
| | | { title: '绩效工资', dataIndex: 'performanceSalary', width: 110 }, |
| | | { title: '加班费', dataIndex: 'overtimePay', width: 110 }, |
| | | { title: '餐补', dataIndex: 'mealAllowance', width: 100 }, |
| | | { title: '交通补贴', dataIndex: 'transportAllowance', width: 100 }, |
| | | { title: '其他补贴', dataIndex: 'otherAllowance', width: 100 }, |
| | | { title: '社保扣款', dataIndex: 'socialSecurityDeduction', width: 110 }, |
| | | { title: '公积金扣款', dataIndex: 'housingFundDeduction', width: 110 }, |
| | | { title: '个税扣款', dataIndex: 'taxDeduction', width: 110 }, |
| | | { title: '其他扣款', dataIndex: 'otherDeduction', width: 100 }, |
| | | { title: '请假扣款', dataIndex: 'leaveDeduction', width: 110 }, |
| | | { title: '出勤天数', dataIndex: 'workDays', width: 90 }, |
| | | { title: '标准出勤天数', dataIndex: 'workDays', width: 110 }, |
| | | { title: '计薪出勤', dataIndex: 'paidAttendanceDays', width: 100 }, |
| | | { title: '标准工资', dataIndex: 'standardSalary', width: 100 }, |
| | | { title: '基本工资', dataIndex: 'baseSalary', width: 100 }, |
| | | { title: '岗位工资', dataIndex: 'postSalary', width: 100 }, |
| | | { title: '加班工资(标准)', dataIndex: 'overtimeHourlyRate', width: 120 }, |
| | | { title: '安全环保奖(标准)', dataIndex: 'safetyAwardStandard', width: 130 }, |
| | | { title: '绩效(标准)', dataIndex: 'performanceStandard', width: 110 }, |
| | | { title: '标准出勤时长', dataIndex: 'standardWorkHours', width: 120 }, |
| | | { title: '加班时长', dataIndex: 'overtimeHours', width: 100 }, |
| | | { title: '排班工时(h)', dataIndex: 'totalWorkHours', width: 100 }, |
| | | { title: '实发工资', dataIndex: 'actualSalary', width: 100, fixed: 'right' }, |
| | | { title: '法定假日时长', dataIndex: 'legalHolidayHours', width: 120 }, |
| | | { title: '标准出勤工资', dataIndex: 'standardAttendancePay', width: 120 }, |
| | | { title: '加班工资', dataIndex: 'overtimePay', width: 100 }, |
| | | { title: '法定假工资', dataIndex: 'legalHolidayPay', width: 110 }, |
| | | { title: '安全环保奖', dataIndex: 'safetyAward', width: 110 }, |
| | | { title: '浮动绩效工资', dataIndex: 'floatingPerformancePay', width: 120 }, |
| | | { title: '应付工资', dataIndex: 'payableSalary', width: 100 }, |
| | | { title: '工龄工资', dataIndex: 'seniorityPay', width: 100 }, |
| | | { title: '夜班补助', dataIndex: 'nightShiftAllowance', width: 100 }, |
| | | { title: '学历补贴', dataIndex: 'educationAllowance', width: 100 }, |
| | | { title: '体检', dataIndex: 'healthCheckAllowance', width: 90 }, |
| | | { title: '优秀员工奖', dataIndex: 'excellentEmployeeAward', width: 110 }, |
| | | { title: '其他补助', dataIndex: 'otherSubsidy', width: 100 }, |
| | | { title: '部门绩效', dataIndex: 'deptPerformance', width: 100 }, |
| | | { title: '加项合计', dataIndex: 'additionTotal', width: 100 }, |
| | | { title: '漏打卡', dataIndex: 'missedPunchDeduction', width: 90 }, |
| | | { title: '迟到早退', dataIndex: 'lateEarlyDeduction', width: 100 }, |
| | | { title: '其他扣款', dataIndex: 'otherDeduction', width: 100 }, |
| | | { title: '扣款合计', dataIndex: 'deductionTotal', width: 100 }, |
| | | { title: '社保', dataIndex: 'socialSecurityDeduction', width: 100 }, |
| | | { title: '公积金', dataIndex: 'housingFundDeduction', width: 100 }, |
| | | { title: '个税', dataIndex: 'taxDeduction', width: 100 }, |
| | | { title: '税后工资', dataIndex: 'actualSalary', width: 110, fixed: 'right' }, |
| | | ]; |
| | | } |
| | |
| | | import { handleTree } from '#/packages/utils/src'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema, usePreviewColumns } from './data'; |
| | | import { |
| | | PREVIEW_ADDITION_FIELDS, |
| | | PREVIEW_DEDUCTION_FIELDS, |
| | | PREVIEW_EDITABLE_FIELDS, |
| | | useGridColumns, |
| | | useGridFormSchema, |
| | | usePreviewColumns, |
| | | } from './data'; |
| | | |
| | | // 核算弹窗相关 |
| | | const calculateVisible = ref(false); |
| | |
| | | period: calculatePeriod.value, |
| | | deptId: calculateDeptId.value, |
| | | }); |
| | | // 预览行先按当前口径重算合计,保证弹窗里显示的合计与保存结果一致 |
| | | previewData.value.forEach((record) => recalculateRecord(record)); |
| | | if (previewData.value.length === 0) { |
| | | message.info('该条件下没有需要核算的员工'); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** 计算实发工资 */ |
| | | function calculateActualSalary(record: HrmSalaryCalculationApi.SalaryCalculation) { |
| | | const income = (record.baseSalary || 0) + |
| | | (record.performanceSalary || 0) + |
| | | (record.overtimePay || 0) + |
| | | (record.mealAllowance || 0) + |
| | | (record.transportAllowance || 0) + |
| | | (record.otherAllowance || 0); |
| | | const deduction = (record.socialSecurityDeduction || 0) + |
| | | (record.housingFundDeduction || 0) + |
| | | (record.taxDeduction || 0) + |
| | | (record.otherDeduction || 0) + |
| | | (record.leaveDeduction || 0); |
| | | return income - deduction; |
| | | /** 预览表中按原样展示的字段(非金额) */ |
| | | const PLAIN_FIELDS = [ |
| | | 'userName', |
| | | 'hireDate', |
| | | 'postName', |
| | | 'deptName', |
| | | 'workDays', |
| | | 'paidAttendanceDays', |
| | | 'standardWorkHours', |
| | | 'overtimeHours', |
| | | 'legalHolidayHours', |
| | | ]; |
| | | |
| | | /** 是否为可手工调整的列 */ |
| | | function isEditableField(field: string) { |
| | | return (PREVIEW_EDITABLE_FIELDS as readonly string[]).includes(field); |
| | | } |
| | | |
| | | /** 更新行数据并计算实发工资 */ |
| | | function updateFieldValue(record: HrmSalaryCalculationApi.SalaryCalculation, field: keyof HrmSalaryCalculationApi.SalaryCalculation, value: number) { |
| | | (record as any)[field] = value; |
| | | record.actualSalary = calculateActualSalary(record); |
| | | /** 读取行字段值 */ |
| | | function fieldValue( |
| | | record: HrmSalaryCalculationApi.SalaryCalculation, |
| | | field: string, |
| | | ): number | string | undefined { |
| | | return (record as unknown as Record<string, number | string | undefined>)[ |
| | | field |
| | | ]; |
| | | } |
| | | |
| | | /** 金额展示:保留两位小数 */ |
| | | function formatAmount(value?: number | string) { |
| | | if (value === null || value === undefined || value === '' || Number.isNaN(Number(value))) { |
| | | return '-'; |
| | | } |
| | | return Number(value).toFixed(2); |
| | | } |
| | | |
| | | /** 求和(空值按 0 计) */ |
| | | function sumFields( |
| | | record: HrmSalaryCalculationApi.SalaryCalculation, |
| | | fields: readonly string[], |
| | | ) { |
| | | return fields.reduce( |
| | | (total, field) => |
| | | total + Number((record as Record<string, unknown>)[field] ?? 0), |
| | | 0, |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 按九泓工资表口径重算: |
| | | * 应付工资 = 标准出勤工资 + 加班工资 + 法定假工资 + 安全环保奖 + 浮动绩效工资 |
| | | * 加项合计 = 工龄工资 + 夜班补助 + 学历补贴 + 体检 + 优秀员工奖 + 其他补助 + 部门绩效 |
| | | * 扣款合计 = 漏打卡 + 迟到早退 + 其他扣款 |
| | | * 税后工资 = 应付工资 + 加项合计 - 扣款合计 - 社保 - 公积金 - 个税 |
| | | */ |
| | | function recalculateRecord( |
| | | record: HrmSalaryCalculationApi.SalaryCalculation, |
| | | ) { |
| | | record.payableSalary = |
| | | Number(record.standardAttendancePay ?? 0) + |
| | | Number(record.overtimePay ?? 0) + |
| | | Number(record.legalHolidayPay ?? 0) + |
| | | Number(record.safetyAward ?? 0) + |
| | | Number(record.floatingPerformancePay ?? 0); |
| | | record.additionTotal = sumFields(record, PREVIEW_ADDITION_FIELDS); |
| | | record.deductionTotal = sumFields(record, PREVIEW_DEDUCTION_FIELDS); |
| | | record.actualSalary = |
| | | record.payableSalary + |
| | | record.additionTotal - |
| | | record.deductionTotal - |
| | | Number(record.socialSecurityDeduction ?? 0) - |
| | | Number(record.housingFundDeduction ?? 0) - |
| | | Number(record.taxDeduction ?? 0); |
| | | } |
| | | |
| | | /** 更新行数据并重算合计 */ |
| | | function updateFieldValue( |
| | | record: HrmSalaryCalculationApi.SalaryCalculation, |
| | | field: string, |
| | | value: number, |
| | | ) { |
| | | (record as Record<string, unknown>)[field] = value; |
| | | recalculateRecord(record); |
| | | } |
| | | |
| | | /** 提交薪酬核算(手动调整后保存) */ |
| | |
| | | <Modal |
| | | v-model:open="calculateVisible" |
| | | title="薪酬核算" |
| | | width="1200px" |
| | | width="95%" |
| | | :footer="null" |
| | | :maskClosable="false" |
| | | > |
| | | <Alert |
| | | message="核算流程:选好核算周期与部门后点「预览数据」→ 核对/调整各项金额 → 点「保存核算」落库" |
| | | message="核算流程:选好核算周期与部门后点「预览数据」→ 核对自动计算结果,可手工调整浮动绩效、加项与扣款 → 点「保存核算」落库。标准工资/岗位工资/加班工资(标准)/安全环保奖(标准)/绩效(标准) 在员工档案中维护。" |
| | | type="info" |
| | | show-icon |
| | | style="margin-bottom: 16px" |
| | |
| | | :columns="usePreviewColumns()" |
| | | :data-source="previewData" |
| | | :pagination="{ pageSize: 20, showSizeChanger: true, showTotal: (t) => `共 ${t} 人` }" |
| | | :scroll="{ x: 1800, y: 400 }" |
| | | :scroll="{ x: 4000, y: 420 }" |
| | | size="small" |
| | | row-key="userId" |
| | | bordered |
| | | class="mb-4" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.dataIndex === 'baseSalary'"> |
| | | <template v-if="isEditableField(String(column.dataIndex))"> |
| | | <InputNumber |
| | | v-model:value="record.baseSalary" |
| | | :value="fieldValue(record, String(column.dataIndex)) as number" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'baseSalary', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'performanceSalary'"> |
| | | <InputNumber |
| | | v-model:value="record.performanceSalary" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'performanceSalary', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'overtimePay'"> |
| | | <InputNumber |
| | | v-model:value="record.overtimePay" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'overtimePay', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'mealAllowance'"> |
| | | <InputNumber |
| | | v-model:value="record.mealAllowance" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 80px" |
| | | @change="(val: number) => updateFieldValue(record, 'mealAllowance', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'transportAllowance'"> |
| | | <InputNumber |
| | | v-model:value="record.transportAllowance" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 80px" |
| | | @change="(val: number) => updateFieldValue(record, 'transportAllowance', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'otherAllowance'"> |
| | | <InputNumber |
| | | v-model:value="record.otherAllowance" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 80px" |
| | | @change="(val: number) => updateFieldValue(record, 'otherAllowance', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'socialSecurityDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.socialSecurityDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'socialSecurityDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'housingFundDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.housingFundDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'housingFundDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'taxDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.taxDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'taxDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'otherDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.otherDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 80px" |
| | | @change="(val: number) => updateFieldValue(record, 'otherDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'leaveDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.leaveDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'leaveDeduction', val)" |
| | | @change=" |
| | | (val: number | string | null) => |
| | | updateFieldValue( |
| | | record, |
| | | String(column.dataIndex), |
| | | Number(val ?? 0), |
| | | ) |
| | | " |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'actualSalary'"> |
| | | <span class="font-bold text-green-600">{{ record.actualSalary?.toFixed(2) }}</span> |
| | | <span class="font-bold text-green-600">{{ |
| | | formatAmount(record.actualSalary) |
| | | }}</span> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'workDays'"> |
| | | <InputNumber |
| | | v-model:value="record.workDays" |
| | | :min="0" |
| | | :max="31" |
| | | :precision="0" |
| | | size="small" |
| | | style="width: 70px" |
| | | /> |
| | | <template v-else-if="PLAIN_FIELDS.includes(String(column.dataIndex))"> |
| | | {{ fieldValue(record, String(column.dataIndex)) ?? '-' }} |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'overtimeHours'"> |
| | | <InputNumber |
| | | v-model:value="record.overtimeHours" |
| | | :min="0" |
| | | :precision="1" |
| | | size="small" |
| | | style="width: 80px" |
| | | /> |
| | | <template v-else> |
| | | {{ formatAmount(fieldValue(record, String(column.dataIndex))) }} |
| | | </template> |
| | | </template> |
| | | </Table> |