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

288 lines
9.3 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"
"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前天需从 startDateUnix时间戳反算
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_statSalesCount=0sales_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
}