修正了运险模板保存失败问题

This commit is contained in:
xiaodongzhu825 2026-06-27 10:54:41 +08:00
parent 2d00956212
commit 8ffe485fa2
6 changed files with 111 additions and 132 deletions

3
.gitignore vendored
View File

@ -9,3 +9,6 @@ fonts/
# Runtime logs
runtime/logs/
# Test directory (temporarily excluded from git)
_test/

View File

@ -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

View File

@ -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表增量迁移成功")
}
}
}

View File

@ -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 {
// 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"`
ReceivingNum int64 `gorm:"column:receiving_num"`
OutboundNum int64 `gorm:"column:outbound_num"`
SalesCount int64 `gorm:"column:sales_count"`
}
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_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: 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),
})
}

View File

@ -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+"%")

View File

@ -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
}