Commit 6996feeb authored by ‘老张’'s avatar ‘老张’

考勤管理中请假申请和请假审批(审批通过更新员工年假)以及加班列表分页问题的解决和加班申请更新员工年假

parent fab3c56d
......@@ -10,6 +10,7 @@
placeholder="请选择姓名"
class="custom-input"
@focus="loadUsernames"
@change="handleUserNameChange"
>
<el-option
v-for="user in userOptions"
......@@ -230,6 +231,20 @@ const loadUsernames = () => {
}
};
// 处理姓名选择变化
const handleUserNameChange = () => {
const selectedUser = userOptions.value.find(user => user.label === form.uname);
if (selectedUser) {
const selectedUserId = selectedUser.value;
// 把选中员工的id赋值给form.userId
form.userId = selectedUserId;
getBalanceInfo(selectedUserId).then(() => {
uploadPromptMessage.value = `已获取 ${form.uname} 的余额信息`;
}).catch(() => {
uploadPromptMessage.value = `获取 ${form.uname} 的余额信息失败`;
});
}
};
// 存储后端返回的项目负责人列表
const projectManagers = ref([])
......@@ -301,7 +316,7 @@ const handleLeaveTypeChange = (value) => {
}
// 如果选择倒休(假设 leaveType 值为 2 表示倒休)
else if (value == 2) {
getBalanceInfo().then(() => {
getBalanceInfo(form.userId).then(() => {
uploadPromptMessage.value = `剩余可抵消加班时长 ${form.overtimeHoursBalance} 小时`;
}).catch(() => {
uploadPromptMessage.value = '获取加班时长失败';
......@@ -309,7 +324,7 @@ const handleLeaveTypeChange = (value) => {
}
// 如果选择年假(leaveType 值为 4 表示年假)
else if (value == 4) {
getBalanceInfo().then(() => {
getBalanceInfo(form.userId).then(() => {
uploadPromptMessage.value = `剩余可用年假 ${form.annualLeaveBalance} 天`;
}).catch(() => {
uploadPromptMessage.value = '获取年假余额失败';
......@@ -384,7 +399,8 @@ const handleTimeChange = () => {
// 如果当前请假类型需要进行余额抵扣(例如病假、倒休、年假、事假等)
if (["0", "2", "4", "7"].includes(form.leaveType)) {
// 调用余额和抵扣逻辑
getBalanceInfo().then(() => {
if(form.userId){
getBalanceInfo(form.userId).then(() => {
const success = offsetLeave();
uploadPromptMessage.value = success
? `已抵消:${form.deductionOvertimeHours}`
......@@ -393,6 +409,7 @@ const handleTimeChange = () => {
uploadPromptMessage.value = '获取余额失败';
});
}
}
};
......@@ -465,31 +482,23 @@ const calculateLeaveTime = (start, end, holidays) => {
return { leaveDays, leaveHours };
};
//判断userId 是否为空
function getBalanceInfo() {
// 如果还没有 userId,就先调用 userStore.getInfo()
if (!userStore.userId) {
return userStore.getInfo().then(userInfo => {
userStore.userId = userInfo.user.userId;
return callBalanceAPI(userStore.userId);
});
} else {
return callBalanceAPI(userStore.userId);
}
function getBalanceInfo(userId) {
return fetchBalanceInfo(userId);
}
// 通过userId 调用 API,查询个人加班时长和年假天数
function callBalanceAPI(userId) {
function fetchBalanceInfo(userId) {
return getDeductionOvertimeHoursByUserId(userId)
.then(response => {
console.log('接口返回:', response);
// 假设返回结构为: { "overtimeHoursBalance": 17.0, "annualLeaveBalance": 10.0 }
form.overtimeHoursBalance = response.overtimeHoursBalance;
form.annualLeaveBalance = response.annualLeaveBalance;
})
.catch(error => {
console.error('获取加班/年假余额失败', error);
throw error; // 抛出错误,以便在调用处捕获
});
.then(response => {
console.log('接口返回:', response);
// 假设返回结构为: { "overtimeHoursBalance": 17.0, "annualLeaveBalance": 10.0 }
form.overtimeHoursBalance = response.overtimeHoursBalance;
form.annualLeaveBalance = response.annualLeaveBalance;
})
.catch(error => {
console.error('获取加班/年假余额失败', error);
throw error; // 抛出错误,以便在调用处捕获
});
}
/**
......@@ -511,17 +520,23 @@ function offsetLeave() {
let remainingGap = 0; // 不足部分(小时)
if (totalAvailableHours >= requiredHours) {
if (availableOvertime >= requiredHours) {
overtimeUsed = requiredHours;
if (form.leaveType === '2') { // 如果是倒休
overtimeUsed = requiredHours <= availableOvertime? requiredHours : availableOvertime;
annualLeaveUsedHours = 0;
} else {
overtimeUsed = availableOvertime;
annualLeaveUsedHours = requiredHours - availableOvertime;
} else if (form.leaveType === '4') { // 如果是年假
const requiredDays = requiredHours / 8;
annualLeaveUsedHours = requiredDays <= availableAnnualLeave? requiredHours : availableAnnualLeaveInHours;
overtimeUsed = 0;
}
remainingGap = 0;
} else {
overtimeUsed = availableOvertime;
annualLeaveUsedHours = availableAnnualLeaveInHours;
if (form.leaveType === '2') { // 如果是倒休
overtimeUsed = availableOvertime;
annualLeaveUsedHours = 0;
} else if (form.leaveType === '4') { // 如果是年假
annualLeaveUsedHours = availableAnnualLeaveInHours;
overtimeUsed = 0;
}
remainingGap = requiredHours - totalAvailableHours;
}
......@@ -539,48 +554,22 @@ function offsetLeave() {
}
/** 保存按钮操作 **/
const handleSubmit = () => {
formRef.value.validate((valid) => {
if (valid) {
// 如果 secondApproverDisabled 为 true,说明请假天数小于等于 3 天,将 secondApprover 清空
if (secondApproverDisabled.value) {
form.secondApprover = null;
}
// 先提交请假申请(存储到 leave_application 表)
addApplication(form)
.then(response => {
// 提交成功后,根据 leaveType 判断是否需要更新余额
const skipTypes = ['1', '5', '6', '3', '8', '9']; // leaveType 为这些值时跳过余额更新
if (skipTypes.includes(String(form.leaveType))) {
ElMessage.success('保存成功');
router.push({ path: '/attendance/leave' });
} else {
// 构造更新余额数据
const updateData = {
employeeId: form.userId, // 用登录时的 userId 作为 employeeId
overtimeHoursBalance: form.overtimeHoursBalance,
annualLeaveBalance: form.annualLeaveBalance
};
updateOvertimeHoursBalance(updateData)
.then(res => {
console.log('更新加班时长成功', res);
ElMessage.success('保存成功');
router.push({ path: '/attendance/leave' });
})
.catch(err => {
console.error('更新加班时长失败', err);
// 如果余额更新失败,但申请已提交,也提示成功,并跳转
ElMessage.success('保存成功,但余额更新失败');
router.push({ path: '/attendance/leave' });
});
}
})
.catch(error => {
ElMessage.error('保存失败');
console.error(error);
});
addApplication(form)
.then(response => {
ElMessage.success('保存成功');
router.push({ path: '/attendance/leave' });
})
.catch(error => {
ElMessage.error('保存失败');
console.error(error);
});
} else {
ElMessage.error('表单验证失败,请检查输入');
}
......
......@@ -118,10 +118,10 @@
</el-table-column>
<el-table-column label="操作" align="center" width="150px" height="56px">
<template #default="scope">
<el-button link type="primary" size="small" style="font-size: 15px" @click="handlePass(scope.row)">
<el-button link type="primary" size="small" style="font-size: 15px" @click="handlePass(scope.row)" :disabled="scope.row.approvalStatus!== '1'">
通过
</el-button>
<el-button link type="danger" size="small" style="font-size: 15px" @click="handleReject(scope.row)">
<el-button link type="danger" size="small" style="font-size: 15px" @click="handleReject(scope.row)" :disabled="scope.row.approvalStatus!== '1'">
驳回
</el-button>
</template>
......@@ -146,12 +146,14 @@
<script setup>
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
import { useRouter } from "vue-router";
import {ElMessage} from "element-plus";
import useUserStore from '@/store/modules/user';
import {
listApplication,
getShenpiList,
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus,
} from "../../../api/application/application";
import { listBalance, updateBalance } from "@/api/system/balance";
// ----- 状态及字典定义 -----
const userStore = useUserStore();
......@@ -194,6 +196,55 @@ const handleSelectionChange = (selection) => {
ids.value = selection.map(item => item.id);
};
// 检查是否满足请假抵消条件
const offsetLeave = (row) => {
const requiredHours = parseFloat(row.leavetimeHours) || 0; // 请假时长(小时)
let availableOvertime = parseFloat(row.overtimeHoursBalance) || 0; // 加班余额(小时)
let availableAnnualLeave = parseFloat(row.annualLeaveBalance) || 0; // 年假余额(天)
// 将年假转换为小时(1天 = 8小时)
const availableAnnualLeaveInHours = availableAnnualLeave * 8;
// 总可用小时数
const totalAvailableHours = availableOvertime + availableAnnualLeaveInHours;
let overtimeUsed = 0;
let annualLeaveUsedHours = 0; // 单位:小时
let remainingGap = 0; // 不足部分(小时)
if (totalAvailableHours >= requiredHours) {
if (row.leaveType === '2') { // 如果是倒休
overtimeUsed = requiredHours <= availableOvertime? requiredHours : availableOvertime;
annualLeaveUsedHours = 0;
} else if (row.leaveType === '4') { // 如果是年假
const requiredDays = requiredHours / 8;
annualLeaveUsedHours = requiredDays <= availableAnnualLeave? requiredHours : availableAnnualLeaveInHours;
overtimeUsed = 0;
}
remainingGap = 0;
} else {
if (row.leaveType === '2') { // 如果是倒休
overtimeUsed = availableOvertime;
annualLeaveUsedHours = 0;
} else if (row.leaveType === '4') { // 如果是年假
annualLeaveUsedHours = availableAnnualLeaveInHours;
overtimeUsed = 0;
}
remainingGap = requiredHours - totalAvailableHours;
}
// 更新余额:加班余额依旧以小时计,年假余额以天计
row.overtimeHoursBalance = availableOvertime - overtimeUsed;
row.annualLeaveBalance = availableAnnualLeave - (annualLeaveUsedHours / 8);
// 这里我们直接将总抵扣小时数(加班抵扣小时数 + 年假抵扣转换为小时)赋值给 deductionOvertimeHours
const numericDeduction = overtimeUsed + annualLeaveUsedHours;
console.log('numericDeduction (offset in hours):', numericDeduction);
row.deductionOvertimeHours = numericDeduction;
// 返回余额是否足够(true 表示足够抵扣)
return remainingGap === 0;
};
// ----- 核心逻辑函数 -----
/**
......@@ -257,70 +308,166 @@ const handleView = (row) => {
* 若请假天数 < 3,则 nodeId=111, approvalStatus=2
* 若请假天数 >= 3,则根据角色判断
*/
const handlePass = (row) => {
if (row.leaveDaysSubtotal < 3) {
const params = {
ids: [row.id],
nodeId: 111,
approvalStatus: 2
};
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params)
.then(() => {
console.log("更新成功");
getList();
})
.catch((err) => console.error("更新失败:", err));
} else {
userStore.getInfo()
.then(userInfo => {
const role = userInfo.roles[0];
let nodeIdVal, approvalStatusVal;
if (role === 'project-manager') {
nodeIdVal = 112;
approvalStatusVal = 1;
} else if (role === 'general-manager') {
nodeIdVal = 112;
approvalStatusVal = 2;
} else {
return Promise.reject("角色不匹配,无法更新");
}
const params = {
ids: [row.id],
nodeId: nodeIdVal,
approvalStatus: approvalStatusVal
};
return batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params);
})
.then(() => {
console.log("更新成功");
getList();
})
.catch(err => console.error("更新失败:", err));
const handlePass = async (row) => {
try {
let params;
if (row.leaveDaysSubtotal < 3) {
params = {
ids: [row.id],
nodeId: 111,
approvalStatus: 2
};
} else {
const userInfo = await userStore.getInfo();
const role = userInfo.roles[0];
let nodeIdVal, approvalStatusVal;
if (role === 'project-manager') {
nodeIdVal = 112;
approvalStatusVal = 1;
} else if (role === 'general-manager') {
nodeIdVal = 112;
approvalStatusVal = 2;
} else {
throw new Error("角色不匹配,无法更新");
}
params = {
ids: [row.id],
nodeId: nodeIdVal,
approvalStatus: approvalStatusVal
};
}
await batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params);
console.log("更新成功");
// 根据请假类型扣除余额
const employeeId = row.userId;
const balanceRes = await listBalance({ employeeId });
const balanceData = balanceRes.rows[0];
let updatedBalanceData;
if (row.leaveType === '2') { // 倒休
const usedOvertime = parseFloat(row.leavetimeHours) || 0;
if (usedOvertime > balanceData.overtimeHoursBalance) {
throw new Error('加班时长余额不足');
}
const newOvertimeBalance = (balanceData.overtimeHoursBalance - usedOvertime).toFixed(1);
updatedBalanceData = {
...balanceData,
overtimeHoursBalance: newOvertimeBalance
};
} else if (row.leaveType === '4') { // 年假
const usedAnnualLeave = (parseFloat(row.leavetimeHours) || 0) / 8;
if (usedAnnualLeave > balanceData.annualLeaveBalance) {
throw new Error('年假余额不足');
}
const newAnnualLeaveBalance = (balanceData.annualLeaveBalance - usedAnnualLeave).toFixed(1);
updatedBalanceData = {
...balanceData,
annualLeaveBalance: newAnnualLeaveBalance
};
}
// 更新员工余额表
await updateBalance(updatedBalanceData);
console.log('更新余额成功');
// 更新列表数据
const index = applicationList.value.findIndex(item => item.id === row.id);
if (index!== -1) {
applicationList.value[index] = { ...row };
}
// 重新查询列表
getList();
} catch (error) {
console.error('审批通过操作失败:', error);
}
};
/**
* 一键通过:对选中且请假天数 < 3 的记录设置 nodeId=111, approvalStatus=2
*/
const handleBatchPass = () => {
const rowsToPass = applicationList.value.filter(
(item) => ids.value.includes(item.id) && item.leaveDaysSubtotal < 3
);
if (!rowsToPass.length) {
console.warn("没有符合条件的记录");
const handleBatchPass = async () => {
if (ids.value.length === 0) {
console.log('请选择要审批通过的记录');
return;
}
const params = {
ids: rowsToPass.map((row) => row.id),
nodeId: 111,
approvalStatus: 2
};
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params)
.then(() => {
console.log("批量通过成功");
getList();
})
.catch(err => console.error("批量通过出错:", err));
try {
const selectedRows = applicationList.value.filter(item => ids.value.includes(item.id));
const nodeIdAndStatusPromises = selectedRows.map(async (row) => {
let nodeIdVal, approvalStatusVal;
if (row.leaveDaysSubtotal < 3) {
nodeIdVal = 111;
approvalStatusVal = 2;
} else {
const userInfo = await userStore.getInfo();
const role = userInfo.roles[0];
if (role === 'project-manager') {
nodeIdVal = 112;
approvalStatusVal = 1;
} else if (role === 'general-manager') {
nodeIdVal = 112;
approvalStatusVal = 2;
} else {
throw new Error("角色不匹配,无法更新");
}
}
return {
id: row.id,
nodeId: nodeIdVal,
approvalStatus: approvalStatusVal
};
});
const nodeIdAndStatusResults = await Promise.all(nodeIdAndStatusPromises);
const batchParams = {
ids: ids.value,
nodeIds: nodeIdAndStatusResults.map(item => item.nodeId),
approvalStatuses: nodeIdAndStatusResults.map(item => item.approvalStatus)
};
await batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(batchParams);
console.log("批量更新成功");
// 批量更新余额
const balanceUpdatePromises = selectedRows.map(async (row) => {
const employeeId = row.userId;
const balanceRes = await listBalance({ employeeId });
const balanceData = balanceRes.rows[0];
let updatedBalanceData;
if (row.leaveType === '2') { // 倒休
const usedOvertime = parseFloat(row.leavetimeHours) || 0;
if (usedOvertime > balanceData.overtimeHoursBalance) {
throw new Error(`员工 ${row.uname} 的加班时长余额不足`);
}
const newOvertimeBalance = (balanceData.overtimeHoursBalance - usedOvertime).toFixed(1);
updatedBalanceData = {
...balanceData,
overtimeHoursBalance: newOvertimeBalance
};
} else if (row.leaveType === '4') { // 年假
const usedAnnualLeave = (parseFloat(row.leavetimeHours) || 0) / 8;
if (usedAnnualLeave > balanceData.annualLeaveBalance) {
throw new Error(`员工 ${row.uname} 的年假余额不足`);
}
const newAnnualLeaveBalance = (balanceData.annualLeaveBalance - usedAnnualLeave).toFixed(1);
updatedBalanceData = {
...balanceData,
annualLeaveBalance: newAnnualLeaveBalance
};
}
return updateBalance(updatedBalanceData);
});
await Promise.all(balanceUpdatePromises);
console.log('批量更新余额成功');
// 重新查询列表
getList();
} catch (error) {
console.error('批量审批通过操作失败:', error);
}
};
/**
......@@ -334,10 +481,10 @@ const handleReject = (row) => {
};
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params)
.then(() => {
console.log("驳回成功");
ElMessage.success("驳回成功");
getList();
})
.catch(err => console.error("驳回失败:", err));
.catch(err => ElMessage.error("驳回失败:", err));
};
/**
......@@ -348,7 +495,7 @@ const handleBatchReject = () => {
(item) => ids.value.includes(item.id)
);
if (!rowsToReject.length) {
console.warn("未选中任何记录");
ElMessage.warn("未选中任何记录");
return;
}
const params = {
......@@ -358,10 +505,10 @@ const handleBatchReject = () => {
};
batchUpdateLeaveApplicationByNodeIdAndApprovalStatus(params)
.then(() => {
console.log("一键驳回成功");
ElMessage.success("一键驳回成功");
getList();
})
.catch(err => console.error("一键驳回出错:", err));
.catch(err => ElMessage.error("一键驳回出错:", err));
};
// ----- 生命周期钩子 -----
......
......@@ -516,7 +516,7 @@ const handleCancel = () => {
}
::v-deep(.el-input__inner) {
ont-family: PingFangSC-Medium;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 16px;
color: #0062FF;
......
......@@ -176,6 +176,7 @@ import {
getOvertimeApplication
} from '../../../api/application/overtimeApplication.js'
import { listAllUser } from '../../../api/onboardmanage/onboardmanage.js'
import { getBalance, addBalance, updateBalance } from '../../../api/system/balance.js'
// 创建 formRef 引用
const formRef = ref(null)
const router = useRouter() // 创建路由器实例
......@@ -212,6 +213,7 @@ watch(() => form.employeeId, (newValue) => {
const selectedOption = userOptions.value.find(option => option.value === newValue);
if (selectedOption) {
form.uname = selectedOption.label;
console.log('id:', form.employeeId); // 调试日志
} else {
form.uname = undefined;
}
......@@ -392,21 +394,49 @@ const handleCancel = () => {
const handleSave = () => {
formRef.value.validate(valid => {
if (valid) {
// 保存加班申请
addOvertimeApplication(form)
.then(response => {
ElMessage.success('保存成功')
// 跳转到 application/index.vue 页面
router.push({ path: '/attendance/overtime' })
.then(response => {
ElMessage.success('保存成功');
const employeeId = form.employeeId;
// 查询员工余额
return getBalance(employeeId);
})
.catch(error => {
ElMessage.error('保存失败')
console.error(error)
.then(balanceResponse => {
console.log('Balance Response:', balanceResponse.data); // 调试日志
if (balanceResponse.data) {
// 员工存在,更新加班时长余额
const currentOvertimeHoursBalance = parseFloat(balanceResponse.data.overtimeHoursBalance);
const overtimeHours = parseFloat(form.overtimeHours);
const newOvertimeHoursBalance = currentOvertimeHoursBalance + overtimeHours;
const updateData = {
...balanceResponse.data,
overtimeHoursBalance: newOvertimeHoursBalance
};
return updateBalance(updateData);
} else {
// 员工不存在,新增记录
const newData = {
employeeId: form.employeeId,
annualLeaveBalance: 0,
overtimeHoursBalance: parseFloat(form.overtimeHours)
};
return addBalance(newData);
}
})
.then(() => {
// 跳转到加班申请列表页面
router.push({ path: '/attendance/overtime' });
})
.catch(error => {
ElMessage.error('保存或更新余额失败');
console.error(error);
});
} else {
ElMessage.error('表单验证失败,请检查输入')
ElMessage.error('表单验证失败,请检查输入');
}
})
}
});
};
function getAllUserList() {
listAllUser().then(response => {
......
......@@ -517,7 +517,7 @@ getList()
/* 主体样式*/
.main {
width: 1622px;
height: 823px;
height: 880px;
background: #ffffff;
box-shadow: 0 2px 2px 0 #b3b3b380;
border-radius: 2px;
......
......@@ -82,12 +82,12 @@
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="唯一标识每条记录" align="center" prop="id" />
<el-table-column label="员工ID" align="center" prop="employeeId" />
<el-table-column label="年假余额" align="center" prop="annualLeaveBalance">
<el-table-column label="年假余额/天" align="center" prop="annualLeaveBalance">
<template #default="scope">
<span>{{ scope.row.annualLeaveBalance }}</span>
</template>
</el-table-column>
<el-table-column label="加班时长余额" align="center" prop="overtimeHoursBalance">
<el-table-column label="加班时长余额/小时" align="center" prop="overtimeHoursBalance">
<template #default="scope">
<span>{{ scope.row.overtimeHoursBalance }}</span>
</template>
......@@ -144,27 +144,11 @@
v-model="form.overtimeHoursBalance"
:min="0"
:precision="1"
:step="0.5"
:max="999.5"
:step="0.1"
:max="999.9"
placeholder="请输入加班时长余额"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createdAt">
<el-date-picker clearable
v-model="form.createdAt"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择创建时间">
</el-date-picker>
</el-form-item>
<el-form-item label="更新时间" prop="updatedAt">
<el-date-picker clearable
v-model="form.updatedAt"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择更新时间">
</el-date-picker>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment