From 8fd74fd78cbdb241f63eb72da6856a6615677474 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Thu, 13 Aug 2026 14:45:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=A9=E6=B6=A6=E6=9C=BA=E4=BC=9A?= =?UTF-8?q?=E6=B1=A0=E6=94=B9=E4=B8=BA=E9=97=A8=E5=BA=97=E7=BA=A7=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=95=B4=E4=BD=93=E5=B9=B3?= =?UTF-8?q?=E5=9D=87=E6=8E=A9=E7=9B=96=E9=97=AE=E9=A2=98=E9=97=A8=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:利润机会池6项中有4项返回0,总额仅15.3万 原因:用整体平均费率判断是否超标,整体已达标则机会=0, 掩盖了个别门店严重超标的问题 修复: 1. 食材成本差异回收:改用 mv_store_theoretical_actual_cost_monthly (门店级理论vs实际成本),只对正差异(超耗)门店求和 × 30% 修复前 0 → 修复后 1,898,587 2. 标准店人工优化:改为门店级,只对费率>24%的门店计算机会 修复前 0 → 修复后 1,561,133(58家超标) 3. 标准店能源优化:改为门店级,只对费率>5%的门店计算机会 修复前 0 → 修复后 466,609(49家超标) 4. 平台佣金优化:改为门店级,只对佣金率>20%的门店计算机会 修复前 0 → 修复后 3,430(1家超标) 5. 高优惠门店治理和SKU复杂度压缩逻辑不变 总额:152,995 → 4,082,755(约408万) Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- server/src/routes/data.ts | 125 ++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/server/src/routes/data.ts b/server/src/routes/data.ts index 283ab16..431987a 100644 --- a/server/src/routes/data.ts +++ b/server/src/routes/data.ts @@ -1144,6 +1144,7 @@ router.get('/overview/store-profit-ranking', async (req: AuthRequest, res) => { }) // 利润机会池 +// 门店级计算:只对超标门店计算机会金额,避免整体平均掩盖问题门店 router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => { try { const month = parseMonth(req) @@ -1154,64 +1155,67 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => { sku_abc AS ( SELECT * FROM analytics.mv_dish_sku_abc_monthly WHERE month_start = $1 ), - standard_stores AS ( - SELECT r.store_code, r.store_name, r.received, - 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 - ), + -- 食材成本差异:改用 mv_store_theoretical_actual_cost_monthly(门店级理论vs实际成本) cost_diff AS ( SELECT - round(sum(actual_food_cost)::numeric, 2) AS actual_cost, - round(sum(received * (1 - COALESCE(theoretical_margin_pct, 0) / 100))::numeric, 2) AS theoretical_cost - FROM standard_stores + count(*) FILTER (WHERE food_cost_variance > 0) AS over_cost_stores, + round(sum(food_cost_variance) FILTER (WHERE food_cost_variance > 0)::numeric, 2) AS total_positive_variance, + round(sum(theoretical_cost)::numeric, 2) AS total_theoretical_cost, + round(sum(actual_food_cost)::numeric, 2) AS total_actual_cost, + round(sum(sales_received)::numeric, 2) AS total_received + FROM analytics.mv_store_theoretical_actual_cost_monthly + WHERE month_start = $1 ), cost_diff_stores AS ( - SELECT store_name, received, - round(actual_food_cost::numeric, 2) AS actual_food_cost, - round((received * (1 - COALESCE(theoretical_margin_pct, 0) / 100))::numeric, 2) AS theoretical_cost, - round((actual_food_cost - received * (1 - COALESCE(theoretical_margin_pct, 0) / 100))::numeric, 2) AS diff_amount, - round((actual_food_cost / nullif(received, 0) * 100)::numeric, 2) AS actual_cost_rate, - round((received * (1 - COALESCE(theoretical_margin_pct, 0) / 100) / nullif(received, 0) * 100)::numeric, 2) AS theoretical_cost_rate - FROM standard_stores - WHERE actual_food_cost IS NOT NULL - ORDER BY (actual_food_cost - received * (1 - COALESCE(theoretical_margin_pct, 0) / 100)) DESC + SELECT store_name, sales_received AS received, + round(food_cost_variance::numeric, 2) AS diff_amount, + round(actual_food_cost_rate_pct::numeric, 2) AS actual_cost_rate, + round(theoretical_cost_rate_pct::numeric, 2) AS theoretical_cost_rate + FROM analytics.mv_store_theoretical_actual_cost_monthly + WHERE month_start = $1 AND food_cost_variance > 0 + ORDER BY food_cost_variance DESC LIMIT 5 ), + -- 人工优化:门店级,只对费率>24%的门店计算机会 labor AS ( SELECT + count(*) FILTER (WHERE wage_rate_pct > 24) AS over_wage_stores, + round(sum(wage_expense) FILTER (WHERE wage_rate_pct > 24)::numeric, 2) AS over_wage_total, + round(sum(wage_expense * GREATEST(1 - 24.0 / nullif(wage_rate_pct, 0), 0)) FILTER (WHERE wage_rate_pct > 24)::numeric, 2) AS opportunity, round(sum(wage_expense)::numeric, 2) AS total_wage, round(sum(received)::numeric, 2) AS total_received - FROM standard_stores + FROM analytics.mv_store_operating_expense_monthly + WHERE report_month = $1::date AND wage_expense IS NOT NULL AND wage_expense > 0 AND received > 0 ), labor_stores AS ( - SELECT store_name, received, + SELECT sales_store_name AS store_name, received, round(wage_expense::numeric, 2) AS wage, - round((wage_expense / nullif(received, 0) * 100)::numeric, 2) AS wage_rate - FROM standard_stores - WHERE wage_expense IS NOT NULL - ORDER BY wage_expense / nullif(received, 0) DESC + round(wage_rate_pct::numeric, 2) AS wage_rate + FROM analytics.mv_store_operating_expense_monthly + WHERE report_month = $1::date AND wage_expense IS NOT NULL AND wage_expense > 0 AND received > 0 + AND wage_rate_pct > 24 + ORDER BY wage_rate_pct DESC LIMIT 5 ), + -- 能源优化:门店级,只对费率>5%的门店计算机会 energy AS ( SELECT + count(*) FILTER (WHERE utility_rate_pct > 5) AS over_utility_stores, + round(sum(utility_expense) FILTER (WHERE utility_rate_pct > 5)::numeric, 2) AS over_utility_total, + round(sum(utility_expense * GREATEST(1 - 5.0 / nullif(utility_rate_pct, 0), 0)) FILTER (WHERE utility_rate_pct > 5)::numeric, 2) AS opportunity, round(sum(utility_expense)::numeric, 2) AS total_utility, round(sum(received)::numeric, 2) AS total_received - FROM standard_stores + FROM analytics.mv_store_operating_expense_monthly + WHERE report_month = $1::date AND utility_expense IS NOT NULL AND utility_expense > 0 AND received > 0 ), energy_stores AS ( - SELECT store_name, received, + SELECT sales_store_name AS store_name, received, round(utility_expense::numeric, 2) AS utility, - round((utility_expense / nullif(received, 0) * 100)::numeric, 2) AS utility_rate - FROM standard_stores - WHERE utility_expense IS NOT NULL - ORDER BY utility_expense / nullif(received, 0) DESC + round(utility_rate_pct::numeric, 2) AS utility_rate + FROM analytics.mv_store_operating_expense_monthly + WHERE report_month = $1::date AND utility_expense IS NOT NULL AND utility_expense > 0 AND received > 0 + AND utility_rate_pct > 5 + ORDER BY utility_rate_pct DESC LIMIT 5 ), discount AS ( @@ -1230,19 +1234,22 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => { ORDER BY discount_rate_pct DESC LIMIT 5 ), + -- 平台佣金优化:门店级,只对佣金率>20%的门店计算机会 platform AS ( SELECT + count(*) FILTER (WHERE delivery_commission_expense > 0 AND delivery_commission_expense / nullif(received, 0) * 100 > 20) AS over_commission_stores, round(sum(delivery_commission_expense)::numeric, 2) AS total_commission, - round(sum(received)::numeric, 2) AS platform_received + round(sum(received)::numeric, 2) AS platform_received, + round(sum(delivery_commission_expense * GREATEST(1 - 20.0 / nullif(delivery_commission_expense / nullif(received, 0) * 100, 0), 0)) FILTER (WHERE delivery_commission_expense > 0 AND delivery_commission_expense / nullif(received, 0) * 100 > 20)::numeric, 2) AS opportunity FROM analytics.mv_store_operating_expense_monthly - WHERE report_month = $1::date AND delivery_commission_expense IS NOT NULL + WHERE report_month = $1::date AND delivery_commission_expense IS NOT NULL AND received > 0 ), platform_stores AS ( SELECT e.sales_store_code AS store_code, e.received, round(e.delivery_commission_expense::numeric, 2) AS commission, round((e.delivery_commission_expense / nullif(e.received, 0) * 100)::numeric, 2) AS commission_rate FROM analytics.mv_store_operating_expense_monthly e - WHERE e.report_month = $1::date AND e.delivery_commission_expense IS NOT NULL + WHERE e.report_month = $1::date AND e.delivery_commission_expense IS NOT NULL AND e.received > 0 ORDER BY e.delivery_commission_expense / nullif(e.received, 0) DESC LIMIT 5 ), @@ -1264,47 +1271,47 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => { 'items', json_build_array( json_build_object( 'category', '食材成本差异回收', - 'baseline', (SELECT round((actual_cost - theoretical_cost)::numeric, 2) FROM cost_diff), + 'baseline', (SELECT total_positive_variance FROM cost_diff), 'target_pct', 30, - 'opportunity', GREATEST(round((SELECT (actual_cost - theoretical_cost) FROM cost_diff) * 0.30, 2), 0), + 'opportunity', round((SELECT total_positive_variance FROM cost_diff) * 0.30, 2), 'confidence', '中高', 'owner', '商品/供应链/门店', 'evidence', '采购价差、用量差、盘点差、报损差', 'detail', (SELECT - '实际食材成本' || (SELECT actual_cost FROM cost_diff) || '元 vs 理论成本' || (SELECT theoretical_cost FROM cost_diff) || '元,差异' || round((SELECT actual_cost - theoretical_cost FROM cost_diff)::numeric, 2) || '元(成本率' || round((SELECT actual_cost FROM cost_diff) / nullif((SELECT sum(received) FROM standard_stores), 0) * 100, 2) || '% vs 理论' || round((SELECT theoretical_cost FROM cost_diff) / nullif((SELECT sum(received) FROM standard_stores), 0) * 100, 2) || '%)。\n\n' || - '差异TOP5门店(需优先排查):\n' || + '超耗门店' || (SELECT over_cost_stores FROM cost_diff) || '家,实际食材成本' || (SELECT total_actual_cost FROM cost_diff) || '元 vs 理论成本' || (SELECT total_theoretical_cost FROM cost_diff) || '元,正差异合计' || (SELECT total_positive_variance FROM cost_diff) || '元。\n\n' || + '超耗TOP5门店(需优先排查):\n' || string_agg(store_name || ':差异' || diff_amount || '元(实际成本率' || actual_cost_rate || '% vs 理论' || theoretical_cost_rate || '%,实收' || received || '元)', ';\n') || - '\n\n行动指向:\n1)上述5家门店实际成本率均远超理论值,需逐店排查采购单价与BOM标准价差异;\n2)盘点差——核查月末盘点与系统库存一致性,差异>3%需复盘;\n3)报损差——对比报损记录与行业基准,报损率>2%的门店需检查存储和加工流程;\n4)30天目标:将TOP5门店成本率降低5个百分点,预计回收' || round((SELECT (actual_cost - theoretical_cost) FROM cost_diff) * 0.30, 2) || '元。' + '\n\n行动指向:\n1)上述5家门店实际成本率均远超理论值,需逐店排查采购单价与BOM标准价差异;\n2)盘点差——核查月末盘点与系统库存一致性,差异>3%需复盘;\n3)报损差——对比报损记录与行业基准,报损率>2%的门店需检查存储和加工流程;\n4)30天目标:回收正差异的30%,预计节约' || round((SELECT total_positive_variance FROM cost_diff) * 0.30, 2) || '元。' FROM cost_diff_stores) ), json_build_object( 'category', '标准店人工优化', - 'baseline', (SELECT round(total_wage / total_received * 100, 2) FROM labor), + 'baseline', (SELECT round(total_wage / nullif(total_received, 0) * 100, 2) FROM labor), 'target_pct', 100, - 'opportunity', GREATEST(round((SELECT total_wage * (1 - 24.0 / nullif(total_wage / total_received * 100, 0)) FROM labor), 2), 0), + 'opportunity', (SELECT opportunity FROM labor), 'confidence', '中', 'owner', '运营/人力', 'evidence', '工时、工资、餐段销售与服务质量', 'detail', (SELECT - '标准店人工合计' || (SELECT total_wage FROM labor) || '元,费率' || round((SELECT total_wage FROM labor) / nullif((SELECT total_received FROM labor), 0) * 100, 2) || '%,目标降至24%。\n\n' || - '人工费率TOP5门店(需重点督导):\n' || - string_agg(store_name || ':人工' || wage || '元(费率' || wage_rate || '%,实收' || received || '元)', ';\n') || - '\n\n行动指向:\n1)上述门店人工费率远超24%目标,需核查排班与实际打卡工时,识别冗余工时;\n2)低峰时段用小时工替代月薪员工,预计可降费率2-3个百分点;\n3)对费率>30%的门店启动人效专项督导,要求店长提交排班优化方案;\n4)30天目标:TOP5门店人工费率平均降低2个百分点。' + '人工费率超24%的门店共' || (SELECT over_wage_stores FROM labor) || '家,超标门店人工合计' || (SELECT over_wage_total FROM labor) || '元,理论可优化' || (SELECT opportunity FROM labor) || '元。\n\n' || + '人工费率TOP5超标门店(需重点督导):\n' || + COALESCE(string_agg(store_name || ':人工' || wage || '元(费率' || wage_rate || '%,实收' || received || '元)', ';\n'), '无超标门店') || + '\n\n行动指向:\n1)上述门店人工费率远超24%目标,需核查排班与实际打卡工时,识别冗余工时;\n2)低峰时段用小时工替代月薪员工,预计可降费率2-3个百分点;\n3)对费率>30%的门店启动人效专项督导,要求店长提交排班优化方案;\n4)30天目标:将超标门店人工费率降至24%以内。' FROM labor_stores) ), json_build_object( 'category', '标准店能源优化', - 'baseline', (SELECT round(total_utility / total_received * 100, 2) FROM energy), + 'baseline', (SELECT round(total_utility / nullif(total_received, 0) * 100, 2) FROM energy), 'target_pct', 100, - 'opportunity', GREATEST(round((SELECT total_utility * (1 - 5.0 / nullif(total_utility / total_received * 100, 0)) FROM energy), 2), 0), + 'opportunity', (SELECT opportunity FROM energy), 'confidence', '中', 'owner', '工程/门店', 'evidence', '账单/抄表、面积、营业时长', 'detail', (SELECT - '标准店水电合计' || (SELECT total_utility FROM energy) || '元,费率' || round((SELECT total_utility FROM energy) / nullif((SELECT total_received FROM energy), 0) * 100, 2) || '%,目标降至5%。\n\n' || - '水电费率TOP5门店(需排查设备):\n' || - string_agg(store_name || ':水电' || utility || '元(费率' || utility_rate || '%,实收' || received || '元)', ';\n') || - '\n\n行动指向:\n1)上述门店水电费率远超5%目标,需排查是否存在设备老化、管道泄漏或空调空转;\n2)对比近3个月水电账单,波动>20%的门店需现场检查;\n3)缩短非营业时段的照明和空调,预计可降费率0.3-0.5个百分点;\n4)30天目标:TOP5门店水电费率平均降低0.5个百分点。' + '水电费率超5%的门店共' || (SELECT over_utility_stores FROM energy) || '家,超标门店水电合计' || (SELECT over_utility_total FROM energy) || '元,理论可优化' || (SELECT opportunity FROM energy) || '元。\n\n' || + '水电费率TOP5超标门店(需排查设备):\n' || + COALESCE(string_agg(store_name || ':水电' || utility || '元(费率' || utility_rate || '%,实收' || received || '元)', ';\n'), '无超标门店') || + '\n\n行动指向:\n1)上述门店水电费率远超5%目标,需排查是否存在设备老化、管道泄漏或空调空转;\n2)对比近3个月水电账单,波动>20%的门店需现场检查;\n3)缩短非营业时段的照明和空调,预计可降费率0.3-0.5个百分点;\n4)30天目标:将超标门店水电费率降至5%以内。' FROM energy_stores) ), json_build_object( @@ -1326,15 +1333,15 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => { 'category', '平台佣金优化', 'baseline', (SELECT round(total_commission / nullif(platform_received, 0) * 100, 2) FROM platform), 'target_pct', 100, - 'opportunity', GREATEST(round((SELECT total_commission * (1 - 20.0 / nullif(total_commission / nullif(platform_received, 0) * 100, 0)) FROM platform), 2), 0), + 'opportunity', (SELECT opportunity FROM platform), 'confidence', '中低', 'owner', '外卖/采购', 'evidence', '平台结算单与订单对账', 'detail', (SELECT - '平台佣金合计' || (SELECT total_commission FROM platform) || '元,佣金率' || round((SELECT total_commission FROM platform) / nullif((SELECT platform_received FROM platform), 0) * 100, 2) || '%。\n\n' || + '平台佣金合计' || (SELECT total_commission FROM platform) || '元,整体佣金率' || round((SELECT total_commission FROM platform) / nullif((SELECT platform_received FROM platform), 0) * 100, 2) || '%,佣金率超20%的门店' || (SELECT over_commission_stores FROM platform) || '家。\n\n' || '佣金费率TOP5门店(需对账核查):\n' || COALESCE(string_agg(store_code || ':佣金' || commission || '元(费率' || commission_rate || '%,实收' || received || '元)', ';\n'), '无数据') || - '\n\n行动指向:\n1)当前整体佣金率6.08%已低于20%目标,暂无大幅优化空间;\n2)上述门店佣金费率偏高,需逐月核对平台结算单与订单明细,识别多扣佣金;\n3)平台活动费与佣金应分离核算,避免活动费被计入佣金;\n4)提升自配送比例,降低对平台配送依赖;\n5)30天目标:完成TOP5门店平台结算单对账。' + '\n\n行动指向:\n1)佣金率超20%的门店需逐月核对平台结算单与订单明细,识别多扣佣金;\n2)平台活动费与佣金应分离核算,避免活动费被计入佣金;\n3)提升自配送比例,降低对平台配送依赖;\n4)30天目标:完成TOP5门店平台结算单对账。' FROM platform_stores) ), json_build_object(