From a6d83be0ad0ea5eac377da895e7a18887a53673e Mon Sep 17 00:00:00 2001 From: freedakgmail Date: Thu, 30 Jul 2026 09:09:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=97=A8=E5=BA=97=E6=88=90=E6=9C=AC?= =?UTF-8?q?=E5=88=86=E6=9E=90Tab=20-=20=E7=94=A8v=5Finventory=5Fcost=5Fcla?= =?UTF-8?q?ssified=5Fapril=E6=9B=BF=E4=BB=A3=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84v=5Fstore=5Fcategory=5Fcost=5Fbenchmark=5Fapril?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/StoreDetailPage.tsx | 8 ++++---- server/src/routes/data.ts | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/client/src/pages/StoreDetailPage.tsx b/client/src/pages/StoreDetailPage.tsx index 0309e05..ea31aea 100644 --- a/client/src/pages/StoreDetailPage.tsx +++ b/client/src/pages/StoreDetailPage.tsx @@ -261,10 +261,10 @@ export function StoreDetailPage() { formatPercent(r.theoretical_cost_rate_pct) }, - { key: 'actual_cost_rate_pct', label: '实际成本率', align: 'right', render: (r) => formatPercent(r.actual_cost_rate_pct) }, - { key: 'variance_pct', label: '差异', align: 'right', render: (r) => 20 ? 'text-red-600' : 'text-green-600'}>{Number(r.variance_pct).toFixed(1)}% }, - { key: 'benchmark_rate_pct', label: '同类基准', align: 'right', render: (r) => formatPercent(r.benchmark_rate_pct) }, + { key: 'consumption_amount', label: '消耗金额', align: 'right', render: (r) => formatCurrency(r.consumption_amount) }, + { key: 'actual_cost_rate_pct', label: '占比', align: 'right', render: (r) => formatPercent(r.actual_cost_rate_pct) }, + { key: 'benchmark_rate_pct', label: '公司基准', align: 'right', render: (r) => formatPercent(r.benchmark_rate_pct) }, + { key: 'variance_pct', label: '差异', align: 'right', render: (r) => 2 ? 'text-red-600' : 'text-green-600'}>{Number(r.variance_pct).toFixed(1)}% }, ]} data={cost.categories} /> diff --git a/server/src/routes/data.ts b/server/src/routes/data.ts index 7879496..975698a 100644 --- a/server/src/routes/data.ts +++ b/server/src/routes/data.ts @@ -383,7 +383,30 @@ router.get('/stores/:code/cost', async (req: AuthRequest, res) => { try { const [costResult, catResult] = await Promise.all([ query(`SELECT * FROM analytics.v_store_theoretical_actual_cost_april WHERE store_code = $1`, [req.params.code]), - query(`SELECT * FROM analytics.v_store_category_cost_benchmark_april WHERE store_code = $1`, [req.params.code]), + query(` + WITH store_cat AS ( + SELECT finance_category AS category_name, + round(sum(consumption_amount)::numeric, 2) AS consumption_amount, + round(sum(consumption_amount)::numeric / nullif(sum(sum(consumption_amount)) OVER (), 0) * 100, 1) AS actual_cost_rate_pct + FROM analytics.v_inventory_cost_classified_april + WHERE sales_store_code = $1 AND finance_category IS NOT NULL + GROUP BY finance_category + ORDER BY sum(consumption_amount) DESC + ), + company_cat AS ( + SELECT finance_category, + round(sum(consumption_amount)::numeric / nullif(sum(sum(consumption_amount)) OVER (), 0) * 100, 1) AS benchmark_rate_pct + FROM analytics.v_inventory_cost_classified_april + WHERE finance_category IS NOT NULL + GROUP BY finance_category + ) + SELECT s.category_name, s.consumption_amount, s.actual_cost_rate_pct, + COALESCE(c.benchmark_rate_pct, 0) AS benchmark_rate_pct, + round(s.actual_cost_rate_pct - COALESCE(c.benchmark_rate_pct, 0), 1) AS variance_pct + FROM store_cat s + LEFT JOIN company_cat c ON s.category_name = c.finance_category + ORDER BY s.consumption_amount DESC + `, [req.params.code]), ]) sendSuccess(res, { cost: costResult.rows[0], categories: catResult.rows }) } catch (err: any) { sendError(res, err.message) }