Files
SBrainCO/docs/智脑实施方法论/14-技术参考-数据溯源与API全量清单.md
T

3505 lines
224 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 14 · 技术参考:全页面数据溯源审计报告
> 目标:逐页面、逐指标追溯显示名称 → API → SQL → 数据源表字段,标出跨页面重复指标口径是否一致。
## 一、页面与 API 总览
| 页面 | 文件 | 调用的 API |
|------|------|-----------|
| 总部驾驶舱 | DashboardPage.tsx | /overview, /overview/daily, /stores/risk, /stores/priority, /tasks/loop-health, /stores/quadrant, /platform/economics, /situational-awareness/alerts, /store-expense/overview, /overview/yoy, /overview/mom, /overview/trend, /overview/profit-trend |
| 老板驾驶舱 | BossPage.tsx | /overview, /overview/daily, /store-expense/overview, /overview/profit-waterfall, /stores/risk, /stores/priority, /cost-analysis/store-overview, /store-expense/expense-structure, /overview/store-profit-ranking, /overview/profit-opportunity |
| 门店工作台 | StorePage.tsx | /stores/risk, /stores/priority, /tasks/stores/:code/daily-card, /tasks, /stores/:code/daily, /tasks/followup, /sku/attach, /sku/abc, /stores/:code, /situational-awareness/health-score, /stores/:code/meal-period, /stores/:code/category-mix, /stores/:code/cost, /stores/:code/member, /stores/:code/anomalies, /stores/:code/staffing |
| 成本分析 | CostPage.tsx | /cost/comparison, /cost/inventory, /cost/category-benchmark |
| 成本分析(Tab) | cost-analysis/*.tsx | /cost-analysis/overview, /category-comparison, /margin-deviation, /variance-top, /menu-engineering, /profitability, /pricing, /store-overview, /store-ranking, /bom-*, /packaging-*, /material-*, /data-quality, /unmatched-* |
| 费用分析(Tab) | store-expense/*.tsx | /store-expense/overview, /expense-structure, /store-ranking, /store-contribution, /break-even, /loss-diagnosis, /delivery-commission, /rent-risk, /fixed-variable, /efficiency, /store-evaluation |
| 风险监控 | RiskPage.tsx | /risk/anomaly, /risk/zero-received, /risk/cashier |
| 会员复购 | MemberPage.tsx | /member/comparison, /member/repeat |
| SKU分析 | SKUPage.tsx | /sku/abc, /sku/category |
| 菜单工程 | MenuEngineeringPage.tsx | /analytics-enhanced/menu-engineering/actions |
| 产品生命周期 | ProductLifecyclePage.tsx | /product/products, /product/reviews/overview, /product/reviews |
| BOM穿透 | BomPenetrationPage.tsx | /central-kitchen/bom-penetration |
| 分销对账 | DistributionReconciliationPage.tsx | /distribution/reconciliation |
| 生产计划 | ProductionPlanPage.tsx | /sales-driven/production-plan |
| 任务闭环 | TasksPage.tsx | /tasks |
| 数据导入 | DataImportPage.tsx | /product/import-logs, /product/quality-rules |
| 本体浏览 | OntologyPage.tsx | /tasks/ontology/* |
---
## 二、页面1:总部驾驶舱 DashboardPage
> 无Tab,用 CollapsibleSection 分区。共调用13个API。
### 区块①:门店风险分布条
| 显示指标 | 前端变量 | API | API字段 | SQL来源 | 数据源表.字段 |
|---------|---------|-----|---------|---------|-------------|
| 红色门店数 | `riskSummary['红色']` | /stores/risk | `risk_level` | `SELECT * FROM mv_store_risk_rating_monthly` | analytics.mv_store_risk_rating_monthly.risk_level |
| 黄色门店数 | `riskSummary['黄色']` | 同上 | 同上 | 同上 | 同上 |
| 绿色门店数 | `riskSummary['绿色']` | 同上 | 同上 | 同上 | 同上 |
| 总门店数 | `totalStores = riskRows.length` | 同上 | 行数 | 同上 | 同上 |
### 区块②:态势感知预警
| 显示指标 | 前端变量 | API | API字段 | SQL来源 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 红色预警数 | `alertSummary.red` | /situational-awareness/alerts | `red` | 5类预警合并: 营收异动/成本异动/人力异动/平台依赖/任务逾期 | mv_daily_revenue, dish_cost_analysis_summary, salary_detail_records, mv_store_platform_economics_monthly, store_task |
| 橙色预警数 | `alertSummary.orange` | 同上 | `orange` | 同上 | 同上 |
| 预警总数 | `alertSummary.total` | 同上 | `total` | 同上 | 同上 |
| 预警列表(前6条) | `alerts.slice(0,6)` | 同上 | `alerts[]` | 同上,每条含 title/detail/level | 同上 |
### 区块③:经营指标
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表.字段 |
|---------|---------|-----|---------|---------|-------------|
| 营业收入 | `od?.consumption` | /overview | `consumption` | `sum(consumption)` | analytics.bill_fact.consumption |
| 优惠 | `od?.discount` | /overview | `discount` | `sum(discount_total)` | analytics.bill_fact.discount_total |
| 实收 | `od?.received` | /overview | `received` | `sum(received_total)` | analytics.bill_fact.received_total |
| 账单数 | `od?.bill_count` | /overview | `bill_count` | `count(*)` | analytics.bill_fact |
| 客单价 | `od?.avg_bill_value` | /overview | `avg_bill_value` | `avg(received_total)` | analytics.bill_fact.received_total |
| P0+P1门店数 | `p0p1Stores.length` | /stores/priority | 行数过滤 | `action_priority LIKE 'P0%' OR 'P1%'` | analytics.mv_store_action_priority_deep_monthly.action_priority |
| 实收环比趋势 | `trendReceived` | /overview/daily | 前端计算 | `(last7days.received - prev7days.received) / prev7days.received * 100` | analytics.mv_overview_daily.received |
| 账单数环比趋势 | `trendBills` | /overview/daily | 前端计算 | 同上,用 bill_count | analytics.mv_overview_daily.bill_count |
| 客单价环比趋势 | `trendAvgBill` | /overview/daily | 前端计算 | `last7.avg - prev7.avg` | analytics.mv_overview_daily |
### 区块④:运营指标
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表.字段 |
|---------|---------|-----|---------|---------|-------------|
| 优惠率 | `od?.discount_rate_pct` | /overview | `discount_rate_pct` | `avg(discount_total / consumption * 100)` per bill | analytics.bill_fact.discount_total, consumption |
| 理论毛利率 | `od?.theoretical_margin_pct` | /overview | `theoretical_margin_pct` | `sum(theoretical_profit) / sum(received_total) * 100` | analytics.bill_fact.theoretical_profit, received_total |
| 会员占比 | `od?.member_share_pct` | /overview | `member_share_pct` | `sum(received_total) FILTER(member_id非空) / sum(received_total) * 100` | analytics.bill_fact.received_total, member_id |
### 区块⑤:同比/环比对比
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 实收同比% | `yoyData_.received_change_pct` | /overview/yoy | `yoy.received_change_pct` | `(当月received - 去年同月received) / |去年同月received| * 100` | analytics.mv_overview_monthly.received |
| 实收环比% | `momData_.received_change_pct` | /overview/mom | `mom.received_change_pct` | `(当月received - 上月received) / |上月received| * 100` | analytics.mv_overview_monthly.received |
| 账单数同比% | `yoyData_.bill_count_change_pct` | /overview/yoy | `yoy.bill_count_change_pct` | 同上,用 bill_count | analytics.mv_overview_monthly.bill_count |
| 账单数环比% | `momData_.bill_count_change_pct` | /overview/mom | `mom.bill_count_change_pct` | 同上 | analytics.mv_overview_monthly.bill_count |
| 客单价同比% | `yoyData_.avg_bill_value_change_pct` | /overview/yoy | `yoy.avg_bill_value_change_pct` | 同上,用 avg_bill_value | analytics.mv_overview_monthly.avg_bill_value |
| 客单价环比% | `momData_.avg_bill_value_change_pct` | /overview/mom | `mom.avg_bill_value_change_pct` | 同上 | analytics.mv_overview_monthly.avg_bill_value |
| 优惠率同比变化 | `yoyData_.discount_rate_change` | /overview/yoy | `yoy.discount_rate_change` | `当月discount_rate_pct - 去年同月discount_rate_pct` | analytics.mv_overview_monthly.discount_rate_pct |
| 优惠率环比变化 | `momData_.discount_rate_change` | /overview/mom | `mom.discount_rate_change` | `当月 - 上月` | analytics.mv_overview_monthly.discount_rate_pct |
### 区块⑥:利润与成本
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 理论贡献利润 | `ex.theoretical_net_profit` | /store-expense/overview | `theoretical_net_profit` | `sum(received) FILTER(费用匹配) - sum(theoretical_cost) - sum(operating_expense)` | mv_store_risk_rating_monthly.received + mv_store_operating_expense_monthly |
| 理论贡献率 | `ex.theoretical_net_margin_pct` | 同上 | `theoretical_net_margin_pct` | `theoretical_net_profit / matched_received * 100` | 同上 |
| 实际贡献利润 | `ex.actual_net_profit` | 同上 | `actual_net_profit` | `sum(received) FILTER(费用匹配) - sum(actual_food_cost) - sum(operating_expense)` | 同上 |
| 实际贡献率 | `ex.actual_net_margin_pct` | 同上 | `actual_net_margin_pct` | `actual_net_profit / matched_received * 100` | 同上 |
| 盈利门店数 | `ex.profitable_stores` | 同上 | `profitable_stores` | `count(*) FILTER(actual_store_contribution > 0)` | 同上 |
| 亏损门店数 | `ex.loss_stores` | 同上 | `loss_stores` | `count(*) FILTER(actual_store_contribution <= 0 AND received > 0)` | 同上 |
| 食材成本 | `ex.total_food_cost` | 同上 | `total_food_cost` | `sum(actual_food_cost)` | mv_store_operating_expense_monthly.actual_food_cost |
| 实际食材成本率 | `ex.actual_food_cost_rate_pct` | 同上 | `actual_food_cost_rate_pct` | `sum(actual_food_cost) / matched_received * 100` | 同上 |
| 理论食材成本率 | `ex.theoretical_food_cost_rate_pct` | 同上 | `theoretical_food_cost_rate_pct` | `sum(theoretical_cost) / matched_received * 100` | 同上 |
| 经营费用 | `ex.total_expense` | 同上 | `total_expense` | `sum(operating_expense)` | mv_store_operating_expense_monthly.operating_expense |
| 费用率 | `ex.overall_expense_rate_pct` | 同上 | `overall_expense_rate_pct` | `sum(operating_expense) / matched_received * 100` | 同上 |
| 人工费用 | `ex.total_wage` | 同上 | `total_wage` | `sum(wage_expense)` | mv_store_operating_expense_monthly.wage_expense |
| 人工费率 | `ex.overall_wage_rate_pct` | 同上 | `overall_wage_rate_pct` | `sum(wage_expense) / matched_received * 100` | 同上 |
| 房租费用 | `ex.total_rent` | 同上 | `total_rent` | `sum(rent_expense)` | mv_store_operating_expense_monthly.rent_expense |
| 房租费率 | `ex.overall_rent_rate_pct` | 同上 | `overall_rent_rate_pct` | `sum(rent_expense) / matched_received * 100` | 同上 |
| 水电费用 | `ex.total_utility` | 同上 | `total_utility` | `sum(utility_expense)` | mv_store_operating_expense_monthly.utility_expense |
| 水电费率 | `ex.overall_utility_rate_pct` | 同上 | `overall_utility_rate_pct` | `sum(utility_expense) / matched_received * 100` | 同上 |
| 外卖佣金 | `ex.total_commission` | 同上 | `total_commission` | `sum(delivery_commission_expense)` | mv_store_operating_expense_monthly.delivery_commission_expense |
| 外卖佣金占实收 | 前端计算 | 同上 | 前端除法 | `total_commission / total_received * 100` | 同上 |
### 区块⑦:闭环健康度
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 综合得分 | `loopScore` (前端5项平均) | /tasks/loop-health | 5个rate字段 | analytics.mv_loop_health |
| 任务生成率 | `lh.task_generation_rate` | 同上 | `task_generation_rate` | analytics.mv_loop_health.task_generation_rate |
| 店长执行率 | `lh.store_execution_rate` | 同上 | `store_execution_rate` | analytics.mv_loop_health.store_execution_rate |
| 区域周检率 | `lh.weekly_check_rate` | 同上 | `weekly_check_rate` | analytics.mv_loop_health.weekly_check_rate |
| 月度验收率 | `lh.monthly_review_rate` | 同上 | `monthly_review_rate` | analytics.mv_loop_health.monthly_review_rate |
| 经验推广率 | `lh.practice_promotion_rate` | 同上 | `practice_promotion_rate` | analytics.mv_loop_health.practice_promotion_rate |
### 区块⑧:图表区 - 日度实收趋势
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 日度实收折线 | `dailyRows[].received` | /overview/daily | `received` | analytics.mv_overview_daily.received (无scope) 或 bill_fact (有scope) |
| 日期 | `dailyRows[].business_date` | 同上 | `business_date` | analytics.mv_overview_daily.business_date |
### 区块⑨:图表区 - 门店风险分级分布
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 风险分布饼图 | `riskPieData` | /stores/risk | `risk_level` | analytics.mv_store_risk_rating_monthly.risk_level |
### 区块⑩:图表区 - 门店经营象限
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| X轴: 日均实收 | `quadrantScatterData.x = r.avg_daily_received` | /stores/quadrant | `avg_daily_received` | analytics.mv_store_benchmark_composite_monthly.avg_daily_received |
| Y轴: 毛利率 | `quadrantScatterData.y = r.theoretical_margin_pct` | 同上 | `theoretical_margin_pct` | analytics.mv_store_benchmark_composite_monthly.theoretical_margin_pct |
| 象限分类 | `r.management_quadrant` | 同上 | `management_quadrant` | analytics.mv_store_benchmark_composite_monthly.management_quadrant |
### 区块⑪:图表区 - P0/P1实收覆盖瀑布图
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| P0门店实收 | `p0Received` | /stores/priority | `received` | `sum(received) FILTER(action_priority LIKE 'P0%')` | analytics.mv_store_action_priority_deep_monthly.received |
| P1门店实收 | `p1Received` | /stores/priority | `received` | `sum(received) FILTER(action_priority LIKE 'P1%')` | 同上 |
| 其他门店实收 | `otherReceived = od.received - p0Received - p1Received` | /overview + /stores/priority | 前端计算 | `总实收 - P0 - P1` | bill_fact + mv_store_action_priority_deep_monthly |
| 合计实收 | `od?.received` | /overview | `received` | `sum(received_total)` | analytics.bill_fact.received_total |
### 区块⑫:图表区 - 平台成本率TOP15
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 美团成本率 | `r.meituan_cost_rate_pct` | /platform/economics | `meituan_cost_rate_pct` | analytics.mv_store_platform_economics_monthly.meituan_cost_rate_pct |
| 饿了么成本率 | `r.eleme_cost_rate_pct` | 同上 | `eleme_cost_rate_pct` | analytics.mv_store_platform_economics_monthly.eleme_cost_rate_pct |
| 抖音成本率 | `r.douyin_cost_rate_pct` | 同上 | `douyin_cost_rate_pct` | analytics.mv_store_platform_economics_monthly.douyin_cost_rate_pct |
### 区块⑬:月度经营趋势
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 月度实收折线 | `trendRows[].received` | /overview/trend | `received` | analytics.mv_overview_monthly.received |
| 月度账单数柱状 | `trendRows[].bill_count` | 同上 | `bill_count` | analytics.mv_overview_monthly.bill_count |
### 区块⑭:利润趋势
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 月度实收 | `profitTrendRows[].received` | /overview/profit-trend | `received` | `sum(r.received)` | v_store_scorecard.received |
| 食材成本 | `profitTrendRows[].food_cost` | 同上 | `food_cost` | `sum(COALESCE(e.actual_food_cost, 0))` | mv_store_operating_expense_monthly.actual_food_cost |
| 人工 | `profitTrendRows[].wage` | 同上 | `wage` | `sum(COALESCE(e.wage_expense, 0))` | mv_store_operating_expense_monthly.wage_expense |
| 房租 | `profitTrendRows[].rent` | 同上 | `rent` | `sum(COALESCE(e.rent_expense, 0))` | mv_store_operating_expense_monthly.rent_expense |
| 水电 | `profitTrendRows[].utility` | 同上 | `utility` | `sum(COALESCE(e.utility_expense, 0))` | mv_store_operating_expense_monthly.utility_expense |
| 贡献利润 | `profitTrendRows[].store_contribution` | 同上 | `store_contribution` | `sum(r.received) - sum(food_cost) - sum(operating_expense)` | 同上 |
> ⚠️ **profit-trend 口径注意**:该API用 `v_store_scorecard` JOIN `mv_store_operating_expense_monthly`WHERE `e.report_month >= $1 AND <= $2`。LEFT JOIN后WHERE过滤掉无费用门店(e.report_month IS NULL),所以实际只含费用匹配门店,口径与 store-expense/overview 一致。但COALESCE(...,0)是多余的(不会出现NULL行)。
### 区块⑮:P0/P1重点整改门店列表
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 门店名 | `r.store_name` | /stores/priority | `store_name` | analytics.mv_store_action_priority_deep_monthly.store_name |
| 优先级 | `r.action_priority` | 同上 | `action_priority` | 同上 |
| 问题数 | `r.problem_count` | 同上 | `problem_count` | 同上 |
| 问题组合 | `r.problem_combination` | 同上 | `problem_combination` | 同上 |
| 实收 | `r.received` | 同上 | `received` | 同上 |
---
## 三、跨页面核心指标溯源与口径对比
### 3.1 营业收入(消费金额)
| 页面 | 前端变量 | API | API字段 | SQL来源 | 数据源表.字段 |
|------|---------|-----|---------|---------|-------------|
| 总部驾驶舱 | `od?.consumption` | /overview | `consumption` | `sum(consumption)` from bill_fact | analytics.bill_fact.consumption |
| 老板驾驶舱 | `od?.consumption` | /overview | `consumption` | 同上 | 同上 |
| 费用总览Tab | `ov.total_consumption` | /store-expense/overview | `total_consumption` | `sum(consumption)` from bill_fact CTE | analytics.bill_fact.consumption |
**口径:一致 ✅** — 三个页面均来自 `analytics.bill_fact.consumption` 的月度汇总。
### 3.2 优惠
| 页面 | 前端变量 | API | API字段 | SQL来源 | 数据源表.字段 |
|------|---------|-----|---------|---------|-------------|
| 总部驾驶舱 | `od?.discount` | /overview | `discount` | `sum(discount_total)` from bill_fact | analytics.bill_fact.discount_total |
| 老板驾驶舱 | `od?.discount` | /overview | `discount` | 同上 | 同上 |
| 费用总览Tab | `ov.total_discount` | /store-expense/overview | `total_discount` | `sum(discount_total)` from bill_fact CTE | analytics.bill_fact.discount_total |
**口径:一致 ✅**(已修复,BossPage 之前用 `ex?.total_discount` 也一致)
### 3.3 实收
| 页面 | 前端变量 | API | API字段 | SQL来源 | 数据源表.字段 |
|------|---------|-----|---------|---------|-------------|
| 总部驾驶舱 | `od?.received` | /overview | `received` | `sum(received_total)` from bill_fact | analytics.bill_fact.received_total |
| 老板驾驶舱 | `od?.received` | /overview | `received` | 同上 | 同上 |
| 费用总览Tab | `ov.total_received` | /store-expense/overview | `total_received` | `sum(r.received)` from mv_store_risk_rating_monthly | analytics.mv_store_risk_rating_monthly.received |
| 利润瀑布 | `wf.received` | /overview/profit-waterfall | `received` | `sum(r.received)` from mv_store_risk_rating_monthly | analytics.mv_store_risk_rating_monthly.received |
**口径:一致 ✅**(已修复)— `bill_fact.received_total` 汇总和 `mv_store_risk_rating_monthly.received` 汇总结果相同(均为 64,409,339.09),因为后者物化视图就来自前者。
### 3.4 账单数
| 页面 | 前端变量 | API | API字段 | SQL来源 | 数据源表.字段 |
|------|---------|-----|---------|---------|-------------|
| 总部驾驶舱 | `od?.bill_count` | /overview | `bill_count` | `count(*)` from bill_fact | analytics.bill_fact |
| 老板驾驶舱 | `ex?.total_bills` | /store-expense/overview | `total_bills` | `sum(bill_count)` from mv_store_risk_rating_monthly | analytics.mv_store_risk_rating_monthly.bill_count |
**口径:一致 ✅** — 值均为 1,682,360。
### 3.5 客单价
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `od?.avg_bill_value` | /overview | `avg_bill_value` | `avg(received_total)` from bill_fact = 38.29 |
| 老板驾驶舱 | `ex?.avg_bill_value` | /store-expense/overview | `avg_bill_value` | `sum(received)/sum(bill_count)` = 38.29 |
**口径:一致 ✅** — 数学结果相同。
### 3.6 优惠率
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `od?.discount_rate_pct` | /overview | `discount_rate_pct` | `avg(discount_total/consumption*100)` from bill_fact = 19.52% |
| 老板驾驶舱 | 无(不显示此指标) | - | - | - |
**口径:N/A** — 仅总部驾驶舱显示。
### 3.7 理论毛利率
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `od?.theoretical_margin_pct` | /overview | `theoretical_margin_pct` | `sum(theoretical_profit)/sum(received)*100` from bill_fact = 61.84% |
**口径:N/A** — 仅总部驾驶舱显示。(已修复,之前硬编码为0)
### 3.8 会员占比
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `od?.member_share_pct` | /overview | `member_share_pct` | `sum(received_total) FILTER(member_id非空)/sum(received_total)*100` = 14.25% |
**口径:N/A** — 仅总部驾驶舱显示。(已修复,之前硬编码为0)
### 3.9 门店贡献利润(实际)
| 页面 | 前端变量 | API | API字段 | 计算方式 | 值 |
|------|---------|-----|---------|---------|-----|
| 总部驾驶舱 | `ex?.actual_net_profit` | /store-expense/overview | `actual_net_profit` | `sum(received) FILTER(费用匹配) - sum(food_cost) - sum(operating_expense)` | 6,982,110.42 |
| 老板驾驶舱(指标卡) | `ex?.actual_net_profit` | /store-expense/overview | `actual_net_profit` | 同上 | 6,982,110.42 |
| 老板驾驶舱(瀑布图) | `wf.store_contribution` | /overview/profit-waterfall | `store_contribution` | `sum(received) FILTER(has_expense) - sum(food_cost) - sum(operating_expense)` | 6,982,110.42 |
**口径:一致 ✅**(已修复)— 瀑布图已改为仅含 has_expense 门店。
### 3.10 贡献率(实际)
| 页面 | 前端变量 | API | API字段 | 计算方式 | 值 |
|------|---------|-----|---------|---------|-----|
| 总部驾驶舱 | `ex?.actual_net_margin_pct` | /store-expense/overview | `actual_net_margin_pct` | `(matched_received - food_cost - operating_expense)/matched_received*100` | 10.96% |
| 老板驾驶舱(指标卡) | `ex?.actual_net_margin_pct` | /store-expense/overview | `actual_net_margin_pct` | 同上 | 10.96% |
| 老板驾驶舱(瀑布图) | 前端计算 `wf.store_contribution/wf.received*100` | /overview/profit-waterfall | 前端除法 | `6982110/63687977*100` | 10.96% |
**口径:一致 ✅**(已修复)— 瀑布图 received 和 contribution 均改为仅含 has_expense 门店。
### 3.11 食材成本
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `ex?.total_food_cost` | /store-expense/overview | `total_food_cost` | `sum(actual_food_cost)` from mv_store_operating_expense_monthly |
| 老板驾驶舱(瀑布图) | `wf.food_cost` | /overview/profit-waterfall | `food_cost` | `sum(COALESCE(actual_food_cost,0))` — 含2家0值门店 |
| 费用总览Tab | `ov.total_food_cost` | /store-expense/overview | `total_food_cost` | 同总部 |
**口径:一致 ✅** — 2家无费用门店的 food_cost 为 NULL`sum(NULL)` 忽略,`sum(COALESCE(NULL,0))` 加0,结果相同。
### 3.12 食材成本率
| 页面 | 前端变量 | API | API字段 | 计算方式 | 值 |
|------|---------|-----|---------|---------|-----|
| 总部驾驶舱 | `ex?.actual_food_cost_rate_pct` | /store-expense/overview | `actual_food_cost_rate_pct` | `sum(food_cost)/matched_received*100` | 35.26% |
| 老板驾驶舱(瀑布图) | 前端计算 `wf.food_cost/wf.received*100` | /overview/profit-waterfall | 前端除法 | `22459074/63687977*100` | 35.26% |
**口径:一致 ✅**(已修复)— 瀑布图 received 改为 matched 口径。
### 3.13 经营费用
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `ex?.total_expense` | /store-expense/overview | `total_expense` | `sum(operating_expense)` — NULL被sum忽略 |
| 老板驾驶舱(瀑布图) | `wf.total_expense` | /overview/profit-waterfall | `total_expense` | `sum(COALESCE(operating_expense,0))` — 2家为0 |
| 费用总览Tab | `ov.total_expense` | /store-expense/overview | `total_expense` | 同总部 |
**口径:一致 ✅** — sum忽略NULL vs COALESCE加0,结果相同。
### 3.14 费用率
| 页面 | 前端变量 | API | API字段 | 计算方式 | 值 |
|------|---------|-----|---------|---------|-----|
| 总部驾驶舱 | `ex?.overall_expense_rate_pct` | /store-expense/overview | `overall_expense_rate_pct` | `sum(operating_expense)/matched_received*100` | 53.77% |
| 老板驾驶舱(瀑布图) | 前端计算 `wf.total_expense/wf.received*100` | /overview/profit-waterfall | 前端除法 | `34246793/63687977*100` | 53.77% |
| 费用总览Tab | `ov.overall_expense_rate_pct` | /store-expense/overview | `overall_expense_rate_pct` | 同总部 | 53.77% |
**口径:一致 ✅**(已修复)— 瀑布图分母改为 matched 口径。
### 3.15 人工费用 / 人工费率
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `ex?.total_wage` / `ex?.overall_wage_rate_pct` | /store-expense/overview | `total_wage` / `overall_wage_rate_pct` | `sum(wage_expense)` / `sum(wage)/matched_received*100` |
| 老板驾驶舱(瀑布图) | `wf.wage` | /overview/profit-waterfall | `wage` | `sum(COALESCE(wage_expense,0))` |
| 费用总览Tab | `ov.total_wage` / `ov.overall_wage_rate_pct` | /store-expense/overview | 同总部 | 同总部 |
**金额口径:一致 ✅** — sum忽略NULL vs COALESCE加0结果相同。
**费率口径:一致 ✅**(已修复)— 瀑布图 received 改为 matched 口径后,前端除法分母一致。
### 3.16 房租费用 / 房租费率
同 3.15,金额一致,费率已修复一致。
### 3.17 水电费用 / 水电费率
同 3.15,金额一致,费率已修复一致。
### 3.18 外卖佣金
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `ex?.total_commission` | /store-expense/overview | `total_commission` | `sum(delivery_commission_expense)` |
| 老板驾驶舱(瀑布图) | `wf.commission` | /overview/profit-waterfall | `commission` | `sum(COALESCE(delivery_commission_expense,0))` |
**口径:一致 ✅** — 金额相同。
### 3.19 门店数
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `riskRows.length` | /stores/risk | count of rows | `count(*)` from mv_store_risk_rating_monthly |
| 老板驾驶舱 | `ex?.total_stores` | /store-expense/overview | `total_stores` | `count(*)` from full_scope (mv_store_risk_rating_monthly) |
**口径:一致 ✅** — 均为94家。
### 3.20 盈利/亏损门店数
| 页面 | 前端变量 | API | API字段 | 计算方式 |
|------|---------|-----|---------|---------|
| 总部驾驶舱 | `ex?.profitable_stores` / `ex?.loss_stores` | /store-expense/overview | `profitable_stores` / `loss_stores` | `count(*) FILTER(actual_store_contribution>0)` / `count(*) FILTER(<=0 AND received>0)` |
| 老板驾驶舱 | 同上 | 同上 | 同上 | 同上 |
**口径:一致 ✅** — 盈利78家/亏损13家。
### 3.21 门店风险分布
| 页面 | 前端变量 | API | 数据源 |
|------|---------|-----|--------|
| 总部驾驶舱 | `riskSummary` | /stores/risk | analytics.mv_store_risk_rating_monthly.risk_level |
| 老板驾驶舱 | `riskSummary` | /stores/risk | 同上 |
**口径:一致 ✅**
### 3.22 P0/P1 门店
| 页面 | 前端变量 | API | 数据源 |
|------|---------|-----|--------|
| 总部驾驶舱 | `priorityRows` | /stores/priority | analytics.mv_store_action_priority_deep_monthly |
| 老板驾驶舱 | `priorityRows` | /stores/priority | 同上 |
**口径:一致 ✅**
### 3.23 成本异常分布
| 页面 | 前端变量 | API | 数据源 |
|------|---------|-----|--------|
| 老板驾驶舱 | `costOv.red_count` 等 | /cost-analysis/store-overview | analytics.mv_store_theoretical_actual_cost_monthly |
| 成本分析Tab | 同上 | 同上 | 同上 |
**口径:一致 ✅**
### 3.24 利润机会池
| 页面 | 前端变量 | API | 数据源 |
|------|---------|-----|--------|
| 老板驾驶舱 | `profitOpp` | /overview/profit-opportunity | analytics.mv_store_risk_rating_monthly + mv_store_operating_expense_monthly |
**口径:N/A** — 仅老板驾驶舱显示。
### 3.25 日度趋势
| 页面 | 前端变量 | API | 数据源 |
|------|---------|-----|--------|
| 总部驾驶舱 | `dailyRows` | /overview/daily | analytics.mv_overview_daily 或 bill_fact |
| 老板驾驶舱 | `dailyRows` | /overview/daily | 同上 |
**口径:一致 ✅**
---
## 三、发现的问题汇总
### 问题1:利润瀑布 store_contribution 口径不一致 ✅ 已修复
**位置**`/overview/profit-waterfall` API (`data.ts:1082`)
**已修复**received/consumption/discount/store_contribution 均改为 `FILTER (WHERE has_expense)`,仅含92家匹配门店,与 store-expense/overview 一致。
### 问题2:利润瀑布各项费率分母不一致 ✅ 已修复
**已修复**:瀑布图 received 改为 matched 口径后,前端除法分母与 store-expense/overview 一致。
### 问题3mv_overview_monthly 刷新机制不完整 ✅ 已修复
**现状**`mv_overview_monthly` 是普通表(非物化视图),之前刷新脚本未包含它的更新步骤,导致数据导入后该表不更新。
**已修复**:刷新脚本已加入 DELETE+INSERT 步骤。
### 问题4:KPI达成率利润口径不一致 ⚠️ 待确认
**位置**`/analytics-enhanced/kpi` API (`analytics-enhanced.ts:944-953`)
**现状**
- `actual_revenue = sum(r.received)` — 包含全部94家门店
- `actual_profit = sum(e.actual_store_contribution)` — LEFT JOIN后NULL被sum忽略,实际只含92家有费用门店
- `profit_margin_pct = actual_profit / actual_revenue * 100` — 分子92家/分母94家,偏低
**影响**:利润率指标偏低(分母多含2家无费用门店的实收429万),但影响较小(~0.7pp)。
### 问题5store-profit-ranking 用COALESCE导致无费用门店排名异常 ⚠️ 待确认
**位置**`/overview/store-profit-ranking` API (`data.ts:1094-1112`)
**现状**`store_contribution = r.received - COALESCE(e.actual_food_cost,0) - COALESCE(e.operating_expense,0)`,2家无费用门店的贡献利润 = received - 0 - 0 = received(全部实收算作利润),会排在利润TOP5最前面。
**影响**BossPage利润排名TOP5可能包含无费用门店,显示"贡献利润=实收"的异常数据。
### 问题6StorePage公司均值硬编码 ⚠️ 待确认
**位置**`StorePage.tsx:188``companyAvg` 对象
**现状**:8个指标的公司均值全部硬编码(日均营收20144、客单价36.8、优惠率20.5%等),不随数据更新而变化。
**影响**:如果实际公司均值发生变化,偏差对比将不准确。建议改为API动态获取。
### 问题7StorePage scorecard 直接查bill_records而非物化视图 ⚠️ 待确认
**位置**`/stores/:code` API (`data.ts:177-196`)
**现状**scorecard 直接从 `bill_records` 原始表聚合查询,而其他页面用 `mv_store_risk_rating_monthly` 物化视图。
**影响**:两表同月数据应一致,但若物化视图刷新不及时可能存在差异。且直接查原始表性能较差。
---
## 四、页面2:老板驾驶舱 BossPage
> 无Tab,用区块布局。共调用10个API + KPISection组件1个API。
### 区块①:核心经营指标(6个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 营业收入 | `od?.consumption` | /overview | `consumption` | `sum(consumption)` | analytics.bill_fact.consumption |
| 优惠 | `od?.discount` | /overview | `discount` | `sum(discount_total)` | analytics.bill_fact.discount_total |
| 实收总额 | `od?.received` | /overview | `received` | `sum(received_total)` | analytics.bill_fact.received_total |
| 实收环比趋势 | `trendReceived` | /overview/daily | 前端计算 | `(last7.received - prev7.received) / prev7.received * 100` | analytics.mv_overview_daily.received |
| 门店贡献利润估算 | `ex?.actual_net_profit` | /store-expense/overview | `actual_net_profit` | `sum(received) FILTER(费用匹配) - sum(food_cost) - sum(operating_expense)` | mv_store_risk_rating_monthly + mv_store_operating_expense_monthly |
| 贡献率(描述) | `ex?.actual_net_margin_pct` | /store-expense/overview | `actual_net_margin_pct` | `actual_net_profit / matched_received * 100` | 同上 |
| 门店数 | `ex?.total_stores` | /store-expense/overview | `total_stores` | `count(*)` | mv_store_risk_rating_monthly |
| 盈利门店数(描述) | `ex?.profitable_stores` | /store-expense/overview | `profitable_stores` | `count(*) FILTER(actual_store_contribution > 0)` | 同上 |
| 亏损门店数(描述) | `ex?.loss_stores` | /store-expense/overview | `loss_stores` | `count(*) FILTER(<=0 AND received>0)` | 同上 |
| 客单价 | `ex?.avg_bill_value` | /store-expense/overview | `avg_bill_value` | `sum(received) / sum(bill_count)` | 同上 |
| 客单价环比趋势 | 前端计算 | /overview/daily | 前端计算 | `last7.avg - prev7.avg` | analytics.mv_overview_daily |
| 账单数(描述) | `ex?.total_bills` | /store-expense/overview | `total_bills` | `sum(bill_count)` | mv_store_risk_rating_monthly.bill_count |
### 区块①bKPI达成率(KPISection组件)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 收入达成率 | `kpi.revenue_achievement_pct` | /analytics-enhanced/kpi | `revenue_achievement_pct` | `sum(r.received) / sum(t.revenue_target) * 100` | mv_store_risk_rating_monthly.received + dim_store_target.revenue_target |
| 利润达成率 | `kpi.profit_achievement_pct` | 同上 | `profit_achievement_pct` | `sum(e.actual_store_contribution) / sum(t.profit_target) * 100` | mv_store_operating_expense_monthly.actual_store_contribution + dim_store_target.profit_target |
| 目标收入 | `kpi.revenue_target` | 同上 | `revenue_target` | `sum(t.revenue_target)` | dim_store_target.revenue_target |
| 利润率 | `kpi.profit_margin_pct` | 同上 | `profit_margin_pct` | `sum(actual_store_contribution) / sum(received) * 100` | 同上 |
| 目标利润(图表) | `kpi.profit_target` | 同上 | `profit_target` | `sum(t.profit_target)` | dim_store_target.profit_target |
| 实际收入(图表) | `kpi.actual_revenue` | 同上 | `actual_revenue` | `sum(r.received)` | mv_store_risk_rating_monthly.received |
| 实际利润(图表) | `kpi.actual_profit` | 同上 | `actual_profit` | `sum(e.actual_store_contribution)` | mv_store_operating_expense_monthly.actual_store_contribution |
> ⚠️ **KPI口径注意**`actual_profit` 用 `sum(e.actual_store_contribution)`LEFT JOIN 费用表后无费用门店的 `actual_store_contribution` 为 NULL`sum(NULL)` 忽略,所以实际只含有费用的门店。但 `actual_revenue` 用 `sum(r.received)` 包含全部94家。利润率分母含全部门店收入,分子仅含费用匹配门店利润,可能偏低。
### 区块②:利润结构(瀑布图)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 营业收入 | `wf.consumption` | /overview/profit-waterfall | `consumption` | `sum(consumption) FILTER(has_expense)` | bill_fact.consumption |
| 优惠 | `wf.discount` | 同上 | `discount` | `sum(discount) FILTER(has_expense)` | bill_fact.discount_total |
| 实收 | `wf.received` | 同上 | `received` | `sum(received) FILTER(has_expense)` | mv_store_risk_rating_monthly.received |
| 食材成本 | `wf.food_cost` | 同上 | `food_cost` | `sum(actual_food_cost)` | mv_store_operating_expense_monthly.actual_food_cost |
| 人工 | `wf.wage` | 同上 | `wage` | `sum(wage_expense)` | mv_store_operating_expense_monthly.wage_expense |
| 房租 | `wf.rent` | 同上 | `rent` | `sum(rent_expense)` | mv_store_operating_expense_monthly.rent_expense |
| 水电 | `wf.utility` | 同上 | `utility` | `sum(utility_expense)` | mv_store_operating_expense_monthly.utility_expense |
| 宿舍 | `wf.dorm` | 同上 | `dorm` | `sum(dorm_expense)` | mv_store_operating_expense_monthly.dorm_expense |
| 外卖佣金 | `wf.commission` | 同上 | `commission` | `sum(delivery_commission_expense)` | mv_store_operating_expense_monthly.delivery_commission_expense |
| 其他费用 | `wf.other_expense` | 同上 | `other_expense` | `sum(card_fee + repair_clean)` | mv_store_operating_expense_monthly.card_fee_expense + repair_clean_expense |
| 门店贡献利润 | `wf.store_contribution` | 同上 | `store_contribution` | `sum(received) FILTER(has_expense) - sum(food_cost) - sum(operating_expense)` | 同上 |
| 食材成本率(占比条) | 前端计算 | 同上 | 前端除法 | `wf.food_cost / wf.received * 100` | 同上 |
| 人工费率(占比条) | 前端计算 | 同上 | 前端除法 | `wf.wage / wf.received * 100` | 同上 |
| 房租费率(占比条) | 前端计算 | 同上 | 前端除法 | `wf.rent / wf.received * 100` | 同上 |
| 水电费率(占比条) | 前端计算 | 同上 | 前端除法 | `wf.utility / wf.received * 100` | 同上 |
| 其他费率(占比条) | 前端计算 | 同上 | 前端除法 | `(dorm+commission+other) / wf.received * 100` | 同上 |
| 贡献率(占比条) | 前端计算 | 同上 | 前端除法 | `wf.store_contribution / wf.received * 100` | 同上 |
| 费用率(汇总) | 前端计算 | 同上 | 前端除法 | `wf.total_expense / wf.received * 100` | 同上 |
> ✅ **口径已修复**:所有指标均基于 `FILTER(has_expense)` 的92家门店,与 store-expense/overview 一致。
### 区块②b:利润机会池
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 机会类别 | `o.category` | /overview/profit-opportunity | `items[].category` | 5类: 食材成本差异/人工优化/水电节能/优惠管控/外卖佣金/SKU精简 | 多表 |
| 机会金额 | `o.opportunity` | 同上 | `items[].opportunity` | 各类不同公式,如食材=diff*30% | mv_store_risk_rating_monthly + mv_store_operating_expense_monthly + mv_dish_sku_abc_monthly |
| 基线值 | `o.baseline` | 同上 | `items[].baseline` | 各类基线值 | 同上 |
| 信心度 | `o.confidence` | 同上 | `items[].confidence` | 中高/中/低 | — |
| 责任方 | `o.owner` | 同上 | `items[].owner` | 静态文本 | — |
| 验收标准 | `o.evidence` | 同上 | `items[].evidence` | 静态文本 | — |
| 详情 | `o.detail` | 同上 | `items[].detail` | 动态拼接TOP5门店数据 | 同上 |
| 机会合计 | `totalOpportunity` | 前端计算 | — | `sum(items[].opportunity)` | 同上 |
| 30天承诺值 | 静态文本 | — | — | `≥ 150万元`(硬编码) | — |
| 占比 | `pct` | 前端计算 | — | `opp / totalOpportunity * 100` | 同上 |
### 区块③:风险态势(3个卡片)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 红色门店数 | `riskSummary['红色']` | /stores/risk | `risk_level` count | `count(*) FILTER(risk_level='红色')` | mv_store_risk_rating_monthly.risk_level |
| 红色门店实收 | `riskRevSummary['红色']` | 同上 | `received` sum | `sum(received) FILTER(risk_level='红色')` | mv_store_risk_rating_monthly.received |
| 红色实收占比 | 前端计算 | 同上 | 前端除法 | `riskRevSummary['红色'] / od.received * 100` | 同上 |
| 黄色门店数 | `riskSummary['黄色']` | 同上 | 同上 | 同上 | 同上 |
| 黄色门店实收 | `riskRevSummary['黄色']` | 同上 | 同上 | 同上 | 同上 |
| 黄色实收占比 | 前端计算 | 同上 | 前端除法 | 同上 | 同上 |
| 绿色门店数 | `riskSummary['绿色']` | 同上 | 同上 | 同上 | 同上 |
| 绿色门店实收 | `riskRevSummary['绿色']` | 同上 | 同上 | 同上 | 同上 |
| 绿色实收占比 | 前端计算 | 同上 | 前端除法 | 同上 | 同上 |
> ✅ **口径已修复**:实收占比分母用 `od.received`/overview API),与DashboardPage一致。
### 区块③b:重点整改门店清单
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 门店名 | `s.store_name` | /stores/priority | `store_name` | mv_store_action_priority_deep_monthly.store_name |
| 实收 | `s.received` | 同上 | `received` | 同上.received |
| 优先级 | `s.action_priority` | 同上 | `action_priority` | 同上.action_priority |
| 风险等级 | `s.risk_level` | 同上 | `risk_level` | 同上.risk_level |
| 问题组合 | `s.problem_combination` | 同上 | `problem_combination` | 同上.problem_combination |
### 区块④:成本异常分布
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 严重超耗门店数 | `costOv.red_count` | /cost-analysis/store-overview | `red_count` | `count(*) FILTER(variance_level='红色-严重超耗')` | mv_store_theoretical_actual_cost_monthly.variance_level |
| 明显超耗门店数 | `costOv.orange_count` | 同上 | `orange_count` | `count(*) FILTER(variance_level='橙色-明显超耗')` | 同上 |
| 基本正常门店数 | `costOv.green_count` | 同上 | `green_count` | `count(*) FILTER(variance_level='绿色-基本正常')` | 同上 |
| 总差异金额 | `costOv.total_variance` | 同上 | `total_variance` | `sum(food_cost_variance)` | mv_store_theoretical_actual_cost_monthly.food_cost_variance |
### 区块④b:费用结构(饼图)
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 费用科目名 | `r.account_name` | /store-expense/expense-structure | `account_name` | v_operating_expense_account_monthly.account_name |
| 费用金额 | `r.amount` | 同上 | `amount` | v_operating_expense_account_monthly.amount |
| 费用占比 | `r.expense_share_pct` | 同上 | `expense_share_pct` | v_operating_expense_account_monthly.expense_share_pct |
### 区块⑤:门店营收排名
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| TOP5门店名 | `r.store_name` | /stores/risk | `store_name` | 前端按 received 降序取前5 | mv_store_risk_rating_monthly |
| TOP5实收 | `r.received` | 同上 | `received` | 同上 | mv_store_risk_rating_monthly.received |
| BOTTOM5门店名 | 同上 | 同上 | 同上 | 前端按 received 升序取末5received>0 | 同上 |
| BOTTOM5实收 | 同上 | 同上 | 同上 | 同上 | 同上 |
### 区块⑤b:门店利润排名
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店名 | `s.store_name` | /overview/store-profit-ranking | `store_name` | — | mv_store_risk_rating_monthly.store_name |
| 实收 | `s.received` | 同上 | `received` | `r.received` | mv_store_risk_rating_monthly.received |
| 贡献利润 | `s.store_contribution` | 同上 | `store_contribution` | `r.received - COALESCE(e.actual_food_cost,0) - COALESCE(e.operating_expense,0)` | 同上 + mv_store_operating_expense_monthly |
| 贡献率 | `s.contribution_margin_pct` | 同上 | `contribution_margin_pct` | `store_contribution / received * 100` | 同上 |
> ⚠️ **store-profit-ranking 口径注意**:用 `COALESCE(...,0)` 处理无费用门店,2家无费用门店的贡献利润 = received - 0 - 0 = received(全部实收算作利润),这会导致排名中这2家门店排在最前面。与 store-expense/overview 的 `actual_net_profit` 口径不一致。
### 区块⑥:日度实收趋势
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 日度实收折线 | `dailyRows[].received` | /overview/daily | `received` | analytics.mv_overview_daily.received |
| 日期 | `dailyRows[].business_date` | 同上 | `business_date` | analytics.mv_overview_daily.business_date |
| 环比上周% | `trendReceived` | 前端计算 | — | `(last7 - prev7) / prev7 * 100` |
### 区块⑦:本月经营态势弹窗(BusinessReportDialog
> 该弹窗接收 BossPage 已加载的数据(od, ex, wf, riskRows, priorityRows, costOv, profitOpp, trendReceived)进行语音播报,不额外调用API。仅调用 `/tts` API进行语音合成。
---
## 五、页面3:门店工作台 StorePage
> 7个Tab + 概览折叠区。共调用13个API(含KPISection组件1个)。
### 概览折叠区(门店概况与经营概览)
#### 门店概况(4个卡片)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店名称 | `storeName` | /stores/risk | `store_name` | — | mv_store_risk_rating_monthly.store_name |
| 风险等级 | `storeInfo.risk` | 同上 | `risk_level` | — | mv_store_risk_rating_monthly.risk_level |
| 主要问题 | `primaryIssue` | /stores/priority | `problem_combination` | — | mv_store_action_priority_deep_monthly.problem_combination |
| 任务完成率 | 前端计算 | /tasks | `status` count | `doneTasks / tasks * 100` | analytics.store_task.status |
| 已完成/总数(描述) | 前端计算 | 同上 | 同上 | `doneTasks.length / tasks.length` | 同上 |
#### KPI达成率(KPISection组件,level=store
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 收入达成率 | `kpi.revenue_achievement_pct` | /analytics-enhanced/kpi | `revenue_achievement_pct` | `r.received / t.revenue_target * 100` | mv_store_risk_rating_monthly.received + dim_store_target.revenue_target |
| 利润达成率 | `kpi.profit_achievement_pct` | 同上 | `profit_achievement_pct` | `e.actual_store_contribution / t.profit_target * 100` | mv_store_operating_expense_monthly.actual_store_contribution + dim_store_target.profit_target |
| 目标收入 | `kpi.revenue_target` | 同上 | `revenue_target` | `t.revenue_target` | dim_store_target.revenue_target |
| 利润率 | `kpi.profit_margin_pct` | 同上 | `profit_margin_pct` | `e.actual_store_contribution / r.received * 100` | 同上 |
#### 最新经营日概览(daily-card
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 营业收入(当日) | `item.value`(metric=营业收入) | /tasks/stores/:code/daily-card | `anomalies[].value` | `sum(c009)` 当日 | bill_records.c009 |
| 营业收入基准 | `item.baseline` | 同上 | `anomalies[].baseline` | `sum(c009)` 一周前同日 | bill_records.c009 |
| 营业收入异常 | `item.is_anomaly` | 同上 | `anomalies[].is_anomaly` | `value < baseline * 0.8` | — |
| 优惠(当日) | `item.value`(metric=优惠) | 同上 | 同上 | `sum(c068)` 当日 | bill_records.c068 |
| 优惠异常 | 同上 | 同上 | 同上 | `value > baseline * 1.2` | — |
| 实收(当日) | `item.value`(metric=实收) | 同上 | 同上 | `sum(c114)` 当日 | bill_records.c114 |
| 实收异常 | 同上 | 同上 | 同上 | `value < baseline * 0.8` | — |
| 账单数(当日) | `item.value`(metric=账单数) | 同上 | 同上 | `count(*)` 当日 | bill_records |
| 账单数异常 | 同上 | 同上 | 同上 | `value < baseline * 0.8` | — |
| 客单价(当日) | `item.value`(metric=客单价) | 同上 | 同上 | `sum(c114)/count(*)` 当日 | bill_records |
| 客单价异常 | 同上 | 同上 | 同上 | `value < baseline * 0.9` | — |
| 异常项数 | `anomalyCount` | 前端计算 | — | `count(is_anomaly=true)` | — |
| 对比日期 | `card.target_date` / `card.baseline_date` | 同上 | `target_date` / `baseline_date` | `max(c176::date)` / `target_date - 7天` | bill_records.c176 |
#### 本周指标进度(followup
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 实收(基线/目标/实际) | `followup.baseline_received` / `target_received` / `actual_received` | /tasks/followup | 同名 | analytics.mv_store_monthly_followup |
| 客单价(基线/目标/实际) | `followup.baseline_avg_bill` / `target_avg_bill` / `actual_avg_bill` | 同上 | 同名 | 同上 |
| 优惠率(基线/目标/实际) | `followup.baseline_discount_rate` / `target_discount_rate` / `actual_discount_rate` | 同上 | 同名 | 同上 |
| 毛利率(基线/目标/实际) | `followup.baseline_margin_rate` / `target_margin_rate` / `actual_margin_rate` | 同上 | 同名 | 同上 |
| 达成率 | 前端计算 | — | — | `actual / target * 100` |
| 异常率(基线/目标/实际) | `followup.baseline_anomaly_rate` / `target_anomaly_rate` / `actual_anomaly_rate` | 同上 | 同名 | 同上 |
| 会员占比(基线/目标/实际) | `followup.baseline_member_share` / `target_member_share` / `actual_member_share` | 同上 | 同名 | 同上 |
| 工作重点 | `followup.work_focus` | 同上 | `work_focus` | 同上 |
| 行动计划 | `followup.action_plan` | 同上 | `action_plan` | 同上 |
#### 日度实收趋势
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 日度实收折线 | `dailyRows[].received` | /stores/:code/daily | `received` | bill_records.c114 (按c176::date分组) |
| 日期 | `dailyRows[].business_date` | 同上 | `business_date` | bill_records.c176::date |
#### 核心SKU备货提醒
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| SKU名称 | `s.dish_name` | /sku/abc | `dish_name` | analytics.mv_dish_sku_abc_monthly.dish_name |
| ABC等级 | `s.abc_class` | 同上 | `abc_class` | 同上.abc_class |
| 营收占比 | `s.revenue_share_pct` | 同上 | `revenue_share_pct` | 同上.revenue_share_pct |
| 累计占比 | `s.cumulative_revenue_share_pct` | 同上 | `cumulative_revenue_share_pct` | 同上.cumulative_revenue_share_pct |
> 前端过滤 `abc_class === 'A'` 取前10条。
#### 待办/已完成任务
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 优先级 | `t.priority` | /tasks | `priority` | analytics.store_task.priority |
| 问题指标 | `t.problem_indicator` | 同上 | `problem_indicator` | 同上.problem_indicator |
| 状态 | `t.status` | 同上 | `status` | 同上.status |
| 行动要求 | `t.action_required` | 同上 | `action_required` | 同上.action_required |
| 截止日 | `t.deadline` | 同上 | `deadline` | 同上.deadline |
| 验收结果 | `t.verification_result` | 同上 | `verification_result` | 同上.verification_result |
| 执行证据 | `t.process_evidence` | 同上 | `process_evidence` | 同上.process_evidence |
| 后续计划 | `t.next_step` | 同上 | `next_step` | 同上.next_step |
### 核心指标行(8个MetricCardscorecard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 营业收入 | `sc.consumption` | /stores/:code | `scorecard.consumption` | `sum(c009)` | bill_records.c009 |
| 优惠 | `sc.discount` | 同上 | `scorecard.discount` | `sum(c068)` | bill_records.c068 |
| 月度实收 | `sc.received` | 同上 | `scorecard.received` | `sum(c114)` | bill_records.c114 |
| 月度账单数 | `sc.bill_count` | 同上 | `scorecard.bill_count` | `count(*)` | bill_records |
| 客单价 | `sc.avg_bill_value` | 同上 | `scorecard.avg_bill_value` | `sum(c114)/count(*)` | bill_records |
| 优惠率 | `sc.discount_rate_pct` | 同上 | `scorecard.discount_rate_pct` | `sum(c068)/sum(c009)*100` | bill_records |
| 理论毛利率 | `sc.theoretical_margin_pct` | 同上 | `scorecard.theoretical_margin_pct` | `sum(c181)/sum(c114)*100` | bill_records.c181/c114 |
| 会员占比 | `sc.member_bill_share_pct` | 同上 | `scorecard.member_bill_share_pct` | `count(c185非空)/count(*)*100` | bill_records.c185 |
> ⚠️ **scorecard数据源注意**`/stores/:code` 的 scorecard 直接从 `bill_records` 原始表查询(非物化视图),而其他页面用 `mv_store_risk_rating_monthly`。两表同月数据应一致,但若刷新不及时可能存在差异。
### 风险与经营状态说明
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 风险等级 | `risk.risk_level` | /stores/:code | `risk.risk_level` | mv_store_risk_rating_monthly.risk_level |
| 问题组合 | `action.problem_combination` | 同上 | `action.problem_combination` | mv_store_action_priority_deep_monthly.problem_combination |
| 经营象限 | `action.management_quadrant` | 同上 | `action.management_quadrant` | 后端计算:日均营收 vs 中位18642 & 毛利率 vs 中位70% |
| 规模层级 | `action.scale_tier` | 同上 | `action.scale_tier` | mv_store_action_priority_deep_monthly.scale_tier |
| 综合基准分 | `action.benchmark_score` | 同上 | `benchmark.benchmark_score` | mv_store_benchmark_composite_monthly |
| 行动优先级 | `action.action_priority` | 同上 | `action.action_priority` | mv_store_action_priority_deep_monthly.action_priority |
| 成本差异率 | `action.variance_to_theoretical_pct` | 同上 | `action.variance_to_theoretical_pct` | 同上.variance_to_theoretical_pct |
| 成本差异等级 | `action.variance_level` | 同上 | `action.variance_level` | 同上.variance_level |
#### 月度指标偏差对比(vs 公司均值,硬编码)
| 显示指标 | 前端变量 | 门店值来源 | 公司均值 | 数据源表 |
|---------|---------|-----------|---------|---------|
| 日均营收 | `risk.avg_daily_received` | /stores/:code → risk | 20144(硬编码) | mv_store_risk_rating_monthly.avg_daily_received |
| 客单价 | `risk.avg_bill_value` | 同上 | 36.8(硬编码) | 同上.avg_bill_value |
| 优惠率 | `risk.discount_rate_pct` | 同上 | 20.5(硬编码) | 同上.discount_rate_pct |
| 毛利率 | `risk.theoretical_margin_pct` | 同上 | 71.5(硬编码) | 同上.theoretical_margin_pct |
| 会员占比 | `risk.member_bill_share_pct` | 同上 | 15.9(硬编码) | 同上.member_bill_share_pct |
| 复购率 | `action.repeat_rate_pct` | 同上 → action | 37.0(硬编码) | mv_store_action_priority_deep_monthly.repeat_rate_pct |
| 外卖占比 | `action.delivery_bill_share_pct` | 同上 | 36.1(硬编码) | 同上.delivery_bill_share_pct |
| 成本差异率 | `action.variance_to_theoretical_pct` | 同上 | 20(硬编码) | 同上.variance_to_theoretical_pct |
> ⚠️ **公司均值硬编码**`companyAvg` 对象中的值是硬编码的,不随数据更新而变化。如果实际公司均值发生变化,偏差对比将不准确。
---
### Tab1:概览
#### 健康度评分维度(雷达图 + 进度条)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 健康总分 | `health.health_score` | /situational-awareness/health-score | `health_score` | 8维加权总分(0-100) | 多表联合计算 |
| 营收规模得分 | `health.score_revenue` | 同上 | `score_revenue` | `20 * LEAST(1.0, avg_daily_received/30000)` | mv_store_risk_rating_monthly.avg_daily_received |
| 成本控制得分 | `health.score_cost` | 同上 | `score_cost` | `20 * GREATEST(0, 1 - LEAST(variance,50)/50)` | mv_store_theoretical_actual_cost_monthly.variance_to_theoretical_pct |
| 毛利率得分 | `health.score_margin` | 同上 | `score_margin` | `15 * LEAST(1.0, GREATEST(0, margin-60)/15)` | mv_store_risk_rating_monthly.theoretical_margin_pct |
| 风险控制得分 | `health.score_risk` | 同上 | `score_risk` | 绿色=10, 黄色=6, 红色=2 | mv_store_risk_rating_monthly.risk_level |
| 复购率得分 | `health.score_repeat` | 同上 | `score_repeat` | `10 * LEAST(1.0, repeat_rate/40)` | mv_store_repeat_summary_monthly.repeat_rate_pct |
| 会员占比得分 | `health.score_member` | 同上 | `score_member` | `10 * LEAST(1.0, member_share/20)` | mv_store_risk_rating_monthly.member_bill_share_pct |
| 任务完成得分 | `health.score_task` | 同上 | `score_task` | `10 * completion_rate/100` | analytics.store_task |
| 客单价得分 | `health.score_bill` | 同上 | `score_bill` | `5 * LEAST(1.0, avg_bill_value/40)` | mv_store_risk_rating_monthly.avg_bill_value |
| 健康状态 | `health.health_status` | 同上 | `health_status` | ≥75=健康, ≥55=亚健康, <55=需干预 | — |
#### 平台经济性
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 美团实收 | `sd.platform.meituan_received` | /stores/:code | `platform.meituan_received` | mv_store_platform_economics_monthly.meituan_received |
| 饿了么实收 | `sd.platform.eleme_received` | 同上 | `platform.eleme_received` | 同上.eleme_received |
| 抖音实收 | `sd.platform.douyin_received` | 同上 | `platform.douyin_received` | 同上.douyin_received |
| 平台加权成本率 | `sd.platform.weighted_cost_rate_pct` | 同上 | `platform.weighted_cost_rate_pct` | 同上.weighted_cost_rate_pct |
---
### Tab2:餐段分析
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 餐段 | `mealRows[].meal_period` | /stores/:code/meal-period | `meal_period` | — | analytics.bill_fact.meal_period |
| 账单数 | `mealRows[].bill_count` | 同上 | `bill_count` | `count(*)` | analytics.bill_fact |
| 实收 | `mealRows[].received` | 同上 | `received` | `sum(received_total)` | analytics.bill_fact.received_total |
| 平均单价 | `mealRows[].avg_bill` | 同上 | `avg_bill` | `sum(received_total)/count(*)` | 同上 |
| 全网均价 | `mealRows[].network_avg_bill` | 同上 | `network_avg_bill` | 全门店同餐段 `sum(received)/count(*)` | analytics.bill_fact |
| vs全网% | `mealRows[].vs_network_pct` | 同上 | `vs_network_pct` | `(store.avg / network.avg - 1) * 100` | — |
---
### Tab3:品类结构
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店各品类金额 | `s.lanzhou_noodle` 等 | /stores/:code/category-mix | `store.*` | 各品类 `sum(consumption)` | mv_store_category_mix_monthly |
| 公司各品类金额 | `c.total_noodle` 等 | 同上 | `company.*` | 全门店 `sum(各品类)` | 同上 |
| 门店品类占比 | 前端计算 | — | — | `品类金额 / 门店总金额 * 100` | — |
| 公司品类占比 | 前端计算 | — | — | `品类金额 / 公司总金额 * 100` | — |
| 主力品类 | `s.top_category` | 同上 | `store.top_category` | — | mv_store_category_mix_monthly.top_category |
| 主力品类占比 | `s.top_category_share_pct` | 同上 | `store.top_category_share_pct` | — | 同上.top_category_share_pct |
| 面食占比 | `s.noodle_share_pct` | 同上 | `store.noodle_share_pct` | — | 同上.noodle_share_pct |
| 外卖占比 | `s.delivery_package_share_pct` | 同上 | `store.delivery_package_share_pct` | — | 同上.delivery_package_share_pct |
---
### Tab4:成本分析
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 理论成本率 | `cost.cost.theoretical_cost_rate_pct` | /stores/:code/cost | `cost.theoretical_cost_rate_pct` | — | mv_store_theoretical_actual_cost_monthly |
| 实际成本率 | `cost.cost.actual_food_cost_rate_pct` | 同上 | `cost.actual_food_cost_rate_pct` | — | 同上 |
| 差异率 | `cost.cost.variance_to_theoretical_pct` | 同上 | `cost.variance_to_theoretical_pct` | — | 同上 |
| 差异等级 | `cost.cost.variance_level` | 同上 | `cost.variance_level` | — | 同上 |
| 原料分类名 | `cost.categories[].category_name` | 同上 | `categories[].category_name` | `finance_category` | mv_inventory_cost_classified_monthly.finance_category |
| 消耗金额 | `cost.categories[].consumption_amount` | 同上 | `categories[].consumption_amount` | `sum(consumption_amount)` | 同上.consumption_amount |
| 实际占比 | `cost.categories[].actual_cost_rate_pct` | 同上 | `categories[].actual_cost_rate_pct` | `分类消耗/总消耗*100` | — |
| 公司基准 | `cost.categories[].benchmark_rate_pct` | 同上 | `categories[].benchmark_rate_pct` | 全门店同分类占比 | — |
| 差异 | `cost.categories[].variance_pct` | 同上 | `categories[].variance_pct` | `actual - benchmark` | — |
---
### Tab5:会员分析
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 会员占比 | `member.opportunity.member_share_pct` | /stores/:code/member | `opportunity.member_share_pct` | — | mv_store_member_opportunity_monthly |
| 公司均值会员占比 | `member.opportunity.company_member_share_pct` | 同上 | `opportunity.company_member_share_pct` | — | 同上 |
| 复购率 | `member.repeat.repeat_rate_pct` | 同上 | `repeat.repeat_rate_pct` | — | mv_store_repeat_summary_monthly |
| 复购收入占比 | `member.repeat.repeat_revenue_share_pct` | 同上 | `repeat.repeat_revenue_share_pct` | — | 同上 |
| 活跃会员数 | `member.monthly.member_count` | 同上 | `monthly.member_count` | `count(*)` | v_store_member_monthly_activity |
| 总订单数 | `member.monthly.total_orders` | 同上 | `monthly.total_orders` | `sum(orders)` | 同上 |
| 人均订单 | `member.monthly.avg_orders` | 同上 | `monthly.avg_orders` | `avg(orders)` | 同上 |
| 人均消费 | `member.monthly.avg_received` | 同上 | `monthly.avg_received` | `avg(received)` | 同上 |
| 提升机会(文本) | `member.opportunity.conversion_bill_scenario` | 同上 | `opportunity.conversion_bill_scenario` | — | mv_store_member_opportunity_monthly |
| 收入提升场景(文本) | `member.opportunity.revenue_uplift_scenario` | 同上 | `opportunity.revenue_uplift_scenario` | — | 同上 |
---
### Tab6:异常账单
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 账单号 | `r.bill_no` | /stores/:code/anomalies | `bill_no` | mv_risk_anomaly.bill_no |
| 餐段 | `r.meal_period` | 同上 | `meal_period` | 同上.meal_period |
| 消费额 | `r.consumption` | 同上 | `consumption` | 同上.consumption |
| 优惠 | `r.discount_total` | 同上 | `discount_total` | 同上.discount_total |
| 实收 | `r.received_total` | 同上 | `received_total` | 同上.received_total |
| 异常原因 | `r.anomaly_reason` | 同上 | `anomaly_reason` | 同上.anomaly_reason |
| 结账时间 | `r.closed_at` | 同上 | `closed_at` | 同上.closed_at |
| 总数 | `anomalyDetailTotal` | 同上 | `meta.total` | — |
> ⚠️ **anomalies数据源注意**`/stores/:code/anomalies` 通过 `store_name`(非 `store_code`)关联 `mv_risk_anomaly` 视图,需先查 `dim_store` 获取 `store_name` 再查异常表。
---
### Tab7:人力排班
#### 人力成本概览(6个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 人工成本 | `staffing.summary.wage_expense` | /stores/:code/staffing | `summary.wage_expense` | — | mv_store_operating_expense_monthly.wage_expense |
| 人工成本率 | `staffing.summary.wage_rate_pct` | 同上 | `summary.wage_rate_pct` | — | 同上.wage_rate_pct |
| 公司均值人工率 | `staffing.summary.avg_wage_rate` | 同上 | `summary.avg_wage_rate` | `avg(wage_rate_pct)` 排除>100% | 同上 |
| 人效 | `staffing.summary.revenue_per_wage` | 同上 | `summary.revenue_per_wage` | `received / wage_expense` | 同上 |
| 每单人工成本 | `staffing.summary.wage_per_bill` | 同上 | `summary.wage_per_bill` | `wage_expense / bill_count` | 同上 |
| 公司均值人效 | `staffing.summary.avg_rev_per_wage` | 同上 | `summary.avg_rev_per_wage` | `avg(received/wage_expense)` | 同上 |
#### 人力成本优化空间
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 建议人工成本 | `staffing.summary.suggested_wage` | 同上 | `summary.suggested_wage` | `received * max(20%, avgWageRate-3%) / 100` | 后端计算 |
| 可节省金额 | `staffing.summary.wage_savings` | 同上 | `summary.wage_savings` | `wage_expense - suggested_wage` | 后端计算 |
| 节省比例 | `staffing.summary.savings_pct` | 同上 | `summary.savings_pct` | `wage_savings / wage_expense * 100` | 后端计算 |
#### 按星期排班分析
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 星期 | `weekdays[].weekday` | 同上 | `weekdays[].weekday` | — | v_store_daily |
| 日均实收 | `weekdays[].avg_received` | 同上 | 同上 | `avg(received)` 按星期分组 | v_store_daily.received |
| 日均账单 | `weekdays[].avg_bills` | 同上 | 同上 | `avg(bill_count)` | v_store_daily.bill_count |
| 最高实收 | `weekdays[].max_received` | 同上 | 同上 | `max(received)` | v_store_daily.received |
| 最低实收 | `weekdays[].min_received` | 同上 | 同上 | `min(received)` | v_store_daily.received |
| 建议排班比 | `weekdays[].suggested_headcount_ratio` | 同上 | 同上 | `avg_received / 全周日均 * 100` | 后端计算 |
| 排班等级 | `weekdays[].staffing_level` | 同上 | 同上 | >110%=高峰, <85%=低谷, else=正常 | — |
| 排班建议 | `weekdays[].staffing_action` | 同上 | 同上 | 静态文本 | — |
#### 餐段排班分析
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 餐段 | `meals[].meal_period` | 同上 | `meals[].meal_period` | — | v_store_meal_opportunity |
| 账单数 | `meals[].bill_count` | 同上 | 同上 | — | 同上 |
| 实收 | `meals[].received` | 同上 | 同上 | — | 同上 |
| 客单价 | `meals[].avg_bill` | 同上 | 同上 | — | 同上 |
| 账单占比 | `meals[].bill_share_pct` | 同上 | 同上 | `餐段账单/总账单*100` | 后端计算 |
| 排班等级 | `meals[].staffing_level` | 同上 | 同上 | >40%=主力, >20%=次高峰, >5%=辅助, else=低谷 | — |
| 排班建议 | `meals[].staffing_action` | 同上 | 同上 | 静态文本 | — |
#### 日营收明细(排班参考)
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 日期 | `daily[].business_date` | 同上 | `daily[].business_date` | v_store_daily.business_date |
| 实收 | `daily[].received` | 同上 | 同上 | v_store_daily.received |
---
## 六、页面4:菜品成本 CostAnalysisPage
> 10个Tab,每个Tab独立组件。共调用22个API(含1个POST、1个PUT)。
> 核心数据源:`public.dish_cost_analysis_summary`(菜品成本汇总表,按月导入)+ `public.dish_cost_analysis_material_detail`(原料明细表)+ `analytics.fact_recipe_bom`BOM配方表)。
### Tab1:成本总览(OverviewTab
#### 概览指标(8个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 总销售额 | `ov.total_sales` | /cost-analysis/overview | `total_sales` | `sum(sales_amount)` | dish_cost_analysis_summary.sales_amount |
| 理论成本合计 | `ov.total_theo_cost` | 同上 | `total_theo_cost` | `sum(theoretical_cost)` | 同上.theoretical_cost |
| 实际成本合计 | `ov.total_actual_cost` | 同上 | `total_actual_cost` | `sum(actual_cost)` | 同上.actual_cost |
| 成本差异 | `ov.total_variance` | 同上 | `total_variance` | `sum(cost_variance_amount)` | 同上.cost_variance_amount |
| 平均理论毛利率 | `ov.avg_theo_margin` | 同上 | `avg_theo_margin` | `(1 - sum(theoretical_cost)/sum(sales_amount)) * 100` | 同上 |
| 平均实际毛利率 | `ov.avg_actual_margin` | 同上 | `avg_actual_margin` | `(1 - sum(actual_cost)/sum(sales_amount)) * 100` | 同上 |
| 低毛利菜品数 | `ov.low_margin_dishes` | 同上 | `low_margin_dishes` | `count(*) FILTER(theoretical_margin_rate_pct < 50)` | 同上.theoretical_margin_rate_pct |
| 超理论菜品数 | `ov.over_cost_dishes` | 同上 | `over_cost_dishes` | `count(*) FILTER(cost_variance_amount > 0)` | 同上.cost_variance_amount |
> **数据导入机制**:所有查询通过 `import_id` 关联 `dish_cost_analysis_import_log` 取最新月度导入批次。
#### 品类成本对比
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 品类 | `cats[].category_level1` | /cost-analysis/category-comparison | `category_level1` | — | dish_cost_analysis_summary.category_level1 |
| 菜品数 | `cats[].dishes` | 同上 | `dishes` | `count(*)` | 同上 |
| 理论毛利率 | `cats[].avg_theo_margin` | 同上 | `avg_theo_margin` | `(1 - sum(theoretical_cost)/sum(sales_amount)) * 100` | 同上 |
| 实际毛利率 | `cats[].avg_actual_margin` | 同上 | `avg_actual_margin` | `(1 - sum(actual_cost)/sum(sales_amount)) * 100` | 同上 |
| 销售额 | `cats[].total_sales` | 同上 | `total_sales` | `sum(sales_amount)` | 同上 |
| 成本差异 | `cats[].total_variance` | 同上 | `total_variance` | `sum(cost_variance_amount)` | 同上 |
#### 毛利率偏差分布
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 偏差区间 | `devs[].deviation_band` | /cost-analysis/margin-deviation | `deviation_band` | CASE分组:实际-理论偏差 | dish_cost_analysis_summary |
| 菜品数 | `devs[].cnt` | 同上 | `cnt` | `count(*)` | 同上 |
#### 成本差异TOP榜
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `tops[].dish_name` | /cost-analysis/variance-top | `dish_name` | — | dish_cost_analysis_summary.dish_name |
| 品类 | `tops[].category_level1` | 同上 | `category_level1` | — | 同上 |
| 理论毛利率 | `tops[].theo_margin` | 同上 | `theo_margin` | `theoretical_margin_rate_pct` | 同上 |
| 实际毛利率 | `tops[].actual_margin` | 同上 | `actual_margin` | `actual_margin_rate_pct` | 同上 |
| 成本差异 | `tops[].cost_variance` | 同上 | `cost_variance` | `cost_variance_amount` | 同上 |
| 销售额 | `tops[].sales_amount` | 同上 | `sales_amount` | — | 同上 |
| 分层 | `tops[].cost_tier` | 同上 | `cost_tier` | CASE>20%=紧急, >10%=整改, >0=关注, else=正常 | — |
---
### Tab2:菜品盈利(ProfitabilityTab
#### 菜单工程矩阵(散点图)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `matrixData[].dish_name` | /cost-analysis/menu-engineering | `dish_name` | — | dish_cost_analysis_summary |
| 销量 | `matrixData[].sales_quantity` | 同上 | `sales_quantity` | — | 同上.sales_quantity |
| 实际毛利率 | `matrixData[].actual_margin` | 同上 | `actual_margin` | `actual_margin_rate_pct` | 同上 |
| 销售额 | `matrixData[].sales_amount` | 同上 | `sales_amount` | — | 同上 |
| 菜单类型 | `matrixData[].menu_type` | 同上 | `menu_type` | 双中位分割:销量&毛利率各取中位数 → 明星/高销低利/低销高利/低销低利/数据异常 | — |
#### 菜品盈利明细(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `profitData[].dish_name` | /cost-analysis/profitability | `dish_name` | — | dish_cost_analysis_summary |
| 品类 | `profitData[].category_level1` | 同上 | `category_level1` | — | 同上 |
| 销量 | `profitData[].sales_quantity` | 同上 | `sales_quantity` | — | 同上 |
| 销售额 | `profitData[].sales_amount` | 同上 | `sales_amount` | — | 同上 |
| 理论毛利 | `profitData[].theo_profit` | 同上 | `theo_profit` | `sales_amount - theoretical_cost` | 同上 |
| 实际毛利 | `profitData[].actual_profit` | 同上 | `actual_profit` | `sales_amount - actual_cost` | 同上 |
| 理论毛利率 | `profitData[].theo_margin` | 同上 | `theo_margin` | `theoretical_margin_rate_pct` | 同上 |
| 实际毛利率 | `profitData[].actual_margin` | 同上 | `actual_margin` | `actual_margin_rate_pct` | 同上 |
| 收入贡献度 | `profitData[].revenue_contribution` | 同上 | `revenue_contribution` | `sales_amount / sum(sales_amount) OVER() * 100` | 同上 |
#### 低毛利菜品预警
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `pricingData[].dish_name` | /cost-analysis/pricing | `dish_name` | — | dish_cost_analysis_summary |
| 售价 | `pricingData[].price` | 同上 | `price` | — | 同上.price |
| 理论成本 | `pricingData[].theo_cost` | 同上 | `theo_cost` | `theoretical_cost` | 同上 |
| 理论成本率 | `pricingData[].theo_cost_rate` | 同上 | `theo_cost_rate` | `theoretical_cost / price * 100` | 同上 |
| 理论毛利率 | `pricingData[].theo_margin` | 同上 | `theo_margin` | `theoretical_margin_rate_pct` | 同上 |
| 实际毛利率 | `pricingData[].actual_margin` | 同上 | `actual_margin` | `actual_margin_rate_pct` | 同上 |
| 销售额 | `pricingData[].sales_amount` | 同上 | `sales_amount` | — | 同上 |
> 筛选条件:`theoretical_margin_rate_pct < 50 AND sales_amount > 1000`。
---
### Tab3:原料差异(MaterialTab
#### 菜品原料差异分解(按菜品编码查询)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 原料 | `varianceData[].material_name` | /cost-analysis/material-variance | `material_name` | — | dish_cost_analysis_material_detail.material_name |
| 单位 | `varianceData[].material_unit` | 同上 | `material_unit` | — | 同上.material_unit |
| 理论用量 | `varianceData[].theo_qty` | 同上 | `theo_qty` | `theoretical_quantity` | 同上.theoretical_quantity |
| 实际用量 | `varianceData[].actual_qty` | 同上 | `actual_qty` | `actual_quantity` | 同上.actual_quantity |
| 数量差异 | `varianceData[].loss_qty` | 同上 | `loss_qty` | `loss_quantity` | 同上.loss_quantity |
| 损耗率 | `varianceData[].loss_rate` | 同上 | `loss_rate` | `loss_quantity_rate_pct` | 同上.loss_quantity_rate_pct |
| 金额差异 | `varianceData[].loss_amount` | 同上 | `loss_amount` | `loss_amount` | 同上.loss_amount |
| 原因 | `varianceData[].reason` | 同上 | `reason` | CASE<-100%=分摊遗漏, <-30%=份量超标, =0=未使用, else=正常损耗 | — |
#### 物料损耗率分布
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 损耗区间 | `distData[].loss_band` | /cost-analysis/loss-distribution | `loss_band` | CASE分档:无损耗/轻微/轻度/中度/严重/极重/正向 | dish_cost_analysis_material_detail |
| 明细数 | `distData[].cnt` | 同上 | `cnt` | `count(*)` | 同上 |
#### 超耗物料TOP榜
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料 | `topData[].material_name` | /cost-analysis/material-loss-top | `material_name` | — | dish_cost_analysis_material_detail |
| 类型 | `topData[].material_type` | 同上 | `material_type` | — | 同上 |
| 涉及菜品数 | `topData[].dish_count` | 同上 | `dish_count` | `count(DISTINCT summary_id)` | 同上 |
| 总损耗量 | `topData[].total_loss_qty` | 同上 | `total_loss_qty` | `sum(loss_quantity)` | 同上 |
| 平均损耗率 | `topData[].avg_loss_rate` | 同上 | `avg_loss_rate` | `sum(loss_quantity)/sum(theoretical_quantity)*100` | 同上 |
| 最大损耗率 | `topData[].max_loss_rate` | 同上 | `max_loss_rate` | `max(loss_quantity_rate_pct)` | 同上 |
| 损耗金额 | `topData[].total_loss_amount` | 同上 | `total_loss_amount` | `sum(loss_amount)` | 同上 |
#### 物料类型损耗对比
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料类型 | `typeData[].material_type` | /cost-analysis/material-type-loss | `material_type` | — | dish_cost_analysis_material_detail |
| 明细数 | `typeData[].cnt` | 同上 | `cnt` | `count(*)` | 同上 |
| 物料种类 | `typeData[].unique_materials` | 同上 | `unique_materials` | `count(DISTINCT material_name)` | 同上 |
| 平均损耗率 | `typeData[].avg_loss_rate` | 同上 | `avg_loss_rate` | `sum(loss_quantity)/sum(theoretical_quantity)*100` | 同上 |
| 损耗金额 | `typeData[].total_loss_amount` | 同上 | `total_loss_amount` | `sum(loss_amount)` | 同上 |
---
### Tab4BOM与配方(BomTab
#### BOM总览(6个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| BOM覆盖率 | `ov.coverage_pct` | /cost-analysis/bom-overview | `coverage_pct` | `count(DISTINCT sku_code in BOM) / count(dim_sku) * 100` | fact_recipe_bom + dim_sku |
| 有BOM的SKU | `ov.sku_with_bom` | 同上 | `sku_with_bom` | `count(DISTINCT sku_code)` | fact_recipe_bom.sku_code |
| 缺BOM的SKU | `ov.sku_without_bom` | 同上 | `sku_without_bom` | `total_sku - sku_with_bom` | dim_sku - fact_recipe_bom |
| BOM总行数 | `ov.total_bom_rows` | 同上 | `total_bom_rows` | `count(*)` | fact_recipe_bom |
| 高损耗BOM数 | `ov.high_loss_bom` | 同上 | `high_loss_bom` | `count(*) FILTER(waste_rate > 20)` | fact_recipe_bom.waste_rate |
| 平均物料数/SKU | `ov.avg_materials_per_sku` | 同上 | `avg_materials_per_sku` | `avg(count per sku)` | 同上 |
> ⚠️ **bom-overview 无月份参数**:该API不接收month参数,BOM数据不按月区分。
#### BOM复杂度明细(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| SKU编码 | `compData[].sku_code` | /cost-analysis/bom-complexity | `sku_code` | — | fact_recipe_bom.sku_code |
| 菜品名 | `compData[].dish_name` | 同上 | `dish_name` | `dim_sku.standard_name` | dim_sku.standard_name |
| 品类 | `compData[].category_l1` | 同上 | `category_l1` | — | dim_sku.category_l1 |
| 原料数 | `compData[].material_count` | 同上 | `material_count` | `count(*)` | fact_recipe_bom |
| 半成品数 | `compData[].semi_finished_count` | 同上 | `semi_finished_count` | `count(*) FILTER(major_category='半成品')` | fact_recipe_bom + dim_material |
| 独有原料 | `compData[].unique_material_count` | 同上 | `unique_material_count` | `count(*) FILTER(ref_count=1)` | fact_recipe_bom(子查询ref_count |
| 高损耗原料 | `compData[].high_loss_count` | 同上 | `high_loss_count` | `count(*) FILTER(waste_rate > 20)` | fact_recipe_bom.waste_rate |
| 复杂度 | `compData[].complexity_level` | 同上 | `complexity_level` | CASE:≤5=低, ≤10=正常, ≤15=较复杂, >15=重点评审 | — |
#### 菜品物料构成(按SKU查询)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料 | `compDetail[].material_name` | /cost-analysis/bom-composition | `material_name` | — | dim_material.material_name |
| 类型 | `compDetail[].material_type` | 同上 | `material_type` | `dim_material.major_category` | dim_material.major_category |
| 标准毛用量 | `compDetail[].gross_qty` | 同上 | `gross_qty` | `standard_gross_quantity` | fact_recipe_bom.standard_gross_quantity |
| 标准净用量 | `compDetail[].net_qty` | 同上 | `net_qty` | `standard_net_quantity` | fact_recipe_bom.standard_net_quantity |
| 损耗率 | `compDetail[].waste_rate` | 同上 | `waste_rate` | — | fact_recipe_bom.waste_rate |
| 成本占比 | `compDetail[].cost_share` | 同上 | `cost_share` | `theoretical_amount / sum(theoretical_amount) OVER(PARTITION BY summary_id) * 100` | dish_cost_analysis_material_detail |
#### 高损耗BOM预警
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `lossData[].dish_name` | /cost-analysis/bom-high-loss | `dish_name` | `dim_sku.standard_name` | dim_sku |
| 物料 | `lossData[].material_name` | 同上 | `material_name` | — | dim_material |
| 损耗率 | `lossData[].waste_rate` | 同上 | `waste_rate` | — | fact_recipe_bom.waste_rate |
| 出成率 | `lossData[].yield_rate` | 同上 | `yield_rate` | — | fact_recipe_bom.yield_rate |
| 预警级别 | `lossData[].alert_level` | 同上 | `alert_level` | CASE>100%=极端, >50%=严重, else=关注 | — |
> 筛选条件:`waste_rate >= 20`。无月份参数。
#### 缺BOM的SKU清单(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| SKU编码 | `missData[].sku_code` | /cost-analysis/bom-missing | `sku_code` | — | dim_sku.sku_code |
| 菜品名 | `missData[].dish_name` | 同上 | `dish_name` | `standard_name` | dim_sku.standard_name |
| 品类 | `missData[].category_l1` | 同上 | `category_l1` | — | dim_sku.category_l1 |
| 销售额 | `missData[].sales_amount` | 同上 | `sales_amount` | `COALESCE(dish_cost_analysis_summary.sales_amount, 0)` | dish_cost_analysis_summary |
| 销量 | `missData[].sales_quantity` | 同上 | `sales_quantity` | `COALESCE(dish_cost_analysis_summary.sales_quantity, 0)` | 同上 |
> 筛选条件:`sku_code NOT IN (SELECT DISTINCT sku_code FROM fact_recipe_bom)`。
---
### Tab5:供应链与精简(SupplyTab
#### 原料共用度
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料 | `sharingData[].material_name` | /cost-analysis/material-sharing | `material_name` | — | dim_material.material_name |
| 类型 | `sharingData[].major_category` | 同上 | `major_category` | — | dim_material.major_category |
| 被引用SKU数 | `sharingData[].sku_count` | 同上 | `sku_count` | `count(DISTINCT sku_code)` | fact_recipe_bom.sku_code |
| 分类 | `sharingData[].sharing_type` | 同上 | `sharing_type` | CASE>50=核心原料, =1=独有原料, else=普通共用 | — |
#### 独有原料风险(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| SKU编码 | `riskData[].sku_code` | /cost-analysis/unique-material-risk | `sku_code` | — | fact_recipe_bom |
| 菜品名 | `riskData[].dish_name` | 同上 | `dish_name` | `dim_sku.standard_name` | dim_sku |
| 品类 | `riskData[].category_l1` | 同上 | `category_l1` | — | dim_sku |
| 销售额 | `riskData[].sales_amount` | 同上 | `sales_amount` | `COALESCE(dish_cost_analysis_summary.sales_amount, 0)` | dish_cost_analysis_summary |
| 独有原料数 | `riskData[].unique_material_count` | 同上 | `unique_material_count` | `count(*) where ref_count=1` | fact_recipe_bom |
| 风险等级 | `riskData[].risk_level` | 同上 | `risk_level` | CASEsales<5000 & count>3=高, sales<5000=中, else=低 | — |
#### SKU精简模拟器(POST
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 收入影响 | `simResult.summary.revenue_impact` | POST /cost-analysis/sku-simplify-simulate | `summary.revenue_impact` | `sum(sales_amount) for target SKUs` | dish_cost_analysis_summary |
| 毛利影响 | `simResult.summary.profit_impact` | 同上 | `summary.profit_impact` | `sum(sales_amount - actual_cost)` | 同上 |
| 可减少原料数 | `simResult.summary.releasable_materials` | 同上 | `summary.releasable_materials` | `count(DISTINCT material_code) 独有原料` | fact_recipe_bom |
| 仍需采购的共用原料 | `simResult.summary.shared_materials` | 同上 | `summary.shared_materials` | `count(DISTINCT material_code) 共用原料` | 同上 |
| 负毛利菜品数 | `simResult.summary.negative_margin_count` | 同上 | `summary.negative_margin_count` | `count(*) where actual_margin_rate_pct < 0` | dish_cost_analysis_summary |
| 可释放物料 | `simResult.releasable_materials` | 同上 | `releasable_materials` | 独有物料明细 | fact_recipe_bom + dim_material |
| 库存金额 | `releasable_materials[].inventory_value` | 同上 | `inventory_value` | `sum(actual_amount)` | dish_cost_analysis_material_detail |
#### BOM驱动物料需求预测
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料 | `rawDemandData[].material_name` | /cost-analysis/material-demand | `material_name` | — | dim_material |
| 类型 | `rawDemandData[].major_category` | 同上 | `major_category` | — | dim_material.major_category |
| 单位 | `rawDemandData[].base_unit` | 同上 | `base_unit` | — | dim_material.base_unit |
| 预计需求量 | `rawDemandData[].estimated_demand` | 同上 | `estimated_demand` | `sum(standard_gross_quantity * sales_quantity)` | fact_recipe_bom × dish_cost_analysis_summary |
| 涉及菜品数 | `rawDemandData[].dish_count` | 同上 | `dish_count` | `count(DISTINCT sku_code)` | fact_recipe_bom |
---
### Tab6:包装耗材(PackagingTab
#### 包装总览(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 包装物料种类 | `ovData.packaging_types` | /cost-analysis/packaging-overview | `packaging_types` | `count(DISTINCT material_name)` | dish_cost_analysis_material_detail |
| 包装总成本 | `ovData.total_cost` | 同上 | `total_cost` | `sum(theoretical_amount)` | 同上.theoretical_amount |
| 总损耗量 | `ovData.total_loss_qty` | 同上 | `total_loss_qty` | `sum(loss_quantity)` | 同上.loss_quantity |
| 高损耗包装数 | `ovData.high_loss_count` | 同上 | `high_loss_count` | `count(*) FILTER(loss_rate < -20%)` | 同上 |
> 筛选条件:`material_name ~* '餐盒|餐具|打包|纸巾|碗|袋|杯|盒'`(正则匹配包装物料名称)。
#### 包装物料明细(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 物料 | `detailData[].material_name` | /cost-analysis/packaging-detail | `material_name` | — | dish_cost_analysis_material_detail |
| 涉及菜品数 | `detailData[].dish_count` | 同上 | `dish_count` | `count(DISTINCT summary_id)` | 同上 |
| 总用量 | `detailData[].total_qty` | 同上 | `total_qty` | `sum(theoretical_quantity)` | 同上 |
| 总成本 | `detailData[].total_cost` | 同上 | `total_cost` | `sum(theoretical_amount)` | 同上 |
| 损耗量 | `detailData[].loss_qty` | 同上 | `loss_qty` | `sum(loss_quantity)` | 同上 |
| 平均损耗率 | `detailData[].avg_loss_rate` | 同上 | `avg_loss_rate` | `sum(loss_quantity)/sum(theoretical_quantity)*100` | 同上 |
| 状态 | `detailData[].status` | 同上 | `status` | CASE<-20%=核查, <-5%=关注, else=正常 | — |
---
### Tab7:数据质量(QualityTab
#### 数据质量总览(3个MetricCard + 6项检查)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| BOM覆盖率 | `ovData.bom_coverage` | /cost-analysis/data-quality | `bom_coverage` | `count(DISTINCT sku_code in BOM) / count(dim_sku) * 100` | fact_recipe_bom + dim_sku |
| 菜品匹配率 | 前端计算 | 同上 | `matched_dishes / total_dishes` | `matched_dishes / total_dishes * 100` | dish_cost_analysis_summary + dish_sales_details |
| 物料匹配率 | 前端计算 | 同上 | `matched_materials / total_materials` | `matched_materials / total_materials * 100` | dish_cost_analysis_material_detail + inventory_cost_records |
| 零实际用量BOM | `ovData.zero_actual_bom` | 同上 | `zero_actual_bom` | `count(*) WHERE standard_net_quantity = 0` | fact_recipe_bom |
| 负毛利率菜品 | `ovData.negative_margin_count` | 同上 | `negative_margin_count` | `count(*) WHERE actual_margin_rate_pct < 0` | dish_cost_analysis_summary |
| 理论负毛利菜品 | `ovData.negative_theo_margin_count` | 同上 | `negative_theo_margin_count` | `count(*) WHERE theoretical_margin_rate_pct < 0` | 同上 |
#### 未匹配菜品清单(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 菜品名 | `dishData[].dish_name` | /cost-analysis/unmatched-dishes | `dish_name` | dish_cost_analysis_summary |
| 编码 | `dishData[].dish_code` | 同上 | `dish_code` | 同上 |
| 品类 | `dishData[].category_level1` | 同上 | `category_level1` | 同上 |
| 销售额 | `dishData[].sales_amount` | 同上 | `sales_amount` | 同上 |
> 筛选条件:`NOT EXISTS (SELECT 1 FROM dish_sales_details WHERE dish_name = s.dish_name)`。
#### 未匹配物料清单(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 物料名 | `matData[].material_name` | /cost-analysis/unmatched-materials | `material_name` | dish_cost_analysis_material_detail |
| 类型 | `matData[].material_type` | 同上 | `material_type` | 同上 |
| 涉及菜品数 | `matData[].dish_count` | 同上 | `dish_count` | `count(DISTINCT summary_id)` |
| 损耗金额 | `matData[].loss_amount` | 同上 | `loss_amount` | `sum(loss_amount)` |
> 筛选条件:`NOT EXISTS (SELECT 1 FROM inventory_cost_records WHERE material_name = m.material_name)`。
---
### Tab8:门店成本(StoreTab
#### 门店成本总览(6个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店总数 | `ovData.total_stores` | /cost-analysis/store-overview | `total_stores` | `count(*)` | mv_store_theoretical_actual_cost_monthly |
| 红色-严重超耗 | `ovData.red_count` | 同上 | `red_count` | `count(*) FILTER(variance_level='红色-严重超耗')` | 同上.variance_level |
| 橙色-明显超耗 | `ovData.orange_count` | 同上 | `orange_count` | `count(*) FILTER(variance_level='橙色-明显超耗')` | 同上 |
| 绿色-基本正常 | `ovData.green_count` | 同上 | `green_count` | `count(*) FILTER(variance_level='绿色-基本正常')` | 同上 |
| 灰色-口径异常 | `ovData.gray_count` | 同上 | `gray_count` | `count(*) FILTER(variance_level='灰色-口径异常')` | 同上 |
| 总超耗金额 | `ovData.total_variance` | 同上 | `total_variance` | `sum(food_cost_variance)` | 同上.food_cost_variance |
#### 门店超耗排名(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rankData[].store_name` | /cost-analysis/store-ranking | `store_name` | — | mv_store_theoretical_actual_cost_monthly |
| 理论成本率 | `rankData[].theo_cost_rate` | 同上 | `theo_cost_rate` | `theoretical_cost_rate_pct` | 同上 |
| 实际成本率 | `rankData[].actual_cost_rate` | 同上 | `actual_cost_rate` | `actual_food_cost_rate_pct` | 同上 |
| 偏差 | `rankData[].variance_pct` | 同上 | `variance_pct` | `variance_to_theoretical_pct` | 同上 |
| 超耗金额 | `rankData[].variance_amount` | 同上 | `variance_amount` | `food_cost_variance` | 同上 |
| 负库存项 | `rankData[].negative_item_lines` | 同上 | `negative_item_lines` | — | 同上.negative_item_lines |
| 分级 | `rankData[].variance_level` | 同上 | `variance_level` | — | 同上.variance_level |
---
### Tab9:可视化探索(ExploreTab
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `scatterData[].dish_name` | /cost-analysis/scatter | `dish_name` | — | dish_cost_analysis_summary |
| 品类 | `scatterData[].category_level1` | 同上 | `category_level1` | — | 同上 |
| 销量(X轴) | `scatterData[].x` | 同上 | `x` | `sales_quantity` | 同上 |
| 成本差异(Y轴) | `scatterData[].y` | 同上 | `y` | `cost_variance_amount` | 同上 |
| 销售额(气泡大小) | `scatterData[].size` | 同上 | `size` | `sales_amount` | 同上 |
---
### Tab10:调整管理(AdjustmentTab
#### 子Tab:待处理建议
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品 | `diagData[].dish_name` | /cost-analysis/diagnosis | `dish_name` | — | dish_diagnosis_snapshot |
| 品类 | `diagData[].category_l1` | 同上 | `category_l1` | — | 同上 |
| 诊断类型 | `diagData[].diagnosis_type` | 同上 | `diagnosis_type` | — | 同上(生成时计算) |
| 诊断详情 | `diagData[].diagnosis_detail` | 同上 | `diagnosis_detail` | — | 同上 |
| 理论毛利率 | `diagData[].theoretical_margin_pct` | 同上 | `theoretical_margin_pct` | — | 同上 |
| 实际毛利率 | `diagData[].actual_margin_pct` | 同上 | `actual_margin_pct` | — | 同上 |
| 成本差异 | `diagData[].cost_variance_amount` | 同上 | `cost_variance_amount` | — | 同上 |
| 建议动作 | `diagData[].suggested_action` | 同上 | `suggested_action` | — | 同上 |
| 优先级 | `diagData[].priority` | 同上 | `priority` | P0/P1/P2/P3 | 同上 |
> **诊断生成**POST `/cost-analysis/diagnosis/generate` 从 `dish_cost_analysis_summary` + BOM数据生成快照写入 `dish_diagnosis_snapshot`。
#### 子Tab:调整记录
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 菜品 | `adjData[].dish_name` | /cost-analysis/adjustment | `dish_name` | dish_adjustment_log |
| 调整类型 | `adjData[].adjustment_type` | 同上 | `adjustment_type` | 同上 |
| 执行日期 | `adjData[].effective_date` | 同上 | `effective_date` | 同上 |
| 调整前毛利率 | `adjData[].before_theoretical_margin` | 同上 | `before_theoretical_margin` | 同上 |
| 目标毛利率 | `adjData[].after_target_margin` | 同上 | `after_target_margin` | 同上 |
| 决策人 | `adjData[].decided_by` | 同上 | `decided_by` | 同上 |
| 状态 | `adjData[].status` | 同上 | `status` | 同上 |
> **创建调整**POST `/cost-analysis/adjustment` → INSERT INTO dish_adjustment_log。
> **更新状态**PUT `/cost-analysis/adjustment/:id` → UPDATE status。
#### 子Tab:效果验证
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 调整前日均销量 | `verifyData.before_avg_daily` | /cost-analysis/adjustment/:id/verify | `before_avg_daily` | `avg(bills) 前7天` | dish_sales_details |
| 调整后日均销量 | `verifyData.after_avg_daily` | 同上 | `after_avg_daily` | `avg(bills) 后7天` | 同上 |
| 销量变化率 | `verifyData.sales_change_pct` | 同上 | `sales_change_pct` | `(afterAvg - beforeAvg) / beforeAvg * 100` | — |
| 日销量趋势 | `verifyData.before/after` | 同上 | `before/after` | `count(*) per day` | dish_sales_details |
---
## 七、页面5:门店营业费用 StoreExpensePage
> 10个Tab,每个Tab独立组件。共调用11个API(全部GET)。
> 核心数据源:`analytics.mv_store_operating_expense_monthly`(门店营业费用月度物化视图)+ `analytics.mv_store_risk_rating_monthly`(门店风险评级月度物化视图)+ `analytics.v_operating_expense_account_monthly`(费用科目月度视图)。
### Tab1:费用总览(ExpenseOverviewTab
#### 概览指标(6个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 营业收入 | `ov.total_consumption` | /store-expense/overview | `total_consumption` | `sum(consumption)` | bill_fact.consumption + mv_store_risk_rating_monthly |
| 优惠 | `ov.total_discount` | 同上 | `total_discount` | `sum(discount)` | 同上 |
| 实收合计 | `ov.total_received` | 同上 | `total_received` | `sum(received)` | mv_store_risk_rating_monthly.received |
| 门店贡献利润 | `ov.total_contribution` | 同上 | `total_contribution` | `sum(received - actual_food_cost - operating_expense)` | mv_store_operating_expense_monthly |
| 经营门店 | `ov.total_stores` | 同上 | `total_stores` | `count(*)` | mv_store_risk_rating_monthly |
| 费用率 | `ov.overall_expense_rate_pct` | 同上 | `overall_expense_rate_pct` | `sum(operating_expense) / sum(received) * 100` | 同上 |
> **数据关联机制**overview API通过CTE `full_scope` 关联 `mv_store_risk_rating_monthly`(主表,按month_start过滤)LEFT JOIN `mv_store_operating_expense_monthly`(费用明细,按report_month过滤)LEFT JOIN `bill_fact`(消费/优惠金额)。
#### 费用结构分析(PieChart + 明细表)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 费用科目 | `structureData[].account_name` | /store-expense/expense-structure | `account_name` | — | v_operating_expense_account_monthly.account_name |
| 金额 | `structureData[].amount` | 同上 | `amount` | — | 同上.amount |
| 占比 | `structureData[].expense_share_pct` | 同上 | `expense_share_pct` | — | 同上.expense_share_pct |
| 门店数 | `structureData[].nonzero_cost_unit_count` | 同上 | `nonzero_cost_unit_count` | — | 同上.nonzero_cost_unit_count |
> ⚠️ **CollapsibleSection subtitle 硬编码日期**`subtitle="2026年4月各费用科目占比"` 硬编码了月份,应使用 `month` 变量动态显示。
---
### Tab2:门店费用率排名(StoreRankingTab
#### 门店费用率排名(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/store-ranking | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上.received |
| 费用合计 | `rows[].operating_expense` | 同上 | `operating_expense` | — | 同上.operating_expense |
| 费用率 | `rows[].operating_expense_rate_pct` | 同上 | `operating_expense_rate_pct` | — | 同上 |
| 人工率 | `rows[].wage_rate_pct` | 同上 | `wage_rate_pct` | — | 同上 |
| 租金率 | `rows[].rent_rate_pct` | 同上 | `rent_rate_pct` | — | 同上 |
| 水电率 | `rows[].utility_rate_pct` | 同上 | `utility_rate_pct` | — | 同上 |
| 贡献利润 | `rows[].actual_store_contribution` | 同上 | `actual_store_contribution` | — | 同上 |
| 贡献率 | `rows[].actual_store_contribution_rate_pct` | 同上 | `actual_store_contribution_rate_pct` | — | 同上 |
| 坪效 | `rows[].received_per_sqm` | 同上 | `received_per_sqm` | — | 同上 |
> 筛选条件:filter支持 profitable/loss/zero_sales。排序白名单校验。
---
### Tab3:门店贡献利润(ContributionTab
#### 门店贡献利润分析(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/store-contribution | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 理论食材成本 | `rows[].theoretical_cost` | 同上 | `theoretical_cost` | — | 同上.theoretical_cost |
| 实际食材成本 | `rows[].actual_food_cost` | 同上 | `actual_food_cost` | — | 同上.actual_food_cost |
| 营业费用 | `rows[].operating_expense` | 同上 | `operating_expense` | — | 同上 |
| 理论贡献 | `rows[].theoretical_store_contribution` | 同上 | `theoretical_store_contribution` | — | 同上 |
| 理论贡献率 | `rows[].theoretical_store_contribution_rate_pct` | 同上 | `theoretical_store_contribution_rate_pct` | — | 同上 |
| 实际贡献 | `rows[].actual_store_contribution` | 同上 | `actual_store_contribution` | `received - actual_food_cost - operating_expense` | 同上 |
| 实际贡献率 | `rows[].actual_store_contribution_rate_pct` | 同上 | `actual_store_contribution_rate_pct` | — | 同上 |
| 贡献差异 | `rows[].contribution_variance` | 同上 | `contribution_variance` | `actual_store_contribution - theoretical_store_contribution` | 同上 |
> ⚠️ **ContributionTab 默认排序方向问题**:前端 `order` 初始值为 `'asc'`(升序),但后端 `/store-contribution` 的 `order` 参数处理为 `=== 'desc' ? 'DESC' : 'ASC'`,即非'desc'均为'ASC'。这意味着默认按贡献率升序(最差门店在前),这是合理的行为。但前端 `defaultOrder="asc"` 与 `ContributionTab` 的 `useState<'asc' | 'desc'>('asc')` 一致,无问题。
---
### Tab4:房租及租约风险(RentRiskTab
#### 房租及租约风险(服务端分页 + riskOnly切换)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/rent-risk | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 租金 | `rows[].rent_expense` | 同上 | `rent_expense` | — | 同上 |
| 租金率 | `rows[].rent_rate_pct` | 同上 | `rent_rate_pct` | — | 同上 |
| 面积(㎡) | `rows[].area_sqm` | 同上 | `area_sqm` | — | 同上 |
| 坪效 | `rows[].received_per_sqm` | 同上 | `received_per_sqm` | — | 同上 |
| 租约到期 | `rows[].lease_expiry_date` | 同上 | `lease_expiry_date` | — | 同上.lease_expiry_date |
| 风险等级 | `rows[].risk_level` | 同上 | `risk_level` | CASE:≤3月=即将到期, ≤12月=年内到期, ≤18月=明年上半年到期, rent>20%=租金占比偏高, else=正常 | — |
| 建议 | `rows[].suggestion` | 同上 | `suggestion` | CASE:按到期/租金率生成建议文本 | — |
> **riskOnly 筛选**`lease_expiry_date <= month + 12 months OR rent_rate_pct > 20`。
> ⚠️ **租约到期日期显示**:前端使用 `new Date(r.lease_expiry_date).toLocaleDateString('zh-CN')`,后端返回的是日期类型,可能包含时间部分。根据规范应使用 `substring(0, 10)` 格式化。
---
### Tab5:外卖佣金分析(DeliveryTab
#### 外卖佣金分析(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/delivery-commission | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 外卖收入 | `rows[].delivery_received` | 同上 | `delivery_received` | — | 同上 |
| 外卖占比 | `rows[].delivery_sales_share_pct` | 同上 | `delivery_sales_share_pct` | — | 同上 |
| 佣金支出 | `rows[].delivery_commission_expense` | 同上 | `delivery_commission_expense` | — | 同上 |
| 佣金率 | `rows[].commission_to_delivery_sales_pct` | 同上 | `commission_to_delivery_sales_pct` | — | 同上 |
| 综合平台成本率 | `rows[].combined_platform_cost_rate_pct` | 同上 | `combined_platform_cost_rate_pct` | — | 同上 |
| 外卖类型 | `rows[].delivery_type` | 同上 | `delivery_type` | CASE>40%=重度依赖, >25%=外卖占比较高, >10%=正常, else=以堂食为主 | — |
| 建议 | `rows[].suggestion` | 同上 | `suggestion` | CASE:按佣金率/外卖占比/综合成本率生成建议 | — |
> 筛选条件:`delivery_received > 0`。
---
### Tab6:人效坪效(EfficiencyTab
#### 人效坪效分析(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/efficiency | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 面积(㎡) | `rows[].area_sqm` | 同上 | `area_sqm` | — | 同上 |
| 坪效 | `rows[].received_per_sqm` | 同上 | `received_per_sqm` | — | 同上 |
| 人工成本 | `rows[].wage_expense` | 同上 | `wage_expense` | — | 同上 |
| 人工成本率 | `rows[].wage_rate_pct` | 同上 | `wage_rate_pct` | — | 同上 |
| 人效(元/元人工) | `rows[].revenue_per_wage` | 同上 | `revenue_per_wage` | `received / wage_expense` | 同上 |
| 账单数 | `rows[].bill_count` | 同上 | `bill_count` | — | 同上 |
| 客单价 | `rows[].avg_ticket_size` | 同上 | `avg_ticket_size` | `received / bill_count` | 同上 |
| 效率等级 | `rows[].efficiency_level` | 同上 | `efficiency_level` | CASE<1000=坪效偏低, <2000=坪效一般, <4000=坪效良好, else=坪效优秀 | — |
| 建议 | `rows[].suggestion` | 同上 | `suggestion` | CASE:按人工率/坪效/面积生成建议 | — |
> 筛选条件:`received > 0 AND area_sqm IS NOT NULL`。
> ⚠️ **效率等级缺少"坪效极低"档位**:后端 `efficiency_level` CASE 只有4档(偏低/一般/良好/优秀),但前端 `EfficiencyTab` 的颜色判断有5档(极低<500/偏低<1000/一般/良好/优秀),前端 `efficiency_level === '坪效极低'` 永远不会匹配。后端 loss-diagnosis 中的 `efficiency_status` 才有"坪效极低"档(<500)。
---
### Tab7:固定/变动费用(FixedVariableTab
#### 固定/变动费用拆分(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/fixed-variable | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 固定费用 | `rows[].fixed_expense` | 同上 | `fixed_expense` | `rent_expense + dorm_expense + repair_clean_expense * 0.5` | 同上 |
| 固定费用率 | `rows[].fixed_rate_pct` | 同上 | `fixed_rate_pct` | `fixed_expense / received * 100` | 同上 |
| 变动费用 | `rows[].variable_expense` | 同上 | `variable_expense` | `wage_expense + utility_expense + delivery_commission_expense + card_fee_expense + repair_clean_expense * 0.5` | 同上 |
| 变动费用率 | `rows[].variable_rate_pct` | 同上 | `variable_rate_pct` | `variable_expense / received * 100` | 同上 |
| 边际贡献(扣变动) | `rows[].contribution_after_variable` | 同上 | `contribution_after_variable` | `received - actual_food_cost - wage_expense - utility_expense - delivery_commission_expense - card_fee_expense - repair_clean_expense * 0.5` | 同上 |
| 盈亏平衡销售额 | `rows[].break_even_sales` | 同上 | `break_even_sales` | `fixed_expense`(即 rent_expense + dorm_expense + repair_clean_expense * 0.5 | 同上 |
> ⚠️ **盈亏平衡销售额计算错误**:后端 `break_even_sales` 直接等于 `fixed_expense`(固定费用),但正确的盈亏平衡公式应为 `固定费用 ÷ (1 - 食材成本率)` 或 `固定费用 ÷ 边际贡献率`。当前计算仅返回固定费用金额本身,并非真正的盈亏平衡点。Tab8 `break-even` 中的计算才是正确的。
---
### Tab8:盈亏平衡分析(BreakEvenTab
#### 盈亏平衡分析(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/break-even | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 食材成本 | `rows[].actual_food_cost` | 同上 | `actual_food_cost` | — | 同上 |
| 食材成本率 | `rows[].food_cost_rate_pct` | 同上 | `food_cost_rate_pct` | `actual_food_cost / received * 100` | 同上 |
| 营业费用 | `rows[].operating_expense` | 同上 | `operating_expense` | — | 同上 |
| 费用率 | `rows[].expense_rate_pct` | 同上 | `expense_rate_pct` | `operating_expense / received * 100` | 同上 |
| 盈亏平衡销售额 | `rows[].break_even_sales` | 同上 | `break_even_sales` | `operating_expense / (1 - actual_food_cost/received)` | 同上 |
| 销售缺口 | `rows[].sales_gap` | 同上 | `sales_gap` | `received - break_even_sales` | 同上 |
| 安全边际率 | `rows[].safety_margin_pct` | 同上 | `safety_margin_pct` | `(received - break_even_sales) / break_even_sales * 100` | 同上 |
| 安全状态 | `rows[].safety_status` | 同上 | `safety_status` | CASE:贡献边际≤0=无法盈利, received<平衡点=未达盈亏平衡, <15%=接近, <30%=较低, else=充足 | — |
> **正确的盈亏平衡公式**`break_even_sales = operating_expense / (1 - food_cost_ratio)`,即 `营业费用 ÷ (1 - 食材成本率)`。与Tab7的简单固定费用公式不同。
---
### Tab9:亏损门店诊断(LossDiagnosisTab
#### 概览指标(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 亏损门店数 | `meta.total` | /store-expense/loss-diagnosis | `meta.total` | `count(*) WHERE actual_store_contribution <= 0 AND received > 0` | mv_store_operating_expense_monthly |
| P0 紧急 | `p0Count` | 同上 | 前端计算 | `rows.filter(priority === 'P0').length` | — |
| P1 整改 | `p1Count` | 同上 | 前端计算 | `rows.filter(priority === 'P1').length` | — |
| 亏损金额合计 | `totalLoss` | 同上 | 前端计算 | `rows.reduce(sum + actual_store_contribution)` | — |
> ⚠️ **P0/P1/亏损金额仅统计当前页**`p0Count`、`p1Count`、`totalLoss` 是从当前页 `rows` 计算的,而非全量数据。当翻页时这些指标会变化,不具备全局统计意义。应在后端返回全量统计或单独查询。
#### 亏损门店诊断明细(服务端分页 + 弹窗详情)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/loss-diagnosis | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 优先级 | `rows[].priority` | 同上 | 后端计算 | CASE:贡献率<-20%=P0, <-5%=P1, <0=P2, else=P3 | — |
| 亏损类型 | `rows[].loss_type` | 同上 | `loss_type` | CASE:理论贡献≤0=理论即亏损, 实际超耗=实际超耗导致亏损, else=费用过高导致亏损 | — |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 贡献利润 | `rows[].actual_store_contribution` | 同上 | `actual_store_contribution` | — | 同上 |
| 贡献率 | `rows[].actual_store_contribution_rate_pct` | 同上 | `actual_store_contribution_rate_pct` | — | 同上 |
| 人工率 | `rows[].wage_rate_pct` | 同上 | `wage_rate_pct` | — | 同上 |
| 租金率 | `rows[].rent_rate_pct` | 同上 | `rent_rate_pct` | — | 同上 |
| 坪效 | `rows[].received_per_sqm` | 同上 | `received_per_sqm` | — | 同上 |
| 亏损原因 | `rows[].diagnosis_reasons` | 同上 | 后端JS生成 | 按收入/食材/人工/租金/水电/外卖/坪效/租约/亏损类型多维度拼接 | — |
| 建议动作 | `rows[].suggested_actions` | 同上 | 后端JS生成 | 对应改进建议动作列表 | — |
> **诊断状态字段**(弹窗详情):`revenue_status`、`food_cost_status`、`wage_status`、`rent_status`、`utility_status`、`delivery_status`、`efficiency_status`、`lease_status` 均由后端SQL CASE生成。
> ⚠️ **租约到期日期显示**:弹窗中使用 `new Date(store.lease_expiry_date).toLocaleDateString('zh-CN')`,应使用 `substring(0, 10)` 规范格式。
---
### Tab10:关停/续租/改造评估(StoreEvaluationTab
#### 概览指标(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 关停评估 | `evalStats['关停评估']` | /store-expense/store-evaluation | `meta.evalStats.关停评估` | 后端全量统计 | mv_store_operating_expense_monthly |
| 改造评估 | `evalStats['改造评估']` | 同上 | `meta.evalStats.改造评估` | 后端全量统计 | 同上 |
| 续租评估 | `evalStats['续租评估']` | 同上 | `meta.evalStats.续租评估` | 后端全量统计 | 同上 |
| 关注观察 | `evalStats['关注观察']` | 同上 | `meta.evalStats.关注观察` | 后端全量统计 | 同上 |
> **evalStats 全量统计**:后端在返回分页数据的同时,额外执行一条全量统计SQL获取各评估类型数量,不受分页影响。与Tab9的仅当前页统计形成对比。
#### 门店评估明细(服务端分页 + 弹窗详情)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `rows[].sales_store_name` | /store-expense/store-evaluation | `sales_store_name` | — | mv_store_operating_expense_monthly |
| 优先级 | `rows[].priority` | 同上 | `priority` | CASEreceived<5000=P0, 贡献率<-20%=P0, <-10%=P1, <0=P2, <10=P3, else=P4 | — |
| 评估类型 | `rows[].evaluation_type` | 同上 | `evaluation_type` | CASEreceived<5000=关停评估, 贡献率<-20%&租约到期=关停评估, 贡献率<-10%&坪效<1000=关停评估, 贡献率<0&租约到期=关停或迁址评估, 贡献率<0&坪效<1000&面积>400=改造评估, 贡献率<0&人工率>35=改造评估, 贡献率<0=关注观察, 租约≤3月=续租评估, 贡献率<10=关注观察, else=正常经营 | — |
| 实收 | `rows[].received` | 同上 | `received` | — | 同上 |
| 贡献利润 | `rows[].actual_store_contribution` | 同上 | `actual_store_contribution` | — | 同上 |
| 贡献率 | `rows[].actual_store_contribution_rate_pct` | 同上 | `actual_store_contribution_rate_pct` | — | 同上 |
| 坪效 | `rows[].received_per_sqm` | 同上 | `received_per_sqm` | — | 同上 |
| 租约到期 | `rows[].lease_expiry_date` | 同上 | `lease_expiry_date` | 后端格式化为 `toISOString().split('T')[0]` | 同上 |
| 评估详情 | `rows[].evaluation_detail` | 同上 | 后端JS生成 | 按评估类型生成详细说明 | — |
---
## 八、页面6:风险与内控 RiskPage
> 单页面无子组件文件,4个Tab切换(异常账单/全额优惠/空单/收银员风险),3个API(全部GET)。
> 核心数据源:`mv_risk_anomaly`(异常账单物化视图)、`mv_risk_zero`(零实收账单物化视图)、`mv_risk_cashier`(收银员风险物化视图)。均按 `month = to_char($1::date, 'YYYY-MM')` 过滤。
### 概览指标(5个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 异常账单数 | `totalAnomaly` | /risk/anomaly | `meta.total` | `count(*)` | mv_risk_anomaly |
| 全额优惠账单 | `zeroMeta.full_discount` | /risk/zero-received | `meta.full_discount` | `count(*) FILTER (zero_received_type='全额优惠')` | mv_risk_zero |
| 空单账单 | `zeroMeta.empty_bill` | 同上 | `meta.empty_bill` | `count(*) FILTER (zero_received_type='无消费无优惠')` | 同上 |
| 高风险收银员(≥10%) | `highRiskCashiers` | /risk/cashier | 前端计算 | `cashierRows.filter(anomaly_rate_pct >= 10).length` | mv_risk_cashier |
| 平均异常率 | `avgAnomalyRate` | 同上 | 前端计算 | `sum(anomaly_rate_pct) / cashierRows.length` | 同上 |
> ⚠️ **highRiskCashiers 和 avgAnomalyRate 基于全量收银员数据计算**`/risk/cashier` 不分页,返回全量收银员列表,所以这两个指标是全局准确的。与StoreExpensePage Tab9的当前页统计问题不同。
### 收银员异常账单数TOP15BarChart
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 收银员 | `cashierChartData[].cashier` | /risk/cashier | `cashier` | — | mv_risk_cashier |
| 异常账单数 | `cashierChartData[].anomaly_bills` | 同上 | `anomaly_bills` | `anomaly_no_received + anomaly_discount_over + anomaly_unbalanced` | 同上 |
> 图表数据:取 `cashierRows` 按 `anomaly_bills` 降序排列前15条。
### Tab1:异常账单(anomaly
#### 异常账单明细(服务端分页 + 多维筛选)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `anomalyRows[].store_name` | /risk/anomaly | `store_name` | — | mv_risk_anomaly |
| 账单号 | `anomalyRows[].bill_no` | 同上 | `bill_no` | — | 同上 |
| 餐段 | `anomalyRows[].meal_period` | 同上 | `meal_period` | — | 同上 |
| 消费额 | `anomalyRows[].consumption` | 同上 | `consumption` | — | 同上 |
| 优惠 | `anomalyRows[].discount_total` | 同上 | `discount_total` | — | 同上 |
| 实收 | `anomalyRows[].received_total` | 同上 | `received_total` | — | 同上 |
| 异常原因 | `anomalyRows[].anomaly_reason` | 同上 | `anomaly_reason` | — | 同上 |
| 收银员 | `anomalyRows[].cashier` | 同上 | `cashier` | — | 同上 |
| 结账时间 | `anomalyRows[].closed_at` | 同上 | `closed_at` | — | 同上 |
> **筛选条件**:store(门店精确匹配)、reason(异常原因精确匹配)、cashierILIKE模糊搜索,前端400ms debounce)。
> **排序白名单**`consumption`、`discount_total`、`received_total`、`closed_at`。
> **筛选合计**:后端在count查询中同时返回 `sum_consumption`、`sum_discount`、`sum_received`,前端显示筛选合计行。
> **结账时间格式化**:前端使用 `r.closed_at?.substring(0, 16)` 截取前16位,符合日期格式化规范。
### Tab2:全额优惠(full_discount
#### 全额优惠账单(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `zeroRows[].store_name` | /risk/zero-received | `store_name` | — | mv_risk_zero |
| 账单号 | `zeroRows[].bill_no` | 同上 | `bill_no` | — | 同上 |
| 餐段 | `zeroRows[].meal_period` | 同上 | `meal_period` | — | 同上 |
| 消费额 | `zeroRows[].consumption` | 同上 | `consumption` | — | 同上 |
| 优惠 | `zeroRows[].discount_total` | 同上 | `discount_total` | — | 同上 |
| 收银员 | `zeroRows[].cashier` | 同上 | `cashier` | — | 同上 |
| 结账时间 | `zeroRows[].closed_at` | 同上 | `closed_at` | — | 同上 |
> API请求参数:`type='全额优惠'`,后端按 `zero_received_type = '全额优惠'` 过滤。
> 排序:后端固定 `ORDER BY consumption DESC`,前端 `sortOptions` 不生效(未传sort参数到后端)。
### Tab3:空单(empty_bill
#### 空单账单(服务端分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 同Tab2 | 同上 | /risk/zero-received | 同上 | 同上 | mv_risk_zero |
> API请求参数:`type='无消费无优惠'`,后端按 `zero_received_type = '无消费无优惠'` 过滤。
> ⚠️ **Tab2和Tab3共用同一API和组件结构**`zeroType` 切换时触发重新请求,`zeroPage` 重置为1。
### Tab4:收银员风险(cashier
#### 收银员风险明细(全量不分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 收银员 | `cashierRows[].cashier` | /risk/cashier | `cashier` | — | mv_risk_cashier |
| 账单数 | `cashierRows[].bill_count` | 同上 | `bill_count` | — | 同上 |
| 实收 | `cashierRows[].received` | 同上 | `received` | — | 同上 |
| 异常账单数 | `cashierRows[].anomaly_bills` | 同上 | `anomaly_bills` | `anomaly_no_received + anomaly_discount_over + anomaly_unbalanced` | 同上 |
| 异常率 | `cashierRows[].anomaly_rate_pct` | 同上 | `anomaly_rate_pct` | `(anomaly_bills / bill_count) * 100` | 同上 |
| 异常消费额 | `cashierRows[].anomaly_consumption` | 同上 | `consumption_total` | — | 同上 |
> ⚠️ **前端字段名与后端不匹配**:前端列 `anomaly_consumption` 对应后端返回的 `consumption_total`,前端 `render: (r) => formatCurrency(r.anomaly_consumption)` 访问的 `r.anomaly_consumption` 在后端返回中不存在(后端返回 `consumption_total`),导致该列始终显示 ¥0.00。
> **双击行跳转**`onRowDoubleClick` 设置 `anomalyCashier` 并切换到 anomaly Tab,用收银员名筛选异常账单。
> **排序**:前端 `sortOptions` 有5个选项,但后端固定 `ORDER BY anomaly_bills DESC`,前端排序不生效(未传sort参数到后端)。
---
## 九、页面7:成本与库存 CostPage
> 单页面无子组件文件,4个区块(成本概览+散点图+成本对比表+库存效率+原料分类基准),3个API(全部GET)。
> 核心数据源:`analytics.mv_store_theoretical_actual_cost_monthly`(理论vs实际成本月度物化视图)、`analytics.mv_inventory_cost_classified_monthly`(库存分类成本月度物化视图)、`analytics.mv_store_area_efficiency_monthly`(门店面积效率月度物化视图)。均按 `month_start = $1` 过滤。
### 区块1:成本概览(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 平均理论成本率 | `avgTheoreticalRate` | /cost/comparison | 前端计算 | `avg(theoretical_cost_rate_pct)` 排除口径异常 | mv_store_theoretical_actual_cost_monthly |
| 平均实际成本率 | `avgActualRate` | 同上 | 前端计算 | `avg(actual_food_cost_rate_pct)` 排除口径异常 | 同上 |
| 高偏差门店(>20%) | `highVarianceCount` | 同上 | 前端计算 | `filter(variance_to_theoretical_pct > 20).length` 排除口径异常 | 同上 |
| 口径异常门店 | `abnormalRows.length` | 同上 | 前端计算 | `filter(variance_level includes '口径异常').length` | 同上 |
> **口径异常处理**:前端将 `variance_level` 包含"口径异常"的门店分离为 `abnormalRows`,不纳入平均成本率和高偏差统计。显示异常门店名称列表。
> **全量数据**`/cost/comparison` 不分页,返回全量门店数据,前端统计是全局准确的。
### 区块2:理论vs实际成本率散点图(ScatterChart
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店名 | `scatterData[].name` | /cost/comparison | `store_name` | — | mv_store_theoretical_actual_cost_monthly |
| 理论成本率 | `scatterData[].theoretical` | 同上 | `theoretical_cost_rate_pct` | — | 同上 |
| 实际成本率 | `scatterData[].actual` | 同上 | `actual_food_cost_rate_pct` | — | 同上 |
| 偏差 | `scatterData[].variance` | 同上 | `variance_to_theoretical_pct` | — | 同上 |
> 散点图排除口径异常门店。X轴=理论成本率,Y轴=实际成本率,对角线参考线表示理论=实际。偏离对角线越多偏差越大。
> **动态坐标轴**`useMemo` 计算数据范围 ±1 作为domain,空数据时默认 [0,40]×[0,50]。
### 区块3:门店成本对比表(FilterableTable,全量不分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `costRows[].store_name` | /cost/comparison | `store_name` | — | mv_store_theoretical_actual_cost_monthly |
| 理论成本率 | `costRows[].theoretical_cost_rate_pct` | 同上 | `theoretical_cost_rate_pct` | — | 同上 |
| 实际成本率 | `costRows[].actual_food_cost_rate_pct` | 同上 | `actual_food_cost_rate_pct` | — | 同上 |
| 偏差 | `costRows[].variance_to_theoretical_pct` | 同上 | `variance_to_theoretical_pct` | — | 同上 |
| 偏差等级 | `costRows[].variance_level` | 同上 | `variance_level` | — | 同上 |
| 实际食材成本 | `costRows[].actual_food_cost` | 同上 | `actual_food_cost` | — | 同上 |
> **筛选**`filterKey="variance_level"` 按偏差等级筛选。**排序**:前端 `sortOptions` 有5个选项,但后端固定 `ORDER BY variance_to_theoretical_pct DESC`,前端排序为客户端排序(全量数据)。
> ⚠️ **前端排序 vs 后端排序**`/cost/comparison` 不分页返回全量数据,`FilterableTable` 未设 `serverSide`,排序在前端完成,是有效的。
### 区块4:库存效率(FilterableTable,懒加载)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `invRows[].store_name` | /cost/inventory | `store_name` | — | mv_store_area_efficiency_monthly + mv_store_theoretical_actual_cost_monthly |
| 业态 | `invRows[].business_type` | 同上 | `business_type` | — | mv_store_area_efficiency_monthly |
| 规模 | `invRows[].scale_tier` | 同上 | `scale_tier` | — | 同上 |
| 库存天数 | `invRows[].estimated_inventory_days` | 同上 | `estimated_inventory_days` | — | 同上 |
| 期末库存 | `invRows[].ending_inventory_amount` | 同上 | `ending_inventory_amount` | — | mv_store_theoretical_actual_cost_monthly |
| 负耗用行数 | `invRows[].negative_item_lines` | 同上 | `negative_item_lines` | — | 同上 |
> **懒加载**`enabled: invOpen`,仅在CollapsibleSection展开时请求。`onToggle={setInvOpen}` 控制展开状态。
> **数据关联**`mv_store_area_efficiency_monthly` LEFT JOIN `mv_store_theoretical_actual_cost_monthly` ON store_code + month_start。
### 区块5:原料分类成本基准(FilterableTable,懒加载)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `catRows[].store_name` | /cost/category-benchmark | `store_name` | — | mv_inventory_cost_classified_monthly |
| 财务分类 | `catRows[].finance_category` | 同上 | `finance_category` | — | 同上 |
| 实际成本 | `catRows[].actual_category_cost` | 同上 | `actual_category_cost` | `sum(consumption_amount) GROUP BY store_name, finance_category` | 同上 |
| 每万元成本 | `catRows[].cost_per_10k_sales` | 同上 | `cost_per_10k_sales` | `actual_category_cost / sales_received * 10000` | 同上 + mv_store_theoretical_actual_cost_monthly |
| 同业中位 | `catRows[].peer_median_cost_per_10k` | 同上 | `peer_median_cost_per_10k` | `percentile_cont(0.5) WITHIN GROUP (ORDER BY cost_per_10k_sales)` 按分类 | — |
| 超出同业 | `catRows[].excess_vs_peer_per_10k` | 同上 | `excess_vs_peer_per_10k` | `cost_per_10k_sales - peer_median` | — |
> **懒加载**`enabled: catOpen`。**同业基准**:使用 `percentile_cont(0.5)` 计算各财务分类的每万元成本中位数作为同业基准。
---
## 十、页面8:会员与复购 MemberPage
> 单页面无子组件文件,4个区块(会员概览+对比图+对比明细+复购率),2个API(全部GET)。
> 核心数据源:`analytics.bill_fact`(账单事实表,按closed_at月份过滤)、`analytics.v_store_repeat_summary_monthly`(门店复购汇总月度视图,按month_start过滤)。
### 区块1:会员概览(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 会员账单占比 | `memberBillShare` | /member/comparison | 前端计算 | `memberRow.bill_count / (memberRow.bill_count + nonMemberRow.bill_count) * 100` | bill_fact |
| 会员收入占比 | `memberRevenueShare` | 同上 | 前端计算 | `memberRow.received / (memberRow.received + nonMemberRow.received) * 100` | 同上 |
| 平均复购率 | `avgRepeatRate` | /member/repeat | 前端计算 | `avg(repeat_rate_pct)` | v_store_repeat_summary_monthly |
| 低复购门店(<30%) | `lowRepeatCount` | 同上 | 前端计算 | `filter(repeat_rate_pct < 30).length` | 同上 |
> **全量数据**:两个API均不分页,返回全量数据,前端统计全局准确。
### 区块2:会员vs非会员消费对比(BarChart + PieChart
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 客户类型 | `cmpChartData[].name` | /member/comparison | `customer_type` | `CASE WHEN member_id IS NULL THEN '非会员' ELSE '会员'` | bill_fact |
| 账单数 | `cmpChartData[].账单数` | 同上 | `bill_count` | `count(*)` | 同上 |
| 实收 | `cmpChartData[].实收` | 同上 | `received` | `sum(received_total)` | 同上 |
| 客单价 | `cmpChartData[].客单价` | 同上 | `avg_bill_value` | `avg(received_total)` | 同上 |
> BarChart双Y轴:左轴=账单数+实收,右轴=客单价。PieChart显示会员/非会员账单数占比。
### 区块3:会员与非会员指标对比(FilterableTable,全量不分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 客户类型 | `cmpRows[].customer_type` | /member/comparison | `customer_type` | — | bill_fact |
| 账单数 | `cmpRows[].bill_count` | 同上 | `bill_count` | `count(*)` | 同上 |
| 实收 | `cmpRows[].received` | 同上 | `received` | `sum(received_total)` | 同上 |
| 客单价 | `cmpRows[].avg_bill_value` | 同上 | `avg_bill_value` | `avg(received_total)` | 同上 |
| 优惠率 | `cmpRows[].discount_rate_pct` | 同上 | `discount_rate_pct` | `sum(discount_total) / sum(consumption) * 100` | 同上 |
| 理论毛利率 | `cmpRows[].theoretical_margin_pct` | 同上 | `theoretical_margin_pct` | `sum(theoretical_profit) / sum(received_total) * 100` | 同上 |
> 仅2行数据(会员/非会员),客户端排序有效。
### 区块4:门店复购率(FilterableTable,全量不分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 门店 | `repeatRows[].store_name` | /member/repeat | `store_name` | — | v_store_repeat_summary_monthly |
| 识别会员数 | `repeatRows[].identified_members` | 同上 | `identified_members` | — | 同上 |
| 复购会员数 | `repeatRows[].repeat_members` | 同上 | `repeat_members` | — | 同上 |
| 复购率 | `repeatRows[].repeat_rate_pct` | 同上 | `repeat_rate_pct` | — | 同上 |
| 人均消费次数 | `repeatRows[].avg_orders` | 同上 | `avg_orders` | — | 同上 |
| 复购收入占比 | `repeatRows[].repeat_revenue_share_pct` | 同上 | `repeat_revenue_share_pct` | — | 同上 |
> **筛选**`filterKey="store_name"` 按门店筛选。**排序**:前端 `sortOptions` 客户端排序(全量数据,有效)。后端固定 `ORDER BY repeat_rate_pct DESC`。
> **复购率颜色**:≥50%绿色,≥30%黄色,<30%红色。
### 发现的问题汇总
> MemberPage 无发现新问题。页面结构简洁,API与前端字段匹配,全量数据统计准确,日期无特殊格式化需求。
---
## 十一、页面9:商品SKU管理 SKUPage
> 单页面无子组件文件,5个区块(SKU概览+ABC分布图+品类分布+SKU明细+长尾治理),2个API(全部GET)。
> 核心数据源:`analytics.mv_dish_sku_abc_monthly`(菜品SKU ABC分析月度物化视图,按month_start过滤)、`analytics.category_summary`(品类汇总表,无月份过滤)。
### 区块1SKU概览(4个MetricCard
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 总SKU数 | `totalSku` | /sku/abc | 前端计算 | `data.length` | mv_dish_sku_abc_monthly |
| A类SKU(核心) | `abcSummary['A-核心']?.sku_count` | 同上 | 前端计算 | `reduce(abc_class='A-核心').count` | 同上 |
| C类SKU(长尾) | `cClassCount` | 同上 | 前端计算 | `abcSummary['C-长尾']?.sku_count` | 同上 |
| 低价值SKU(<¥1000) | `lowValueSku` | 同上 | 前端计算 | `filter(received_amount < 1000).length` | 同上 |
> **全量数据**`/sku/abc` 不分页,返回全量SKU列表,前端统计全局准确。
### 区块2SKU ABC分布(PieChart
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| ABC分类 | `abcPieData[].name` | /sku/abc | `abc_class` | — | mv_dish_sku_abc_monthly |
| SKU数量 | `abcPieData[].value` | 同上 | 前端计算 | `reduce(sku_count++)` | 同上 |
| 实收 | `abcPieData[].received` | 同上 | 前端计算 | `reduce(received_amount +=)` | 同上 |
> PieChart按ABC分类显示SKU数量分布,Tooltip同时显示SKU数和实收金额。
### 区块3:品类实收分布(BarChart)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 品类名 | `catRows[].category_name` | /sku/category | `category_name` | — | category_summary |
| 实收 | `catRows[].amount` | 同上 | `amount` | — | 同上 |
> BarChart取 `catRows.slice(0, 10)` 前10个品类。水平柱状图,Y轴=品类名,X轴=金额。
> ⚠️ **`/sku/category` 无月份过滤**:后端查询 `SELECT * FROM analytics.category_summary ORDER BY amount DESC`,不接受month参数,返回全量品类汇总。前端也未传month参数。如果品类数据需要按月过滤,此处存在口径不一致。
### 区块4SKU明细(FilterableTable,全量不分页)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品名称 | `skuRows[].dish_name` | /sku/abc | `dish_name` | — | mv_dish_sku_abc_monthly |
| 一级品类 | `skuRows[].category_level1` | 同上 | `category_level1` | — | 同上 |
| ABC分类 | `skuRows[].abc_class` | 同上 | `abc_class` | — | 同上 |
| 象限 | `skuRows[].sales_quadrant` | 同上 | `sales_quadrant` | — | 同上 |
| 覆盖门店 | `skuRows[].store_count` | 同上 | `store_count` | — | 同上 |
| 账单数 | `skuRows[].bill_count` | 同上 | `bill_count` | — | 同上 |
| 实收 | `skuRows[].received_amount` | 同上 | `received_amount` | — | 同上 |
| 占比 | `skuRows[].revenue_share_pct` | 同上 | `revenue_share_pct` | — | 同上 |
| 优惠率 | `skuRows[].discount_rate_pct` | 同上 | `discount_rate_pct` | — | 同上 |
> **筛选**`filterKey="abc_class"` 按ABC分类筛选。**排序**:前端客户端排序(全量数据,有效)。后端固定 `ORDER BY cumulative_revenue_share`。
### 区块5:长尾治理建议(3个可展开区块)
#### 5a. 低价值SKUreceived_amount < 1000
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 菜品名称 | `lowValueSkuRows[].dish_name` | /sku/abc | `dish_name` | — | mv_dish_sku_abc_monthly |
| 一级品类 | `lowValueSkuRows[].category_level1` | 同上 | `category_level1` | — | 同上 |
| ABC分类 | `lowValueSkuRows[].abc_class` | 同上 | `abc_class` | — | 同上 |
| 覆盖门店 | `lowValueSkuRows[].store_count` | 同上 | `store_count` | — | 同上 |
| 账单数 | `lowValueSkuRows[].bill_count` | 同上 | `bill_count` | — | 同上 |
| 实收 | `lowValueSkuRows[].received_amount` | 同上 | `received_amount` | — | 同上 |
> 前端筛选:`received_amount < 1000`,按 `received_amount` 升序排列。`filterKey="category_level1"` 按品类筛选。
#### 5b. 单店专属SKUstore_count <= 1
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 同5a | `singleStoreSkuRows[]` | /sku/abc | 同上 | `filter(store_count <= 1)` | 同上 |
#### 5c. 合并/归档候选(套餐/赠品/餐具类)
| 显示指标 | 前端变量 | API | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|---------|
| 同5a | `mergeCandidateRows[]` | /sku/abc | 同上 | `filter(dish_name includes 套餐/赠/餐具/打包/纸巾/调料包)` | 同上 |
> 前端按菜品名称关键词筛选合并候选,按 `received_amount` 升序排列。
---
## 十二、页面10:企业协同管理 EnterprisePage
> 单页面10个Tab13个API12个GET + 2个mutation),无MonthPicker。
> 核心数据源:`analytics.v3_*` 系列业务管理表(供应商、采购订单、审批流、消息、HR、培训、营销、财务、闭环、审计)。
### Tab1:供应链SRMsupplier
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/suppliers | analytics.v3_supplier_master | 供应商主数据,按overall_score降序 |
| /enterprise/purchase-orders | analytics.v3_purchase_order | 采购订单,按created_at降序,LIMIT 100 |
> MetricCard:供应商总数、战略供应商、采购订单数、淘汰供应商。
> PieChart:供应商分级分布(战略/优选/合格/淘汰)。
> 日期格式化:`r.order_date?.substring(0, 10)` ✅
### Tab2:供应商对标(benchmark
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/supplier-benchmark | v3_purchase_order_item + v3_purchase_order + v3_supplier_master | 跨供应商同SKU价格对比,CTE查询 |
> MetricCard:对标SKU数、价格差异大(>20%)、有优化空间(10-20%)、潜在节约。
> 后端返回 `{ summary, benchmarks }` 结构,前端正确解构。
### Tab3:审批流(approval
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/approvals | analytics.v3_approval_request | 审批请求列表,LIMIT 100 |
| POST /enterprise/approvals/:id/approve | 同上 + v3_approval_step | 审批操作mutation |
> MetricCard:审批总数、待审批、已审批。
> 日期格式化:`(r.created_at || '').substring(0, 19).replace('T', ' ')` ✅
### Tab4:消息中心(notification
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/notifications | analytics.v3_notification | 按user_id或user_role过滤,LIMIT 50 |
| PATCH /enterprise/notifications/:id/read | 同上 | 标记已读mutation |
> MetricCard:消息总数、未读、已读。
### Tab5HR人才(hr
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/hr/talent-matrix | analytics.dim_employee + analytics.store_task | 人才九宫格(绩效×潜力),LIMIT 200 |
> MetricCard:人才总数、明星员工、核心员工、潜力员工。
> ⚠️ **potential_score 硬编码为60**:后端 `COALESCE(60, 60) AS potential_score`,这意味着所有员工的潜力分都是60,九宫格的"潜力"维度实际上没有区分度。只有performance_score有变化(基于store_task完成情况)。
### Tab6:培训LMSlms
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/training/courses | analytics.v3_training_course | 课程列表 |
| /enterprise/training/records | analytics.v3_learning_record | 学习记录,LIMIT 100 |
> MetricCard:课程总数、学习记录数、已完成、未完成。
> 日期格式化:`(r.created_at || '').substring(0, 10)` ✅
### Tab7:营销MAmarketing
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/campaigns | analytics.v3_marketing_campaign | 营销活动列表,LIMIT 100 |
| /enterprise/coupons | analytics.v3_coupon | 券码列表,LIMIT 100 |
> MetricCard:活动总数、进行中、券码总数、已核销。
> 日期格式化:`(r.start_date || '').substring(0, 10)` ✅
### Tab8:财务ERPfinance
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/budgets | analytics.v3_budget | 按年度过滤,默认当前年 |
> MetricCard:预算总数、总预算、总实际。
> 日期格式化:`r.month?.substring(0, 10)` ✅
### Tab9:闭环验证(loop
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/loop-verification | analytics.v3_loop_verification | 闭环验证列表,LIMIT 100 |
> MetricCard:验证总数、已完成、需升级。
> 日期格式化:`(r.check_date || '').substring(0, 10)` ✅
### Tab10:审计日志(audit
| API | 数据源表 | 说明 |
|-----|---------|------|
| /enterprise/audit-logs | analytics.v3_audit_log | 审计日志,LIMIT 100 |
> 日期格式化:`(r.created_at || '').substring(0, 19).replace('T', ' ')` ✅
---
## 十三、页面11:态势感知 SituationalAwarenessPage
> 单页面4个Tab(健康度/预警/关联/趋势),4个API(全部GET),有MonthPicker。
> 核心数据源:`analytics.mv_store_risk_rating_monthly`、`analytics.mv_store_theoretical_actual_cost_monthly`、`analytics.mv_store_repeat_summary_monthly`、`analytics.store_task`、`analytics.mv_daily_revenue`、`public.dish_cost_analysis_summary`、`salary_detail_records`、`analytics.mv_store_platform_economics_monthly`、`bill_records`、`mv_bill_hourly`、`mv_store_hourly_staffing`、`public.store_name_mapping`。
### Tab1:健康度评分(health
#### API: /situational-awareness/health-score
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 门店 | `rows[].store_name` | `store_name` | — | mv_store_risk_rating_monthly |
| 健康分 | `rows[].health_score` | `health_score` | 8维加权求和(满分100) | 多表CTE |
| 状态 | `rows[].health_status` | `health_status` | ≥75健康, ≥55亚健康, <55需干预 | — |
| 风险 | `rows[].risk_level` | `risk_level` | — | mv_store_risk_rating_monthly |
| 实收 | `rows[].received` | `received` | — | 同上 |
| 客单价 | `rows[].avg_bill_value` | `avg_bill_value` | — | 同上 |
| 毛利率 | `rows[].avg_margin` | `avg_margin` | `COALESCE(theoretical_margin_pct, 0)` | 同上 |
| 成本差异率 | `rows[].avg_cost_variance_pct` | `avg_cost_variance_pct` | `avg(LEAST(variance_to_theoretical_pct, 50))` | mv_store_theoretical_actual_cost_monthly |
| 复购率 | `rows[].repeat_rate_pct` | `repeat_rate_pct` | `max(repeat_rate_pct)` | mv_store_repeat_summary_monthly |
| 任务完成率 | `rows[].task_completion_rate` | `task_completion_rate` | `count(status IN ('已验收','已回滚')) / count(*) * 100` | store_task |
> **8维评分体系**:营收规模(20)、成本控制(20)、毛利率(15)、风险等级(10)、复购率(10)、会员占比(10)、任务完成(10)、客单价(5) = 100分。
> **数据权限**:支持 `getDataScope` 按角色过滤门店。
> **弹窗详情**:点击行展示RadarChart雷达图 + 各维度进度条 + 原始指标 + 跳转门店详情按钮。
> **全量数据**:不分页,客户端排序和筛选。
### Tab2:预警中心(alerts
#### API: /situational-awareness/alerts
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|
| 总预警 | `d.total` | `total` | `alerts.length` | 多源 |
| 红色预警 | `d.red` | `red` | `filter(level='red').length` | 多源 |
| 橙色预警 | `d.orange` | `orange` | `filter(level='orange').length` | 多源 |
| 预警列表 | `alerts[]` | `alerts` | 5类预警合并 | 多源 |
> **5类预警**Promise.all并行查询):
> 1. **营收异动**revenue, red):日营收 < 月均70%`mv_daily_revenue`LIMIT 10
> 2. **成本异动**cost, red):成本差异率>30%`dish_cost_analysis_summary`LIMIT 10
> 3. **人力异动**hr, red/orange):考勤异常>3人,`salary_detail_records`LIMIT 10
> 4. **平台依赖**platform, orange):外卖占比>40%`mv_store_platform_economics_monthly`LIMIT 10
> 5. **任务逾期**task, red/orange):逾期任务,`store_task`LIMIT 10
>
> **前端分页**:客户端分页 PAGE_SIZE=20,使用 `SearchSelect` 做门店/类型/等级三维筛选。
> ⚠️ **预警查询部分不使用month参数**revenue和cost预警查询不传month参数,使用全局最新数据。
### Tab3:关联分析(correlation
#### API: /situational-awareness/correlation
返回4组关联分析数据:
| 数据组 | 前端变量 | API字段 | 数据源表 | 说明 |
|--------|---------|---------|---------|------|
| 成本-风险 | `costRiskRows` | `cost_risk` | mv_store_risk_rating_monthly + mv_store_theoretical_actual_cost_monthly | 成本差异率×风险等级关联状态 |
| 渠道-会员 | `channelRiskRows` | `channel_risk` | mv_store_risk_rating_monthly + mv_store_platform_economics_monthly + mv_store_repeat_summary_monthly | 平台占比×会员占比三角分析 |
| 考勤-营收 | `hrRevenueRows` | `hr_revenue` | salary_detail_records + v_store_scorecard + store_name_mapping | 出勤率×营收人效关联 |
| 客流-人力 | `staffingRows` | `staffing_efficiency` | mv_bill_hourly + mv_store_hourly_staffing | 每小时账单×在岗人数匹配度 |
> **散点图**:成本-风险关联,X=日均实收,Y=超耗率,颜色=风险等级。
> **4个FilterableTable**:每个都有 `filterKey` + `sortOptions` + `statusFilterKey` + `statusOptions`,客户端排序。
> ⚠️ **客流-人力匹配查询有容错处理**`mv_store_hourly_staffing` 可能不存在,try-catch返回空数组。
> ⚠️ **考勤-营收关联不使用month参数**`salary_detail_records` 查询无month过滤,使用全量数据。`v_store_scorecard` 也无month过滤。
### Tab4:趋势预测(forecast
#### API: /situational-awareness/forecast
| 数据组 | 前端变量 | API字段 | 数据源表 | 说明 |
|--------|---------|---------|---------|------|
| 客流预测 | `trafficRows` | `traffic_forecast` | bill_records | 按工作日/周末×小时P85分位预测 |
| 成本趋势 | `costTrendRows` | `cost_trend` | dish_cost_analysis_summary | 成本差异恶化TOP20 |
| 人员流失 | `turnoverRows` | `turnover_alert` | salary_detail_records | 有离职的门店流失率排名 |
> **客流预测**:从 `bill_records` 按小时×日类型聚合,计算 avg/P50/P85/波动率,稳定性分级。
> **LineChart**:工作日和周末分别画两条LineChartavg_bills + p85_bills + p50_bills)。
> ⚠️ **成本趋势查询不使用month参数**`dish_cost_analysis_summary` 查询无month/import_id过滤,使用全量最新数据。LIMIT 20。
> **人员流失**:按month参数过滤 leave_date 和 hire_date。
---
## 十四、页面12:门店详情 StoreDetailPage
> 单页面7个Tab(概览/餐段/品类/成本/会员/异常/任务),8个API(全部GET),有MonthPicker。
> 通过URL参数 `:code` 获取门店编码,复用 `/situational-awareness/health-score` API 获取健康度数据。
> 核心数据源:`bill_records`、`analytics.mv_store_risk_rating_monthly`、`analytics.mv_store_platform_economics_monthly`、`analytics.mv_store_benchmark_composite_monthly`、`analytics.mv_store_action_priority_deep_monthly`、`analytics.bill_fact`、`analytics.mv_store_category_mix_monthly`、`analytics.mv_store_theoretical_actual_cost_monthly`、`analytics.mv_inventory_cost_classified_monthly`、`analytics.mv_store_member_opportunity_monthly`、`analytics.mv_store_repeat_summary_monthly`、`analytics.v_store_member_monthly_activity`、`mv_risk_anomaly`、`analytics.dim_store`。
### API总览
| API | Tab | 数据源表 | 说明 |
|-----|-----|---------|------|
| /stores/:code | 全局 | bill_records + mv_store_risk_rating_monthly + mv_store_platform_economics_monthly + mv_store_benchmark_composite_monthly + mv_store_action_priority_deep_monthly | 门店scorecard+风险+平台+基准+行动优先级 |
| /situational-awareness/health-score | 概览 | 同SituationalAwarenessPage Tab1 | 复用健康度API,前端filter by store_code |
| /stores/:code/daily | 概览 | bill_records | 日度实收趋势 |
| /stores/:code/meal-period | 餐段 | analytics.bill_fact | 餐段分析+全网对标 |
| /stores/:code/category-mix | 品类 | analytics.mv_store_category_mix_monthly | 门店vs公司品类结构 |
| /stores/:code/cost | 成本 | mv_store_theoretical_actual_cost_monthly + mv_inventory_cost_classified_monthly | 成本概览+原料分类对标 |
| /stores/:code/member | 会员 | mv_store_member_opportunity_monthly + mv_store_repeat_summary_monthly + v_store_member_monthly_activity | 会员占比+复购+月度活跃 |
| /stores/:code/anomalies | 异常 | mv_risk_anomaly | 异常账单分页查询 |
| /tasks | 任务 | analytics.store_task | 门店任务列表 |
### Tab1:概览(overview
| 显示指标 | 前端变量 | API | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 门店名 | `sc.store_name` | /stores/:code | `scorecard.store_name` | bill_records |
| 健康分 | `health.health_score` | /situational-awareness/health-score | `health_score` | 多表CTE |
| 风险等级 | `risk.risk_level` | /stores/:code | `risk.risk_level` | mv_store_risk_rating_monthly |
| 经营象限 | `quadrant` | 同上 | 前端计算 | 日均营收≥18642 & 毛利率≥70% |
| 实收 | `sc.received` | 同上 | `scorecard.received` | bill_records |
| 账单数 | `sc.bill_count` | 同上 | `scorecard.bill_count` | 同上 |
| 客单价 | `sc.avg_bill_value` | 同上 | `scorecard.avg_bill_value` | 同上 |
| 优惠率 | `sc.discount_rate_pct` | 同上 | `scorecard.discount_rate_pct` | 同上 |
| 理论毛利率 | `sc.theoretical_margin_pct` | 同上 | `scorecard.theoretical_margin_pct` | 同上 |
| 会员占比 | `sc.member_bill_share_pct` | 同上 | `scorecard.member_bill_share_pct` | 同上 |
| 日均实收 | `risk.avg_daily_received` | 同上 | `risk.avg_daily_received` | mv_store_risk_rating_monthly |
| 平台实收 | `sd.platform.*` | 同上 | `platform.*` | mv_store_platform_economics_monthly |
> **健康度雷达图**:复用SituationalAwarenessPage的8维评分体系,RadarChart + 进度条。
> **日度趋势**:LineChart显示日实收和客单价。X轴 `business_date.substring(5, 10)` ✅。
> **偏差指标**:前端硬编码公司均值 `{ daily_rev: 20144, bill: 36.8, ... }`,与后端计算的中位数(18642)不一致。
### Tab2:餐段分析(meal
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|-----|---------|---------|
| 餐段 | `mealRows[].meal_period` | `meal_period` | — | bill_fact |
| 账单数 | `mealRows[].bill_count` | `bill_count` | — | 同上 |
| 实收 | `mealRows[].received` | `received` | `sum(received_total)` | 同上 |
| 平均单价 | `mealRows[].avg_bill` | `avg_bill` | `sum(received_total)/count(*)` | 同上 |
| 全网均价 | `mealRows[].network_avg_bill` | `network_avg_bill` | 全网同餐段均值 | 同上 |
| vs全网 | `mealRows[].vs_network_pct` | `vs_network_pct` | `(avg_bill/network_avg_bill - 1) * 100` | — |
> BarChart + FilterableTable,客户端排序。
### Tab3:品类结构(category
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 门店品类 | `cat.store.*` | `store.*` | mv_store_category_mix_monthly |
| 公司品类 | `cat.company.*` | `company.*` | 同上(全量汇总) |
> PieChart(门店品类占比) + BarChart(门店vs公司占比对比) + 4个MetricCard。
> 品类包括:兰州拉面、西式简餐、外卖套餐、冷菜、丝路美食。
### Tab4:成本分析(cost
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 理论成本率 | `cost.cost.theoretical_cost_rate_pct` | `theoretical_cost_rate_pct` | mv_store_theoretical_actual_cost_monthly |
| 实际成本率 | `cost.cost.actual_food_cost_rate_pct` | `actual_food_cost_rate_pct` | 同上 |
| 差异率 | `cost.cost.variance_to_theoretical_pct` | `variance_to_theoretical_pct` | 同上 |
| 差异等级 | `cost.cost.variance_level` | `variance_level` | 同上 |
| 原料分类 | `cost.categories[]` | `categories` | mv_inventory_cost_classified_monthly |
> FilterableTable展示原料分类成本对标,含消耗金额、实际占比、公司基准、差异。
### Tab5:会员分析(member
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 会员占比 | `member.opportunity.member_share_pct` | `opportunity.member_share_pct` | mv_store_member_opportunity_monthly |
| 公司均值 | `member.opportunity.company_member_share_pct` | `opportunity.company_member_share_pct` | 同上 |
| 复购率 | `member.repeat.repeat_rate_pct` | `repeat.repeat_rate_pct` | mv_store_repeat_summary_monthly |
| 复购收入占比 | `member.repeat.repeat_revenue_share_pct` | `repeat.repeat_revenue_share_pct` | 同上 |
| 活跃会员数 | `member.monthly.member_count` | `monthly.member_count` | v_store_member_monthly_activity |
| 人均订单 | `member.monthly.avg_orders` | `monthly.avg_orders` | 同上 |
| 人均消费 | `member.monthly.avg_received` | `monthly.avg_received` | 同上 |
> 提升机会文案:`member.opportunity.conversion_bill_scenario` + `revenue_uplift_scenario`。
### Tab6:异常账单(anomaly
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 账单号 | `anomalyRows[].bill_no` | `bill_no` | mv_risk_anomaly |
| 餐段 | `anomalyRows[].meal_period` | `meal_period` | 同上 |
| 消费额 | `anomalyRows[].consumption` | `consumption` | 同上 |
| 优惠 | `anomalyRows[].discount_total` | `discount_total` | 同上 |
| 实收 | `anomalyRows[].received_total` | `received_total` | 同上 |
| 异常原因 | `anomalyRows[].anomaly_reason` | `anomaly_reason` | 同上 |
| 结账时间 | `anomalyRows[].closed_at` | `closed_at` | 同上 |
> 后端分页查询,前端 `page_size=200` 一次性加载。日期格式化:`r.closed_at?.substring(0, 16)` ✅。
### Tab7:任务列表(tasks
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|-----|---------|---------|
| 问题指标 | `tasks[].problem_indicator` | `problem_indicator` | store_task |
| 优先级 | `tasks[].priority` | `priority` | 同上 |
| 行动要求 | `tasks[].action_required` | `action_required` | 同上 |
| 状态 | `tasks[].status` | `status` | 同上 |
| 截止日 | `tasks[].deadline` | `deadline` | 同上 |
> 日期格式化:`r.deadline?.substring(0, 10)` ✅。点击行跳转 `/tasks/:task_id`。
---
## 十五、页面13:门店分级与风险等级 StoreGradePage
> 单页面2个Tab(门店分级/关停测算),3个API2 GET + 1 POST mutation),有月份选择器(原生 `<input type="month">` 而非 MonthPicker 组件)。
> 核心数据源:`analytics.v3_store_grade`、`analytics.mv_store_risk_rating`、`analytics.v3_store_monthly_target`、`analytics.dim_store`、`analytics.mv_store_operating_expense_monthly`、`analytics.mv_store_area_efficiency_monthly`。
### API总览
| API | Tab | 方法 | 数据源表 | 说明 |
|-----|-----|------|---------|------|
| /store-grade/grades | 分级 | GET | v3_store_grade + mv_store_risk_rating | 分级列表+风险详情 |
| /store-grade/auto-grade | 分级 | POST | dim_store + mv_store_risk_rating + v3_store_monthly_target → v3_store_grade | 自动分级计算并写入 |
| /store-grade/closure-analysis | 关停 | GET | v3_store_grade + mv_store_risk_rating + mv_store_operating_expense_monthly + mv_store_area_efficiency_monthly | 关停成本测算 |
### Tab1:门店分级(grade
#### API: /store-grade/grades
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 门店编码 | `r.store_code` | `store_code` | — | v3_store_grade |
| 门店名称 | `r.store_name` | `store_name` | — | 同上 |
| 区域 | `r.region` | `region` | — | 同上 |
| 分级 | `r.grade` | `grade` | A≥85, B≥70, C≥50, D<50 | auto-grade计算 |
| 综合分 | `r.overall_score` | `overall_score` | 达成率×0.4+毛利率×0.25+会员占比×0.15+风险×0.2 | auto-grade计算 |
| 营收达成率 | `r.revenue_achievement_pct` | `revenue_achievement_pct` | `revenue / revenue_target × 100` | auto-grade计算 |
| 风险等级 | `r.risk_level` | `risk_level` | — | mv_store_risk_rating |
| 分级原因 | `r.reason` | `reason` | — | auto-grade写入 |
| 营收 | `r.revenue` | `revenue` | — | mv_store_risk_rating |
| 毛利率 | `r.margin_pct` | `margin_pct` | — | 同上 |
| 优惠率 | `r.discount_rate` | `discount_rate` | — | 同上 |
| 异常率 | `r.anomaly_rate` | `anomaly_rate` | — | 同上 |
| 会员占比 | `r.member_pct` | `member_pct` | — | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | — | 同上 |
| 活跃天数 | `r.active_days` | `active_days` | — | 同上 |
> **自动分级公式**overallScore = achievementScore×0.4 + marginScore×0.25 + memberScore×0.15 + riskScore×0.2
> - achievementScore = min(achievement, 100)
> - marginScore = min(margin_pct/50×100, 100)
> - memberScore = min(member_pct/50×100, 100)
> - riskScore = 红色30, 黄色60, 绿色100
>
> **弹窗详情**RadarChart 4维(达成率/综合分/毛利率/异常率) + 13项指标列表。
> **PieChart**:分级分布 + 风险等级分布。
> **FilterableTable**filterKey=grade, sortOptions=综合分/达成率/门店。
#### API: /store-grade/auto-grade (POST mutation)
> 前端点击"自动分级"按钮触发 POST,后端读取 `dim_store` + `mv_store_risk_rating` + `v3_store_monthly_target`,计算综合分和分级,写入 `v3_store_grade` 表(UPSERT)。成功后前端 invalidateQueries 刷新列表。
### Tab2:关停测算(closure
#### API: /store-grade/closure-analysis
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 月收入 | `r.monthly_revenue` | `monthly_revenue` | — | mv_store_risk_rating |
| 月利润 | `r.monthly_profit` | `monthly_profit` | `actual_store_contribution` | mv_store_operating_expense_monthly |
| 月租 | `r.monthly_rent` | `monthly_rent` | `rent_expense` | 同上 |
| 月人工 | `r.monthly_wage` | `monthly_wage` | `wage_expense` | 同上 |
| 关停成本 | `r.total_closure_cost` | `total_closure_cost` | `wage×1.5 + rent×2 + revenue×0.05` | — |
| 遣散费 | — | `severance_cost` | `monthly_wage × 1.5` | — |
| 违约金 | — | `lease_termination_cost` | `monthly_rent × 2` | — |
| 资产处置 | — | `asset_disposal_cost` | `monthly_revenue × 0.05` | — |
| 12月亏损 | `r.projected_12m_loss` | `projected_12m_loss` | `monthly_profit < 0 ? monthly_profit × 12 : 0` | — |
| 月节省 | `r.monthly_savings_if_closed` | `monthly_savings_if_closed` | `monthly_profit < 0 ? abs(monthly_profit) : 0` | — |
| 回本月数 | `r.payback_months` | `payback_months` | `total_closure_cost / abs(monthly_profit)` | — |
| 建议 | `r.recommendation` | `recommendation` | 亏损+D级→关停, 亏损+C级→整改, 微利+低分→重点整改, else→持续经营 | — |
| 原因 | `r.reason` | `reason` | — | — |
> **汇总指标**total_stores, recommend_close, recommend_rectify, total_closure_cost, total_projected_loss, total_monthly_savings。
> **PieChart**:关停建议分布(建议关停/整改观察/持续经营)。
> **FilterableTable**filterKey=recommendation, sortOptions=月利润/关停成本/12月亏损/回本月数/综合分。
---
## 十六、页面14:平台与优惠管理 PlatformPage
> 单页面无Tab2个API(全部GET),有MonthPicker。
> 核心数据源:`analytics.mv_store_platform_economics_monthly`、`analytics.bill_fact`。
### API总览
| API | 数据源表 | 说明 |
|-----|---------|------|
| /platform/economics | mv_store_platform_economics_monthly | 门店三平台经济性数据 |
| /marketing/plans | bill_fact | 营销方案聚合分析 |
### Block1:平台概览
#### API: /platform/economics
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 平台总实收 | `totalPlatformReceived` | — | 前端聚合:`Σ(meituan+taobao+jd)` | mv_store_platform_economics_monthly |
| 合并加权成本率 | `blendedCostRate` | — | 前端计算:`(Σ折扣+Σ佣金) / Σ实收 × 100` | 同上 |
| 高成本门店 | `highCostStores.length` | — | 前端过滤:任一平台cost_rate≥38% | 同上 |
| 营销方案数 | `marketingRows.length` | — | 来自/marketing/plans | bill_fact |
> 后端 `SELECT *` 返回全字段,前端聚合计算。无分页,全量返回。
> **平台字段**`meituan_received/discount/commission/cost_rate_pct`、`taobao_*`、`jd_*`。
### Block2:三平台对比图
> BarChart:美团/淘宝/京东三组,每组3条柱(实收/折扣/佣金)。前端聚合 `storesWithPlatform` 数据。
### Block3:门店平台经济性明细
> FilterableTablefilterKey=store_name, sortOptions=美团实收/美团成本率/淘宝实收/京东实收/门店。
> **成本率颜色**:≥38%红色, 35-38%黄色, <35%绿色。
> **经济性最优列**:前端计算三平台中成本率最低的平台,显示名称和与最高的差距。
### Block4:营销方案ROI
#### API: /marketing/plans
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 营销方案 | `r.marketing_plan` | `marketing_plan` | — | bill_fact |
| 账单数 | `r.bill_count` | `bill_count` | `count(*)` | 同上 |
| 消费额 | `r.consumption` | `consumption` | `sum(consumption)` | 同上 |
| 优惠额 | `r.discounts` | `discounts` | `sum(discount_total)` | 同上 |
| 实收 | `r.received` | `received` | `sum(received_total)` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | `avg(received_total)` | 同上 |
| 优惠率 | `r.discount_rate_pct` | `discount_rate_pct` | `sum(discount_total)/sum(consumption)×100` | — |
| 理论毛利率 | `r.theoretical_margin_pct` | `theoretical_margin_pct` | `sum(theoretical_profit)/sum(received_total)×100` | — |
> 前端Top5营销方案卡片展示 + FilterableTable完整列表。
> **优惠率颜色**:≥25%红色, 15-25%黄色, <15%绿色。
> **毛利率颜色**:≥70%绿色, 60-70%黄色, <60%红色。
---
## 十七、页面15:营收分析 RevenuePage
> 单页面无Tab4个API(全部GET),有MonthPicker。
> 核心数据源:`analytics.v_store_daily`、`analytics.v_channel_daily`、`analytics.v_meal_period_daily`。
### API总览
| API | 数据源表 | 说明 |
|-----|---------|------|
| /revenue/daily-summary | v_store_daily | 日度营收汇总(全门店合计) |
| /revenue/channel | v_channel_daily | 渠道日度收入结构 |
| /revenue/meal-period | v_meal_period_daily | 时段收入分布 |
| /revenue/store-ranking | v_store_daily | 门店营收排名 |
### Block1:核心指标
#### API: /revenue/daily-summary
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 营业收入 | `totalConsumption` | — | 前端聚合:`Σ consumption` | v_store_daily |
| 优惠总额 | `totalDiscounts` | — | 前端聚合:`Σ discounts` | 同上 |
| 实收总额 | `totalReceived` | — | 前端聚合:`Σ received` | 同上 |
| 账单数 | `totalBills` | — | 前端聚合:`Σ bill_count` | 同上 |
| 客单价 | `avgBillValue` | — | 前端计算:`totalReceived / totalBills` | — |
| 优惠率 | `avgDiscountRate` | — | 前端计算:`totalDiscounts / (totalReceived + totalDiscounts) × 100` | — |
| 客流量 | `totalGuests` | — | 前端聚合:`Σ guests` | v_store_daily |
| 人均消费 | `avgGuestValue` | — | 前端计算:`totalReceived / totalGuests` | — |
> 后端SQL`sum(received + discounts) AS consumption, sum(received) AS received, sum(discounts) AS discounts, sum(bill_count), sum(guests)`,按 `business_date` 分组。
> **环比趋势**:前端计算 last7 vs prev7 的实收和账单数环比。
### Block2:日度营收趋势
> ComposedChartBar(received) + Line(avg_bill) + Line(discount_rate),双Y轴。
> X轴 `business_date.substring(5, 10)` ✅。
### Block3:渠道收入结构 + 渠道趋势
#### API: /revenue/channel
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|---------|
| 微信 | `channelTotals.wechat` | `wechat` | v_channel_daily |
| 支付宝 | `channelTotals.alipay` | `alipay` | 同上 |
| 美团外卖 | `channelTotals.meituan_delivery` | `meituan_delivery` | 同上 |
| 美团堂食 | `channelTotals.meituan` | `meituan` | 同上 |
| 淘宝外卖 | `channelTotals.taobao_delivery` | `taobao_delivery` | 同上 |
| 抖音 | `channelTotals.douyin` | `douyin` | 同上 |
| 现金 | `channelTotals.cash` | `cash` | 同上 |
| 京东外卖 | `channelTotals.jd_delivery` | `jd_delivery` | 同上 |
| 银联 | `channelTotals.unionpay` | `unionpay` | 同上 |
| 挂账 | `channelTotals.credit` | `credit` | 同上 |
> PieChart(渠道占比) + LineChart(前5大渠道日度趋势)。
> 前端聚合日度数据为渠道总量,按值排序。
### Block4:时段收入分布
#### API: /revenue/meal-period
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 餐段 | `r.meal_period` | `meal_period` | — | v_meal_period_daily |
| 账单数 | `r.bill_count` | `bill_count` | `sum(bill_count)` | 同上 |
| 实收 | `r.received` | `received` | `sum(received)` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | `sum(received)/sum(bill_count)` | — |
> PieChart + 卡片列表展示各餐段收入和占比。
### Block5:门店营收排名
#### API: /revenue/store-ranking
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 门店 | `r.store_name` | `store_name` | — | v_store_daily |
| 实收 | `r.received` | `received` | `sum(received)` | 同上 |
| 账单数 | `r.bill_count` | `bill_count` | `sum(bill_count)` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | `sum(received)/sum(bill_count)` | — |
| 优惠率 | `r.avg_discount_rate_pct` | `avg_discount_rate_pct` | `sum(discounts)/(sum(received)+sum(discounts))×100` | — |
| 客流量 | `r.guests` | `guests` | `sum(guests)` | 同上 |
| 理论毛利率 | `r.avg_theoretical_margin_pct` | `avg_theoretical_margin_pct` | `avg(theoretical_margin_pct)` | 同上 |
> TOP10/BOTTOM10 水平BarChart(点击跳转门店详情) + FilterableTable完整列表。
> **优惠率颜色**>25%红色, >20%黄色。
> 后端 `ORDER BY sum(received) DESC`,前端 slice(0,10) 和 slice(-10).reverse()。
---
## 十八、页面16:区域经理工作台 RegionalPage
> 单页面无Tab5个API4 GET + 1 PUT mutation),有MonthPicker。
> 核心数据源:`analytics.mv_store_risk_rating_monthly`、`analytics.mv_store_action_priority_deep_monthly`、`analytics.mv_store_benchmark_composite_monthly`、`analytics.bill_fact`、`analytics.dim_store`、`analytics.v_task_weekly_check`、`analytics.store_task`、`analytics.task_weekly_check`。
### API总览
| API | 方法 | 数据源表 | 说明 |
|-----|------|---------|------|
| /stores/risk | GET | mv_store_risk_rating_monthly | 门店风险分级列表 |
| /stores/priority | GET | mv_store_action_priority_deep_monthly | 门店行动优先级 |
| /benchmark/composite | GET | mv_store_benchmark_composite_monthly | 同类门店基准对比 |
| /region/summary | GET | bill_fact + dim_store + mv_store_risk_rating_monthly | 区域经营汇总 |
| /tasks/weekly-check | GET | v_task_weekly_check | 周检任务列表 |
| /tasks/:id/weekly-check | PUT | store_task + store_task_log + task_weekly_check | 周检录入 |
### Block1:汇总卡片
| 显示指标 | 前端变量 | 数据来源 | 计算公式 |
|---------|---------|---------|---------|
| 营业收入 | `Σ regionRows.total_consumption` | /region/summary | 前端聚合 |
| 优惠 | `Σ regionRows.total_discount` | 同上 | 前端聚合 |
| 实收 | `Σ regionRows.total_received` | 同上 | 前端聚合 |
| 红黄门店数 | `riskRows.filter(risk_level !== '绿色').length` | /stores/risk | 前端过滤 |
| P0+P1门店 | `p0p1.length` | /stores/priority | 前端过滤 action_priority.startsWith('P0'/'P1') |
| 需到店检查 | `needCheck.length` | /tasks/weekly-check | 前端过滤 consecutive_no_improve_weeks ≥ 2 |
| 周检待录入 | `weeklyRows.filter(!last_check_date).length` | 同上 | 前端过滤 |
### Block2:区域经营汇总
#### API: /region/summary
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 区域 | `r.region` | `region` | — | dim_store |
| 门店数 | `r.store_count` | `store_count` | `count(DISTINCT store_code)` | bill_fact |
| 营业收入 | `r.total_consumption` | `total_consumption` | `sum(consumption)` | 同上 |
| 优惠 | `r.total_discount` | `total_discount` | `sum(discount_total)` | 同上 |
| 实收 | `r.total_received` | `total_received` | `sum(received_total)` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | `avg(received_total)` | 同上 |
| 优惠率 | `r.avg_discount_rate` | `avg_discount_rate` | `sum(discount_total)/sum(consumption)×100` | — |
| 毛利率 | `r.avg_margin_rate` | `avg_margin_rate` | `sum(theoretical_profit)/sum(received_total)×100` | — |
| 异常率 | `r.avg_anomaly_rate` | `avg_anomaly_rate` | **硬编码 0** | — |
| 红数 | `r.red_count` | `red_count` | `count(DISTINCT CASE WHEN risk_level='红色')` | mv_store_risk_rating_monthly |
| 黄数 | `r.yellow_count` | `yellow_count` | 同上黄色 | 同上 |
| 绿数 | `r.green_count` | `green_count` | 同上绿色 | 同上 |
> FilterableTablesortOptions=区域/营业收入/优惠/实收/客单价/门店数。
> **数据权限**:使用 `getDataScope(req)` + `scopeStoreFilter` 实现区域经理只能看自己区域。
### Block3:门店风险分级
#### API: /stores/risk
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店 | `r.store_name` | `store_name` | mv_store_risk_rating_monthly |
| 风险 | `r.risk_level` | `risk_level` | 同上 |
| 实收 | `r.received` | `received` | 同上 |
| 优惠率 | `r.discount_rate_pct` | `discount_rate_pct` | 同上 |
| 毛利率 | `r.theoretical_margin_pct` | `theoretical_margin_pct` | 同上 |
| 异常率 | `r.anomaly_rate_pct` | `anomaly_rate_pct` | 同上 |
> 后端 `SELECT *` + `ORDER BY risk_level, received DESC`。前端风险筛选按钮(全部/红色/黄色/绿色)+ FilterableTable。
> 点击行跳转 `/stores/:store_code`。
### Block4:需到店检查门店
#### API: /tasks/weekly-check
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店 | `r.store_name` | `store_name` | v_task_weekly_check |
| 问题指标 | `r.problem_indicator` | `problem_indicator` | 同上 |
| 连续未改善周数 | `r.consecutive_no_improve_weeks` | `consecutive_no_improve_weeks` | 同上 |
| 任务状态 | `r.status` | `status` | 同上 |
> 前端过滤 `consecutive_no_improve_weeks ≥ 2`。点击"录入周检"打开弹窗。
> **PUT API**`/tasks/:id/weekly-check`,更新 `store_task.process_evidence/next_step`,插入 `store_task_log` 和 `task_weekly_check` 记录。
### Block5:同类门店基准对比
#### API: /benchmark/composite
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店 | `r.store_name` | `store_name` | mv_store_benchmark_composite_monthly |
| 标杆得分 | `r.benchmark_score` | `benchmark_score` | 同上 |
| 实收 | `r.received` | `received` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | 同上 |
| 优惠率 | `r.discount_rate_pct` | `discount_rate_pct` | 同上 |
| 毛利率 | `r.theoretical_margin_pct` | `theoretical_margin_pct` | 同上 |
| 会员占比 | `r.member_bill_share_pct` | `member_bill_share_pct` | 同上 |
> 后端 `SELECT * ORDER BY benchmark_score DESC`。**标杆得分颜色**:≥80绿色, ≥60黄色, <60红色。
---
## 十九、页面17:门店选址分析 SiteSelectionPage
> 单页面4个Tab(分段基准/复制评分/重叠风险/区域基准),5个API(全部GET),有MonthPicker。
> 核心数据源:`analytics.mv_store_site_profile_monthly`、`analytics.mv_site_segment_benchmark_monthly`、`analytics.mv_store_site_replication_monthly`、`analytics.mv_store_overlap_risk_monthly`、`analytics.mv_district_site_benchmark_monthly`。
### API总览
| API | Tab | 数据源表 | 说明 |
|-----|-----|---------|------|
| /site-selection/profile | 全局 | mv_store_site_profile_monthly | 门店选址画像 |
| /site-selection/segment-benchmark | 分段基准 | mv_site_segment_benchmark_monthly | 场景×面积分段基准 |
| /site-selection/replication | 复制评分 | mv_store_site_replication_monthly | 门店复制评分 |
| /site-selection/overlap-risk | 重叠风险 | mv_store_overlap_risk_monthly | 门店空间重叠风险 |
| /site-selection/district-benchmark | 区域基准 | mv_district_site_benchmark_monthly | 区域选址基准 |
### 全局:概览指标 + 散点图 + 地图
#### API: /site-selection/profile
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 标准门店数 | `profileRows.length` | — | mv_store_site_profile_monthly |
| 场景类型数 | `totalScenes` | — | 前端聚合 site_scene 去重 |
| 高风险重叠对 | `highRiskCount` | — | 来自 /overlap-risk 前端过滤 |
| 优先提炼原型 | `topReplication` | — | 来自 /replication 前端过滤 |
> **StoreMap**:自定义地图组件,按场景类型颜色标注门店位置。
> **ScatterChart**X=面积(㎡), Y=月坪效(元/㎡), Z=实收(气泡大小),按场景颜色区分。
> 后端 `WHERE business_type='标准门店' AND received>0``ORDER BY received DESC`。
### Tab1:分段基准(benchmark
#### API: /site-selection/segment-benchmark
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 场景 | `r.site_scene` | `site_scene` | mv_site_segment_benchmark_monthly |
| 面积段 | `r.area_band` | `area_band` | 同上 |
| 门店数 | `r.store_count` | `store_count` | 同上 |
| 平均面积 | `r.avg_area_sqm` | `avg_area_sqm` | 同上 |
| 平均实收 | `r.avg_received` | `avg_received` | 同上 |
| 中位实收 | `r.median_received` | `median_received` | 同上 |
| 平均坪效 | `r.avg_received_per_sqm` | `avg_received_per_sqm` | 同上 |
| 中位坪效 | `r.median_received_per_sqm` | `median_received_per_sqm` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | 同上 |
| 复购率 | `r.avg_repeat_rate_pct` | `avg_repeat_rate_pct` | 同上 |
> FilterableTablefilterKey=site_scenesortOptions=平均坪效/中位坪效/平均实收/场景。
> 后端 `ORDER BY avg_received_per_sqm DESC`。
> **门店画像明细**:同 profile API 数据,filterKey=site_scene,点击行跳转门店详情。
### Tab2:复制评分(replication
#### API: /site-selection/replication
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店 | `r.store_name` | `store_name` | mv_store_site_replication_monthly |
| 场景 | `r.site_scene` | `site_scene` | 同上 |
| 面积 | `r.area_sqm` | `area_sqm` | 同上 |
| 实收 | `r.received` | `received` | 同上 |
| 坪效 | `r.monthly_received_per_sqm` | `monthly_received_per_sqm` | 同上 |
| 最近门店 | `r.nearest_store_name` | `nearest_store_name` | 同上 |
| 距离 | `r.nearest_distance_km` | `nearest_distance_km` | 同上 |
| 复制评分 | `r.site_replication_score` | `site_replication_score` | 同上 |
| 复制建议 | `r.replication_recommendation` | `replication_recommendation` | 同上 |
| 空间建议 | `r.spatial_recommendation` | `spatial_recommendation` | 同上 |
> FilterableTablefilterKey=replication_recommendationsortOptions=复制评分/实收/坪效/距离。
> **复制评分颜色**:≥75绿色, ≥60蓝色, <40红色。
> **复制建议标签**:优先提炼选址原型(绿)、不宜作为选址标杆(红)、其他(蓝)。
> 后端 `ORDER BY site_replication_score DESC`。
### Tab3:重叠风险(overlap
#### API: /site-selection/overlap-risk
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店A | `r.store_name_a` | `store_name_a` | mv_store_overlap_risk_monthly |
| 门店B | `r.store_name_b` | `store_name_b` | 同上 |
| 距离 | `r.distance_km` | `distance_km` | 同上 |
| A实收 | `r.received_a` | `received_a` | 同上 |
| B实收 | `r.received_b` | `received_b` | 同上 |
| 风险等级 | `r.overlap_risk` | `overlap_risk` | 同上 |
> 3个MetricCard<1km/1-2km/2-3km+ FilterableTable。
> filterKey=overlap_risksortOptions=距离/A实收/B实收。
> 后端 `ORDER BY distance_km`。
### Tab4:区域基准(district
#### API: /site-selection/district-benchmark
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 区域 | `r.district` | `district` | mv_district_site_benchmark_monthly |
| 门店数 | `r.store_count` | `store_count` | 同上 |
| 平均面积 | `r.avg_area_sqm` | `avg_area_sqm` | 同上 |
| 总实收 | `r.total_received` | `total_received` | 同上 |
| 平均实收 | `r.avg_received` | `avg_received` | 同上 |
| 中位实收 | `r.median_received` | `median_received` | 同上 |
| 平均坪效 | `r.avg_received_per_sqm` | `avg_received_per_sqm` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | 同上 |
| 复购率 | `r.avg_repeat_rate_pct` | `avg_repeat_rate_pct` | 同上 |
| P0数 | `r.p0_count` | `p0_count` | 同上 |
| P1数 | `r.p1_count` | `p1_count` | 同上 |
> BarChart(前15区域平均/中位实收对比) + FilterableTable。
> sortOptions=平均实收/平均坪效/总实收/区域。
> 后端 `ORDER BY total_received DESC`。
---
## 二十、页面18:时间维度分析 TimePage
> 单页面无Tab3个API(全部GET),有MonthPicker。
> 核心数据源:`mv_time_weekday`、`mv_time_hourly`、`mv_channel_daily`。
### API总览
| API | 数据源表 | 说明 |
|-----|---------|------|
| /time/weekday | mv_time_weekday | 星期维度分析 |
| /time/hourly | mv_time_hourly | 小时维度分析 |
| /channel | mv_channel_daily | 渠道日度趋势 |
### Block1:时间概览
| 显示指标 | 前端变量 | 数据来源 | 计算公式 |
|---------|---------|---------|---------|
| 高峰日 | `peakWeekday.weekday` | /time/weekday | 前端 reduce 找最大 received |
| 低谷日 | `lowWeekday.weekday` | 同上 | 前端 reduce 找最小 received |
| 高峰时段 | `peakHour.closing_hour` | /time/hourly | 前端 reduce 找最大 bill_count |
| 渠道数 | `channelTotals.length` | /channel | 前端聚合非零渠道数 |
### Block2:星期维度分析
#### API: /time/weekday
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 星期 | `r.weekday` | `weekday` | mv_time_weekday |
| 实收 | `r.received` | `received` | 同上 |
| 账单数 | `r.bill_count` | `bill_count` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | 同上 |
> BarChart(实收+账单数)+ Line(客单价),双Y轴。后端 `WHERE month = to_char($1::date, 'YYYY-MM')`。
### Block3:小时维度分析
#### API: /time/hourly
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 小时 | `r.closing_hour` | `closing_hour` | mv_time_hourly |
| 账单数 | `r.bill_count` | `bill_count` | 同上 |
| 实收 | `r.received` | `received` | 同上 |
| 客单价 | `r.avg_bill_value` | `avg_bill_value` | 同上 |
> AreaChart(实收+账单数),双Y轴。后端 `WHERE month = to_char($1::date, 'YYYY-MM')`。
### Block4:渠道趋势 + 渠道汇总
#### API: /channel
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 日期 | `r.business_date` | `business_date` | mv_channel_daily |
| 现金 | `r.cash` | `cash` | 同上 |
| 支付宝 | `r.alipay` | `alipay` | 同上 |
| 微信 | `r.wechat` | `wechat` | 同上 |
| 美团到店 | `r.meituan` | `meituan` | 同上 |
| 银联 | `r.unionpay` | `unionpay` | 同上 |
| 抖音 | `r.douyin` | `douyin` | 同上 |
| 挂账 | `r.credit` | `credit` | 同上 |
| 京东到家 | `r.jd_delivery` | `jd_delivery` | 同上 |
| 美团外卖 | `r.meituan_delivery` | `meituan_delivery` | 同上 |
| 淘宝外卖 | `r.taobao_delivery` | `taobao_delivery` | 同上 |
> LineChart(前6大渠道日度趋势)+ 渠道汇总卡片。X轴 `business_date.substring(5, 10)` ✅。
> **注意**:此API与 RevenuePage 的 `/revenue/channel` 数据源相同(`mv_channel_daily`),但查询无 schema 前缀(`mv_channel_daily` vs `analytics.v_channel_daily`)。
---
## 二十一、页面19:指标字典 IndicatorsPage
> 单页面无Tab1个APIGET),无MonthPicker。
> 核心数据源:`analytics.indicator_dictionary`。
### API: /tasks/indicators
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 指标名称 | `r.indicator_name` | `indicator_name` | indicator_dictionary |
| 业务定义 | `r.business_definition` | `business_definition` | 同上 |
| 计算公式 | `r.formula` | `formula` | 同上 |
| 更新频率 | `r.update_frequency` | `update_frequency` | 同上 |
| 负责人 | `r.owner` | `owner` | 同上 |
| 黄线 | `r.yellow_threshold` | `yellow_threshold` | 同上 |
| 红线 | `r.red_threshold` | `red_threshold` | 同上 |
| 版本日期 | `r.version_date` | `version_date` | 同上 |
> FilterableTablefilterKey=update_frequencysortOptions=指标名称/更新频率/负责人。
> `version_date?.substring(0, 10)` ✅。后端 `SELECT * ORDER BY id`,无分页。
---
## 二十二、页面20:任务管理 TasksPage
> 单页面无Tab1个APIGET),有MonthPicker + 优先级/状态筛选。
> 核心数据源:`analytics.store_task`。
### API: /tasks
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 问题指标 | `r.problem_indicator` | `problem_indicator` | store_task |
| 行动要求 | `r.action_required` | `action_required` | 同上 |
| 负责人 | `r.owner` | `owner` | 同上 |
| 截止日 | `r.deadline?.substring(0,10)` | `deadline` | 同上 |
| 状态 | `r.status` | `status` | 同上 |
| 优先级 | `r.priority` | `priority` | 同上 |
| 门店 | `r.store_name` | `store_name` | 同上 |
| 门店编码 | `r.store_code` | `store_code` | 同上 |
> 后端支持分页(`parsePagination`),前端传 `page_size: 200`。
> 前端按 `store_code` 分组展示,每组内 FilterableTable。
> **筛选**priority + status 参数传递到后端 WHERE 条件。
> `deadline?.substring(0, 10)` ✅。
---
## 二十三、页面21:任务详情 TaskDetailPage
> 单页面无Tab2个API1 GET + 1 GET indicators+ 3个mutationPUT execute, PUT verify, POST rollback)。
> 核心数据源:`analytics.store_task`、`analytics.store_task_log`、`analytics.indicator_dictionary`。
### API: /tasks/:id (GET)
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店名 | `task.store_name` | `store_name` | store_task |
| 问题指标 | `task.problem_indicator` | `problem_indicator` | 同上 |
| 优先级 | `task.priority` | `priority` | 同上 |
| 状态 | `task.status` | `status` | 同上 |
| 当前值 | `task.current_value` | `current_value` | 同上 |
| 基准值 | `task.benchmark_value` | `benchmark_value` | 同上 |
| 目标值 | `task.target_value` | `target_value` | 同上 |
| 问题描述 | `task.problem_description` | `problem_description` | 同上 |
| 验收指标 | `task.verification_indicator` | `verification_indicator` | 同上 |
| 行动要求 | `task.action_required` | `action_required` | 同上 |
| 负责人 | `task.owner` | `owner` | 同上 |
| 截止日 | `task.deadline?.substring(0,10)` | `deadline` | 同上 |
| 过程证据 | `task.process_evidence` | `process_evidence` | 同上 |
| 未完成原因 | `task.incomplete_reason` | `incomplete_reason` | 同上 |
| 验收结果 | `task.verification_result` | `verification_result` | 同上 |
| 操作日志 | `logs` | — | store_task_log |
> 后端 `SELECT * FROM store_task WHERE task_id = $1` + `SELECT * FROM store_task_log WHERE task_id = $1`。
> `deadline?.substring(0, 10)` ✅,`log.created_at?.substring(0, 19)` ✅。
### Mutations
| Mutation | API | 说明 |
|----------|-----|------|
| executeMutation | PUT /tasks/:id/execute | 提交执行反馈,状态改为"进行中" |
| verifyMutation | PUT /tasks/:id/verify | 月度验收,传 review_result/actual_value 等 |
| rollbackMutation | POST /tasks/:id/rollback | 回滚任务 |
> **指标说明**:从 `/tasks/indicators` 获取指标字典,匹配 `problem_indicator` 显示业务定义、计算公式、黄线/红线阈值。
---
## 二十四、页面22:月度验收与复盘 MonthlyReviewPage
> 7个Tab(验收汇总/升降级/任务完成率/活动清单/SKU治理/经验标准化/复盘优化),7个API6 GET + 1 POST mutation),有MonthPicker。
> 核心数据源:`analytics.v_task_monthly_review`、`analytics.store_grade_change`、`analytics.store_task`、`analytics.bill_fact`、`analytics.mv_dish_sku_abc_monthly`、`analytics.standardized_practice`。
### API总览
| API | Tab | 数据源表 | 说明 |
|-----|-----|---------|------|
| /tasks/monthly-review | 验收汇总 | v_task_monthly_review | 验收汇总+明细 |
| /tasks/grade-change | 升降级 | store_grade_change | P0/P1/P2升降级 |
| /tasks/monthly-review/completion | 完成率 | store_task | 按优先级汇总完成率 |
| /tasks/monthly-review/activity-list | 活动清单 | bill_fact | 平台活动停/改/留 |
| /tasks/monthly-review/sku-governance | SKU治理 | mv_dish_sku_abc_monthly | ABC分类+长尾清单 |
| /tasks/practices | 经验标准化 | standardized_practice | 标准化经验列表 |
| POST /tasks/practices | 经验标准化 | standardized_practice | 新增经验 |
### Tab1:验收汇总
#### API: /tasks/monthly-review
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 总任务数 | `summary.total` | — | 后端计算 result.rows.length |
| 达标 | `summary.passed` | — | 后端 filter review_result='达标' |
| 改善中 | `summary.improving` | — | 同上 |
| 未改善 | `summary.failed` | — | 同上 |
| 门店 | `r.store_name` | `store_name` | v_task_monthly_review |
| 问题指标 | `r.problem_indicator` | `problem_indicator` | 同上 |
| 基线 | `r.baseline_value` | `baseline_value` | 同上 |
| 目标 | `r.target_value` | `target_value` | 同上 |
| 实际 | `r.actual_value` | `actual_value` | 同上 |
| 结果 | `r.review_result` | `review_result` | 同上 |
> PieChart(验收结果分布)+ FilterableTable。后端 `SELECT * WHERE plan_month = $1`。
### Tab2:升降级面板
#### API: /tasks/grade-change
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 月份 | `r.change_month?.substring(0,7)` | `change_month` | store_grade_change |
| 门店 | `r.store_name` | `store_name` | 同上 |
| 原级别 | `r.old_priority` | `old_priority` | 同上 |
| 新级别 | `r.new_priority` | `new_priority` | 同上 |
| 变化 | `r.change_type` | `change_type` | 同上 |
| 原因 | `r.reason` | `reason` | 同上 |
> `change_month?.substring(0, 7)` ✅。后端 `SELECT * WHERE change_month = $1::date`。
### Tab3:任务完成率
#### API: /tasks/monthly-review/completion
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 优先级 | `c.priority` | `priority` | — | store_task |
| 总数 | `c.total` | `total` | `count(*)` | 同上 |
| 已完成 | `c.completed` | `completed` | `count FILTER(status='已验收'/'已回滚')` | 同上 |
| 达标 | `c.passed` | `passed` | `count FILTER(verification_result='达标')` | 同上 |
| 改善中 | `c.improving` | `improving` | 同上 | 同上 |
| 未改善 | `c.failed` | `failed` | 同上 | 同上 |
| 完成率 | `c.completion_rate` | `completion_rate` | `completed/total×100` | 同上 |
> 进度条卡片 + FilterableTable。后端 `GROUP BY priority`。
### Tab4:活动清单
#### API: /tasks/monthly-review/activity-list
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 活动方案 | `r.marketing_plan` | `marketing_plan` | — | bill_fact |
| 账单数 | `r.bill_count` | `bill_count` | `count(*)` | 同上 |
| 消费额 | `r.consumption` | `consumption` | `sum(consumption)` | 同上 |
| 优惠额 | `r.discounts` | `discounts` | `sum(discount_total)` | 同上 |
| 实收 | `r.received` | `received` | `sum(received_total)` | 同上 |
| 优惠成本率 | `r.discount_cost_rate` | `discount_cost_rate` | `discounts/consumption×100` | — |
| 净贡献 | `r.net_contribution` | `net_contribution` | `received - discounts` | — |
| 建议 | 前端计算 | — | net>0 && rate<15→留, rate>30→停, else→改 | — |
> FilterableTablesortOptions=净贡献/实收/优惠成本率/账单数。
### Tab5SKU治理
#### API: /tasks/monthly-review/sku-governance
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| ABC分类 | `s.abc_class` | `abc_class` | mv_dish_sku_abc_monthly |
| SKU数 | `s.sku_count` | `sku_count` | 同上 |
| 营收占比 | `s.total_revenue_share` | `total_revenue_share` | 同上 |
| 菜品名 | `r.dish_name` | `dish_name` | 同上(C类) |
| 分类 | `r.category_level1` | `category_level1` | 同上 |
| 账单数 | `r.bill_count` | `bill_count` | 同上 |
| 实收 | `r.received_amount` | `received_amount` | 同上 |
| 营收占比 | `r.revenue_share_pct` | `revenue_share_pct` | 同上 |
> 后端返回 `{ summary, longtail }`longtail `LIMIT 50`。
### Tab6:经验标准化
#### API: /tasks/practices (GET + POST)
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 模块 | `p.practice_module` | `practice_module` | standardized_practice |
| 标杆门店 | `p.benchmark_store_name` | `benchmark_store_name` | 同上 |
| 关键动作 | `p.key_actions` | `key_actions` | 同上 |
| 状态 | `p.status` | `status` | 同上 |
> POST 新增经验,字段 `practice_module/benchmark_store_code/benchmark_store_name/key_actions/verification_indicators`。
### Tab7:复盘优化
#### API: /tasks/monthly-review/indicator-effectiveness
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 指标 | `r.problem_indicator` | `problem_indicator` | — | store_task |
| 任务数 | `r.task_count` | `task_count` | `count(*)` | 同上 |
| 达标 | `r.passed` | `passed` | `count FILTER(verification_result='达标')` | 同上 |
| 改善中 | `r.improving` | `improving` | 同上 | 同上 |
| 未改善 | `r.failed` | `failed` | 同上 | 同上 |
| 达标率 | `r.pass_rate` | `pass_rate` | `passed/task_count×100` | — |
| 评价 | 前端计算 | — | ≥50%有效, ≥30%待观察, <30%低效 | — |
> 后端 `GROUP BY problem_indicator ORDER BY pass_rate DESC`。
---
## 二十五、页面23:数据质量看板 DataQualityPage
> 单页面无Tab1个APIGET),有MonthPicker。
> 核心数据源:`analytics.bill_fact`、`public.dish_sales_details`、`analytics.mv_inventory_cost_classified_monthly`。
### API: /data-quality
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 总账单数 | `totalBills` | `total_bills` | `count(*)` | bill_fact |
| 覆盖门店数 | `storeCount` | `store_count` | `count(DISTINCT store_code)` | 同上 |
| 缺失账单号 | `missingBillNo` | `missing_bill_no` | `count FILTER(bill_no IS NULL/='')` | 同上 |
| 缺失门店编码 | `missingStoreCode` | `missing_store_code` | 同上 | 同上 |
| 零消费账单 | `zeroConsumption` | `zero_consumption` | `count FILTER(consumption=0/NULL)` | 同上 |
| 负实收账单 | `negativeReceived` | `negative_received` | `count FILTER(received_total<0)` | 同上 |
| 零实收账单 | `zeroReceived` | `zero_received` | `count FILTER(received_total=0/NULL)` | 同上 |
| 优惠大于消费 | `discountGtConsumption` | `discount_gt_consumption` | `count FILTER(discount_total>consumption AND consumption>0)` | 同上 |
| 日期范围 | `dq.min_date?.substring(0,10)` | `min_date/max_date` | `min/max(closed_at)` | 同上 |
| 总菜品记录数 | `totalDishRecords` | `total_dish_records` | `count(*)` | dish_sales_details |
| 缺失门店(菜品) | `dishMissingStore` | `dish_missing_store` | `count FILTER(store_code IS NULL/='')` | 同上 |
| 缺失菜品名 | `dishMissingDish` | `dish_missing_dish` | `count FILTER(dish_name IS NULL/='')` | 同上 |
| 负耗用记录数 | `negativeConsumptionCount` | `negative_consumption_count` | `count FILTER(consumption_amount<0)` | mv_inventory_cost_classified_monthly |
| 负耗用金额 | `negativeConsumptionAmount` | `negative_consumption_amount` | `sum(consumption_amount) FILTER(<0)` | 同上 |
> **注意**:账单和菜品统计无月份过滤(全量扫描),仅库存统计按 `month_start` 过滤。
> `min_date/max_date?.substring(0, 10)` ✅。
> **四层状态**:完整性/准确性/一致性/时效性,前端根据指标值计算。
> **置信等级**A(正常)/B(待桥接)/C(异常)。
---
## 二十六、页面24:银行授信数据报告 BankPage
> 单页面无Tab1个APIGET),有MonthPicker + 评分规则配置面板。
> 核心数据源:`analytics.mv_overview_monthly`、`analytics.mv_overview_daily`、`analytics.bill_fact`、`analytics.mv_store_operating_expense_monthly`、`analytics.mv_store_risk_rating_monthly`、`analytics.v_channel_daily`。
### API: /bank/report
后端 `Promise.all` 并发6个查询,返回 `{ overview, daily, waterfall, risk, channel, stores, stability }`
### Block1:银行授信评估摘要
| 评分指标 | 前端变量 | 数据来源 | 计算公式 |
|---------|---------|---------|---------|
| 净利率 | `metrics.netMargin` | waterfall | `store_contribution/received×100` |
| 营收稳定性 | `metrics.cv` | stability | `stdDev/meanDailyRevenue` |
| 门店健康度 | `metrics.redPct` | risk | `红色门店数/总门店数×100` |
| 规模体量 | `metrics.scale` | overview | `ov.received` |
| 优惠率 | `metrics.discountRate` | overview | `ov.discount_rate_pct` |
| 会员占比 | `metrics.memberShare` | overview | `ov.member_share_pct` |
> **评分规则**:可配置权重和阈值,localStorage 持久化。单项得分 0-100,加权总分 → 信用等级 A(≥85)/B(≥70)/C(≥50)/D(<50)。
### Block2:企业经营概况
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 营业收入 | `wf.consumption` | `waterfall.consumption` | bill_fact |
| 优惠 | `wf.discount` | `waterfall.discount` | 同上 |
| 月度实收 | `ov.received` | `overview.received` | mv_overview_monthly |
| 客单价 | `ov.avg_bill_value` | `overview.avg_bill_value` | 同上 |
| 理论毛利率 | `ov.theoretical_margin_pct` | `overview.theoretical_margin_pct` | 同上 |
| 门店数量 | `stores.length` | — | mv_store_risk_rating_monthly |
| 日实收趋势 | `dailyChart` | `daily` | mv_overview_daily |
> `business_date?.substring(5, 10)` ✅。
### Block3:盈利能力分析(利润瀑布)
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 营业收入 | `wf.consumption` | `consumption` | `sum(consumption)` | bill_fact |
| 优惠 | `wf.discount` | `discount` | `sum(discount_total)` | 同上 |
| 实收 | `wf.received` | `received` | `sum(received)` | mv_store_risk_rating_monthly |
| 食材 | `wf.food_cost` | `food_cost` | `sum(actual_food_cost)` | mv_store_operating_expense_monthly |
| 人工 | `wf.wage` | `wage` | `sum(wage_expense)` | 同上 |
| 房租 | `wf.rent` | `rent` | `sum(rent_expense)` | 同上 |
| 水电 | `wf.utility` | `utility` | `sum(utility_expense)` | 同上 |
| 其他 | `wf.dorm+commission+other` | `dorm/commission/other_expense` | `sum(dorm+commission+card_fee+repair)` | 同上 |
| 门店贡献利润 | `wf.store_contribution` | `store_contribution` | `received - food_cost - operating_expense` | — |
> 自定义 BarChart shape 实现瀑布图效果。
### Block4:门店资产质量
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 风险分布 | `riskPie` | `risk` | mv_store_risk_rating_monthly |
| TOP10门店 | `top10` | `stores` | 同上 |
> PieChart(风险等级分布)+ 横向 BarChart(TOP10门店实收)。
### Block5:经营稳定性指标
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 日度营收波动率 | `stability.cv` | `stability.cv` | `stdDev/mean` | mv_overview_daily |
| 日均收入 | `stability.mean_daily_revenue` | `stability.mean_daily_revenue` | — | 同上 |
| 优惠率 | `ov.discount_rate_pct` | `overview.discount_rate_pct` | — | mv_overview_monthly |
| 会员消费占比 | `ov.member_share_pct` | `overview.member_share_pct` | — | 同上 |
| 活跃渠道数 | `channelPie.length` | `channel` | 前端非零渠道数 | v_channel_daily |
> 渠道 PieChart,标签显示渠道名称和占比。
---
## 二十七、页面25:本体标准 OntologyPage
> 5个Tab(概览/维度主数据/事实表/枚举标准/指标标准),5个API(全部GET),无MonthPicker。
> 核心数据源:`information_schema.columns`、`analytics.indicator_dictionary`、各 dim_/fact_/enum_ 表。
### API总览
| API | Tab | 数据源 | 说明 |
|-----|-----|--------|------|
| /tasks/ontology/overview | 概览 | information_schema + indicator_dictionary | 表统计概览 |
| /tasks/ontology/dim/:table | 维度主数据 | 对应 dim_ 表 | 维度表数据+字段定义 |
| /tasks/ontology/fact/:table | 事实表 | 对应 fact_ 表 | 事实表字段定义 |
| /tasks/ontology/enum/:table | 枚举标准 | 对应 enum_ 表 | 枚举值列表 |
| /tasks/ontology/metrics | 指标标准 | indicator_dictionary | 指标字典 |
### Tab1:概览
> 显示维度表数、事实表数、枚举表数、指标标准数。点击可跳转到对应Tab。
### Tab2-4:维度/事实/枚举
> 列表→详情模式。列表显示表名、字段数、行数。点击进入详情,FilterableTable 展示数据(pageSize=20),下方显示字段定义表。
> 后端使用正则校验表名 `^dim_\w+$` / `^fact_\w+$` / `^enum_\w+$` 防注入。
> 时间戳字段 `String(val).substring(0, 19)` ✅。
### Tab5:指标标准
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 指标编码 | `r.metric_code` | `metric_code` | indicator_dictionary |
| 指标名称 | `r.indicator_name` | `indicator_name` | 同上 |
| 分类 | `r.metric_category` | `metric_category` | 同上 |
| 业务定义 | `r.business_definition` | `business_definition` | 同上 |
| 计算公式 | `r.formula` | `formula` | 同上 |
| 单位 | `r.unit` | `unit` | 同上 |
| 方向 | `r.direction` | `direction` | 同上 |
| 负责人 | `r.owner` | `owner` | 同上 |
| 黄线 | `r.yellow_threshold` | `yellow_threshold` | 同上 |
| 红线 | `r.red_threshold` | `red_threshold` | 同上 |
| 更新频率 | `r.update_frequency` | `update_frequency` | 同上 |
| 版本 | `r.version` | `version` | 同上 |
> 前端分类筛选(全部/收入/成本/优惠/会员/商品/库存/风险)+ FilterableTable。
---
## 二十八、页面26:中央厨房成本驾驶舱 CentralKitchenPage
> 单页面无Tab1个APIGET),有MonthPicker。
> 核心数据源:`analytics.v_central_kitchen_product_full_cost`、`central_kitchen_processing_cost`、`central_kitchen_recipe_consumption`、`central_kitchen_manufacturing_cost_pool`、`central_kitchen_finished_receipt`。
### API: /central-kitchen/dashboard
后端 `Promise.all` 并发7个查询,返回 `{ summary, reconciliation, products, categoryCost, recipeEfficiency, mfgPool, yieldAnalysis }`
### Block1:核心指标卡片
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 产品数 | `summary.product_count` | `product_count` | `count(*)` | v_central_kitchen_product_full_cost |
| 完工入库量 | `summary.total_inbound_qty` | `total_inbound_qty` | `sum(inbound_quantity)` | 同上 |
| 理论材料成本 | `summary.theoretical_cost` | `theoretical_cost` | `sum(theoretical_cost)` | 同上 |
| 实际材料成本 | `summary.material_actual_cost` | `material_actual_cost` | `sum(material_actual_cost)` | 同上 |
| 完整制造成本 | `summary.full_manufacturing_cost` | `full_manufacturing_cost` | `sum(full_manufacturing_cost)` | 同上 |
| 材料效率差异 | `summary.efficiency_variance_pct` | `efficiency_variance_pct` | `(实际-理论)/理论×100` | — |
| 制造费用率 | `summary.mfg_cost_rate` | `mfg_cost_rate` | `制造费用池/实际材料×100` | — |
> 后端 `WHERE report_month = $1::date`。
### Block2:成本对账瀑布
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 理论成本 | `reconciliation.theoretical_cost` | `theoretical_cost` | v_central_kitchen_product_full_cost |
| 标准成本 | `reconciliation.standard_cost` | `standard_cost` | 同上 |
| 实际材料 | `reconciliation.material_actual_cost` | `material_actual_cost` | 同上 |
| 制造费用池 | `reconciliation.manufacturing_cost_pool` | `manufacturing_cost_pool` | 同上 |
| 全制造成本 | `reconciliation.full_manufacturing_cost` | `full_manufacturing_cost` | 同上 |
| 入库价值 | `reconciliation.calculated_inbound_value` | `calculated_inbound_value` | central_kitchen_processing_cost |
| 制造毛利 | `reconciliation.manufacturing_margin` | `manufacturing_margin` | 入库价值-全制造成本 |
> 后端 JOIN `central_kitchen_processing_cost c ON p.report_month = c.report_month AND p.product_code = c.product_code`。
> 自定义 BarChart shape 实现瀑布图效果。
### Block3:品类成本结构
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 品类 | `c.category_minor` | `category_minor` | v_central_kitchen_product_full_cost |
| 产品数 | `c.product_count` | `product_count` | 同上 |
| 入库量 | `c.total_qty` | `total_qty` | 同上 |
| 理论成本 | `c.theoretical_cost` | `theoretical_cost` | 同上 |
| 实际材料 | `c.material_actual_cost` | `material_actual_cost` | 同上 |
| 制造费用 | `c.allocated_mfg_cost` | `allocated_mfg_cost` | 同上 |
| 全成本 | `c.full_cost` | `full_cost` | 同上 |
| 效率差异 | `c.efficiency_var_pct` | `efficiency_var_pct` | 同上 |
> 堆叠 BarChart(材料成本+制造费用)+ FilterableTable。后端 `GROUP BY category_minor ORDER BY full_cost DESC`。
### Block4:产品成本偏差散点图
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 产品名 | `p.product_name` | `product_name` | v_central_kitchen_product_full_cost |
| 理论成本(X) | `p.theoretical_cost` | `theoretical_cost` | 同上 |
| 实际成本(Y) | `p.material_actual_cost` | `material_actual_cost` | 同上 |
| 偏差率(颜色) | `p.efficiency_variance_pct` | `efficiency_variance_pct` | 同上 |
> ScatterChart,颜色按偏差率分4档:节约(≤-5%)/正常(-5%~0%)/轻微超耗(0~5%)/严重超耗(≥5%)。
### Block5:产品成本明细表
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 编码 | `p.product_code` | `product_code` | v_central_kitchen_product_full_cost |
| 产品名 | `p.product_name` | `product_name` | 同上 |
| 品类 | `p.category_minor` | `category_minor` | 同上 |
| 入库量 | `p.inbound_quantity` | `inbound_quantity` | 同上 |
| 理论成本 | `p.theoretical_cost` | `theoretical_cost` | 同上 |
| 标准成本 | `p.standard_cost` | `standard_cost` | 同上 |
| 实际材料 | `p.material_actual_cost` | `material_actual_cost` | 同上 |
| 制造费用 | `p.allocated_manufacturing_cost` | `allocated_manufacturing_cost` | 同上 |
| 全成本 | `p.full_manufacturing_cost` | `full_manufacturing_cost` | 同上 |
| 单位成本 | `p.full_unit_cost` | `full_unit_cost` | 同上 |
| 效率差异 | `p.efficiency_variance_pct` | `efficiency_variance_pct` | 同上 |
| 标准差异 | `p.standard_variance_pct` | `standard_variance_pct` | 同上 |
| 入库价值 | `p.inbound_value` | `inbound_value` | central_kitchen_processing_cost |
| 产品毛利 | `p.product_margin` | `product_margin` | 入库价值-全制造成本 |
> FilterableTablesearchKeys=product_code/product_name/category_minor。后端 `ORDER BY p.full_manufacturing_cost DESC`。
### Block6:配方效率分析 TOP20
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 配方 | `r.recipe_name` | `recipe_name` | central_kitchen_recipe_consumption |
| 物料 | `r.item_name` | `item_name` | 同上 |
| 规格 | `r.specification` | `specification` | 同上 |
| 理论用量 | `r.theoretical_qty` | `theoretical_qty` | 同上 |
| 实际用量 | `r.actual_qty` | `actual_qty` | 同上 |
| 理论金额 | `r.theoretical_amt` | `theoretical_amt` | 同上 |
| 实际金额 | `r.actual_amt` | `actual_amt` | 同上 |
| 差异率 | `r.variance_pct` | `variance_pct` | 同上 |
| 实际出成率 | `r.avg_actual_yield` | `avg_actual_yield` | 同上 |
| 标准出成率 | `r.avg_recipe_yield` | `avg_recipe_yield` | 同上 |
| 出成差异 | `r.yield_diff` | `yield_diff` | 同上 |
> 后端 `WHERE business_date >= $1 AND business_date < ($1 + INTERVAL '1 month')``GROUP BY recipe_name, item_name, specification, unit``LIMIT 20`。
### Block7:出成率分析 TOP20
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 配方 | `y.recipe_name` | `recipe_name` | central_kitchen_finished_receipt |
| 产品名 | `y.product_name` | `product_name` | 同上 |
| 理论入库量 | `y.theoretical_inbound_qty` | `theoretical_inbound_qty` | 同上 |
| 实际入库量 | `y.actual_inbound_qty` | `actual_inbound_qty` | 同上 |
| 达成率 | `y.avg_achievement_rate` | `avg_achievement_rate` | 同上 |
| 预期偏差率 | `y.avg_expected_var_rate` | `avg_expected_var_rate` | 同上 |
| 入库金额 | `y.total_inbound_amt` | `total_inbound_amt` | 同上 |
| 退库量 | `y.total_return_qty` | `total_return_qty` | 同上 |
| 退库率 | `y.return_rate` | `return_rate` | 同上 |
> 后端 `WHERE receipt_date >= $1 AND receipt_date < ($1 + INTERVAL '1 month')``LIMIT 20`。
### Block8:制造费用池明细
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 费用类型 | `m.cost_type` | `cost_type` | central_kitchen_manufacturing_cost_pool |
| 费用子类 | `m.cost_subtype` | `cost_subtype` | 同上 |
| 来源 | `m.source_type` | `source_type` | 同上 |
| 原始金额 | `m.source_amount` | `source_amount` | 同上 |
| 分摊比例 | `m.share_pct` | `share_pct` | 同上 |
| 分摊金额 | `m.allocated_amount` | `allocated_amount` | 同上 |
| 分摊方法 | `m.allocation_method` | `allocation_method` | 同上 |
| 计入重建 | `m.include_in_rebuilt_cost` | `include_in_rebuilt_cost` | 同上 |
| 临时 | `m.is_provisional` | `is_provisional` | 同上 |
> 后端 `WHERE report_month = $1::date ORDER BY include_in_rebuilt_cost DESC, allocated_amount DESC`。
---
## 二十九、页面27:配送—倒挤成本对账 DistributionReconciliationPage
> 单页面无Tab1个APIGET),有MonthPicker。
> 核心数据源:`mv_distribution_monthly`、`analytics.fact_inventory_snapshot`。
### API: /distribution/reconciliation
后端先检查 `mv_distribution_monthly` 是否包含当月数据,如缺失则 `REFRESH MATERIALIZED VIEW CONCURRENTLY`。然后 `Promise.all` 并发5个查询,返回 `{ summary, storeReconciliation, topVariances, unmatchedItems, categoryReconciliation }`
### Block1:核心指标卡片
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 对账品项行 | `summary.total_lines` | `total_lines` | `count(*)` | mv_distribution_monthly + fact_inventory_snapshot |
| 匹配行数 | `summary.matched_lines` | `matched_lines` | `count FILTER(dist>0 AND consumption>0)` | 同上 |
| 配送总额 | `summary.total_dist_amt` | `total_dist_amt` | `sum(dist_amt)` | mv_distribution_monthly |
| 实际耗用 | `summary.total_consumption_amt` | `total_consumption_amt` | `sum(consumption_amt)` | fact_inventory_snapshot |
| 倒挤应耗用 | `summary.reverse_consumption_amt` | `reverse_consumption_amt` | `期初+配送-期末` | — |
| 差异金额 | `summary.variance_amt` | `variance_amt` | `实际耗用-倒挤应耗用` | — |
| 差异率 | `summary.variance_pct` | `variance_pct` | `差异/实际耗用×100` | — |
> 倒挤公式:应耗用 = 期初 + 配送 - 期末 | 差异 = 实际 - 应耗用。
### Block2:倒挤成本对账瀑布
> 自定义 BarChart shape 实现瀑布图:期初库存→配送入库→应耗用(倒挤)→实际耗用→期末库存→差异金额。
### Block3:品类维度对账
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 品类 | `c.minor_category` | `minor_category` | mv_distribution_monthly |
| 品项数 | `c.item_count` | `item_count` | 同上 |
| 配送量 | `c.dist_qty` | `dist_qty` | 同上 |
| 配送金额 | `c.dist_amt` | `dist_amt` | 同上 |
| 不含税成本 | `c.dist_cost_excl_tax` | `dist_cost_excl_tax` | 同上 |
| 耗用金额 | `c.consumption_amt` | `consumption_amt` | fact_inventory_snapshot |
| 期末库存 | `c.ending_amt` | `ending_amt` | 同上 |
| 差异金额 | `c.variance_amt` | `variance_amt` | — |
| 差异率 | `c.variance_pct` | `variance_pct` | — |
> 分组 BarChart(配送金额/耗用金额/期末库存)+ FilterableTable。
> 后端使用 CTEdist(按 minor_category 汇总配送)+ item_cat(品项分类映射)+ inv(库存按品项分类汇总),`FULL OUTER JOIN`。
### Block4:门店差异散点图
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店名 | `s.store_name` | `store_name` | mv_distribution_monthly |
| 配送金额(X) | `s.dist_amt` | `dist_amt` | 同上 |
| 差异率(Y) | `s.variance_pct` | `variance_pct` | — |
> ScatterChart,颜色按差异率分3档:正常(±3%)/关注(3~10%)/异常(>10%)。
### Block5:门店对账明细
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店编码 | `s.store_code` | `store_code` | mv_distribution_monthly + fact_inventory_snapshot |
| 门店名称 | `s.store_name` | `store_name` | mv_distribution_monthly |
| 配送金额 | `s.dist_amt` | `dist_amt` | 同上 |
| 不含税成本 | `s.dist_cost_excl_tax` | `dist_cost_excl_tax` | 同上 |
| 期初库存 | `s.opening_amt` | `opening_amt` | fact_inventory_snapshot |
| 实际耗用 | `s.consumption_amt` | `consumption_amt` | 同上 |
| 期末库存 | `s.ending_amt` | `ending_amt` | 同上 |
| 倒挤应耗用 | `s.reverse_consumption_amt` | `reverse_consumption_amt` | — |
| 差异金额 | `s.variance_amt` | `variance_amt` | — |
| 差异率 | `s.variance_pct` | `variance_pct` | — |
| 负库存数 | `s.neg_inventory_count` | `neg_inventory_count` | fact_inventory_snapshot |
> 后端 CTEdist(按 store_code 汇总)+ inv(按 store_code 汇总,含 `count FILTER(is_negative)`+ store_names`FULL OUTER JOIN`。
> `ORDER BY abs(差异金额) DESC`。
### Block6Top30差异品项
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店 | `v.store_code` | `store_code` | mv_distribution_monthly |
| 品项编码 | `v.item_code` | `item_code` | 同上 |
| 品项名称 | `v.item_name` | `item_name` | 同上 |
| 品类 | `v.minor_category` | `minor_category` | 同上 |
| 配送量 | `v.dist_qty` | `dist_qty` | 同上 |
| 配送金额 | `v.dist_amt` | `dist_amt` | 同上 |
| 期初 | `v.opening_amt` | `opening_amt` | fact_inventory_snapshot |
| 实际耗用 | `v.consumption_amt` | `consumption_amt` | 同上 |
| 期末 | `v.ending_amt` | `ending_amt` | 同上 |
| 倒挤应耗用 | `v.reverse_consumption_amt` | `reverse_consumption_amt` | — |
| 差异金额 | `v.variance_amt` | `variance_amt` | — |
| 差异率 | `v.variance_pct` | `variance_pct` | — |
> 后端 CTEdist(按 store_code+item_code 汇总,含 item_name/unit/category+ inv`LEFT JOIN``WHERE consumption_amt > 0``LIMIT 30`。
### Block7:未匹配品项(有配送无库存耗用)
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 品项编码 | `u.item_code` | `item_code` | mv_distribution_monthly |
| 品项名称 | `u.item_name` | `item_name` | 同上 |
| 品类 | `u.minor_category` | `minor_category` | 同上 |
| 配送量 | `u.dist_qty` | `dist_qty` | 同上 |
| 配送金额 | `u.dist_amt` | `dist_amt` | 同上 |
| 涉及门店数 | `u.store_count` | `store_count` | 同上 |
> 后端 CTEdist_items(按 item_code 汇总,含 `count(DISTINCT store_code)`+ inv_items(库存中的 material_code),`WHERE d.item_code NOT IN (SELECT material_code FROM inv_items)``LIMIT 20`。
---
## 三十、页面28:多级BOM成本穿透 BomPenetrationPage
> 单页面无Tab2个APIGET overview + GET product detail),有MonthPicker。
> 核心数据源:`central_kitchen_processing_cost`、`central_kitchen_recipe_consumption`、`analytics.fact_recipe_bom`、`analytics.dim_material`。
### API总览
| API | 触发条件 | 数据源表 | 说明 |
|-----|---------|---------|------|
| /central-kitchen/bom-penetration | 页面加载 | central_kitchen_processing_cost + recipe_consumption | 概览:产品列表+BOM汇总+多级链 |
| /central-kitchen/bom-penetration?productCode=xxx | 点击产品 | 同上 | BOM树递归展开 |
### Block1:核心指标卡片
| 显示指标 | 前端变量 | 计算公式 | 数据来源 |
|---------|---------|---------|---------|
| 完工产品数 | `totalProducts` | `productList.length` | /central-kitchen/bom-penetration |
| 含多级BOM | `multiLevelProducts` | `productList.filter(has_multi_level_bom).length` | 同上 |
| BOM理论成本 | `totalBomTheoretical` | `bomSummary.reduce(bom_theoretical_amt)` | 同上 |
| BOM领用成本 | `totalBomIssue` | `bomSummary.reduce(bom_issue_amt)` | 同上 |
| 差异金额 | `totalVariance` | `bomSummary.reduce(variance_amt)` | 同上 |
| 半成品依赖链 | `multiLevelChains.length` | — | 同上 |
### Block2BOM成本结构Top15
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 产品编码 | `b.product_code` | `product_code` | central_kitchen_processing_cost |
| 产品名称 | `b.product_name` | `product_name` | 同上 |
| 材料数 | `b.material_count` | `material_count` | central_kitchen_recipe_consumption |
| 半成品数 | `b.sub_product_count` | `sub_product_count` | 同上 |
| 理论成本 | `b.bom_theoretical_amt` | `bom_theoretical_amt` | 同上 |
| 领用成本 | `b.bom_issue_amt` | `bom_issue_amt` | 同上 |
| 单位理论成本 | `b.bom_unit_theoretical_cost` | `bom_unit_theoretical_cost` | — |
| 单位领用成本 | `b.bom_unit_issue_cost` | `bom_unit_issue_cost` | — |
| 差异金额 | `b.variance_amt` | `variance_amt` | — |
| 差异率 | `b.variance_pct` | `variance_pct` | — |
> 分组 BarChart(理论成本+领用成本)+ FilterableTable,点击行可打开BOM树弹窗。
> 后端 CTEbomJOIN recipe_consumption + LEFT JOIN processing_cost 判断半成品),`GROUP BY product_code, product_name, inbound_quantity`。
### Block3BOM成本分布Treemap
> Treemapsize=bom_theoretical_amt,颜色按差异率分3档。
> 数据来源同 Block2 的 bomSummary。
### Block4:产品列表
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 产品编码 | `p.product_code` | `product_code` | central_kitchen_processing_cost |
| 产品名称 | `p.product_name` | `product_name` | 同上 |
| 品类 | `p.category_minor` | `category_minor` | 同上 |
| 入库量 | `p.inbound_quantity` | `inbound_quantity` | 同上 |
| 理论成本 | `p.theoretical_cost` | `theoretical_cost` | 同上 |
| 实际成本 | `p.actual_cost` | `actual_cost` | 同上 |
| 材料数 | `p.material_count` | `material_count` | central_kitchen_recipe_consumption |
| 多级BOM | `p.has_multi_level_bom` | `has_multi_level_bom` | — |
> 后端 CTEbom_materialsrecipe_consumption 中的 material_code+ finished_productsprocessing_cost 中的 product_code+ multi_levelbom_materials JOIN finished_products),判断产品是否为半成品。
### Block5:半成品依赖链
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 成品编码 | `c.finished_code` | `finished_code` | central_kitchen_processing_cost |
| 成品名称 | `c.finished_name` | `finished_name` | 同上 |
| 成品配方 | `c.finished_recipe` | `finished_recipe` | 同上 |
| 半成品编码 | `c.sub_product_code` | `sub_product_code` | central_kitchen_recipe_consumption |
| 半成品名称 | `c.sub_product_finished_name` | `sub_product_finished_name` | central_kitchen_processing_cost |
| 半成品配方 | `c.sub_product_recipe` | `sub_product_recipe` | 同上 |
> 后端 CTEbom_materials + finished_products`WHERE bm.material_code IN (SELECT product_code FROM finished_products)`。
### Block6BOM树弹窗(选中产品后)
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 物料名称 | `node.material_name` | `material_name` | central_kitchen_recipe_consumption |
| 编码 | `node.material_code` | `material_code` | 同上 |
| 单位 | `node.unit` | `unit` | 同上 |
| 理论量 | `node.theoretical_qty` | `theoretical_qty` | 同上 |
| 领用量 | `node.issue_qty` | `issue_qty` | 同上 |
| 理论金额 | `node.theoretical_amt` | `theoretical_amt` | 同上 |
| 领用金额 | `node.issue_amt` | `issue_amt` | 同上 |
| 差异金额 | 前端计算 | — | `issue_amt - theoretical_amt` |
> 后端使用 `WITH RECURSIVE bom_tree` 递归展开BOM,最大5层。
> Level 1:直接原材料(JOIN recipe_agg ON pc.recipe_name = ra.recipe_name)。
> Level 2+:递归展开半成品(`WHERE bt.is_finished_product AND bt.level < 5`)。
> 无 productCode 时 `LIMIT 200`,有 productCode 时按指定产品展开。
> 前端 `buildTree()` 将扁平数据构建为树结构,支持懒加载子节点。
---
## 三十一、页面29:销量驱动生产与要货计划 ProductionPlanPage
> 单页面无Tab1个APIGET),有MonthPicker。
> 核心数据源:`mv_dish_sales_monthly`、`analytics.fact_recipe_bom`、`analytics.dim_sku`、`analytics.dim_material`、`mv_distribution_monthly`、`analytics.fact_inventory_snapshot`、`central_kitchen_processing_cost`、`inventory_store_mapping`。
### API: /sales-driven/production-plan
后端使用单一连接(`pool.connect()`),先检查并刷新 `mv_dish_sales_monthly``mv_distribution_monthly` 物化视图,然后创建临时表 `tmp_sales_agg`(从物化视图导入销量数据并建索引),再 `Promise.all` 并发6个查询,返回 `{ summary, skuSales, materialDemand, storeDemand, ckProductionPlan, productionCoord }`
### Block1:核心指标卡片
| 显示指标 | 前端变量 | API字段 | 计算公式 | 数据源表 |
|---------|---------|---------|---------|---------|
| 总菜品数 | `summary.total_dish_count` | `total_dish_count` | `count(DISTINCT dish_name)` | mv_dish_sales_monthly |
| 有BOM菜品 | `summary.matched_sku_count` | `matched_sku_count` | `count(*) JOIN fact_recipe_bom` | 同上 + fact_recipe_bom |
| BOM覆盖率 | `summary.bom_coverage_pct` | `bom_coverage_pct` | `matched/total×100` | — |
| BOM原料数 | `summary.total_bom_materials` | `total_bom_materials` | `count(DISTINCT material_code)` | fact_recipe_bom |
| 匹配销售额 | `summary.matched_amt` | `matched_amt` | `sum(amt) JOIN bom` | mv_dish_sales_monthly + fact_recipe_bom |
| 总销售额 | `summary.total_sales_amt` | `total_sales_amt` | `sum(amt)` | mv_dish_sales_monthly |
| CK生产计划 | 前端计算 | — | `ckProductionPlan.filter(demand_qty>0).length` | — |
### Block2BOM覆盖率饼图 + Top15原料需求
> PieChart(有BOM vs 无BOM菜品数)。
> BarChartTop15原料需求量),数据来自 materialDemand。
### Block3:中央厨房生产计划
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 产品编码 | `r.product_code` | `product_code` | central_kitchen_processing_cost |
| 产品名称 | `r.product_name` | `product_name` | 同上 |
| 需求量 | `r.demand_qty` | `demand_qty` | mv_dish_sales_monthly × fact_recipe_bom |
| 实际入库 | `r.actual_inbound_qty` | `actual_inbound_qty` | central_kitchen_processing_cost |
| 差异量 | `r.variance_qty` | `variance_qty` | 需求-实际入库 |
| 差异率 | `r.variance_pct` | `variance_pct` | 差异/实际入库×100 |
| 涉及门店 | `r.store_count` | `store_count` | — |
| 实际成本 | `r.actual_cost` | `actual_cost` | central_kitchen_processing_cost |
> 后端 CTEck_demand(销量×BOM 中 material_name IN processing_cost.product_name 的记录)+ ck_actualprocessing_cost 按产品汇总),`LEFT JOIN` + `UNION ALL`(补全有实际无需求的产品)。
> **注意**:需求与实际通过 `material_name = product_name` 名称匹配,非编码匹配。
### Block4:生产与配送协同
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 产品编码 | `r.product_code` | `product_code` | central_kitchen_processing_cost |
| 产品名称 | `r.product_name` | `product_name` | 同上 |
| 完工入库 | `r.inbound_qty` | `inbound_qty` | 同上 |
| 实际成本 | `r.actual_cost` | `actual_cost` | 同上 |
| 理论成本 | `r.theoretical_cost` | `theoretical_cost` | 同上 |
| 配送量 | `r.dist_qty` | `dist_qty` | mv_distribution_monthly (DC=2) |
| 配送金额 | `r.dist_amt` | `dist_amt` | 同上 |
| 中央厨房出库 | `r.ck_consumption_qty` | `ck_consumption_qty` | fact_inventory_snapshot (store_code='2') |
| 中央厨房期末 | `r.ck_ending_qty` | `ck_ending_qty` | 同上 |
| 其他仓库消耗 | `r.other_wh_consumption_qty` | `other_wh_consumption_qty` | fact_inventory_snapshot (非门店非CK) |
| 其他仓库期末 | `r.other_wh_ending_qty` | `other_wh_ending_qty` | 同上 |
| 门店消耗 | `r.consumption_qty` | `consumption_qty` | fact_inventory_snapshot (门店) |
| 门店期末库存 | `r.ending_qty` | `ending_qty` | 同上 |
| 完工配送率 | `r.completion_distribution_rate` | `completion_distribution_rate` | 配送/完工×100 |
| 配送消耗率 | `r.distribution_consumption_rate` | `distribution_consumption_rate` | 门店消耗/配送×100 |
| 库存积压率 | `r.inventory_accumulation_rate` | `inventory_accumulation_rate` | 门店期末/门店消耗×100 |
> 后端 CTEck_production + ck_distribution(DC=2) + ck_inventory(store=2) + other_wh_inventory + store_consumption4个 `FULL OUTER JOIN`。
> 其他仓库定义:`inventory_store_mapping WHERE sales_store_code IS NULL OR = ''` 且 `store_code != '2'`。
> 门店定义:`store_code NOT IN (非门店的 cost_unit_code)`。
> `LIMIT 50`。
### Block5:门店要货建议
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 门店编码 | `r.store_code` | `store_code` | mv_dish_sales_monthly |
| 门店名称 | `r.store_name` | `store_name` | 同上 |
| 有BOM菜品数 | `r.sku_count` | `sku_count` | 同上 + fact_recipe_bom |
| 销量 | `r.total_qty` | `total_qty` | mv_dish_sales_monthly |
| 销售额 | `r.total_amt` | `total_amt` | 同上 |
| 原料需求量 | `r.total_material_demand_qty` | `total_material_demand_qty` | 同上 × fact_recipe_bom |
| 期末库存量 | `r.ending_inventory_qty` | `ending_inventory_qty` | fact_inventory_snapshot |
| 期末库存额 | `r.ending_inventory_amt` | `ending_inventory_amt` | 同上 |
| 建议要货量 | `r.suggested_order_qty` | `suggested_order_qty` | 原料需求-期末库存 |
> 后端 CTEstore_demand(按 store_code 汇总销量+BOM展开)+ inv(按 store_code 汇总期末库存),`LEFT JOIN`。
### Block6:原料需求明细
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 原料编码 | `r.material_code` | `material_code` | fact_recipe_bom |
| 原料名称 | `r.material_name` | `material_name` | dim_material |
| 单位 | `r.unit` | `unit` | fact_recipe_bom |
| 总需求量 | `r.total_demand_qty` | `total_demand_qty` | mv_dish_sales_monthly × fact_recipe_bom |
| 总需求金额 | `r.total_demand_amt` | `total_demand_amt` | 同上(含配送单价) |
| 涉及门店数 | `r.store_count` | `store_count` | — |
| 门店均需求 | `r.avg_store_demand` | `avg_store_demand` | — |
> 后端 CTEdemandtmp_sales_agg JOIN fact_recipe_bom LEFT JOIN dim_material LEFT JOIN mv_distribution_monthly 获取单价),`LIMIT 100`。
> **注意**:原料单价通过 `m.material_name = ic.item_name` 名称匹配配送记录。
### Block7SKU销量Top50
| 显示指标 | 前端变量 | API字段 | 数据源表 |
|---------|---------|---------|---------|
| 菜品名称 | `r.dish_name` | `dish_name` | mv_dish_sales_monthly |
| SKU编码 | `r.sku_code` | `sku_code` | dim_sku |
| 销量 | `r.qty` | `qty` | mv_dish_sales_monthly |
| 销售额 | `r.amt` | `amt` | 同上 |
| 门店数 | `r.store_count` | `store_count` | 同上 |
| 均价 | `r.avg_unit_price` | `avg_unit_price` | 同上 |
| 有BOM | `r.has_bom` | `has_bom` | fact_recipe_bom |
| 材料数 | `r.material_count` | `material_count` | 同上 |
> 后端 CTEsales(按 dish_name 汇总)LEFT JOIN dim_sku ON `s.dish_name = sk.standard_name` LEFT JOIN fact_recipe_bom`LIMIT 50`。
> **注意**SKU匹配通过 `dish_name = standard_name` 名称匹配。
---