feat(dashboard): 增加极品专区与保险成交统计
- 仪表盘新增今日极品专区、保险8%、保险4%成交数量 - 展示近7日每日成交趋势和明细 - 按已支付订单口径统计,补充回归测试
This commit is contained in:
@@ -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 统计今日(北京时间自然日)各客服的手动录单数量。
|
||||
// 录单表是后来新增的功能表,若迁移尚未执行(表不存在)则降级为空统计,
|
||||
// 避免连带整个仪表盘不可用。
|
||||
|
||||
Reference in New Issue
Block a user