daShangDao_psiServer/service/statist.go
2026-06-27 17:16:36 +08:00

491 lines
19 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 (
"fmt"
"gorm.io/gorm"
"psi/database"
"psi/models"
systemReq "psi/models/request"
systemRes "psi/models/response"
"sync"
"time"
)
type StatistService struct{}
// DashboardStatist 获取仪表盘统计数据
/*func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
databaseConn := database.OptionalDB(db...)
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
if req.StartDate == 0 {
req.StartDate = startOfDay
}
if req.EndDate == 0 {
req.EndDate = endOfDay
}
statDateStr := now.Format("20060102")
var statDate int64
if _, err := fmt.Sscanf(statDateStr, "%d", &statDate); err != nil {
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
}
var dashboardStat models.DashboardDailyStat
err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).First(&dashboardStat).Error
if err != nil {
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
}
var userStats []systemRes.UserStatItem
var userDailyStats []models.UserDailyStat
if err := databaseConn.Where("stat_date = ? AND is_del = ?", statDate, 0).Find(&userDailyStats).Error; err == nil {
for _, userStat := range userDailyStats {
userStats = append(userStats, systemRes.UserStatItem{
UserID: userStat.UserID,
UserName: userStat.UserName,
ReceivingCount: userStat.ReceivingNum,
OutboundCount: userStat.OutboundNum,
})
}
}
return &systemRes.DashboardStatistResponse{
TotalReceivingCount: dashboardStat.TotalReceivingNum,
TotalOutboundCount: dashboardStat.TotalOutboundNum,
TotalSaleCount: dashboardStat.TodaySalesCount,
UserStats: userStats,
ProductTotal: dashboardStat.ProductTotal,
InventoryTotal: dashboardStat.InventoryTotal,
TodayInbound: dashboardStat.TodayInbound,
TodayOutbound: dashboardStat.TodayOutbound,
YesterdayInbound: dashboardStat.YesterdayInbound,
YesterdayOutbound: dashboardStat.YesterdayOutbound,
}, nil
}*/
// DashboardStatist 获取仪表盘统计数据
/*func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
databaseConn := database.OptionalDB(db...)
// 计算今日的时间范围(如果未指定)
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
if req.StartDate == 0 {
req.StartDate = startOfDay
}
if req.EndDate == 0 {
req.EndDate = endOfDay
}
// 查询今日销售订单总数
var totalSaleCount int64
saleOrderQuery := databaseConn.Model(&models.SalesOrder{}).
Where("created_at >= ? AND created_at <= ? AND is_del = ?", req.StartDate, req.EndDate, 0)
saleOrderQuery.Count(&totalSaleCount)
// 从 statist 表查询总入库和出库次数
var totalReceivingCount, totalOutboundCount int64
statistQuery := databaseConn.Model(&models.Statist{}).
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", req.StartDate, req.EndDate, 0)
var statistList []models.Statist
if err := statistQuery.Find(&statistList).Error; err != nil {
return nil, utils.NewError("查询统计数据失败")
}
if len(statistList) == 0 {
return &systemRes.DashboardStatistResponse{
TotalReceivingCount: 0,
TotalOutboundCount: 0,
TotalSaleCount: totalSaleCount,
UserStats: []systemRes.UserStatItem{},
}, nil
}
for _, stat := range statistList {
totalReceivingCount += stat.ReceivingNum
totalOutboundCount += stat.OutboundNum
}
// 查询个人统计数据
var userStats []systemRes.UserStatItem
// 按用户分组统计
type UserStatGroup struct {
CreateBy int64 `gorm:"column:create_by"`
TotalReceiving int64 `gorm:"column:total_receiving"`
TotalOutbound int64 `gorm:"column:total_outbound"`
}
var userStatGroups []UserStatGroup
databaseConn.Model(&models.Statist{}).
Select("create_by, SUM(receiving_num) as total_receiving, SUM(outbound_num) as total_outbound").
Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", req.StartDate, req.EndDate, 0).
Group("create_by").
Scan(&userStatGroups)
// 获取用户信息并构建返回数据
for _, group := range userStatGroups {
var employee models.Employee
if err := database.DB.Where("id = ?", group.CreateBy).First(&employee).Error; err == nil {
userStats = append(userStats, systemRes.UserStatItem{
UserID: employee.ID,
UserName: employee.Username,
ReceivingCount: group.TotalReceiving,
OutboundCount: group.TotalOutbound,
})
}
}
return &systemRes.DashboardStatistResponse{
TotalReceivingCount: totalReceivingCount,
TotalOutboundCount: totalOutboundCount,
TotalSaleCount: totalSaleCount,
UserStats: userStats,
}, nil
}*/
// DashboardStatist 获取仪表盘统计数据
// 统一口径:所有统计字段改为实时查原始单据表,不再依赖 dashboard_daily_stat 预计算表
func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) {
databaseConn := database.OptionalDB(db...)
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
if req.StartDate == 0 {
req.StartDate = startOfDay
}
if req.EndDate == 0 {
req.EndDate = endOfDay
}
return s.getDashboardStatRealtime(databaseConn, req.StartDate, req.EndDate)
}
// getDashboardStatRealtime 实时查询仪表盘统计数据
// 统一口径所有统计字段直接查原始单据表sales_order / receiving_order / outbound_order
// 所有独立查询通过 goroutine 并发执行,总耗时 = max(各查询耗时)
func (s *StatistService) getDashboardStatRealtime(databaseConn *gorm.DB, startDate, endDate int64) (*systemRes.DashboardStatistResponse, error) {
endTime := time.Unix(endDate, 0)
yesterdayTime := endTime.AddDate(0, 0, -1)
todayStart := time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 0, 0, 0, 0, endTime.Location()).Unix()
todayEnd := time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 23, 59, 59, 0, endTime.Location()).Unix()
yesterdayStart := time.Date(yesterdayTime.Year(), yesterdayTime.Month(), yesterdayTime.Day(), 0, 0, 0, 0, yesterdayTime.Location()).Unix()
yesterdayEnd := time.Date(yesterdayTime.Year(), yesterdayTime.Month(), yesterdayTime.Day(), 23, 59, 59, 0, yesterdayTime.Location()).Unix()
// 结果结构体
type salesTodayYesterday struct {
TodayCount int64 `gorm:"column:today_count"`
YesterdayCount int64 `gorm:"column:yesterday_count"`
}
type receivingTodayYesterday struct {
TodayCount int64 `gorm:"column:today_count"`
YesterdayCount int64 `gorm:"column:yesterday_count"`
}
type outboundTodayYesterday struct {
TodayCount int64 `gorm:"column:today_count"`
YesterdayCount int64 `gorm:"column:yesterday_count"`
}
type UserStatGroup struct {
CreateBy int64 `gorm:"column:create_by"`
TotalReceiving int64 `gorm:"column:total_receiving"`
TotalOutbound int64 `gorm:"column:total_outbound"`
}
var (
wg sync.WaitGroup
salesStat salesTodayYesterday
receivingStat receivingTodayYesterday
outboundStat outboundTodayYesterday
userStatGroups []UserStatGroup
productTotal int64
inventoryTotal int64
statErr error
receivingErr error
outboundErr error
userErr error
productErr error
inventoryErr error
)
// 1. 销售订单统计(并发)
wg.Add(1)
go func() {
defer wg.Done()
statErr = databaseConn.Model(&models.SalesOrder{}).
Select(`
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as today_count,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as yesterday_count
`, todayStart, todayEnd, yesterdayStart, yesterdayEnd).
Where("is_del = ?", 0).
Scan(&salesStat).Error
}()
// 2. 入库单统计(并发)
wg.Add(1)
go func() {
defer wg.Done()
receivingErr = databaseConn.Model(&models.ReceivingOrder{}).
Select(`
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as today_count,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as yesterday_count
`, todayStart, todayEnd, yesterdayStart, yesterdayEnd).
Where("is_del = ?", 0).
Scan(&receivingStat).Error
}()
// 3. 出库单统计(并发)
wg.Add(1)
go func() {
defer wg.Done()
outboundErr = databaseConn.Model(&models.OutboundOrder{}).
Select(`
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as today_count,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as yesterday_count
`, todayStart, todayEnd, yesterdayStart, yesterdayEnd).
Where("is_del = ?", 0).
Scan(&outboundStat).Error
}()
// 4. 用户个人统计(并发)
wg.Add(1)
go func() {
defer wg.Done()
singleDayYYYYMMDD := fmt.Sprintf("%04d%02d%02d", endTime.Year(), endTime.Month(), endTime.Day())
var singleDayStatDate int64
fmt.Sscanf(singleDayYYYYMMDD, "%d", &singleDayStatDate)
userErr = databaseConn.Model(&models.Statist{}).
Select("create_by, SUM(receiving_num) as total_receiving, SUM(outbound_num) as total_outbound").
Where("stat_date = ? AND is_del = ?", singleDayStatDate, 0).
Group("create_by").
Scan(&userStatGroups).Error
}()
// 5. 商品总数(并发)
wg.Add(1)
go func() {
defer wg.Done()
productErr = databaseConn.Model(&models.Product{}).Where("is_del = ?", 0).Count(&productTotal).Error
}()
// 6. 库存总量(并发)
wg.Add(1)
go func() {
defer wg.Done()
inventoryErr = databaseConn.Model(&models.Inventory{}).Where("is_del = ?", 0).Select("COALESCE(SUM(quantity), 0)").Row().Scan(&inventoryTotal)
}()
// 等待所有 goroutine 完成
wg.Wait()
// 检查关键查询错误(订单/入库/出库为核心统计,出错则返回)
if statErr != nil {
return nil, statErr
}
if receivingErr != nil {
return nil, receivingErr
}
if outboundErr != nil {
return nil, outboundErr
}
// 用户统计、商品、库存出错不阻塞,仅打印并置零
if userErr != nil {
userStatGroups = nil
}
if productErr != nil {
productTotal = 0
}
if inventoryErr != nil {
inventoryTotal = 0
}
// 根据用户统计结果批量查询员工信息
var userStats []systemRes.UserStatItem
if len(userStatGroups) > 0 {
userIDs := make([]int64, len(userStatGroups))
for i, group := range userStatGroups {
userIDs[i] = group.CreateBy
}
var employees []models.Employee
if err := database.DB.Where("id IN ?", userIDs).Find(&employees).Error; err == nil {
empMap := make(map[int64]models.Employee, len(employees))
for _, emp := range employees {
empMap[emp.ID] = emp
}
for _, group := range userStatGroups {
if emp, ok := empMap[group.CreateBy]; ok {
userStats = append(userStats, systemRes.UserStatItem{
UserID: emp.ID,
UserName: emp.Username,
ReceivingCount: group.TotalReceiving,
OutboundCount: group.TotalOutbound,
})
}
}
}
}
return &systemRes.DashboardStatistResponse{
TotalOrderCount: salesStat.TodayCount,
TotalReceivingCount: receivingStat.TodayCount,
TotalOutboundCount: outboundStat.TodayCount,
TotalSaleCount: salesStat.TodayCount,
YesterdayOrderCount: salesStat.YesterdayCount,
YesterdayReceivingCount: receivingStat.YesterdayCount,
YesterdayOutboundCount: outboundStat.YesterdayCount,
YesterdaySaleCount: salesStat.YesterdayCount,
UserStats: userStats,
ProductTotal: productTotal,
InventoryTotal: inventoryTotal,
}, nil
}
// GetWarehouseStatist 获取仓库统计数据
func (s *StatistService) GetWarehouseStatist(req systemReq.WarehouseStatistRequest, db ...*gorm.DB) (*systemRes.WarehouseStatistResponse, error) {
databaseConn := database.OptionalDB(db...)
now := time.Now()
yesterdayTime := now.AddDate(0, 0, -1)
// statist.stat_date 存储 YYYYMMDD 格式 int64需转换为 YYYYMMDD 再比较
todayYYYYMMDD := fmt.Sprintf("%04d%02d%02d", now.Year(), now.Month(), now.Day())
yesterdayYYYYMMDD := fmt.Sprintf("%04d%02d%02d", yesterdayTime.Year(), yesterdayTime.Month(), yesterdayTime.Day())
var todayStatDate, yesterdayStatDate int64
fmt.Sscanf(todayYYYYMMDD, "%d", &todayStatDate)
fmt.Sscanf(yesterdayYYYYMMDD, "%d", &yesterdayStatDate)
// 若用户传了日期范围,从 req.EndDate 反算 YYYYMMDD否则默认今天
var reqEndStatDate int64
if req.EndDate != 0 {
reqEndTime := time.Unix(req.EndDate, 0)
reqEndYYYYMMDD := fmt.Sprintf("%04d%02d%02d", reqEndTime.Year(), reqEndTime.Month(), reqEndTime.Day())
fmt.Sscanf(reqEndYYYYMMDD, "%d", &reqEndStatDate)
} else {
reqEndStatDate = todayStatDate
}
var productTotal int64
databaseConn.Model(&models.Product{}).Where("is_del = ?", 0).Count(&productTotal)
var inventoryTotal int64
databaseConn.Model(&models.Inventory{}).Where("is_del = ?", 0).Select("COALESCE(SUM(quantity), 0)").Row().Scan(&inventoryTotal)
type DailyStat struct {
TodayInbound int64 `gorm:"column:today_inbound"`
TodayOutbound int64 `gorm:"column:today_outbound"`
YesterdayInbound int64 `gorm:"column:yesterday_inbound"`
YesterdayOutbound int64 `gorm:"column:yesterday_outbound"`
}
// 计算昨天对应的 YYYYMMDD基于 reqEndDate 或今天)
var yesterdayReqStatDate int64
if req.EndDate != 0 {
yesterdayReqTime := time.Unix(req.EndDate, 0).AddDate(0, 0, -1)
yesterdayReqYYYYMMDD := fmt.Sprintf("%04d%02d%02d", yesterdayReqTime.Year(), yesterdayReqTime.Month(), yesterdayReqTime.Day())
fmt.Sscanf(yesterdayReqYYYYMMDD, "%d", &yesterdayReqStatDate)
} else {
yesterdayReqStatDate = yesterdayStatDate
}
var dailyStat DailyStat
databaseConn.Model(&models.Statist{}).
Select(`
COALESCE(SUM(CASE WHEN stat_date = ? THEN receiving_num ELSE 0 END), 0) as today_inbound,
COALESCE(SUM(CASE WHEN stat_date = ? THEN outbound_num ELSE 0 END), 0) as today_outbound,
COALESCE(SUM(CASE WHEN stat_date = ? THEN receiving_num ELSE 0 END), 0) as yesterday_inbound,
COALESCE(SUM(CASE WHEN stat_date = ? THEN outbound_num ELSE 0 END), 0) as yesterday_outbound
`, reqEndStatDate, reqEndStatDate, yesterdayReqStatDate, yesterdayReqStatDate).
Where("is_del = ?", 0).
Scan(&dailyStat)
return &systemRes.WarehouseStatistResponse{
ProductTotal: productTotal,
InventoryTotal: inventoryTotal,
TodayInbound: dailyStat.TodayInbound,
TodayOutbound: dailyStat.TodayOutbound,
YesterdayInbound: dailyStat.YesterdayInbound,
YesterdayOutbound: dailyStat.YesterdayOutbound,
}, nil
}
// GetOrderStatist 获取订单统计数据
func (s *StatistService) GetOrderStatist(req systemReq.OrderStatistRequest, db ...*gorm.DB) (*systemRes.OrderStatistResponse, error) {
databaseConn := database.OptionalDB(db...)
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
yesterdayStart := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()).Unix()
yesterdayEnd := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location()).Unix()
if req.StartDate == 0 {
req.StartDate = startOfDay
}
if req.EndDate == 0 {
req.EndDate = endOfDay
}
const (
StatusDraft = 1
StatusConfirmed = 2
StatusShipped = 5
StatusCancelled = 6
)
type OrderDailyStat struct {
TodayQuantity int64 `gorm:"column:today_quantity"`
TodayAmount int64 `gorm:"column:today_amount"`
TodayShipped int64 `gorm:"column:today_shipped"`
TodayUnshipped int64 `gorm:"column:today_unshipped"`
TodayPending int64 `gorm:"column:today_pending"`
YesterdayQuantity int64 `gorm:"column:yesterday_quantity"`
YesterdayAmount int64 `gorm:"column:yesterday_amount"`
YesterdayShipped int64 `gorm:"column:yesterday_shipped"`
YesterdayUnshipped int64 `gorm:"column:yesterday_unshipped"`
YesterdayPending int64 `gorm:"column:yesterday_pending"`
}
var orderStat OrderDailyStat
databaseConn.Model(&models.SalesOrder{}).
Select(`
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as today_quantity,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN total_amount ELSE 0 END), 0) as today_amount,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status = ? THEN 1 ELSE 0 END), 0) as today_shipped,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status NOT IN (?, ?) THEN 1 ELSE 0 END), 0) as today_unshipped,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status IN (?, ?) THEN 1 ELSE 0 END), 0) as today_pending,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END), 0) as yesterday_quantity,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN total_amount ELSE 0 END), 0) as yesterday_amount,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status = ? THEN 1 ELSE 0 END), 0) as yesterday_shipped,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status NOT IN (?, ?) THEN 1 ELSE 0 END), 0) as yesterday_unshipped,
COALESCE(SUM(CASE WHEN created_at >= ? AND created_at <= ? AND status IN (?, ?) THEN 1 ELSE 0 END), 0) as yesterday_pending
`, req.StartDate, req.EndDate, req.StartDate, req.EndDate, req.StartDate, req.EndDate, StatusShipped, req.StartDate, req.EndDate, StatusShipped, StatusCancelled, req.StartDate, req.EndDate, StatusDraft, StatusConfirmed,
yesterdayStart, yesterdayEnd, yesterdayStart, yesterdayEnd, yesterdayStart, yesterdayEnd, StatusShipped, yesterdayStart, yesterdayEnd, StatusShipped, StatusCancelled, yesterdayStart, yesterdayEnd, StatusDraft, StatusConfirmed).
Where("is_del = ? AND created_at >= ? AND created_at <= ?", 0, yesterdayStart, req.EndDate).
Scan(&orderStat)
return &systemRes.OrderStatistResponse{
Today: systemRes.OrderStatItem{
Quantity: orderStat.TodayQuantity,
Amount: orderStat.TodayAmount,
Shipped: orderStat.TodayShipped,
Unshipped: orderStat.TodayUnshipped,
Pending: orderStat.TodayPending,
},
Yesterday: systemRes.OrderStatItem{
Quantity: orderStat.YesterdayQuantity,
Amount: orderStat.YesterdayAmount,
Shipped: orderStat.YesterdayShipped,
Unshipped: orderStat.YesterdayUnshipped,
Pending: orderStat.YesterdayPending,
},
}, nil
}