import { Router, Response, NextFunction } from 'express' import prisma from '../lib/prisma' import { decrypt } from '../lib/crypto' import { authMiddleware, AuthRequest } from '../middleware/auth' import { z } from 'zod' import { isInProbation } from '../services/contract.service' const router = Router() router.use(authMiddleware) // ========== 加班费记录 ========== const overtimeSchema = z.object({ employeeId: z.string().min(1), month: z.string().regex(/^\d{4}-\d{2}$/), monthlyWage: z.number().positive(), weekdayHours: z.number().min(0).default(0), weekendHours: z.number().min(0).default(0), holidayHours: z.number().min(0).default(0), }) // 获取加班费记录列表 router.get('/overtime', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { employeeId, month } = req.query const records = await prisma.overtimeRecord.findMany({ where: { orgId: req.user!.orgId, ...(employeeId ? { employeeId: String(employeeId) } : {}), ...(month ? { month: String(month) } : {}), }, include: { employee: { select: { id: true, name: true, department: true } } }, orderBy: { createdAt: 'desc' }, }) res.json({ success: true, data: records }) } catch (err) { next(err) } }) // 保存加班费记录 router.post('/overtime', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const data = overtimeSchema.parse(req.body) const otConfig = await prisma.overtimeConfig.findUnique({ where: { orgId: req.user!.orgId } }) ?? { weekdayRate: 1.5, weekendRate: 2.0, holidayRate: 3.0, monthlyDays: 21.75, dailyHours: 8 } const hourlyWage = data.monthlyWage / otConfig.monthlyDays / otConfig.dailyHours const weekdayPay = hourlyWage * otConfig.weekdayRate * data.weekdayHours const weekendPay = hourlyWage * otConfig.weekendRate * data.weekendHours const holidayPay = hourlyWage * otConfig.holidayRate * data.holidayHours const totalPay = weekdayPay + weekendPay + holidayPay const record = await prisma.overtimeRecord.upsert({ where: { employeeId_month: { employeeId: data.employeeId, month: data.month }, }, update: { weekdayHours: data.weekdayHours, weekendHours: data.weekendHours, holidayHours: data.holidayHours, weekdayPay, weekendPay, holidayPay, totalPay, }, create: { orgId: req.user!.orgId, employeeId: data.employeeId, month: data.month, weekdayHours: data.weekdayHours, weekendHours: data.weekendHours, holidayHours: data.holidayHours, weekdayPay, weekendPay, holidayPay, totalPay, }, }) res.json({ success: true, data: record }) } catch (err) { next(err) } }) // 更新加班记录(按ID) const overtimeUpdateSchema = z.object({ weekdayHours: z.number().min(0).optional(), weekendHours: z.number().min(0).optional(), holidayHours: z.number().min(0).optional(), monthlyWage: z.number().positive().optional(), }) router.put('/overtime/:id', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { id } = req.params const data = overtimeUpdateSchema.parse(req.body) const existing = await prisma.overtimeRecord.findUnique({ where: { id } }) if (!existing) { res.status(404).json({ success: false, message: '记录不存在' }) return } const monthlyWage = data.monthlyWage ?? 0 const weekdayHours = data.weekdayHours ?? existing.weekdayHours const weekendHours = data.weekendHours ?? existing.weekendHours const holidayHours = data.holidayHours ?? existing.holidayHours const otConfig = await prisma.overtimeConfig.findUnique({ where: { orgId: req.user!.orgId } }) ?? { weekdayRate: 1.5, weekendRate: 2.0, holidayRate: 3.0, monthlyDays: 21.75, dailyHours: 8 } const hourlyWage = monthlyWage / otConfig.monthlyDays / otConfig.dailyHours const weekdayPay = hourlyWage * otConfig.weekdayRate * weekdayHours const weekendPay = hourlyWage * otConfig.weekendRate * weekendHours const holidayPay = hourlyWage * otConfig.holidayRate * holidayHours const totalPay = weekdayPay + weekendPay + holidayPay const record = await prisma.overtimeRecord.update({ where: { id }, data: { weekdayHours, weekendHours, holidayHours, weekdayPay, weekendPay, holidayPay, totalPay, }, }) res.json({ success: true, data: record }) } catch (err) { next(err) } }) // 从考勤记录同步加班工时 router.post('/overtime/sync-from-attendance', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const orgId = req.user!.orgId const { month } = req.body as { month: string } if (!month || !/^\d{4}-\d{2}$/.test(month)) { return res.status(400).json({ success: false, message: '请提供有效的月份(YYYY-MM)' }) } const monthStart = new Date(month + '-01') const monthEnd = new Date(monthStart) monthEnd.setMonth(monthEnd.getMonth() + 1) // 获取该月所有考勤记录(含加班工时) const records = await prisma.attendanceRecord.findMany({ where: { orgId, date: { gte: monthStart, lt: monthEnd }, overtimeHours: { gt: 0 } }, }) if (records.length === 0) { return res.json({ success: false, message: '该月考勤记录中无加班工时' }) } // 按员工汇总加班工时,按日期类型分类 const empMap = new Map() for (const r of records) { const day = new Date(r.date) const dayOfWeek = day.getDay() // 0=周日, 6=周六 let type: 'weekday' | 'weekend' | 'holiday' = 'weekday' if (dayOfWeek === 0 || dayOfWeek === 6) { type = 'weekend' } // 简单判断法定节假日:这里使用周末判断,实际法定节假日需要额外配置 // 如果有 holidayHours 字段在 attendanceRecord 中,优先使用 if (!empMap.has(r.employeeId)) { empMap.set(r.employeeId, { weekday: 0, weekend: 0, holiday: 0 }) } const entry = empMap.get(r.employeeId)! entry[type] += r.overtimeHours || 0 } // 获取员工月工资用于计算加班费 let config = await prisma.overtimeConfig.findUnique({ where: { orgId } }) if (!config) config = await prisma.overtimeConfig.create({ data: { orgId } }) let synced = 0 for (const [employeeId, hours] of empMap) { const emp = await prisma.employee.findFirst({ where: { id: employeeId }, select: { monthlySalary: true } }) let monthlyWage = 0 try { monthlyWage = emp?.monthlySalary ? Number(decrypt(emp.monthlySalary)) : 0 } catch { monthlyWage = Number(emp?.monthlySalary) || 0 } const hourlyWage = monthlyWage / config.monthlyDays / config.dailyHours const weekdayPay = hourlyWage * config.weekdayRate * hours.weekday const weekendPay = hourlyWage * config.weekendRate * hours.weekend const holidayPay = hourlyWage * config.holidayRate * hours.holiday const totalPay = weekdayPay + weekendPay + holidayPay await prisma.overtimeRecord.upsert({ where: { employeeId_month: { employeeId, month } }, update: { weekdayHours: hours.weekday, weekendHours: hours.weekend, holidayHours: hours.holiday, weekdayPay, weekendPay, holidayPay, totalPay, }, create: { orgId, employeeId, month, weekdayHours: hours.weekday, weekendHours: hours.weekend, holidayHours: hours.holiday, weekdayPay, weekendPay, holidayPay, totalPay, }, }) synced++ } res.json({ success: true, data: { synced, totalEmployees: empMap.size } }) } catch (err) { next(err) } }) // ========== 工资条管理 ========== const payslipSchema = z.object({ employeeId: z.string().min(1), month: z.string().regex(/^\d{4}-\d{2}$/), baseSalary: z.number().min(0).default(0), overtimePay: z.number().min(0).default(0), weekdayOvertimePay: z.number().min(0).default(0), weekendOvertimePay: z.number().min(0).default(0), holidayOvertimePay: z.number().min(0).default(0), allowance: z.number().min(0).default(0), deduction: z.number().min(0).default(0), }) // 获取工资条列表 router.get('/payslip', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { month, employeeId } = req.query const payslips = await prisma.payslip.findMany({ where: { orgId: req.user!.orgId, ...(month ? { month: String(month) } : {}), ...(employeeId ? { employeeId: String(employeeId) } : {}), }, include: { employee: { select: { id: true, name: true, department: true } } }, orderBy: [{ month: 'desc' }, { employee: { name: 'asc' } }], }) res.json({ success: true, data: payslips }) } catch (err) { next(err) } }) // 创建/更新工资条 router.post('/payslip', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const data = payslipSchema.parse(req.body) const totalPay = data.baseSalary + data.overtimePay + data.allowance - data.deduction const payslip = await prisma.payslip.upsert({ where: { employeeId_month: { employeeId: data.employeeId, month: data.month }, }, update: { baseSalary: data.baseSalary, overtimePay: data.overtimePay, weekdayOvertimePay: data.weekdayOvertimePay, weekendOvertimePay: data.weekendOvertimePay, holidayOvertimePay: data.holidayOvertimePay, allowance: data.allowance, deduction: data.deduction, totalPay, }, create: { orgId: req.user!.orgId, employeeId: data.employeeId, month: data.month, baseSalary: data.baseSalary, overtimePay: data.overtimePay, weekdayOvertimePay: data.weekdayOvertimePay, weekendOvertimePay: data.weekendOvertimePay, holidayOvertimePay: data.holidayOvertimePay, allowance: data.allowance, deduction: data.deduction, totalPay, }, }) res.json({ success: true, data: payslip }) } catch (err) { next(err) } }) // 从加班费记录自动生成工资条 router.post('/payslip/generate', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { month, employeeId, baseSalary, allowance, deduction } = req.body as { month: string employeeId: string baseSalary: number allowance?: number deduction?: number } const overtime = await prisma.overtimeRecord.findUnique({ where: { employeeId_month: { employeeId, month } }, }) const overtimePay = overtime?.totalPay || 0 const totalPay = baseSalary + overtimePay + (allowance || 0) - (deduction || 0) const payslip = await prisma.payslip.upsert({ where: { employeeId_month: { employeeId, month } }, update: { baseSalary, overtimePay, weekdayOvertimePay: overtime?.weekdayPay || 0, weekendOvertimePay: overtime?.weekendPay || 0, holidayOvertimePay: overtime?.holidayPay || 0, allowance: allowance || 0, deduction: deduction || 0, totalPay, }, create: { orgId: req.user!.orgId, employeeId, month, baseSalary, overtimePay, weekdayOvertimePay: overtime?.weekdayPay || 0, weekendOvertimePay: overtime?.weekendPay || 0, holidayOvertimePay: overtime?.holidayPay || 0, allowance: allowance || 0, deduction: deduction || 0, totalPay, }, }) res.json({ success: true, data: payslip }) } catch (err) { next(err) } }) // 删除工资条 router.delete('/payslip/:id', async (req: AuthRequest, res: Response, next: NextFunction) => { try { await prisma.payslip.delete({ where: { id: req.params.id, orgId: req.user!.orgId }, }) res.json({ success: true }) } catch (err) { next(err) } }) // ========== 批量生成工资条 ========== const batchGenerateSchema = z.object({ month: z.string().regex(/^\d{4}-\d{2}$/), allowances: z.record(z.string(), z.number().default(0)).optional(), deductions: z.record(z.string(), z.number().default(0)).optional(), }) // 批量生成全员工资条 router.post('/payslip/batch-generate', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { month, allowances = {}, deductions = {} } = batchGenerateSchema.parse(req.body) const employees = await prisma.employee.findMany({ where: { orgId: req.user!.orgId, status: 'ACTIVE' }, include: { contracts: { orderBy: { createdAt: 'desc' }, take: 1 }, }, }) const results: any[] = [] for (const emp of employees) { const overtime = await prisma.overtimeRecord.findUnique({ where: { employeeId_month: { employeeId: emp.id, month } }, }) const overtimePay = overtime?.totalPay || 0 const allowance = allowances[emp.id] || 0 const deduction = deductions[emp.id] || 0 let baseSalary = 0 // 按月份判定是否仍在试用期 const monthEnd = new Date(`${month}-28T23:59:59`) const latestContract = emp.contracts[0] if (isInProbation(latestContract, monthEnd) && latestContract?.probationSalary > 0) { baseSalary = latestContract.probationSalary } else if (emp.monthlySalary) { try { baseSalary = Number(decrypt(emp.monthlySalary)) || 0 } catch { baseSalary = Number(emp.monthlySalary) || 0 } } const totalPay = baseSalary + overtimePay + allowance - deduction const payslip = await prisma.payslip.upsert({ where: { employeeId_month: { employeeId: emp.id, month } }, update: { baseSalary, overtimePay, allowance, deduction, totalPay }, create: { orgId: req.user!.orgId, employeeId: emp.id, month, baseSalary, overtimePay, weekdayOvertimePay: overtime?.weekdayPay || 0, weekendOvertimePay: overtime?.weekendPay || 0, holidayOvertimePay: overtime?.holidayPay || 0, allowance, deduction, totalPay, }, }) results.push(payslip) } res.json({ success: true, data: { generated: results.length, payslips: results } }) } catch (err) { next(err) } }) // ========== 加班费计算规则配置 ========== const overtimeConfigSchema = z.object({ weekdayRate: z.number().min(1).default(1.5), weekendRate: z.number().min(1).default(2.0), holidayRate: z.number().min(1).default(3.0), monthlyDays: z.number().min(1).default(21.75), dailyHours: z.number().min(1).default(8), }) // 获取加班费计算规则 router.get('/overtime/config', async (req: AuthRequest, res: Response, next: NextFunction) => { try { let config = await prisma.overtimeConfig.findUnique({ where: { orgId: req.user!.orgId } }) if (!config) { config = await prisma.overtimeConfig.create({ data: { orgId: req.user!.orgId } }) } res.json({ success: true, data: config }) } catch (err) { next(err) } }) // 保存加班费计算规则 router.post('/overtime/config', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const data = overtimeConfigSchema.parse(req.body) const config = await prisma.overtimeConfig.upsert({ where: { orgId: req.user!.orgId }, update: data, create: { orgId: req.user!.orgId, ...data }, }) res.json({ success: true, data: config }) } catch (err) { next(err) } }) // ========== 批量导入加班工时 ========== const batchOvertimeSchema = z.array( z.object({ employeeId: z.string().min(1), month: z.string().regex(/^\d{4}-\d{2}$/), weekdayHours: z.number().min(0).default(0), weekendHours: z.number().min(0).default(0), holidayHours: z.number().min(0).default(0), }), ) router.post('/overtime/batch', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const items = batchOvertimeSchema.parse(req.body) const results: any[] = [] for (const data of items) { const record = await prisma.overtimeRecord.upsert({ where: { employeeId_month: { employeeId: data.employeeId, month: data.month } }, update: { weekdayHours: data.weekdayHours, weekendHours: data.weekendHours, holidayHours: data.holidayHours, weekdayPay: 0, weekendPay: 0, holidayPay: 0, totalPay: 0, }, create: { orgId: req.user!.orgId, employeeId: data.employeeId, month: data.month, weekdayHours: data.weekdayHours, weekendHours: data.weekendHours, holidayHours: data.holidayHours, }, }) results.push(record) } res.json({ success: true, data: { imported: results.length } }) } catch (err) { next(err) } }) // ========== 批次导入加班费 ========== router.post('/overtime/import-to-batch/:batchId', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { batchId } = req.params const orgId = req.user!.orgId const batch = await prisma.payrollBatch.findFirst({ where: { id: batchId, orgId } }) if (!batch) return res.status(404).json({ success: false, message: '批次不存在' }) if (batch.status === 'ARCHIVED') return res.status(400).json({ success: false, message: '已归档批次不可操作' }) // 获取加班费计算规则 let config = await prisma.overtimeConfig.findUnique({ where: { orgId } }) if (!config) config = await prisma.overtimeConfig.create({ data: { orgId } }) // 获取该月未关联批次的加班记录 const overtimeRecords = await prisma.overtimeRecord.findMany({ where: { orgId, month: batch.month, batchId: null }, include: { employee: { select: { id: true, name: true, monthlySalary: true } } }, }) if (overtimeRecords.length === 0) { return res.json({ success: false, message: '没有可导入的加班记录(所有记录已关联批次或无数据)' }) } const results: any[] = [] for (const ot of overtimeRecords) { // 获取员工月工资 let monthlyWage = 0 try { monthlyWage = ot.employee.monthlySalary ? Number(decrypt(ot.employee.monthlySalary)) : 0 } catch { monthlyWage = Number(ot.employee.monthlySalary) || 0 } if (!monthlyWage) continue // 根据规则计算加班费 const hourlyWage = monthlyWage / config.monthlyDays / config.dailyHours const weekdayPay = hourlyWage * config.weekdayRate * ot.weekdayHours const weekendPay = hourlyWage * config.weekendRate * ot.weekendHours const holidayPay = hourlyWage * config.holidayRate * ot.holidayHours const totalPay = weekdayPay + weekendPay + holidayPay // 更新加班记录:计算金额并锁定到批次 await prisma.overtimeRecord.update({ where: { id: ot.id }, data: { weekdayPay, weekendPay, holidayPay, totalPay, batchId }, }) // 更新批次条目的加班费 const entry = await prisma.batchEntry.findUnique({ where: { batchId_employeeId: { batchId, employeeId: ot.employeeId } }, }) if (entry) { await prisma.batchEntry.update({ where: { id: entry.id }, data: { overtimePay: totalPay }, }) // 重新计算条目 const newTotalPay = entry.baseSalary + totalPay + entry.allowance + entry.bonus - entry.deduction await prisma.batchEntry.update({ where: { id: entry.id }, data: { totalPay: newTotalPay }, }) } results.push({ employeeId: ot.employeeId, employeeName: ot.employee.name, totalPay }) } res.json({ success: true, data: { imported: results.length, details: results } }) } catch (err) { next(err) } }) // ========== 税率试算 ========== router.post('/tax-preview', async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { employeeId, month, baseSalary, overtimePay, allowance, deduction, bonus, specialDeduction } = req.body const orgId = req.user!.orgId // 获取员工和配置 const [employee, socialConfig, housingConfig] = await Promise.all([ employeeId ? prisma.employee.findFirst({ where: { id: employeeId, orgId }, include: { contracts: { orderBy: { createdAt: 'desc' }, take: 1 } } }) : null, prisma.socialInsuranceConfig.findFirst({ where: { orgId, effectiveFrom: { lte: month }, OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }] }, orderBy: { effectiveFrom: 'desc' }, }), prisma.housingFundConfig.findFirst({ where: { orgId, effectiveFrom: { lte: month }, OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }] }, orderBy: { effectiveFrom: 'desc' }, }), ]) const emp = employee || { socialInsBase: baseSalary, housingFundBase: baseSalary } as any // 非劳动合同类型不缴纳社保公积金 const contractType = (employee as any)?.contracts?.[0]?.contractType const isNoSocialContract = contractType && ['LABOR', 'INTERNSHIP', 'PARTTIME', 'OUTSOURCING', 'UNSIGNED'].includes(contractType) const socialBase = isNoSocialContract ? 0 : (emp.socialInsBase != null ? emp.socialInsBase : baseSalary) const housingBase = isNoSocialContract ? 0 : (emp.housingFundBase != null ? emp.housingFundBase : baseSalary) // 计算社保公积金 let socialEmp = 0, housingEmp = 0 if (socialConfig) { const { calcSocialInsurance } = await import('../services/payroll.service') const social = calcSocialInsurance(socialBase, socialConfig) socialEmp = social.socialEmp } if (housingConfig) { const { calcHousingFund } = await import('../services/payroll.service') const housing = calcHousingFund(housingBase, housingConfig) housingEmp = housing.housingEmp } // 获取 YTD 数据计算累计个税 const year = month.slice(0, 4) const ytdPayslips = employeeId ? await prisma.payslip.findMany({ where: { employeeId, month: { startsWith: year }, status: 'PUBLISHED' }, orderBy: { month: 'asc' }, }) : [] const ytdTaxableIncome = ytdPayslips.reduce((sum, p) => sum + (p.totalPay - p.deduction - socialEmp - housingEmp - (specialDeduction || 0)), 0) const ytdTaxDeducted = ytdPayslips.reduce((sum, p) => sum + (p.tax || 0), 0) const { calcCumulativeTax } = await import('../services/payroll.service') const totalPay = (baseSalary || 0) + (overtimePay || 0) + (allowance || 0) - (deduction || 0) + (bonus || 0) const taxableIncome = totalPay - socialEmp - housingEmp - (specialDeduction || 0) const tax = calcCumulativeTax(ytdTaxableIncome + taxableIncome, ytdTaxDeducted) const netPay = totalPay - socialEmp - housingEmp - tax res.json({ success: true, data: { baseSalary: baseSalary || 0, overtimePay: overtimePay || 0, allowance: allowance || 0, deduction: deduction || 0, bonus: bonus || 0, totalPay, socialEmp, housingEmp, specialDeduction: specialDeduction || 0, taxableIncome, estimatedTax: tax, netPay, ytdPayslipCount: ytdPayslips.length, breakdown: [ { label: '应发合计', value: totalPay }, { label: '个人社保', value: -socialEmp }, { label: '个人公积金', value: -housingEmp }, { label: '专项附加扣除', value: -(specialDeduction || 0) }, { label: '应纳税所得额', value: taxableIncome }, { label: '当月个税', value: -tax }, { label: '实发工资', value: netPay }, ], }, }) } catch (err) { next(err) } }) // ========== 薪资汇总表 & 明细表 ========== // 薪资汇总表(按部门维度统计) 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() 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