feat(dashboard): 增加极品专区与保险成交统计

- 仪表盘新增今日极品专区、保险8%、保险4%成交数量
- 展示近7日每日成交趋势和明细
- 按已支付订单口径统计,补充回归测试
This commit is contained in:
yml2213
2026-11-06 19:09:35 +08:00
parent ee08e343f4
commit 87d252bbee
6 changed files with 382 additions and 0 deletions
@@ -14,8 +14,11 @@ import (
const (
listingDailyTrendDays = 7
tradingDailyTrendDays = 7
listingChannelWebsite = "站内发布"
listingChannelExternalUnknown = "未填写"
// 成交口径:排除待支付、取消、关闭,与推广归因的成交统计保持一致。
tradingOrderPaidCondition = "status NOT IN ('pending_payment', 'cancelled', 'closed')"
)
type Repository struct {
@@ -78,6 +81,10 @@ func (r *Repository) Summary(ctx context.Context) (*DashboardDTO, error) {
if err != nil {
return nil, err
}
tradingDaily, err := r.tradingDailyOverview(ctx, now, tradingDailyTrendDays)
if err != nil {
return nil, err
}
manualEntryToday, err := r.manualEntryToday(ctx, now)
if err != nil {
return nil, err
@@ -94,6 +101,7 @@ func (r *Repository) Summary(ctx context.Context) (*DashboardDTO, error) {
Metrics: metrics,
Pending: pending,
ListingDaily: listingDaily,
TradingDaily: tradingDaily,
ManualEntryToday: manualEntryToday,
RecentOrders: recentOrders,
RecentDisputes: recentDisputes,
@@ -353,6 +361,76 @@ func isTradeLeaveEvent(eventType, source string) bool {
}
}
// tradingDailyOverview 统计经营成交数据:极品专区成交、保险 8%/4% 成交。
// 订单在下单时已快照 is_premium / compensation_plan / insurance_fee_bps,
// 因此按订单统计不受商品后续修改影响。
func (r *Repository) tradingDailyOverview(ctx context.Context, now time.Time, days int) (TradingDailyOverviewDTO, error) {
if days <= 0 {
days = tradingDailyTrendDays
}
loc := timeutil.ShanghaiLocation()
now = now.In(loc)
end := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).AddDate(0, 0, 1)
start := end.AddDate(0, 0, -days)
type row struct {
CreatedAt time.Time
IsPremium bool
CompensationPlan string
InsuranceFeeBps int
}
rows := make([]row, 0)
// 读取区间内订单后在 Go 侧按上海时区归日,避免依赖 MySQL 会话时区。
if err := r.db.WithContext(ctx).Table("rental_orders").
Select("created_at, is_premium, compensation_plan, insurance_fee_bps").
Where(tradingOrderPaidCondition).
Where("created_at >= ? AND created_at < ?", start, end).
Scan(&rows).Error; err != nil {
return TradingDailyOverviewDTO{}, err
}
byDay := make(map[string]TradingDayStatsDTO, days)
for _, item := range rows {
key := item.CreatedAt.In(loc).Format("2006-01-02")
stats := byDay[key]
stats.Date = key
if item.IsPremium {
stats.PremiumOrderCount++
}
if item.CompensationPlan == "insurance" {
switch item.InsuranceFeeBps {
case 800:
stats.Insurance8OrderCount++
case 400:
stats.Insurance4OrderCount++
}
}
byDay[key] = stats
}
trend := make([]TradingDayStatsDTO, 0, days)
for i := days - 1; i >= 0; i-- {
day := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).AddDate(0, 0, -i)
key := day.Format("2006-01-02")
item := TradingDayStatsDTO{Date: key}
if v, ok := byDay[key]; ok {
item = v
}
trend = append(trend, item)
}
today := TradingDayStatsDTO{Date: now.Format("2006-01-02")}
if len(trend) > 0 {
today = trend[len(trend)-1]
}
return TradingDailyOverviewDTO{
Today: today,
Trend: trend,
Days: days,
Timezone: "Asia/Shanghai",
}, nil
}
// manualEntryToday 统计今日(北京时间自然日)各客服的手动录单数量。
// 录单表是后来新增的功能表,若迁移尚未执行(表不存在)则降级为空统计,
// 避免连带整个仪表盘不可用。