风险与内控管理优化: 异常账单服务端分页/筛选/收银员搜索/合计, 收银员TOP15按异常账单数排序, 双击跳转明细, MonthPicker靠右, 侧边栏底部间距

This commit is contained in:
freedakgmail
2026-08-01 21:28:15 +08:00
parent ac8e39e2f9
commit d724a565f5
60 changed files with 3154 additions and 2561 deletions
+19 -6
View File
@@ -152,7 +152,7 @@ router.get('/dow-traffic', async (req: AuthRequest, res) => {
const storeName = req.query.store as string
const month = parseMonth(req)
const params: any[] = [month]
let where = `WHERE c175 IS NOT NULL AND c175 != '' AND c175 >= $1 AND c175 < $1::date + interval '1 month'`
let where = `WHERE c175 IS NOT NULL AND c175 != '' AND c175::timestamp >= $1::date AND c175::timestamp < $1::date + interval '1 month'`
if (storeName) {
params.push(storeName)
where += ` AND c003 = $${params.length}`
@@ -455,7 +455,7 @@ router.get('/scheduling-suggestion', async (req: AuthRequest, res) => {
count(*) AS bills
FROM bill_records
WHERE c003 = $1 AND c175 IS NOT NULL AND c175 != ''
AND c175 >= $4 AND c175 < $4::date + interval '1 month'
AND c175::timestamp >= $4::date AND c175::timestamp < $4::date + interval '1 month'
GROUP BY hour, day_type, bill_date
),
hourly_stats AS (
@@ -721,12 +721,18 @@ router.get('/employee-analysis', async (req: AuthRequest, res) => {
params.push(storeName)
where += ` AND org_level5 = $${params.length}`
}
params.push(month)
const monthParam = `$${params.length}`
// 将 2026-04 转为 2026年4月 格式匹配 salary_period
const [yr, mo] = month.split('-')
const periodLabel = `${parseInt(yr)}${parseInt(mo)}`
params.push(periodLabel)
where += ` AND salary_period = $${params.length}`
const countResult = await query(`SELECT count(*) FROM salary_detail_records ${where}`, params)
const total = countResult.rows[0].count
params.push(month)
const monthParam = `$${params.length}`
params.push(pageSize, offset)
const result = await query(`
SELECT employee_code,
@@ -767,6 +773,9 @@ router.get('/employee-analysis', async (req: AuthRequest, res) => {
// 岗位薪资对比
router.get('/position-salary-compare', async (req: AuthRequest, res) => {
try {
const month = parseMonth(req)
const [yr, mo] = month.split('-')
const periodLabel = `${parseInt(yr)}${parseInt(mo)}`
const result = await query(`
SELECT position,
count(*) AS emp_count,
@@ -781,10 +790,11 @@ router.get('/position-salary-compare', async (req: AuthRequest, res) => {
FROM salary_detail_records
WHERE org_level2 = '西部马华品牌门店'
AND position IS NOT NULL AND position != ''
AND salary_period = $1
GROUP BY position
HAVING count(*) >= 5
ORDER BY avg_gross DESC
`)
`, [periodLabel])
sendSuccess(res, result.rows)
} catch (err: any) {
sendError(res, err.message)
@@ -795,6 +805,8 @@ router.get('/position-salary-compare', async (req: AuthRequest, res) => {
router.get('/turnover-stats', async (req: AuthRequest, res) => {
try {
const month = parseMonth(req)
const [yr, mo] = month.split('-')
const periodLabel = `${parseInt(yr)}${parseInt(mo)}`
const result = await query(`
SELECT org_level5 AS store_name,
count(*) AS total_emp,
@@ -804,9 +816,10 @@ router.get('/turnover-stats', async (req: AuthRequest, res) => {
round(count(*) FILTER (WHERE hire_date IS NOT NULL AND hire_date >= $1)::numeric / nullif(count(*), 0) * 100, 2) AS new_hire_rate
FROM salary_detail_records
WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
AND salary_period = $2
GROUP BY org_level5
ORDER BY turnover_rate DESC NULLS LAST
`, [month])
`, [month, periodLabel])
sendSuccess(res, result.rows)
} catch (err: any) {
sendError(res, err.message)