feat: 工作日历/考勤管理重构/AI人力报告/工作台员工分布/筛选优化/导入导出增强
- 新增工作日历页面(月历视图、事件管理、自定义事件) - 考勤管理重构为6 Tab模块(班次/排班/每日出勤/月度报表/休假记录) - AI顾问新增人力报告Tab,支持流式生成+Word导出 - 工作台总览新增员工分布统计(性别/年龄/学历/司龄饼图)+部门成本拆分 - 花名册/合同/解聘补偿新增部门和状态筛选 - 薪税管理新增工资表导入模板下载、银行代发CSV导出 - 社保公积金支持多公积金账户类型显示 - 数据导出新增花名册/解聘记录导出,中文文件名编码修复 - 数据导入新增模板下载(员工/增减员/工资表)+错误日志导出 - 移除工作台日历卡片(已迁移至独立工作日历页面) - 新增20260728/20260729更新测试指导文档
This commit is contained in:
@@ -576,4 +576,96 @@ router.post('/tax-preview', async (req: AuthRequest, res: Response, next: NextFu
|
||||
}
|
||||
})
|
||||
|
||||
// ========== 薪资汇总表 & 明细表 ==========
|
||||
|
||||
// 薪资汇总表(按部门维度统计)
|
||||
router.get('/batch/:id/summary', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const batch = await prisma.payrollBatch.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!batch) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '批次不存在' } })
|
||||
|
||||
const entries = await prisma.batchEntry.findMany({
|
||||
where: { batchId: req.params.id },
|
||||
include: { employee: { select: { id: true, name: true, department: true } } },
|
||||
})
|
||||
|
||||
// 按部门汇总
|
||||
const deptMap = new Map<string, any>()
|
||||
for (const e of entries) {
|
||||
const dept = e.employee.department || '未分配'
|
||||
if (!deptMap.has(dept)) {
|
||||
deptMap.set(dept, { department: dept, headcount: 0, totalPay: 0, totalNetPay: 0, totalSocialEmp: 0, totalSocialOrg: 0, totalHousingEmp: 0, totalHousingOrg: 0, totalTax: 0 })
|
||||
}
|
||||
const d = deptMap.get(dept)
|
||||
d.headcount++
|
||||
d.totalPay += e.totalPay
|
||||
d.totalNetPay += e.netPay
|
||||
d.totalSocialEmp += e.socialEmp
|
||||
d.totalSocialOrg += e.socialOrg
|
||||
d.totalHousingEmp += e.housingEmp
|
||||
d.totalHousingOrg += e.housingOrg
|
||||
d.totalTax += e.tax
|
||||
}
|
||||
|
||||
const departments = Array.from(deptMap.values())
|
||||
const grandTotal = {
|
||||
headcount: entries.length,
|
||||
totalPay: entries.reduce((s, e) => s + e.totalPay, 0),
|
||||
totalNetPay: entries.reduce((s, e) => s + e.netPay, 0),
|
||||
totalSocialEmp: entries.reduce((s, e) => s + e.socialEmp, 0),
|
||||
totalSocialOrg: entries.reduce((s, e) => s + e.socialOrg, 0),
|
||||
totalHousingEmp: entries.reduce((s, e) => s + e.housingEmp, 0),
|
||||
totalHousingOrg: entries.reduce((s, e) => s + e.housingOrg, 0),
|
||||
totalTax: entries.reduce((s, e) => s + e.tax, 0),
|
||||
}
|
||||
|
||||
res.json({ success: true, data: { batch, departments, grandTotal } })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 薪资明细表(全员明细)
|
||||
router.get('/batch/:id/detail', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const batch = await prisma.payrollBatch.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!batch) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '批次不存在' } })
|
||||
|
||||
const entries = await prisma.batchEntry.findMany({
|
||||
where: { batchId: req.params.id },
|
||||
include: { employee: { select: { id: true, name: true, department: true, phone: true } } },
|
||||
orderBy: { employee: { department: 'asc' } },
|
||||
})
|
||||
|
||||
const details = entries.map(e => ({
|
||||
employeeId: e.employeeId,
|
||||
name: e.employee.name,
|
||||
department: e.employee.department,
|
||||
phone: e.employee.phone,
|
||||
baseSalary: e.baseSalary,
|
||||
positionSalary: e.positionSalary,
|
||||
performanceSalary: e.performanceSalary,
|
||||
senioritySalary: e.senioritySalary,
|
||||
overtimePay: e.overtimePay,
|
||||
transportAllowance: e.transportAllowance,
|
||||
mealAllowance: e.mealAllowance,
|
||||
housingAllowance: e.housingAllowance,
|
||||
communicationAllowance: e.communicationAllowance,
|
||||
allowance: e.allowance,
|
||||
bonus: e.bonus,
|
||||
deduction: e.deduction,
|
||||
otherDeduction: e.otherDeduction,
|
||||
socialEmp: e.socialEmp,
|
||||
housingEmp: e.housingEmp,
|
||||
tax: e.tax,
|
||||
totalPay: e.totalPay,
|
||||
netPay: e.netPay,
|
||||
}))
|
||||
|
||||
res.json({ success: true, data: { batch, details } })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user