diff --git a/controllers/logistics.go b/controllers/logistics.go index 8d78075..e5d5348 100644 --- a/controllers/logistics.go +++ b/controllers/logistics.go @@ -16,6 +16,7 @@ type LogisticsApi struct{} var logisticsService = service.LogisticsService{} +// GetLogisticsList 获取物流模板列表 // GetLogisticsList 获取物流模板列表 func (r *LogisticsApi) GetLogisticsList(c *gin.Context) { var req request.QueryLogisticsRequest @@ -24,6 +25,10 @@ func (r *LogisticsApi) GetLogisticsList(c *gin.Context) { return } + // 从JWT中获取当前用户的about_id + userInfo := utils.GetUserInfo(c) + req.AboutID = userInfo.AboutID + list, total, err := logisticsService.GetLogisticsList(req, database.GetDB(c)) if err != nil { utils.FailWithRequestLog(constant.LoggerChannelWork, "查询物流模板列表异常", err, c, req) diff --git a/database/mysql.go b/database/mysql.go index 76980d1..51882ac 100644 --- a/database/mysql.go +++ b/database/mysql.go @@ -275,6 +275,21 @@ func Init() { log.Fatal("Config表迁移失败:", err) } + err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统计表'").AutoMigrate(&models.Statist{}) + if err != nil { + log.Fatal("Statist表迁移失败:", err) + } + + err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'").AutoMigrate(&models.DashboardDailyStat{}) + if err != nil { + log.Fatal("DashboardDailyStat表迁移失败:", err) + } + + err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'").AutoMigrate(&models.UserDailyStat{}) + if err != nil { + log.Fatal("UserDailyStat表迁移失败:", err) + } + // 初始化商品表 InitProductBookTables() // 初始化默认管理员账号 diff --git a/main.go b/main.go index 7cf2e18..67bec36 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "psi/config" "psi/database" router "psi/routes" + "psi/service" "psi/utils" ) @@ -37,6 +38,10 @@ func main() { // ocr.StartService() //}() + // 启动统计任务调度器 + scheduler := service.NewStatistTaskScheduler() + go scheduler.Start() + log.Println("统计任务调度器已启动") // 设置路由并启动服务器 log.Printf("[4/4] 启动服务器,端口: %s...", config.AppConfig.Server.Port) router.Run() diff --git a/models/DashboardDailyStat.go b/models/DashboardDailyStat.go new file mode 100644 index 0000000..30ddab7 --- /dev/null +++ b/models/DashboardDailyStat.go @@ -0,0 +1,29 @@ +package models + +// DashboardDailyStat 仪表盘每日统计表 +type DashboardDailyStat struct { + ID int64 `json:"id" gorm:"primarykey;comment:ID"` + StatDate int64 `json:"stat_date" gorm:"type:bigint;not null;default:0;comment:统计日期(YYYYMMDD格式)"` + TotalReceivingNum int64 `json:"total_receiving_num" gorm:"not null;default:0;comment:总入库次数"` + TotalOutboundNum int64 `json:"total_outbound_num" gorm:"not null;default:0;comment:总出库次数"` + TotalSalesCount int64 `json:"total_sales_count" gorm:"not null;default:0;comment:总销售订单数"` + ProductTotal int64 `json:"product_total" gorm:"not null;default:0;comment:商品总量"` + InventoryTotal int64 `json:"inventory_total" gorm:"not null;default:0;comment:库存总量"` + TodayInbound int64 `json:"today_inbound" gorm:"not null;default:0;comment:今日入库次数"` + TodayOutbound int64 `json:"today_outbound" gorm:"not null;default:0;comment:今日出库次数"` + YesterdayInbound int64 `json:"yesterday_inbound" gorm:"not null;default:0;comment:昨日入库次数"` + YesterdayOutbound int64 `json:"yesterday_outbound" gorm:"not null;default:0;comment:昨日出库次数"` + TodaySalesCount int64 `json:"today_sales_count" gorm:"not null;default:0;comment:今日销售订单数"` + YesterdaySalesCount int64 `json:"yesterday_sales_count" gorm:"not null;default:0;comment:昨日销售订单数"` + CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"` + UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"` + IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"` +} + +func (DashboardDailyStat) TableName() string { + return "dashboard_daily_stat" +} + +func (DashboardDailyStat) TableOptions() string { + return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'" +} diff --git a/models/UserDailyStat.go b/models/UserDailyStat.go new file mode 100644 index 0000000..c72a8b8 --- /dev/null +++ b/models/UserDailyStat.go @@ -0,0 +1,23 @@ +package models + +// UserDailyStat 用户每日统计表 +type UserDailyStat struct { + ID int64 `json:"id" gorm:"primarykey;comment:ID"` + UserID int64 `json:"user_id" gorm:"not null;default:0;comment:用户ID"` + UserName string `json:"user_name" gorm:"type:varchar(50);not null;default:'';comment:用户姓名"` + StatDate int64 `json:"stat_date" gorm:"type:bigint;not null;default:0;comment:统计日期(YYYYMMDD格式)"` + ReceivingNum int64 `json:"receiving_num" gorm:"not null;default:0;comment:入库次数"` + OutboundNum int64 `json:"outbound_num" gorm:"not null;default:0;comment:出库次数"` + SalesCount int64 `json:"sales_count" gorm:"not null;default:0;comment:销售订单数"` + CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"` + UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"` + IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"` +} + +func (UserDailyStat) TableName() string { + return "user_daily_stat" +} + +func (UserDailyStat) TableOptions() string { + return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'" +} diff --git a/models/request/logistics.go b/models/request/logistics.go index a4ed5c7..ba46bb2 100644 --- a/models/request/logistics.go +++ b/models/request/logistics.go @@ -1,10 +1,11 @@ package request type QueryLogisticsRequest struct { - Keyword string `form:"keyword"` - Status *int `form:"status"` - Page int `form:"page,default=1"` - PageSize int `form:"page_size,default=10"` + Keyword string `form:"keyword"` // 物流模板名称 + Status *int `form:"status"` // 物流模板状态 + Page int `form:"page,default=1"` // 页码 + PageSize int `form:"page_size,default=10"` // 每页数量 + AboutID int64 `form:"-"` // 租户ID,从JWT中获取,不参与表单绑定 } type CreateLogisticsRequest struct { diff --git a/models/response/statist.go b/models/response/statist.go index 6f14711..1d71773 100644 --- a/models/response/statist.go +++ b/models/response/statist.go @@ -6,6 +6,12 @@ type DashboardStatistResponse struct { TotalOutboundCount int64 `json:"total_outbound_count"` // 总出库次数 TotalSaleCount int64 `json:"total_sale_count"` // 今日商品总数 UserStats []UserStatItem `json:"user_stats"` // 个人统计数据 + ProductTotal int64 `json:"product_total"` // 商品总量 + InventoryTotal int64 `json:"inventory_total"` // 库存量 + TodayInbound int64 `json:"today_inbound"` // 今日入库 + TodayOutbound int64 `json:"today_outbound"` // 今日出库 + YesterdayInbound int64 `json:"yesterday_inbound"` // 昨日入库 + YesterdayOutbound int64 `json:"yesterday_outbound"` // 昨日出库 } // UserStatItem 个人统计项 diff --git a/routes/routes.go b/routes/routes.go index 559752c..b403f77 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -91,11 +91,13 @@ func initRouter() (r *gin.Engine) { public.GET("/split-account-deduction-log/list", splitAccountDeductionLogApi.GetSplitAccountDeductionLogList) // 获取分账扣钱日志列表 public.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情 public.GET("/open/split-account-deduction-log/list", splitAccountDeductionLogApi.GetOpenSplitAccountDeductionLogList) - public.POST("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账扣钱日志 - public.POST("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存 - /* public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 - public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账 - public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/ + public.POST("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账扣钱日志 + public.POST("/sales-order/unlock-inventory", processApi.UnlockSalesOrderInventory) // 解锁销售订单库存 + public.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 + public.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账 + public.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账 + + public.GET("/logistics/list-c", logisticsApi.GetLogisticsList) // 获取物流模板列表 } @@ -269,9 +271,9 @@ func initRouter() (r *gin.Engine) { auth.GET("/split-account-deduction-log/detail/:id", splitAccountDeductionLogApi.GetSplitAccountDeductionLogDetail) // 获取分账扣钱日志详情 //public.GET("/open/split-account-deduction-log/list", splitAccountDeductionLogApi.GetOpenSplitAccountDeductionLogList) // 公开获取分账扣钱日志列表(无需签名认证)*/ - auth.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 + //auth.POST("/split-account-deduction-log/create", splitAccountDeductionLogApi.CreateSplitAccountDeductionLog) // 创建分账 //auth.PUT("/split-account-deduction-log/update", splitAccountDeductionLogApi.UpdateSplitAccountDeductionLog) // 更新分账 - auth.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/ + //auth.DELETE("/split-account-deduction-log/delete", splitAccountDeductionLogApi.DeleteSplitAccountDeductionLog) // 删除分账*/ // 产品日志管理 auth.GET("/product_log/list", productApi.GetProductLogList) // 获取产品日志列表 auth.POST("/product_log/save", productApi.SaveProductLog) // 保存产品日志 diff --git a/service/StatistTaskScheduler.go b/service/StatistTaskScheduler.go new file mode 100644 index 0000000..df2526c --- /dev/null +++ b/service/StatistTaskScheduler.go @@ -0,0 +1,55 @@ +package service + +import ( + "log" + "time" +) + +// StatistTaskScheduler 统计任务调度器 +type StatistTaskScheduler struct { + taskService *StatistTaskService +} + +// NewStatistTaskScheduler 创建统计任务调度器 +func NewStatistTaskScheduler() *StatistTaskScheduler { + return &StatistTaskScheduler{ + taskService: &StatistTaskService{}, + } +} + +// Start 启动定时任务调度器 +func (s *StatistTaskScheduler) Start() { + log.Println("统计任务调度器启动") + + // 立即执行一次生成昨天的统计数据 + go func() { + if err := s.taskService.GenerateDailyStat(); err != nil { + log.Printf("生成每日统计失败: %v", err) + } + }() + + // 每天凌晨2点执行生成前一天的统计数据 + ticker := time.NewTicker(1 * time.Hour) // 每小时检查一次是否到了凌晨2点 + defer ticker.Stop() + + var lastRunDate string + + for range ticker.C { + now := time.Now() + currentDateStr := now.Format("2006-01-02") + hour := now.Hour() + + // 如果是凌晨2点且今天还没运行过 + if hour == 2 && currentDateStr != lastRunDate { + go func(date string) { + log.Printf("开始执行每日统计任务,日期: %s", date) + if err := s.taskService.GenerateDailyStat(); err != nil { + log.Printf("生成每日统计失败: %v", err) + } else { + log.Printf("每日统计任务执行成功,日期: %s", date) + } + }(currentDateStr) + lastRunDate = currentDateStr + } + } +} diff --git a/service/StatistTaskService.go b/service/StatistTaskService.go new file mode 100644 index 0000000..24d7862 --- /dev/null +++ b/service/StatistTaskService.go @@ -0,0 +1,259 @@ +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, 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 + } + } + + var totalReceivingNum, totalOutboundNum int64 + tx.Model(&models.Statist{}). + Where("stat_date <= ? AND is_del = ?", endDate, 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 stat_date <= ? AND is_del = ?", startDate, endDate, 0). + Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)"). + Row().Scan(&todayInbound, &todayOutbound) + + beforeYesterdayStart := time.Date(time.Unix(startDate, 0).Year(), time.Unix(startDate, 0).Month(), time.Unix(startDate, 0).Day()-1, 0, 0, 0, 0, time.Unix(startDate, 0).Location()).Unix() + beforeYesterdayEnd := time.Date(time.Unix(startDate, 0).Year(), time.Unix(startDate, 0).Month(), time.Unix(startDate, 0).Day()-1, 23, 59, 59, 0, time.Unix(startDate, 0).Location()).Unix() + var yesterdayInbound, yesterdayOutbound int64 + tx.Model(&models.Statist{}). + Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", beforeYesterdayStart, beforeYesterdayEnd, 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 = ?", beforeYesterdayStart, beforeYesterdayEnd, 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 生成用户每日统计 +func (s *StatistTaskService) generateUserDailyStat(tx *gorm.DB, aboutID int64, statDate int64, startDate, endDate int64) error { + type UserStatGroup struct { + UserID int64 `gorm:"column:user_id"` + UserName string `gorm:"column:user_name"` + ReceivingNum int64 `gorm:"column:receiving_num"` + OutboundNum int64 `gorm:"column:outbound_num"` + SalesCount int64 `gorm:"column:sales_count"` + } + + var userStats []UserStatGroup + tx.Table("employee"). + Select(` + employee.id as user_id, + employee.username as user_name, + COALESCE((SELECT SUM(receiving_num) FROM statist WHERE create_by = employee.id AND stat_date >= ? AND stat_date <= ? AND is_del = ?), 0) as receiving_num, + COALESCE((SELECT SUM(outbound_num) FROM statist WHERE create_by = employee.id AND stat_date >= ? AND stat_date <= ? AND is_del = ?), 0) as outbound_num, + COALESCE((SELECT COUNT(*) FROM sales_order WHERE create_by = employee.id AND created_at >= ? AND created_at <= ? AND is_del = ?), 0) as sales_count + `, startDate, endDate, 0, startDate, endDate, 0, startDate, endDate, 0). + Where("employee.about_id = ? AND employee.is_del = ?", aboutID, 0). + Scan(&userStats) + + if len(userStats) == 0 { + return nil + } + + now := time.Now().Unix() + for _, userStat := range userStats { + var existingStat models.UserDailyStat + err := tx.Where("user_id = ? AND stat_date = ? AND is_del = ?", userStat.UserID, statDate, 0).First(&existingStat).Error + + if err == nil { + if err := tx.Model(&models.UserDailyStat{}).Where("id = ?", existingStat.ID).Update("is_del", 1).Error; err != nil { + continue + } + } + + newStat := &models.UserDailyStat{ + UserID: userStat.UserID, + UserName: userStat.UserName, + StatDate: statDate, + ReceivingNum: userStat.ReceivingNum, + OutboundNum: userStat.OutboundNum, + SalesCount: userStat.SalesCount, + CreatedAt: now, + UpdatedAt: now, + IsDel: 0, + } + + if err := tx.Create(newStat).Error; err != nil { + utils.ErrorLog("work", map[string]interface{}{ + "source": "生成用户统计", + "user_id": userStat.UserID, + "error": fmt.Sprintf("创建用户统计记录失败: %v", err), + }) + } + } + + return nil +} diff --git a/service/logistics.go b/service/logistics.go index 65870a4..ea20cdb 100644 --- a/service/logistics.go +++ b/service/logistics.go @@ -27,8 +27,28 @@ func (s *LogisticsService) GetLogisticsList(req request.QueryLogisticsRequest, d req.PageSize = 20 } + // 如果提供了 about_id,先查询该租户下的所有仓库 + var warehouseIDs []int64 + if req.AboutID > 0 { + if err := databaseConn.Model(&models.Warehouse{}). + Where("about_id = ? AND is_del = ?", req.AboutID, 0). + Pluck("id", &warehouseIDs).Error; err != nil { + return nil, 0, errors.New("查询仓库信息失败") + } + + // 如果没有找到仓库,直接返回空结果 + if len(warehouseIDs) == 0 { + return []response.LogisticsResponse{}, 0, nil + } + } + query := databaseConn.Model(&models.Logistics{}).Where("del_flag = ?", "0") + // 如果有仓库ID列表,只查询这些仓库关联的物流模板 + if len(warehouseIDs) > 0 { + query = query.Where("id IN (?)", warehouseIDs) + } + if req.Keyword != "" { query = query.Where("template_name LIKE ? OR delivery_province LIKE ? OR delivery_city LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%") @@ -39,7 +59,7 @@ func (s *LogisticsService) GetLogisticsList(req request.QueryLogisticsRequest, d } // === debug: 打印完整SQL === - log.Printf("[GetLogisticsList] 请求参数 page=%d pageSize=%d keyword=%q", req.Page, req.PageSize, req.Keyword) + log.Printf("[GetLogisticsList] 请求参数 page=%d pageSize=%d keyword=%q about_id=%d", req.Page, req.PageSize, req.Keyword, req.AboutID) // === debug end === var total int64 diff --git a/service/statist.go b/service/statist.go index 92e8fbb..f28638f 100644 --- a/service/statist.go +++ b/service/statist.go @@ -1,6 +1,7 @@ package service import ( + "fmt" "gorm.io/gorm" "psi/database" "psi/models" @@ -13,7 +14,62 @@ import ( type StatistService struct{} // DashboardStatist 获取仪表盘统计数据 -func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, db ...*gorm.DB) (*systemRes.DashboardStatistResponse, error) { +/*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...) // 计算今日的时间范围(如果未指定) @@ -94,6 +150,152 @@ func (s *StatistService) DashboardStatist(req systemReq.DashboardStatistRequest, TotalSaleCount: totalSaleCount, UserStats: userStats, }, 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 + } + + 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 +} + +// getDashboardStatRealtime 实时查询仪表盘统计数据(降级方案) +func (s *StatistService) getDashboardStatRealtime(databaseConn *gorm.DB, startDate, endDate int64) (*systemRes.DashboardStatistResponse, error) { + var totalSaleCount int64 + saleOrderQuery := databaseConn.Model(&models.SalesOrder{}). + Where("created_at >= ? AND created_at <= ? AND is_del = ?", startDate, endDate, 0) + saleOrderQuery.Count(&totalSaleCount) + + var totalReceivingCount, totalOutboundCount int64 + statistQuery := databaseConn.Model(&models.Statist{}). + Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, 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 = ?", startDate, 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, + }) + } + } + + 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) + + now := time.Now() + 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() + + var todayInbound, todayOutbound, yesterdayInbound, yesterdayOutbound int64 + databaseConn.Model(&models.Statist{}). + Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", startDate, endDate, 0). + Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)"). + Row().Scan(&todayInbound, &todayOutbound) + + databaseConn.Model(&models.Statist{}). + Where("stat_date >= ? AND stat_date <= ? AND is_del = ?", yesterdayStart, yesterdayEnd, 0). + Select("COALESCE(SUM(receiving_num), 0), COALESCE(SUM(outbound_num), 0)"). + Row().Scan(&yesterdayInbound, &yesterdayOutbound) + + return &systemRes.DashboardStatistResponse{ + TotalReceivingCount: totalReceivingCount, + TotalOutboundCount: totalOutboundCount, + TotalSaleCount: totalSaleCount, + UserStats: userStats, + ProductTotal: productTotal, + InventoryTotal: inventoryTotal, + TodayInbound: todayInbound, + TodayOutbound: todayOutbound, + YesterdayInbound: yesterdayInbound, + YesterdayOutbound: yesterdayOutbound, + }, nil } // GetWarehouseStatist 获取仓库统计数据