fix: 修复API数据正确性问题
- 移除3个API的operating_expense IS NOT NULL过滤,改用COALESCE - /overview/store-profit-ranking: 93家门店全部纳入排名 - /overview/profit-opportunity: 所有有营收门店纳入机会计算 - /overview/profit-trend: LEFT JOIN替代INNER JOIN - 重建v_store_operating_expense_monthly视图,去除硬编码_april视图 - sales CTE改用mv_store_site_profile_monthly - food CTE改用mv_store_theoretical_actual_cost_monthly - 支持任意月份查询 - 修复人工优化和能源优化opportunity为负数问题 - 新增scripts/refresh_materialized_views.sh自动化刷新脚本 - 新增本体.md/API.md/原始数据级导入处理.md/物化视图更新.md/API数据检查报告.md
This commit is contained in:
+21
-19
@@ -1093,15 +1093,15 @@ router.get('/overview/store-profit-ranking', async (req: AuthRequest, res) => {
|
||||
r.store_code,
|
||||
r.store_name,
|
||||
round(r.received::numeric, 2) AS received,
|
||||
round(e.actual_food_cost::numeric, 2) AS food_cost,
|
||||
round(e.operating_expense::numeric, 2) AS expense,
|
||||
round((r.received - e.actual_food_cost - e.operating_expense)::numeric, 2) AS store_contribution,
|
||||
round((r.received - e.actual_food_cost - e.operating_expense) / nullif(r.received, 0) * 100, 2) AS contribution_margin_pct,
|
||||
round(COALESCE(e.actual_food_cost, 0)::numeric, 2) AS food_cost,
|
||||
round(COALESCE(e.operating_expense, 0)::numeric, 2) AS expense,
|
||||
round((r.received - COALESCE(e.actual_food_cost, 0) - COALESCE(e.operating_expense, 0))::numeric, 2) AS store_contribution,
|
||||
round((r.received - COALESCE(e.actual_food_cost, 0) - COALESCE(e.operating_expense, 0)) / nullif(r.received, 0) * 100, 2) AS contribution_margin_pct,
|
||||
r.risk_level
|
||||
FROM analytics.mv_store_risk_rating_monthly r
|
||||
LEFT JOIN analytics.mv_store_operating_expense_monthly e
|
||||
ON r.store_code = e.sales_store_code AND e.report_month = $1::date
|
||||
WHERE r.month_start = $1 AND r.received IS NOT NULL AND r.received > 0 AND e.operating_expense IS NOT NULL
|
||||
WHERE r.month_start = $1 AND r.received IS NOT NULL AND r.received > 0
|
||||
ORDER BY store_contribution DESC
|
||||
`, [month])
|
||||
sendSuccess(res, result.rows)
|
||||
@@ -1123,13 +1123,15 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
),
|
||||
standard_stores AS (
|
||||
SELECT r.store_code, r.store_name, r.received,
|
||||
e.actual_food_cost, e.operating_expense,
|
||||
e.wage_expense, e.utility_expense,
|
||||
COALESCE(e.actual_food_cost, 0) AS actual_food_cost,
|
||||
COALESCE(e.operating_expense, 0) AS operating_expense,
|
||||
COALESCE(e.wage_expense, 0) AS wage_expense,
|
||||
COALESCE(e.utility_expense, 0) AS utility_expense,
|
||||
r.theoretical_margin_pct, r.discount_rate_pct
|
||||
FROM risk r
|
||||
LEFT JOIN analytics.mv_store_operating_expense_monthly e
|
||||
ON r.store_code = e.sales_store_code AND e.report_month = $1::date
|
||||
WHERE r.received IS NOT NULL AND r.received > 0 AND e.operating_expense IS NOT NULL
|
||||
WHERE r.received IS NOT NULL AND r.received > 0
|
||||
),
|
||||
cost_diff AS (
|
||||
SELECT
|
||||
@@ -1246,7 +1248,7 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
'category', '标准店人工优化',
|
||||
'baseline', (SELECT round(total_wage / total_received * 100, 2) FROM labor),
|
||||
'target_pct', 100,
|
||||
'opportunity', round((SELECT total_wage * (1 - 24.0 / nullif(total_wage / total_received * 100, 0)) FROM labor), 2),
|
||||
'opportunity', GREATEST(round((SELECT total_wage * (1 - 24.0 / nullif(total_wage / total_received * 100, 0)) FROM labor), 2), 0),
|
||||
'confidence', '中',
|
||||
'owner', '运营/人力',
|
||||
'evidence', '工时、工资、餐段销售与服务质量',
|
||||
@@ -1261,7 +1263,7 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
'category', '标准店能源优化',
|
||||
'baseline', (SELECT round(total_utility / total_received * 100, 2) FROM energy),
|
||||
'target_pct', 100,
|
||||
'opportunity', round((SELECT total_utility * (1 - 5.0 / nullif(total_utility / total_received * 100, 0)) FROM energy), 2),
|
||||
'opportunity', GREATEST(round((SELECT total_utility * (1 - 5.0 / nullif(total_utility / total_received * 100, 0)) FROM energy), 2), 0),
|
||||
'confidence', '中',
|
||||
'owner', '工程/门店',
|
||||
'evidence', '账单/抄表、面积、营业时长',
|
||||
@@ -1423,21 +1425,21 @@ router.get('/overview/profit-trend', async (req: AuthRequest, res) => {
|
||||
),
|
||||
monthly AS (
|
||||
SELECT e.report_month,
|
||||
round(sum(e.consumption)::numeric, 2) AS consumption,
|
||||
round(sum(COALESCE(e.consumption, 0))::numeric, 2) AS consumption,
|
||||
round(max(bm.discount)::numeric, 2) AS discount,
|
||||
round(sum(r.received)::numeric, 2) AS received,
|
||||
round(sum(e.actual_food_cost)::numeric, 2) AS food_cost,
|
||||
round(sum(e.wage_expense)::numeric, 2) AS wage,
|
||||
round(sum(e.rent_expense)::numeric, 2) AS rent,
|
||||
round(sum(e.utility_expense)::numeric, 2) AS utility,
|
||||
round(sum(e.operating_expense)::numeric, 2) AS total_expense,
|
||||
round(sum(r.received) - sum(e.actual_food_cost) - sum(e.operating_expense), 2) AS store_contribution
|
||||
round(sum(COALESCE(e.actual_food_cost, 0))::numeric, 2) AS food_cost,
|
||||
round(sum(COALESCE(e.wage_expense, 0))::numeric, 2) AS wage,
|
||||
round(sum(COALESCE(e.rent_expense, 0))::numeric, 2) AS rent,
|
||||
round(sum(COALESCE(e.utility_expense, 0))::numeric, 2) AS utility,
|
||||
round(sum(COALESCE(e.operating_expense, 0))::numeric, 2) AS total_expense,
|
||||
round(sum(r.received) - sum(COALESCE(e.actual_food_cost, 0)) - sum(COALESCE(e.operating_expense, 0)), 2) AS store_contribution
|
||||
FROM analytics.v_store_scorecard r
|
||||
JOIN analytics.mv_store_operating_expense_monthly e
|
||||
LEFT JOIN analytics.mv_store_operating_expense_monthly e
|
||||
ON r.store_code = e.sales_store_code
|
||||
LEFT JOIN bill_monthly bm ON bm.report_month = e.report_month
|
||||
WHERE e.report_month >= $1::date AND e.report_month <= $2::date
|
||||
AND e.operating_expense IS NOT NULL AND r.received > 0
|
||||
AND r.received > 0
|
||||
GROUP BY e.report_month
|
||||
ORDER BY e.report_month
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user