fix: 修复门店评估数据不一致问题

- 恢复被误删的5家门店旧账单数据(58377行)
- 合并哈马尔罕大钟寺店费用到大钟寺店(0022)
- 修改v_store_operating_expense_monthly视图按sales_store_code聚合费用
- 修复利润机会池食材成本差异为负时opportunity不为负
- 修复前端totalOpportunity只累加正数机会
- 刷新bill_fact/mv_store_risk_rating_monthly等物化视图
- 新增PromotionPage和promotion路由
This commit is contained in:
freedakgmail
2026-08-11 07:26:11 +08:00
parent 353dad24a2
commit 3f1c291486
30 changed files with 4521 additions and 149 deletions
+12 -2
View File
@@ -73,42 +73,52 @@ export function BossPage() {
const { data: overview, isLoading: odLoading } = useQuery({
queryKey: ['overview', month],
queryFn: () => api.get('/overview', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: daily, isLoading: dailyLoading } = useQuery({
queryKey: ['overview/daily', month],
queryFn: () => api.get('/overview/daily', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: expenseData, isLoading: exLoading } = useQuery({
queryKey: ['store-expense/overview', month],
queryFn: () => api.get('/store-expense/overview', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: waterfallData, isLoading: wfLoading } = useQuery({
queryKey: ['overview/profit-waterfall', month],
queryFn: () => api.get('/overview/profit-waterfall', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: riskData, isLoading: riskLoading } = useQuery({
queryKey: ['stores/risk', month],
queryFn: () => api.get('/stores/risk', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: priorityData, isLoading: priorityLoading } = useQuery({
queryKey: ['stores/priority', month],
queryFn: () => api.get('/stores/priority', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: costOverview, isLoading: costLoading } = useQuery({
queryKey: ['cost-analysis/store-overview', month],
queryFn: () => api.get('/cost-analysis/store-overview', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: expenseStructure, isLoading: structLoading } = useQuery({
queryKey: ['store-expense/expense-structure', month],
queryFn: () => api.get('/store-expense/expense-structure', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: profitRankingData } = useQuery({
queryKey: ['overview/store-profit-ranking', month],
queryFn: () => api.get('/overview/store-profit-ranking', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const { data: profitOppData } = useQuery({
queryKey: ['overview/profit-opportunity', month],
queryFn: () => api.get('/overview/profit-opportunity', { params: { month } }),
staleTime: 5 * 60 * 1000,
})
const pageLoading = odLoading || dailyLoading || exLoading || wfLoading || riskLoading || priorityLoading
@@ -123,7 +133,7 @@ export function BossPage() {
const structRows = (expenseStructure as any)?.data || []
const profitRanking = (profitRankingData as any)?.data || []
const profitOpp = (profitOppData as any)?.data?.items || []
const totalOpportunity = profitOpp.reduce((s: number, o: any) => s + Number(o.opportunity || 0), 0)
const totalOpportunity = profitOpp.reduce((s: number, o: any) => s + Math.max(Number(o.opportunity || 0), 0), 0)
// 风险分布
const riskSummary = riskRows.reduce((acc: any, r: any) => {
@@ -235,7 +245,7 @@ export function BossPage() {
{/* ① 核心经营指标 */}
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-6">
<MetricCard title="营业收入" value={ex?.total_consumption} format="currency" description="全部门店消费金额合计" />
<MetricCard title="营业收入" value={od?.consumption} format="currency" description="全部门店消费金额合计" />
<MetricCard title="优惠" value={ex?.total_discount} format="currency" description="各类优惠扣减合计" />
<MetricCard title="实收总额" value={ex?.total_received} format="currency" trend={trendReceived} description={`全口径${ex?.total_stores}家门店。环比上周 ${trendReceived > 0 ? '↑' : '↓'} ${Math.abs(trendReceived)}%`} />
<MetricCard title="门店贡献利润估算" value={ex?.actual_net_profit} format="currency" status={Number(ex?.actual_net_margin_pct) < 5 ? 'bad' : Number(ex?.actual_net_margin_pct) < 10 ? 'warn' : 'good'} description={`贡献率 ${formatPercent(ex?.actual_net_margin_pct)}`} />