Files
services/controllers/reports.js
T
董海洋 d529333ba7 API
2026-06-04 19:25:02 +08:00

132 lines
3.5 KiB
JavaScript

const { query } = require('../config/database')
const { COST_RATIO, PROFIT_RATIO } = require('../config/constants')
async function getSalesTrend(ctx) {
const days = parseInt(ctx.query.days) || 30
const group = ctx.query.group || 'day'
let dateFormat
if (group === 'week') {
dateFormat = 'DATE_FORMAT(created_at, \'%x-W%v\')'
} else if (group === 'month') {
dateFormat = 'DATE_FORMAT(created_at, \'%Y-%m\')'
} else {
dateFormat = 'DATE(created_at)'
}
const rows = await query(
`SELECT ${dateFormat} as period,
COUNT(*) as order_count,
COALESCE(SUM(total_price), 0) as total_sales
FROM orders
WHERE status IN ('paid', 'completed')
AND created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY period
ORDER BY period ASC`,
[days]
)
ctx.body = { code: 200, data: rows }
}
async function getHotProducts(ctx) {
const limit = parseInt(ctx.query.limit) || 10
const rows = await query(
`SELECT goods.id, goods.name, goods.price, goods.sales, goods.stock,
COALESCE(s.quantity, 0) as stock_qty
FROM goods
LEFT JOIN stock s ON goods.id = s.goods_id
ORDER BY goods.sales DESC
LIMIT ?`,
[limit]
)
ctx.body = { code: 200, data: rows }
}
async function getProfitAnalysis(ctx) {
const days = parseInt(ctx.query.days) || 30
const revenueRows = await query(
`SELECT DATE(created_at) as date,
COUNT(*) as order_count,
COALESCE(SUM(total_price), 0) as revenue
FROM orders
WHERE status IN ('paid', 'completed')
AND created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY DATE(created_at)
ORDER BY date ASC`,
[days]
)
const avgCost = await query(
`SELECT COALESCE(AVG(purchase_price), 0) as avg_cost
FROM purchase_items`
)
const costPerUnit = parseFloat(avgCost[0]?.avg_cost || 0)
const enriched = revenueRows.map(row => ({
...row,
revenue: parseFloat(row.revenue),
estimated_cost: parseFloat(row.revenue) * COST_RATIO,
estimated_profit: parseFloat(row.revenue) * PROFIT_RATIO
}))
const totalRevenue = enriched.reduce((s, r) => s + r.revenue, 0)
const totalCost = enriched.reduce((s, r) => s + r.estimated_cost, 0)
const totalProfit = enriched.reduce((s, r) => s + r.estimated_profit, 0)
ctx.body = {
code: 200,
data: {
days,
summary: {
total_revenue: totalRevenue,
total_cost: totalCost,
total_profit: totalProfit,
profit_margin: totalRevenue > 0 ? ((totalProfit / totalRevenue) * 100).toFixed(1) : '0.0',
avg_purchase_cost: costPerUnit
},
details: enriched
}
}
}
async function getInventoryTurnover(ctx) {
const rows = await query(
`SELECT g.id, g.name, g.price, g.sales,
COALESCE(s.quantity, 0) as stock_qty,
CASE
WHEN COALESCE(s.quantity, 0) <= 0 THEN g.sales
ELSE ROUND(g.sales / s.quantity, 2)
END as turnover_ratio
FROM goods g
LEFT JOIN stock s ON g.id = s.goods_id
ORDER BY turnover_ratio DESC`
)
const lowStock = rows.filter(r => r.stock_qty <= 5)
const outOfStock = rows.filter(r => r.stock_qty <= 0)
const slowMoving = rows.filter(r => r.turnover_ratio < 0.1 && r.sales > 0)
ctx.body = {
code: 200,
data: {
total_items: rows.length,
low_stock_count: lowStock.length,
out_of_stock_count: outOfStock.length,
slow_moving_count: slowMoving.length,
items: rows
}
}
}
module.exports = {
getSalesTrend,
getHotProducts,
getProfitAnalysis,
getInventoryTurnover
}