| | |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.basicSalary = parseNum(row.basicSalary)" /> |
| | | @input="handleSalaryInput(row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计件工资" |
| | |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.otherIncome = parseNum(row.otherIncome)" /> |
| | | @input="handleSalaryInput(row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="社保个人" |
| | |
| | | size="small" |
| | | disabled |
| | | @input="row.otherDeduct = parseNum(row.otherDeduct)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="社保补缴" |
| | | minWidth="120"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.socialSupplementAmount" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="handleSalaryInput(row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="工资个税" |
| | |
| | | staffSalaryMainAdd, |
| | | staffSalaryMainUpdate, |
| | | staffSalaryMainCalculateSalary, |
| | | staffSalaryMainCalculateByEmployeeId, |
| | | } from "@/api/personnelManagement/staffSalaryMain.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | |
| | |
| | | const selectedEmployees = ref([]); |
| | | const bankOptions = ref([]); |
| | | const userList = ref([]); |
| | | const recalcTimers = new Map(); |
| | | const recalcVersions = new Map(); |
| | | const taxTableData = ref([ |
| | | { level: 1, range: "不超过36000元", rate: 3, quickDeduction: 0 }, |
| | | { level: 2, range: "超过36000-144000元", rate: 10, quickDeduction: 2520 }, |
| | |
| | | const n = Number(v); |
| | | return isNaN(n) ? 0 : n; |
| | | } |
| | | |
| | | function parseNullableNum(v) { |
| | | if (v === "" || v == null) return null; |
| | | const n = Number(v); |
| | | return isNaN(n) ? null : n; |
| | | } |
| | | |
| | | const createEmployeeRow = () => ({ |
| | | staffOnJobId: undefined, |
| | | id: undefined, |
| | | staffName: "", |
| | | postName: "", |
| | | deptName: "", |
| | | basicSalary: 0, |
| | | pieceSalary: 0, |
| | | hourlySalary: 0, |
| | | otherIncome: 0, |
| | | socialPersonal: 0, |
| | | fundPersonal: 0, |
| | | otherDeduct: 0, |
| | | socialSupplementAmount: null, |
| | | salaryTax: 0, |
| | | grossSalary: 0, |
| | | deductSalary: 0, |
| | | netSalary: 0, |
| | | remark: "", |
| | | }); |
| | | |
| | | const normalizeEmployeeRow = (source, fallback = {}) => ({ |
| | | ...createEmployeeRow(), |
| | | ...fallback, |
| | | ...source, |
| | | staffOnJobId: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id, |
| | | id: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id, |
| | | staffName: source?.staffName ?? fallback.staffName ?? "", |
| | | postName: source?.postName ?? fallback.postName ?? "", |
| | | deptName: source?.deptName ?? fallback.deptName ?? "", |
| | | basicSalary: parseNum(source?.basicSalary ?? fallback.basicSalary), |
| | | pieceSalary: parseNum(source?.pieceSalary ?? fallback.pieceSalary), |
| | | hourlySalary: parseNum(source?.hourlySalary ?? fallback.hourlySalary), |
| | | otherIncome: parseNum(source?.otherIncome ?? fallback.otherIncome), |
| | | socialPersonal: parseNum(source?.socialPersonal ?? fallback.socialPersonal), |
| | | fundPersonal: parseNum(source?.fundPersonal ?? fallback.fundPersonal), |
| | | otherDeduct: parseNum(source?.otherDeduct ?? fallback.otherDeduct), |
| | | socialSupplementAmount: |
| | | source?.socialSupplementAmount ?? fallback.socialSupplementAmount ?? null, |
| | | salaryTax: parseNum(source?.salaryTax ?? fallback.salaryTax), |
| | | grossSalary: parseNum(source?.grossSalary ?? fallback.grossSalary), |
| | | deductSalary: parseNum(source?.deductSalary ?? fallback.deductSalary), |
| | | netSalary: parseNum(source?.netSalary ?? fallback.netSalary), |
| | | remark: source?.remark ?? fallback.remark ?? "", |
| | | }); |
| | | |
| | | const applyBackendRow = (targetRow, sourceRow) => { |
| | | Object.assign(targetRow, normalizeEmployeeRow(sourceRow, targetRow)); |
| | | }; |
| | | |
| | | const clearRowRecalcState = row => { |
| | | const key = String(row?.staffOnJobId ?? row?.id ?? ""); |
| | | if (!key) return; |
| | | clearTimeout(recalcTimers.get(key)); |
| | | recalcTimers.delete(key); |
| | | recalcVersions.delete(key); |
| | | }; |
| | | |
| | | const resetRecalcState = () => { |
| | | recalcTimers.forEach(timer => clearTimeout(timer)); |
| | | recalcTimers.clear(); |
| | | recalcVersions.clear(); |
| | | }; |
| | | |
| | | const requestEmployeeRecalculate = row => { |
| | | const key = String(row?.staffOnJobId ?? row?.id ?? ""); |
| | | if (!key || !form.value.salaryMonth) return; |
| | | const version = (recalcVersions.get(key) || 0) + 1; |
| | | recalcVersions.set(key, version); |
| | | clearTimeout(recalcTimers.get(key)); |
| | | const payload = { |
| | | staffOnJobId: row.staffOnJobId ?? row.id, |
| | | salaryMonth: form.value.salaryMonth, |
| | | staffName: row.staffName ?? "", |
| | | postName: row.postName ?? "", |
| | | deptName: row.deptName ?? "", |
| | | basicSalary: parseNum(row.basicSalary), |
| | | pieceSalary: parseNum(row.pieceSalary), |
| | | hourlySalary: parseNum(row.hourlySalary), |
| | | otherIncome: parseNum(row.otherIncome), |
| | | socialPersonal: parseNum(row.socialPersonal), |
| | | fundPersonal: parseNum(row.fundPersonal), |
| | | otherDeduct: parseNum(row.otherDeduct), |
| | | socialSupplementAmount: parseNullableNum(row.socialSupplementAmount), |
| | | remark: row.remark ?? "", |
| | | }; |
| | | recalcTimers.set( |
| | | key, |
| | | setTimeout(() => { |
| | | staffSalaryMainCalculateByEmployeeId(payload).then(res => { |
| | | if (recalcVersions.get(key) !== version) return; |
| | | applyBackendRow(row, res?.data || {}); |
| | | }); |
| | | }, 250) |
| | | ); |
| | | }; |
| | | |
| | | const handleSalaryInput = row => { |
| | | row.basicSalary = parseNum(row.basicSalary); |
| | | row.otherIncome = parseNum(row.otherIncome); |
| | | requestEmployeeRecalculate(row); |
| | | }; |
| | | |
| | | // 基础资料表单 |
| | | const data = reactive({ |
| | |
| | | |
| | | const openDialog = (type, row) => { |
| | | nextTick(() => { |
| | | resetRecalcState(); |
| | | loadDeptOptions(); |
| | | loadBankOptions(); |
| | | loadUserList(); |
| | |
| | | |
| | | // 如果有员工明细数据,直接反显 |
| | | if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) { |
| | | employeeList.value = row.staffSalaryDetailList.map(e => ({ |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | id: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName ?? "", |
| | | postName: e.postName ?? "", |
| | | deptName: e.deptName ?? "", |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })); |
| | | employeeList.value = row.staffSalaryDetailList.map(e => normalizeEmployeeRow(e)); |
| | | } |
| | | } |
| | | }); |
| | |
| | | const id = node.staffId ?? node.id; |
| | | if (existIds.has(id)) return; |
| | | existIds.add(id); |
| | | employeeList.value.push({ |
| | | const newRow = normalizeEmployeeRow({ |
| | | staffOnJobId: id, |
| | | id, |
| | | staffName: node.label, |
| | | postName: node.postName ?? node.post ?? "", |
| | | deptName: node.deptName ?? "", |
| | | basicSalary: 0, |
| | | pieceSalary: 0, |
| | | hourlySalary: 0, |
| | | otherIncome: 0, |
| | | socialPersonal: 0, |
| | | fundPersonal: 0, |
| | | otherDeduct: 0, |
| | | salaryTax: 0, |
| | | grossSalary: 0, |
| | | deductSalary: 0, |
| | | netSalary: 0, |
| | | remark: "", |
| | | socialSupplementAmount: null, |
| | | }); |
| | | employeeList.value.push(newRow); |
| | | requestEmployeeRecalculate(newRow); |
| | | }); |
| | | addPersonVisible.value = false; |
| | | }; |
| | | |
| | | const removeEmployee = row => { |
| | | clearRowRecalcState(row); |
| | | employeeList.value = employeeList.value.filter( |
| | | e => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id) |
| | | ); |
| | |
| | | return; |
| | | } |
| | | const ids = new Set(selectedEmployees.value.map(e => e.staffOnJobId || e.id)); |
| | | selectedEmployees.value.forEach(item => clearRowRecalcState(item)); |
| | | employeeList.value = employeeList.value.filter( |
| | | e => !ids.has(e.staffOnJobId || e.id) |
| | | ); |
| | |
| | | proxy.$modal.msgWarning("未计算到工资数据"); |
| | | return; |
| | | } |
| | | employeeList.value = list.map(e => ({ |
| | | ...e, |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName, |
| | | postName: e.postName, |
| | | deptName: e.deptName, |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })); |
| | | resetRecalcState(); |
| | | employeeList.value = list.map(e => normalizeEmployeeRow(e)); |
| | | proxy.$modal.msgSuccess("生成成功"); |
| | | }); |
| | | }; |
| | |
| | | proxy.$modal |
| | | .confirm("确定清空当前员工列表吗?") |
| | | .then(() => { |
| | | resetRecalcState(); |
| | | employeeList.value = []; |
| | | }) |
| | | .catch(() => {}); |
| | |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | socialSupplementAmount: parseNum(e.socialSupplementAmount), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | resetRecalcState(); |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |