JessieExcel/api/routes/stats.ts
2026-03-25 01:54:12 +08:00

124 lines
3.6 KiB
TypeScript

import { Router, type Request, type Response } from 'express'
import { listFactories, listOrders } from '../db.js'
import { requireAuth } from '../middleware/requireAuth.js'
const router = Router()
router.use(requireAuth)
const cleanText = (v: unknown): string => {
if (v === null || v === undefined) return ''
return String(v).replace(/\u00A0/g, ' ').replace(/[\s\u3000]+/g, ' ').trim()
}
const isDone = (status: string): boolean => {
const s = cleanText(status)
if (!s) return false
return /完成|已出完|出完|结束|已完成/.test(s)
}
const isCanceled = (status: string): boolean => {
const s = cleanText(status)
if (!s) return false
return /取消|作废/.test(s)
}
const normalizeStage = (status: string): string => {
const s = cleanText(status)
if (!s) return '未填写'
if (isCanceled(s)) return '取消'
if (isDone(s)) return '完成'
if (/验货|驗貨|待验货|已验货/.test(s)) return '验货'
if (/订舱|訂艙|订仓|订仓/.test(s)) return '订舱'
if (/包材|唛头|嘜頭/.test(s)) return '包材唛头'
if (/不干胶|外箱/.test(s)) return '外箱不干胶'
if (/出货|出運|出运|发货|發貨|已出货|待送货/.test(s)) return '出货'
if (/生产|待生产|排版|催大货样/.test(s)) return '生产'
if (/下单|下單/.test(s)) return '下单'
return '其他'
}
const dateKey = (s?: string): string => {
const v = cleanText(s)
return v ? v.slice(0, 10) : ''
}
const lastNDaysKeys = (n: number): string[] => {
const out: string[] = []
const now = new Date()
now.setHours(0, 0, 0, 0)
for (let i = n - 1; i >= 0; i -= 1) {
const d = new Date(now)
d.setDate(d.getDate() - i)
out.push(d.toISOString().slice(0, 10))
}
return out
}
router.get('/dashboard', async (_req: Request, res: Response): Promise<void> => {
const orders = await listOrders()
const factories = await listFactories()
const today = new Date().toISOString().slice(0, 10)
const active = orders.filter((o) => !isDone(o.orderProgress) && !isCanceled(o.orderProgress))
const totalAmount = orders.reduce((sum, o) => sum + (o.orderAmount ?? 0), 0)
const overdue = active.filter((o) => {
const due = dateKey(o.customerDeliveryDate)
if (!due) return false
return due < today
})
const statusMap = new Map<string, number>()
for (const o of orders) {
const stage = normalizeStage(o.orderProgress)
statusMap.set(stage, (statusMap.get(stage) ?? 0) + 1)
}
const statusDist = Array.from(statusMap.entries()).map(([name, value]) => ({
name,
value,
}))
const factoryNameById = new Map(factories.map((f) => [f.id, f.name]))
const factoryMap = new Map<string, number>()
for (const o of orders) {
const key = (o.factoryId ? factoryNameById.get(o.factoryId) : undefined) ?? '未指定'
factoryMap.set(key, (factoryMap.get(key) ?? 0) + 1)
}
const factoryDist = Array.from(factoryMap.entries()).map(([name, value]) => ({
name,
value,
}))
const days = lastNDaysKeys(30)
const seriesMap = new Map(days.map((d) => [d, { date: d, amount: 0, count: 0 }]))
for (const o of orders) {
const d = dateKey(o.orderDate) || dateKey(o.createdAt)
if (!d || !seriesMap.has(d)) continue
const row = seriesMap.get(d)!
row.amount += o.orderAmount ?? 0
row.count += 1
}
const series = days.map((d) => seriesMap.get(d)!)
res.json({
success: true,
data: {
kpis: {
totalOrders: orders.length,
activeOrders: active.length,
overdueOrders: overdue.length,
totalAmount,
},
charts: {
statusDist,
factoryDist,
trend: series,
},
},
})
})
export default router