From 8ffe485fa2211e5fc54671614bfa7c9658a5372e Mon Sep 17 00:00:00 2001 From: xiaodongzhu825 <97694732@qq.com> Date: Sat, 27 Jun 2026 10:54:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E8=BF=90=E9=99=A9?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E4=BF=9D=E5=AD=98=E5=A4=B1=E8=B4=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++ config.yaml | 76 ------------------------------ database/tenant.go | 28 +++++++++++ service/StatistTaskService.go | 89 +++++++++++++++++++++-------------- service/logistics.go | 20 -------- service/process.go | 27 +++++++++++ 6 files changed, 111 insertions(+), 132 deletions(-) delete mode 100644 config.yaml diff --git a/.gitignore b/.gitignore index fd6039a..7bc5638 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ fonts/ # Runtime logs runtime/logs/ + +# Test directory (temporarily excluded from git) +_test/ diff --git a/config.yaml b/config.yaml deleted file mode 100644 index fe8b446..0000000 --- a/config.yaml +++ /dev/null @@ -1,76 +0,0 @@ -server: - port: "9090" -# host: "https://psi.api.buzhiyushu.cn/" - host: "http://192.168.101.213:9090/" - -database: - host: 175.27.224.66 - port: "3306" - user: root - password: 5e07c0eec1770c94 - name: psi - encrypt_key: "0123456789abcdef0123456789abcdef" - -task_database: - host: 36.212.7.35 - port: "3306" - user: zhishu - password: KSwx1MDcRW - name: task - -jwt: - secret: "0123456789abcdef0123456789abcdef" - expire_hours: 24 - -api_sign: - app_key: "psi" - app_secret: "psi_api_sign_secret" - client_id: "psi" - sign_method: "md5" - timestamp_tolerance: 300 - -log: - max_age: 600 - rotate_time: 600 - root_path: "./runtime/logs" - channel: - sql: "/sql/err.log" - work: "/work/err.log" - request: "/request/err.log" - es: "/es/err.log" - redis: "/redis/err.log" - -es: - host: "http://36.212.12.92:9527" - index: "books-from-mysql-v2" - username: "elastic" - password: "+Tz5qR_KushZ-bPgZ_H-" - -ocr: - service_url: "http://127.0.0.1:35569/ocr" - exe_url: "./ocr/OCRService.exe" - -external_api: - # sync_product_url: "http://192.168.101.127:8080/zhishu/filterSet/save" - sync_product_url: "https://api.buzhiyushu.cn/zhishu/filterSet/save" - es_update_book_url: "https://book.center.yushutx.com/api/es/updateBookFieldsByISBN" - # sync_task_url: "http://192.168.101.156:8080/task/create" - sync_task_url: "http://36.212.7.246:8283/task/create" - # sync_task_body_url: "http://192.168.101.156:8080/task/setTaskBody" - sync_task_body_url: "http://36.212.7.246:8283/task/setTaskBody" - timeout: 30 - -wangdian: - url: "https://api.wangdian.cn/openapi2/purchase_order_push.php" - sandbox_url: "https://sandbox.wangdian.cn/openapi2/purchase_order_push.php" - provider_query_url: "https://api.wangdian.cn/openapi2/purchase_provider_query.php" - provider_query_sandbox: "https://sandbox.wangdian.cn/openapi2/purchase_provider_query.php" - warehouse_query_url: "https://api.wangdian.cn/openapi2/warehouse_query.php" - warehouse_query_sandbox: "https://sandbox.wangdian.cn/openapi2/warehouse_query.php" - goods_query_url: "https://api.wangdian.cn/openapi2/goods_query.php" - goods_query_sandbox: "https://sandbox.wangdian.cn/openapi2/goods_query.php" - sandbox: true - sid: "apidevnew2" - appkey: "skxz2-test" - appsecret: "85bf423bb" - timeout: 30 \ No newline at end of file diff --git a/database/tenant.go b/database/tenant.go index 5bef4de..2f237b8 100644 --- a/database/tenant.go +++ b/database/tenant.go @@ -322,6 +322,16 @@ func migrateTenantTables(db *gorm.DB) { log.Printf("Config表迁移警告: %v", err) } + err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'").AutoMigrate(&models.DashboardDailyStat{}) + if err != nil { + log.Printf("DashboardDailyStat表迁移警告: %v", err) + } + + err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'").AutoMigrate(&models.UserDailyStat{}) + if err != nil { + log.Printf("UserDailyStat表迁移警告: %v", err) + } + log.Println("租户业务表迁移完成") //tableOptions := "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" // @@ -412,4 +422,22 @@ func migrateIncrementalTenantTables(db *gorm.DB) { log.Println("Config表增量迁移成功") } } + + if !db.Migrator().HasTable(&models.DashboardDailyStat{}) { + err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仪表盘每日统计表'").AutoMigrate(&models.DashboardDailyStat{}) + if err != nil { + log.Printf("DashboardDailyStat表增量迁移警告: %v", err) + } else { + log.Println("DashboardDailyStat表增量迁移成功") + } + } + + if !db.Migrator().HasTable(&models.UserDailyStat{}) { + err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户每日统计表'").AutoMigrate(&models.UserDailyStat{}) + if err != nil { + log.Printf("UserDailyStat表增量迁移警告: %v", err) + } else { + log.Println("UserDailyStat表增量迁移成功") + } + } } diff --git a/service/StatistTaskService.go b/service/StatistTaskService.go index 24d7862..b86a353 100644 --- a/service/StatistTaskService.go +++ b/service/StatistTaskService.go @@ -81,7 +81,7 @@ func (s *StatistTaskService) GenerateDailyStat(db ...*gorm.DB) error { return } - if err := s.generateUserDailyStat(tx, aboutID, statDate, yesterdayStart, yesterdayEnd); err != nil { + if err := s.generateUserDailyStat(tx, mainDB, aboutID, statDate, yesterdayStart, yesterdayEnd); err != nil { tx.Rollback() utils.ErrorLog("work", map[string]interface{}{ "source": "定时任务-生成每日统计", @@ -198,49 +198,66 @@ func (s *StatistTaskService) generateDashboardDailyStat(tx *gorm.DB, statDate in } // 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"` +// 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) } - 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 { + if len(employees) == 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 + // 2. 提取员工ID列表 + userIDs := make([]int64, len(employees)) + for i, emp := range employees { + userIDs[i] = emp.UserID + } - if err == nil { - if err := tx.Model(&models.UserDailyStat{}).Where("id = ?", existingStat.ID).Update("is_del", 1).Error; err != nil { - continue - } - } + // 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 stat_date <= ? AND is_del = ?", userIDs, startDate, endDate, 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: userStat.UserID, - UserName: userStat.UserName, + UserID: emp.UserID, + UserName: emp.UserName, StatDate: statDate, - ReceivingNum: userStat.ReceivingNum, - OutboundNum: userStat.OutboundNum, - SalesCount: userStat.SalesCount, + ReceivingNum: receiving.ReceivingNum, + OutboundNum: receiving.OutboundNum, + SalesCount: 0, CreatedAt: now, UpdatedAt: now, IsDel: 0, @@ -249,7 +266,7 @@ func (s *StatistTaskService) generateUserDailyStat(tx *gorm.DB, aboutID int64, s if err := tx.Create(newStat).Error; err != nil { utils.ErrorLog("work", map[string]interface{}{ "source": "生成用户统计", - "user_id": userStat.UserID, + "user_id": emp.UserID, "error": fmt.Sprintf("创建用户统计记录失败: %v", err), }) } diff --git a/service/logistics.go b/service/logistics.go index ea20cdb..8dd36a6 100644 --- a/service/logistics.go +++ b/service/logistics.go @@ -27,28 +27,8 @@ 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+"%") diff --git a/service/process.go b/service/process.go index d2d6212..6c7ebe7 100644 --- a/service/process.go +++ b/service/process.go @@ -615,6 +615,33 @@ func (s *ProcessService) submitOrderOperation(orderID, waveTaskID int64, items [ return err } + // 入库时回写商品的 warehouse_id、location_id,方便前台查询 + if changeType == constant.InventoryChangeInbound { + productUpdates := make(map[int64]map[string]interface{}) + for _, op := range inventoryOpMap { + if _, ok := productUpdates[op.key.productID]; !ok { + productUpdates[op.key.productID] = map[string]interface{}{ + "warehouse_id": op.key.warehouseID, + "location_id": op.locationID, + } + } + } + for pid, pu := range productUpdates { + wid := pu["warehouse_id"].(int64) + lid := pu["location_id"].(int64) + // 查名称 + var wh models.Warehouse + tx.Where("id = ?", wid).Select("name").First(&wh) + var loc models.Location + tx.Where("id = ?", lid).Select("code").First(&loc) + pu["warehouse_name"] = wh.Name + pu["location_name"] = loc.Code + if err := tx.Model(&models.Product{}).Where("id = ? AND is_del = 0", pid).Updates(pu).Error; err != nil { + return fmt.Errorf("回写商品仓库/库位失败(product_id=%d): %v", pid, err) + } + } + } + if err := s.batchUpdateOrderItems(tx, orderItems, changeType); err != nil { return err }