288 lines
9.3 KiB
Go
288 lines
9.3 KiB
Go
package service
|
||
|
||
import (
|
||
"fmt"
|
||
"gorm.io/gorm"
|
||
"psi/database"
|
||
"psi/models"
|
||
"psi/utils"
|
||
"time"
|
||
)
|
||
|
||
type StatistTaskService struct{}
|
||
|
||
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||
// GenerateDailyStat 生成每日统计数据(定时任务调用)
|
||
func (s *StatistTaskService) GenerateDailyStat(db ...*gorm.DB) error {
|
||
now := time.Now()
|
||
|
||
yesterday := now.AddDate(0, 0, -1)
|
||
statDateStr := yesterday.Format("20060102")
|
||
var statDate int64
|
||
if _, err := fmt.Sscanf(statDateStr, "%d", &statDate); err != nil {
|
||
return fmt.Errorf("日期格式化失败: %v", err)
|
||
}
|
||
|
||
yesterdayStart := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location()).Unix()
|
||
yesterdayEnd := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 23, 59, 59, 0, yesterday.Location()).Unix()
|
||
|
||
mainDB := database.DB
|
||
if len(db) > 0 && db[0] != nil {
|
||
mainDB = db[0]
|
||
}
|
||
|
||
var aboutIDs []int64
|
||
if err := mainDB.Model(&models.Employee{}).Where("deleted_at = ?", 0).Distinct().Pluck("about_id", &aboutIDs).Error; err != nil {
|
||
return fmt.Errorf("查询租户列表失败: %v", err)
|
||
}
|
||
|
||
if len(aboutIDs) == 0 {
|
||
utils.InfoLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"stat_date": statDate,
|
||
"message": "没有找到任何租户",
|
||
})
|
||
return nil
|
||
}
|
||
|
||
for _, aboutID := range aboutIDs {
|
||
if aboutID == 0 {
|
||
continue
|
||
}
|
||
|
||
tenantDB, err := database.GetTenantDB(aboutID)
|
||
if err != nil {
|
||
utils.ErrorLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"about_id": aboutID,
|
||
"error": fmt.Sprintf("获取租户数据库失败: %v", err),
|
||
})
|
||
continue
|
||
}
|
||
|
||
tx := tenantDB.Begin()
|
||
func() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
tx.Rollback()
|
||
}
|
||
}()
|
||
|
||
_, err := s.generateDashboardDailyStat(tx, statDate, yesterdayStart, yesterdayEnd)
|
||
if err != nil {
|
||
tx.Rollback()
|
||
utils.ErrorLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"about_id": aboutID,
|
||
"stat_date": statDate,
|
||
"error": fmt.Sprintf("生成全局统计失败: %v", err),
|
||
})
|
||
return
|
||
}
|
||
|
||
if err := s.generateUserDailyStat(tx, mainDB, aboutID, statDate, yesterdayStart, yesterdayEnd); err != nil {
|
||
tx.Rollback()
|
||
utils.ErrorLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"about_id": aboutID,
|
||
"stat_date": statDate,
|
||
"error": fmt.Sprintf("生成用户统计失败: %v", err),
|
||
})
|
||
return
|
||
}
|
||
|
||
if err := tx.Commit().Error; err != nil {
|
||
utils.ErrorLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"about_id": aboutID,
|
||
"stat_date": statDate,
|
||
"error": fmt.Sprintf("提交事务失败: %v", err),
|
||
})
|
||
return
|
||
}
|
||
|
||
utils.InfoLog("work", map[string]interface{}{
|
||
"source": "定时任务-生成每日统计",
|
||
"about_id": aboutID,
|
||
"stat_date": statDate,
|
||
"message": fmt.Sprintf("成功生成%s的统计数据", yesterday.Format("2006-01-02")),
|
||
})
|
||
}()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// generateDashboardDailyStat 生成全局每日统计
|
||
func (s *StatistTaskService) generateDashboardDailyStat(tx *gorm.DB, statDate int64, startDate, endDate int64) (*models.DashboardDailyStat, error) {
|
||
var existingStat models.DashboardDailyStat
|
||
err := tx.Where("stat_date = ? AND is_del = ?", statDate, 0).First(&existingStat).Error
|
||
|
||
if err == nil {
|
||
if err := tx.Model(&models.DashboardDailyStat{}).Where("id = ?", existingStat.ID).Update("is_del", 1).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// statist.stat_date 存储 YYYYMMDD 格式 int64(如 20260626),非 Unix 时间戳
|
||
// statDate 已经是 YYYYMMDD 格式,直接用于查询
|
||
// 昨天的 YYYYMMDD = statDate;前天需从 startDate(Unix时间戳)反算
|
||
yesterdayTime := time.Unix(startDate, 0)
|
||
dayBeforeTime := yesterdayTime.AddDate(0, 0, -1)
|
||
dayBeforeStatDateStr := fmt.Sprintf("%04d%02d%02d", dayBeforeTime.Year(), dayBeforeTime.Month(), dayBeforeTime.Day())
|
||
var dayBeforeStatDate int64
|
||
fmt.Sscanf(dayBeforeStatDateStr, "%d", &dayBeforeStatDate)
|
||
|
||
// 前天的 Unix 时间戳范围(用于 sales_order 查询,sales_order.created_at 存储 Unix 时间戳)
|
||
dayBeforeStart := time.Date(dayBeforeTime.Year(), dayBeforeTime.Month(), dayBeforeTime.Day(), 0, 0, 0, 0, dayBeforeTime.Location()).Unix()
|
||
dayBeforeEnd := time.Date(dayBeforeTime.Year(), dayBeforeTime.Month(), dayBeforeTime.Day(), 23, 59, 59, 0, dayBeforeTime.Location()).Unix()
|
||
|
||
var totalReceivingNum, totalOutboundNum int64
|
||
tx.Model(&models.Statist{}).
|
||
Where("stat_date <= ? AND is_del = ?", statDate, 0).
|
||
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||
Row().Scan(&totalReceivingNum, &totalOutboundNum)
|
||
|
||
var todayInbound, todayOutbound int64
|
||
tx.Model(&models.Statist{}).
|
||
Where("stat_date = ? AND is_del = ?", statDate, 0).
|
||
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||
Row().Scan(&todayInbound, &todayOutbound)
|
||
|
||
var yesterdayInbound, yesterdayOutbound int64
|
||
tx.Model(&models.Statist{}).
|
||
Where("stat_date = ? AND is_del = ?", dayBeforeStatDate, 0).
|
||
Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)").
|
||
Row().Scan(&yesterdayInbound, &yesterdayOutbound)
|
||
|
||
var totalSalesCount int64
|
||
tx.Model(&models.SalesOrder{}).
|
||
Where("created_at <= ? AND is_del = ?", endDate, 0).
|
||
Count(&totalSalesCount)
|
||
|
||
var todaySalesCount int64
|
||
tx.Model(&models.SalesOrder{}).
|
||
Where("created_at >= ? AND created_at <= ? AND is_del = ?", startDate, endDate, 0).
|
||
Count(&todaySalesCount)
|
||
|
||
var yesterdaySalesCount int64
|
||
tx.Model(&models.SalesOrder{}).
|
||
Where("created_at >= ? AND created_at <= ? AND is_del = ?", dayBeforeStart, dayBeforeEnd, 0).
|
||
Count(&yesterdaySalesCount)
|
||
|
||
var productTotal int64
|
||
tx.Model(&models.Product{}).
|
||
Where("is_del = ?", 0).
|
||
Count(&productTotal)
|
||
|
||
var inventoryTotal int64
|
||
tx.Model(&models.Inventory{}).
|
||
Where("is_del = ?", 0).
|
||
Select("COALESCE(SUM(quantity), 0)").
|
||
Row().Scan(&inventoryTotal)
|
||
|
||
now := time.Now().Unix()
|
||
dashboardStat := &models.DashboardDailyStat{
|
||
StatDate: statDate,
|
||
TotalReceivingNum: totalReceivingNum,
|
||
TotalOutboundNum: totalOutboundNum,
|
||
TotalSalesCount: totalSalesCount,
|
||
ProductTotal: productTotal,
|
||
InventoryTotal: inventoryTotal,
|
||
TodayInbound: todayInbound,
|
||
TodayOutbound: todayOutbound,
|
||
YesterdayInbound: yesterdayInbound,
|
||
YesterdayOutbound: yesterdayOutbound,
|
||
TodaySalesCount: todaySalesCount,
|
||
YesterdaySalesCount: yesterdaySalesCount,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
IsDel: 0,
|
||
}
|
||
|
||
if err := tx.Create(dashboardStat).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return dashboardStat, nil
|
||
}
|
||
|
||
// generateUserDailyStat 生成用户每日统计
|
||
// mainDB: 主库连接(查询 employee 表),tx: 租户事务(查询 statist/sales_order,写入 user_daily_stat)
|
||
func (s *StatistTaskService) generateUserDailyStat(tx *gorm.DB, mainDB *gorm.DB, aboutID int64, statDate int64, startDate, endDate int64) error {
|
||
// 1. 从主库查询该租户下的所有员工
|
||
type EmployeeBrief struct {
|
||
UserID int64 `gorm:"column:user_id"`
|
||
UserName string `gorm:"column:user_name"`
|
||
}
|
||
var employees []EmployeeBrief
|
||
if err := mainDB.Model(&models.Employee{}).
|
||
Where("about_id = ? AND deleted_at = ?", aboutID, 0).
|
||
Select("id as user_id, username as user_name").
|
||
Find(&employees).Error; err != nil {
|
||
return fmt.Errorf("查询员工列表失败: %v", err)
|
||
}
|
||
|
||
if len(employees) == 0 {
|
||
return nil
|
||
}
|
||
|
||
// 2. 提取员工ID列表
|
||
userIDs := make([]int64, len(employees))
|
||
for i, emp := range employees {
|
||
userIDs[i] = emp.UserID
|
||
}
|
||
|
||
// 3. 批量查询租户库的入库/出库统计
|
||
type UserReceivingStat struct {
|
||
CreateBy int64 `gorm:"column:create_by"`
|
||
ReceivingNum int64 `gorm:"column:receiving_num"`
|
||
OutboundNum int64 `gorm:"column:outbound_num"`
|
||
}
|
||
var receivingStats []UserReceivingStat
|
||
tx.Model(&models.Statist{}).
|
||
Where("create_by IN ? AND stat_date = ? AND is_del = ?", userIDs, statDate, 0).
|
||
Select("create_by, COALESCE(SUM(receiving_num), 0) as receiving_num, COALESCE(SUM(outbound_num), 0) as outbound_num").
|
||
Group("create_by").
|
||
Find(&receivingStats)
|
||
|
||
receivingMap := make(map[int64]UserReceivingStat, len(receivingStats))
|
||
for _, rs := range receivingStats {
|
||
receivingMap[rs.CreateBy] = rs
|
||
}
|
||
|
||
// 4. 写入 user_daily_stat(注:SalesCount=0,sales_order 表无 create_by 字段无法归因到员工)
|
||
now := time.Now().Unix()
|
||
for _, emp := range employees {
|
||
// 软删除已有记录
|
||
tx.Model(&models.UserDailyStat{}).
|
||
Where("user_id = ? AND stat_date = ? AND is_del = ?", emp.UserID, statDate, 0).
|
||
Update("is_del", 1)
|
||
|
||
receiving := receivingMap[emp.UserID]
|
||
|
||
newStat := &models.UserDailyStat{
|
||
UserID: emp.UserID,
|
||
UserName: emp.UserName,
|
||
StatDate: statDate,
|
||
ReceivingNum: receiving.ReceivingNum,
|
||
OutboundNum: receiving.OutboundNum,
|
||
SalesCount: 0,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
IsDel: 0,
|
||
}
|
||
|
||
if err := tx.Create(newStat).Error; err != nil {
|
||
utils.ErrorLog("work", map[string]interface{}{
|
||
"source": "生成用户统计",
|
||
"user_id": emp.UserID,
|
||
"error": fmt.Sprintf("创建用户统计记录失败: %v", err),
|
||
})
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|