72a6eab3bd
用户手动修改 risk.service.ts 时在第 966 行重复声明了 priorityOrder(第 955 行已声明),导致 esbuild 报错 "The symbol priorityOrder has already been declared", 后端 502 Bad Gateway。删除重复声明。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
141 lines
5.2 KiB
TypeScript
141 lines
5.2 KiB
TypeScript
/**
|
|
* 提成奖金路由
|
|
* 管理按月提成奖金/扣款的 CRUD、批量导入、模板下载
|
|
*/
|
|
import { Router, Response, NextFunction } from 'express'
|
|
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
|
import multer from 'multer'
|
|
import * as XLSX from 'xlsx'
|
|
import {
|
|
listByMonth,
|
|
summaryByMonth,
|
|
create,
|
|
update,
|
|
remove,
|
|
batchImport,
|
|
} from '../services/commission-bonus.service'
|
|
import { auditLog } from '../middleware/auditLog'
|
|
|
|
const router = Router()
|
|
router.use(authMiddleware)
|
|
|
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 } })
|
|
|
|
/**
|
|
* 按月查询提成奖金列表
|
|
* GET /commission-bonus?month=YYYY-MM
|
|
*/
|
|
router.get('/', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const month = (req.query.month as string) || new Date().toISOString().slice(0, 7)
|
|
const [records, summary] = await Promise.all([
|
|
listByMonth(orgId, month),
|
|
summaryByMonth(orgId, month),
|
|
])
|
|
res.json({ success: true, data: { records, summary, month } })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
/**
|
|
* 新增单条提成奖金
|
|
* POST /commission-bonus body: { employeeId, month, amount, remark? }
|
|
*/
|
|
router.post('/', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const { employeeId, month, amount, remark } = req.body
|
|
if (!employeeId || !month || amount === undefined) {
|
|
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 employeeId/month/amount' } })
|
|
}
|
|
const record = await create(orgId, req.user!.id, { employeeId, month, amount: Number(amount), remark })
|
|
await auditLog(req, 'CREATE', 'COMMISSION_BONUS', record.id, { employeeId, month, amount })
|
|
res.json({ success: true, data: record })
|
|
} catch (err: any) {
|
|
if (err.code === 'NOT_FOUND') return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 更新单条
|
|
* PUT /commission-bonus/:id body: { amount?, remark? }
|
|
*/
|
|
router.put('/:id', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const { amount, remark } = req.body
|
|
const record = await update(orgId, req.params.id, { amount: amount !== undefined ? Number(amount) : undefined, remark })
|
|
await auditLog(req, 'UPDATE', 'COMMISSION_BONUS', req.params.id, { amount, remark })
|
|
res.json({ success: true, data: record })
|
|
} catch (err: any) {
|
|
if (err.code === 'NOT_FOUND') return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 删除单条
|
|
* DELETE /commission-bonus/:id
|
|
*/
|
|
router.delete('/:id', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
await remove(orgId, req.params.id)
|
|
await auditLog(req, 'DELETE', 'COMMISSION_BONUS', req.params.id)
|
|
res.json({ success: true, data: { message: '已删除' } })
|
|
} catch (err: any) {
|
|
if (err.code === 'NOT_FOUND') return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 批量导入 Excel
|
|
* POST /commission-bonus/import multipart: file, month
|
|
* Excel 列:员工姓名 | 证件号码 | 金额 | 备注
|
|
*/
|
|
router.post('/import', upload.single('file'), async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const month = req.body.month || new Date().toISOString().slice(0, 7)
|
|
if (!req.file) {
|
|
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '请上传 Excel 文件' } })
|
|
}
|
|
const wb = XLSX.read(req.file.buffer, { type: 'buffer' })
|
|
const ws = wb.Sheets[wb.SheetNames[0]]
|
|
const rows: any[] = XLSX.utils.sheet_to_json(ws, { defval: '' })
|
|
|
|
const parsed = rows.map((r) => ({
|
|
employeeName: String(r['员工姓名'] || r['姓名'] || '').trim() || undefined,
|
|
idCardNumber: String(r['证件号码'] || r['身份证号'] || '').trim() || undefined,
|
|
amount: parseFloat(r['金额'] || r['提成奖金'] || '0') || 0,
|
|
remark: String(r['备注'] || '').trim() || undefined,
|
|
})).filter((r) => r.employeeName || r.idCardNumber)
|
|
|
|
const result = await batchImport(orgId, req.user!.id, month, parsed)
|
|
await auditLog(req, 'IMPORT', 'COMMISSION_BONUS', undefined, { month, ...result })
|
|
res.json({ success: true, data: result })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
/**
|
|
* 下载导入模板
|
|
* GET /commission-bonus/template
|
|
*/
|
|
router.get('/template', (req: AuthRequest, res: Response) => {
|
|
const ws = XLSX.utils.aoa_to_sheet([
|
|
['员工姓名', '证件号码', '金额', '备注'],
|
|
['张三', '110101199001011234', '5000', '销售提成'],
|
|
['李四', '', '-200', '迟到扣款'],
|
|
])
|
|
const wb = XLSX.utils.book_new()
|
|
XLSX.utils.book_append_sheet(wb, ws, '提成奖金模板')
|
|
const buf = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })
|
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
|
res.setHeader('Content-Disposition', 'attachment; filename="commission-bonus-template.xlsx"')
|
|
res.send(buf)
|
|
})
|
|
|
|
export default router
|