daShangDao_psiServer/service/store_info.go

316 lines
9.1 KiB
Go
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.

package service
import (
"log"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"strings"
"sync"
"time"
"gorm.io/gorm"
)
type StoreInfoService struct{}
// StoreInfo 获取店铺统计数据
//
// 数据口径与 statist.getDashboardStatRealtime 完全对齐。
//
// 名称来源sales/outbound/shipping 直接用 sales_order.sales_person业务表存的实际店铺名
// receiving 用 shop 表名称(通过 car_shop.shop_id 雪花 ID 匹配 shop.id
// 店铺类型:用名称去 shop 表匹配 shop_alias_name 获取,匹配不上显示"未知"。
//
// 时间处理方式与 statist 完全一致today 范围为 00:00:00 ~ 23:59:59。
func (s *StoreInfoService) StoreInfo(req systemReq.StoreInfoRequest, db ...*gorm.DB) ([]systemRes.StoreInfoResponse, error) {
databaseConn := database.OptionalDB(db...)
startTime, endTime := s.calcTimeRange(req.TimeRange)
log.Printf("[store-info] timeRange=%s startTime=%d endTime=%d", req.TimeRange, startTime, endTime)
// Step 1: 获取 shop 表 → name→type 映射(仅用于补全 shop_type
type shopRow struct {
ShopAliasName string `gorm:"column:shop_alias_name"`
ShopType int8 `gorm:"column:shop_type"`
ID int64 `gorm:"column:id"`
}
var shopRows []shopRow
if err := databaseConn.Raw(`SELECT id, shop_alias_name, shop_type FROM shop WHERE del_flag = 0`).Scan(&shopRows).Error; err != nil {
return nil, err
}
nameToType := make(map[string]int8, len(shopRows))
idToName := make(map[int64]string, len(shopRows))
shopNames := make([]string, 0, len(shopRows))
for _, s := range shopRows {
nameToType[s.ShopAliasName] = s.ShopType
idToName[s.ID] = s.ShopAliasName
shopNames = append(shopNames, s.ShopAliasName)
}
log.Printf("[store-info] shop names in DB: %v", shopNames)
// 第三方订单shop 表中无记录,手动补类型
nameToType["kw8750193"] = 2 // 孔夫子
nameToType["图书电商大全"] = 5 // 闲鱼
// Step 2: 获取 sales_person_id → sales_person 名称映射(用于 outbound/shipping 的 ID 转名称)
type idNameRow struct {
ID int64 `gorm:"column:sales_person_id"`
Name string `gorm:"column:sales_person"`
}
var idNames []idNameRow
if err := databaseConn.Raw(`
SELECT sales_person_id, MAX(sales_person) AS sales_person
FROM sales_order WHERE is_del = 0 AND sales_person_id > 0
GROUP BY sales_person_id
`).Scan(&idNames).Error; err != nil {
return nil, err
}
salesIDToName := make(map[int64]string, len(idNames))
for _, r := range idNames {
salesIDToName[r.ID] = strings.TrimSpace(r.Name)
}
// Step 3: 4 goroutine 并发
type storeStat struct {
ShopName string
SaleCount int64
SaleAmount int64
OutboundCount int64
ReceivingCount int64
ShippingCount int64
}
statByName := make(map[string]*storeStat)
var mu sync.Mutex
var wg sync.WaitGroup
var queryErr error
var errOnce sync.Once
// sales_order按 sales_person 名称分组
wg.Add(1)
go func() {
defer wg.Done()
type row struct {
Name string `gorm:"column:sales_person"`
Cnt int64 `gorm:"column:cnt"`
Amt int64 `gorm:"column:amt"`
}
var rows []row
err := databaseConn.Raw(`
SELECT sales_person, COUNT(*) AS cnt, COALESCE(SUM(total_amount), 0) AS amt
FROM sales_order
WHERE is_del = 0 AND created_at >= ? AND created_at <= ? AND sales_person != ''
GROUP BY sales_person
`, startTime, endTime).Scan(&rows).Error
if err != nil {
errOnce.Do(func() { queryErr = err })
return
}
mu.Lock()
for _, r := range rows {
n := strings.TrimSpace(r.Name)
if n == "" {
continue
}
if statByName[n] == nil {
statByName[n] = &storeStat{ShopName: n}
}
statByName[n].SaleCount = r.Cnt
statByName[n].SaleAmount = r.Amt
}
mu.Unlock()
}()
// outbound_ordershop_id → salesIDToName → 名称
wg.Add(1)
go func() {
defer wg.Done()
type row struct {
ShopID int64 `gorm:"column:shop_id"`
Cnt int64 `gorm:"column:cnt"`
}
var rows []row
err := databaseConn.Raw(`
SELECT shop_id, COUNT(*) AS cnt
FROM outbound_order
WHERE is_del = 0 AND created_at >= ? AND created_at <= ? AND shop_id > 0
GROUP BY shop_id
`, startTime, endTime).Scan(&rows).Error
if err != nil {
errOnce.Do(func() { queryErr = err })
return
}
mu.Lock()
for _, r := range rows {
name := salesIDToName[r.ShopID]
if name == "" {
continue
}
if statByName[name] == nil {
statByName[name] = &storeStat{ShopName: name}
}
statByName[name].OutboundCount = r.Cnt
}
mu.Unlock()
}()
// receiving_ordercar_shop.shop_id雪花 ID→ idToName → 名称
wg.Add(1)
go func() {
defer wg.Done()
type row struct {
ShopID int64 `gorm:"column:shop_id"`
Cnt int64 `gorm:"column:cnt"`
}
var rows []row
err := databaseConn.Raw(`
SELECT COALESCE(cs.shop_id, 0) AS shop_id, COUNT(DISTINCT ro.id) AS cnt
FROM receiving_order ro
LEFT JOIN wave_task wt ON ro.wave_task_id = wt.id AND wt.is_del = 0
LEFT JOIN car_shop cs ON wt.car_id = cs.car_id AND cs.is_del = 0
WHERE ro.is_del = 0 AND ro.created_at >= ? AND ro.created_at <= ?
GROUP BY COALESCE(cs.shop_id, 0)
`, startTime, endTime).Scan(&rows).Error
if err != nil {
errOnce.Do(func() { queryErr = err })
return
}
mu.Lock()
for _, r := range rows {
if r.ShopID <= 0 {
continue
}
name := idToName[r.ShopID]
if name == "" {
continue
}
if statByName[name] == nil {
statByName[name] = &storeStat{ShopName: name}
}
statByName[name].ReceivingCount = r.Cnt
}
mu.Unlock()
}()
// shipping_ordershop_id → salesIDToName → 名称
wg.Add(1)
go func() {
defer wg.Done()
type row struct {
ShopID int64 `gorm:"column:shop_id"`
Cnt int64 `gorm:"column:cnt"`
}
var rows []row
err := databaseConn.Raw(`
SELECT shop_id, COUNT(*) AS cnt
FROM shipping_order
WHERE is_del = 0 AND created_at >= ? AND created_at <= ? AND shop_id > 0
GROUP BY shop_id
`, startTime, endTime).Scan(&rows).Error
if err != nil {
errOnce.Do(func() { queryErr = err })
return
}
mu.Lock()
for _, r := range rows {
name := salesIDToName[r.ShopID]
if name == "" {
continue
}
if statByName[name] == nil {
statByName[name] = &storeStat{ShopName: name}
}
statByName[name].ShippingCount = r.Cnt
}
mu.Unlock()
}()
wg.Wait()
if queryErr != nil {
return nil, queryErr
}
// Step 4: 组装结果。名称用业务真实名称,类型从 shop 表匹配
statNames := make([]string, 0, len(statByName))
for n := range statByName {
statNames = append(statNames, n)
}
log.Printf("[store-info] stat names: %v", statNames)
shopTypeMap := map[int8]string{
1: "拼多多",
2: "孔夫子",
5: "闲鱼",
}
result := make([]systemRes.StoreInfoResponse, 0, len(statByName))
for name, st := range statByName {
if req.StoreName != "" && !containsIgnoreCase(name, req.StoreName) {
continue
}
storeType := "未知"
if t, ok := nameToType[name]; ok {
if s, exists := shopTypeMap[t]; exists {
storeType = s
}
}
result = append(result, systemRes.StoreInfoResponse{
StoreName: name,
StoreType: storeType,
SaleAmount: st.SaleAmount,
OutboundCount: st.OutboundCount,
ReceivingCount: st.ReceivingCount,
OrderCount: st.SaleCount,
ShippingCount: st.ShippingCount,
})
}
return result, nil
}
func containsIgnoreCase(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
// calcTimeRange 根据 time_range 计算时间范围
// 时间处理方式与 statist.getDashboardStatRealtime 完全一致today 为 00:00:00 ~ 23:59:59
func (s *StoreInfoService) calcTimeRange(timeRange string) (int64, int64) {
now := time.Now()
switch timeRange {
case "today":
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
end := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
return start, end
case "yesterday":
yesterday := now.AddDate(0, 0, -1)
start := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, now.Location()).Unix()
end := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 23, 59, 59, 0, now.Location()).Unix()
return start, end
case "7days":
start := time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, now.Location()).Unix()
end := now.Unix()
return start, end
case "30days":
start := time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, now.Location()).Unix()
end := now.Unix()
return start, end
case "90days":
start := time.Date(now.Year(), now.Month(), now.Day()-90, 0, 0, 0, 0, now.Location()).Unix()
end := now.Unix()
return start, end
case "180days":
start := time.Date(now.Year(), now.Month(), now.Day()-180, 0, 0, 0, 0, now.Location()).Unix()
end := now.Unix()
return start, end
case "365days":
start := time.Date(now.Year(), now.Month(), now.Day()-365, 0, 0, 0, 0, now.Location()).Unix()
end := now.Unix()
return start, end
default:
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
end := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
return start, end
}
}