| | |
| | | import { Page } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, InputNumber, Alert, Tag } from 'ant-design-vue'; |
| | | import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, Input, InputNumber, Alert, Tag } from 'ant-design-vue'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictLabel } from '#/packages/effects/hooks/src'; |
| | |
| | | previewSalaryCalculation, |
| | | revokeSalaryCalculation, |
| | | saveSalaryCalculation, |
| | | updateSalaryCalculation, |
| | | } from '#/api/hrm/salary/calculation'; |
| | | import { getDeptList } from '#/api/system/dept'; |
| | | import { handleTree } from '#/packages/utils/src'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema, usePreviewColumns } from './data'; |
| | | import FormModal from './modules/form.vue'; |
| | | |
| | | // 核算弹窗相关 |
| | | const formModalRef = ref(); |
| | | const saving = ref(false); |
| | | const calculateVisible = ref(false); |
| | | const calculatePeriod = ref<string>(); |
| | | const calculateDeptId = ref<number>(); |
| | |
| | | previewData.value = []; |
| | | } |
| | | |
| | | /** 确认薪酬核算 */ |
| | | /** 把行内编辑的文本值归一为数字后提交保存 */ |
| | | async function saveRowEdits(row: any) { |
| | | const payload: any = { ...row }; |
| | | EDITABLE_NUMBER_FIELDS.forEach((f) => { |
| | | payload[f] = toNumberOrZero(payload[f]); |
| | | }); |
| | | await updateSalaryCalculation(payload); |
| | | } |
| | | |
| | | /** 确认薪酬核算(方案A:确认前先保存当前行的修改,避免「改了没保存就确认」导致金额丢失) */ |
| | | async function handleConfirm(row: HrmSalaryCalculationApi.SalaryCalculation) { |
| | | if (row.status === 0) { |
| | | await saveRowEdits(row); |
| | | } |
| | | await confirmSalary([row.id!]); |
| | | message.success('确认成功'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 批量确认选中的记录 */ |
| | | /** 批量确认选中的记录(同样先保存勾选的待确认记录,再确认) */ |
| | | async function handleBatchConfirm() { |
| | | const records = gridApi.grid.getCheckboxRecords() as HrmSalaryCalculationApi.SalaryCalculation[]; |
| | | if (records.length === 0) { |
| | | message.warning('请选择要确认的记录'); |
| | | return; |
| | | } |
| | | const pending = records.filter((r) => r.status === 0); |
| | | for (const row of pending) { |
| | | await saveRowEdits(row); |
| | | } |
| | | const ids = records.map((r) => r.id!); |
| | | await confirmSalary(ids); |
| | |
| | | async function handleExport() { |
| | | const data = await exportSalaryCalculation(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '薪酬核算表.xls', source: data }); |
| | | } |
| | | |
| | | /** 列表可编辑的数值字段 */ |
| | | const EDITABLE_NUMBER_FIELDS = [ |
| | | 'baseSalary', |
| | | 'performanceSalary', |
| | | 'overtimePay', |
| | | 'mealAllowance', |
| | | 'transportAllowance', |
| | | 'otherAllowance', |
| | | 'socialSecurityDeduction', |
| | | 'housingFundDeduction', |
| | | 'taxDeduction', |
| | | 'otherDeduction', |
| | | 'leaveDeduction', |
| | | 'workDays', |
| | | 'overtimeHours', |
| | | ]; |
| | | |
| | | /** 文本输入转数字(空值按 0) */ |
| | | function toNumberOrZero(val: any) { |
| | | if (val === '' || val === null || val === undefined) { |
| | | return 0; |
| | | } |
| | | const num = Number(String(val).replace(/,/g, '')); |
| | | return Number.isNaN(num) ? 0 : num; |
| | | } |
| | | |
| | | /** 保存列表内联修改(仅待确认状态) */ |
| | | async function handleSaveEdits() { |
| | | const rows = (gridApi.grid.getData() as any[]).filter((r) => r.status === 0); |
| | | if (rows.length === 0) { |
| | | message.warning('当前页没有待确认的记录可修改'); |
| | | return; |
| | | } |
| | | saving.value = true; |
| | | try { |
| | | for (const row of rows) { |
| | | const payload: any = { ...row }; |
| | | EDITABLE_NUMBER_FIELDS.forEach((f) => { |
| | | payload[f] = toNumberOrZero(payload[f]); |
| | | }); |
| | | await updateSalaryCalculation(payload); |
| | | } |
| | | message.success(`已保存 ${rows.length} 条核算记录`); |
| | | handleRefresh(); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | </div> |
| | | </Modal> |
| | | |
| | | <FormModal ref="formModalRef" @success="handleRefresh" /> |
| | | <Grid table-title="薪酬核算列表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | onClick: handleBatchSave, |
| | | }, |
| | | { |
| | | label: '保存修改', |
| | | type: 'primary', |
| | | auth: ['hrm:salary-calculation:calculate'], |
| | | loading: saving, |
| | | onClick: handleSaveEdits, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #editableCell="{ row, column }"> |
| | | <Input |
| | | v-if="row.status === 0" |
| | | v-model:value="row[column.field]" |
| | | size="small" |
| | | :style="{ width: '100%' }" |
| | | /> |
| | | <span v-else>{{ row[column.field] ?? '' }}</span> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag :color="row.status === 0 ? 'warning' : row.status === 10 ? 'success' : 'processing'"> |
| | | {{ getDictLabel(DICT_TYPE.HRM_SALARY_STATUS, row.status) }} |
| | |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '修改', |
| | | type: 'link', |
| | | auth: ['hrm:salary-calculation:calculate'], |
| | | ifShow: row.status === 0, |
| | | onClick: () => formModalRef.value?.open(row), |
| | | }, |
| | | { |
| | | label: '确认', |
| | | type: 'link', |
| | | auth: ['hrm:salary-calculation:confirm'], |