工时导出模板

parent ab867f5b
import { Request, Response } from 'express';
import path from 'path';
import ExcelJS from 'exceljs';
import pool from '../config/database';
import { AuthRequest } from '../middleware/auth';
......@@ -188,7 +189,7 @@ export const getWorkHourStats = async (req: AuthRequest, res: Response): Promise
}
};
// 导出工时到 Excel
// 导出工时到 Excel(基于模板)
export const exportWorkhours = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const { employee_id, project_id, start_date, end_date } = req.query;
......@@ -209,50 +210,70 @@ export const exportWorkhours = async (req: AuthRequest, res: Response): Promise<
if (start_date) { where += ' AND w.work_date >= ?'; params.push(start_date); }
if (end_date) { where += ' AND w.work_date <= ?'; params.push(end_date); }
// 多表联合查询:工时表 + 员工表 + 项目表 + 加班表 + 日志表(审核人)
const [rows] = await pool.execute(
`SELECT w.*, e.name as employee_name, p.project_name
`SELECT w.id as workhour_id, w.work_date, w.hours, w.description,
e.id as employee_id, e.name as employee_name, e.role as employee_role, e.department,
p.id as project_id, p.project_name, p.project_no,
pm.name as manager_name,
o.id as overtime_id, o.hours as overtime_hours, o.description as overtime_description,
(SELECT COUNT(DISTINCT wh2.work_date)
FROM workhours wh2
WHERE wh2.employee_id = w.employee_id
AND wh2.project_id = w.project_id
${start_date ? ' AND wh2.work_date >= ?' : ''}
${end_date ? ' AND wh2.work_date <= ?' : ''}
) as work_day_count,
(SELECT ol.operator_name
FROM operation_logs ol
WHERE ol.module = '加班管理'
AND ol.rx_action LIKE CONCAT('%', o.id, '%')
AND o.id IS NOT NULL
ORDER BY ol.created_at DESC
LIMIT 1
) as auditor1
FROM workhours w
LEFT JOIN employees e ON w.employee_id = e.id
LEFT JOIN projects p ON w.project_id = p.id
${where} ORDER BY w.work_date DESC`,
LEFT JOIN employees pm ON p.manager_id = pm.id
LEFT JOIN overtimes o ON w.employee_id = o.employee_id AND w.work_date = o.overtime_date AND o.status = '已批准'
${where} ORDER BY w.employee_id, w.work_date`,
params
) as any[];
// 生成 Excel
// 加载模板文件
const templatePath = path.join(__dirname, '../config/工时导出模板.xlsx');
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('工时记录');
// 表头
sheet.columns = [
{ header: '员工', key: 'employee_name', width: 14 },
{ header: '项目', key: 'project_name', width: 22 },
{ header: '工作日期', key: 'work_date', width: 14 },
{ header: '工时(h)', key: 'hours', width: 10 },
{ header: '工作描述', key: 'description', width: 40 },
{ header: '是否外出', key: 'is_outside', width: 10 },
];
// 样式:表头加粗居中
const headerRow = sheet.getRow(1);
headerRow.font = { bold: true };
headerRow.alignment = { horizontal: 'center', vertical: 'middle' };
headerRow.height = 22;
// 填充数据
for (const row of rows as any[]) {
sheet.addRow({
employee_name: row.employee_name,
project_name: row.project_name,
work_date: row.work_date ? new Date(row.work_date).toISOString().slice(0, 10) : '',
hours: row.hours,
description: row.description || '',
is_outside: row.is_outside ? '外出' : '在司',
});
await workbook.xlsx.readFile(templatePath);
const sheet = workbook.getWorksheet(1);
if (!sheet) {
res.status(500).json({ code: 500, message: '模板文件格式错误' });
return;
}
// 数据行对齐
for (let i = 2; i <= sheet.rowCount; i++) {
sheet.getRow(i).alignment = { vertical: 'middle' };
// 从第2行开始填充数据(第1行是表头)
for (const row of rows as any[]) {
const workDate = row.work_date ? new Date(row.work_date) : null;
sheet.addRow([
String(row.workhour_id), // 派工单编号
workDate ? workDate.getFullYear() : '', // 年
workDate ? workDate.getMonth() + 1 : '', // 月份
workDate ? workDate.getDate() : '', // 日
row.employee_role || '', // 职务
row.employee_name || '', // 员工姓名
row.department || '', // 员工所属部门
row.project_name || '', // 项目名称
row.project_no || '', // 项目编号
row.manager_name || '', // 项目经理
row.description || '', // 工作内容
row.overtime_description || '', // 加班内容
row.overtime_hours != null ? Number(row.overtime_hours) : '', // 员工加班
row.auditor1 || '', // 审核人1
'', // 审核人2(暂留空)
row.hours != null ? Number(row.hours) : '', // 实际工作量
row.overtime_hours != null ? Number(row.overtime_hours) : '', // 实际加班
row.work_day_count || '', // 实际工作天数
]);
}
// 设置响应头
......
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