From 2a3805c44f6ccd64253013b48203580ffd9cb183 Mon Sep 17 00:00:00 2001 From: freedakgmail Date: Tue, 11 Aug 2026 19:54:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E6=80=BB=E8=A7=88API=E4=B8=A4=E4=B8=AA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 费用率公式反转: (1-expense/received)*100 → expense/matched_received*100 修复前: 46.83%(实际是非费用率)→ 修复后: 53.77%(正确费用率) 2. 利润口径不一致: sum(received)含全部94家门店,但费用只含92家匹配门店 修复: 利润和各项率指标分母改为仅匹配门店实收(matched_received) 实际贡献利润: 7,703,472→6,982,110 贡献率: 11.96%→10.96% --- server/src/routes/store-expense.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/server/src/routes/store-expense.ts b/server/src/routes/store-expense.ts index 924bc44..0fe8a82 100644 --- a/server/src/routes/store-expense.ts +++ b/server/src/routes/store-expense.ts @@ -57,6 +57,7 @@ router.get('/overview', async (req: AuthRequest, res) => { round(sum(consumption)::numeric, 2) AS total_consumption, round(sum(discount)::numeric, 2) AS total_discount, round(sum(received)::numeric, 2) AS total_received, + round(sum(received) FILTER (WHERE operating_expense IS NOT NULL)::numeric, 2) AS matched_received, round(sum(received) / nullif(sum(bill_count), 0), 2) AS avg_bill_value, round(sum(operating_expense)::numeric, 2) AS total_expense, round(sum(wage_expense)::numeric, 2) AS total_wage, @@ -70,18 +71,18 @@ router.get('/overview', async (req: AuthRequest, res) => { round(sum(theoretical_cost)::numeric, 2) AS total_theoretical_cost, round(sum(actual_store_contribution)::numeric, 2) AS total_contribution, round(sum(theoretical_store_contribution)::numeric, 2) AS total_theoretical_contribution, - round(sum(received) - sum(theoretical_cost) - sum(operating_expense), 2) AS theoretical_net_profit, - round(sum(received) - sum(actual_food_cost) - sum(operating_expense), 2) AS actual_net_profit, + round(sum(received) FILTER (WHERE operating_expense IS NOT NULL) - sum(theoretical_cost) - sum(operating_expense), 2) AS theoretical_net_profit, + round(sum(received) FILTER (WHERE operating_expense IS NOT NULL) - sum(actual_food_cost) - sum(operating_expense), 2) AS actual_net_profit, round(sum(area_sqm)::numeric, 2) AS total_area, - round((1 - sum(operating_expense) / nullif(sum(received), 0)) * 100, 2) AS overall_expense_rate_pct, - round((1 - (sum(actual_food_cost) + sum(operating_expense)) / nullif(sum(received), 0)) * 100, 2) AS overall_contribution_rate_pct, - round((sum(received) - sum(theoretical_cost) - sum(operating_expense)) / nullif(sum(received), 0) * 100, 2) AS theoretical_net_margin_pct, - round((sum(received) - sum(actual_food_cost) - sum(operating_expense)) / nullif(sum(received), 0) * 100, 2) AS actual_net_margin_pct, - round(sum(theoretical_cost) / nullif(sum(received), 0) * 100, 2) AS theoretical_food_cost_rate_pct, - round(sum(actual_food_cost) / nullif(sum(received), 0) * 100, 2) AS actual_food_cost_rate_pct, - round(sum(wage_expense) / nullif(sum(received), 0) * 100, 2) AS overall_wage_rate_pct, - round(sum(rent_expense) / nullif(sum(received), 0) * 100, 2) AS overall_rent_rate_pct, - round(sum(utility_expense) / nullif(sum(received), 0) * 100, 2) AS overall_utility_rate_pct + round(sum(operating_expense) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS overall_expense_rate_pct, + round((sum(received) FILTER (WHERE operating_expense IS NOT NULL) - sum(actual_food_cost) - sum(operating_expense)) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS overall_contribution_rate_pct, + round((sum(received) FILTER (WHERE operating_expense IS NOT NULL) - sum(theoretical_cost) - sum(operating_expense)) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS theoretical_net_margin_pct, + round((sum(received) FILTER (WHERE operating_expense IS NOT NULL) - sum(actual_food_cost) - sum(operating_expense)) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS actual_net_margin_pct, + round(sum(theoretical_cost) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS theoretical_food_cost_rate_pct, + round(sum(actual_food_cost) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS actual_food_cost_rate_pct, + round(sum(wage_expense) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS overall_wage_rate_pct, + round(sum(rent_expense) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS overall_rent_rate_pct, + round(sum(utility_expense) / nullif(sum(received) FILTER (WHERE operating_expense IS NOT NULL), 0) * 100, 2) AS overall_utility_rate_pct FROM full_scope `, [month]) sendSuccess(res, result.rows[0])