package service import ( "errors" "fmt" "github.com/xuri/excelize/v2" "psi/config" systemRes "psi/models/response" "psi/utils" "strconv" "strings" "time" "gorm.io/gorm" "psi/database" "psi/models" systemReq "psi/models/request" ) type WarehouseService struct{} // GetWarehouseList 获取仓库列表 func (s *WarehouseService) GetWarehouseList(req systemReq.QueryWarehouseRequest, db ...*gorm.DB) ([]systemRes.WarehouseResponse, int64, error) { databaseConn := database.OptionalDB(db...) if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } query := databaseConn.Model(&models.Warehouse{}).Where("is_del = ?", 0) if len(req.IDs) > 0 { query = query.Where("id IN ?", req.IDs) } if req.Keyword != "" { query = query.Where("code like ? OR name like ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%") } if req.Type != nil { query = query.Where("type = ?", *req.Type) } if req.Status != nil { query = query.Where("status = ?", *req.Status) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, errors.New("查询仓库总数失败") } var warehouses []models.Warehouse offset := (req.Page - 1) * req.PageSize if err := query.Order("id DESC").Offset(offset).Limit(req.PageSize).Find(&warehouses).Error; err != nil { return nil, 0, errors.New("查询仓库列表失败") } responses := make([]systemRes.WarehouseResponse, 0, len(warehouses)) for _, wh := range warehouses { resp := systemRes.ConvertWarehouseToResponse(wh) responses = append(responses, resp) } return responses, total, nil } // GetWarehouseByID 获取仓库信息 func (s *WarehouseService) GetWarehouseByID(id int64, db ...*gorm.DB) (*systemRes.WarehouseResponse, error) { databaseConn := database.OptionalDB(db...) var warehouse models.Warehouse if err := databaseConn.Where("id = ? AND is_del = ?", id, 0).First(&warehouse).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("仓库不存在") } return nil, errors.New("查询仓库失败") } resp := systemRes.ConvertWarehouseToResponse(warehouse) return &resp, nil } // CreateWarehouse 创建仓库 func (s *WarehouseService) CreateWarehouse(req systemReq.CreateWarehouseRequest, aboutId int64, userName string, db ...*gorm.DB) (int64, error) { databaseConn := database.OptionalDB(db...) var count int64 databaseConn.Model(&models.Warehouse{}). Where("code = ? AND is_del = ?", req.Code, 0). Count(&count) if count > 0 { return 0, errors.New("仓库编码已存在") } warehouse := models.Warehouse{ LogisticsID: req.LogisticsID, Code: req.Code, Name: req.Name, Type: req.Type, ContactPerson: req.ContactPerson, ContactPhone: req.ContactPhone, Province: req.Province, City: req.City, District: req.District, Address: req.Address, Status: req.Status, CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), IsDel: 0, } if warehouse.Type == 0 { warehouse.Type = 1 } if warehouse.Status == 0 { warehouse.Status = 1 } if err := databaseConn.Create(&warehouse).Error; err != nil { return 0, errors.New("创建仓库失败") } if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, req.Code, req.Name, "create"); err != nil { return 0, errors.New("创建仓库映射关系失败") } return warehouse.ID, nil } // UpdateWarehouse 更新仓库 func (s *WarehouseService) UpdateWarehouse(req systemReq.UpdateWarehouseRequest, aboutId int64, userName string, db ...*gorm.DB) error { databaseConn := database.OptionalDB(db...) var warehouse models.Warehouse if err := databaseConn.Where("id = ? AND is_del = ?", req.ID, 0).First(&warehouse).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("仓库不存在") } return errors.New("查询仓库失败") } updates := make(map[string]interface{}) updates["updated_at"] = time.Now().Unix() if req.Name != "" { updates["name"] = req.Name } if req.Type >= 1 && req.Type <= 3 { updates["type"] = req.Type } if req.ContactPerson != "" { updates["contact_person"] = req.ContactPerson } if req.ContactPhone != "" { updates["contact_phone"] = req.ContactPhone } if req.Province != "" { updates["province"] = req.Province } if req.City != "" { updates["city"] = req.City } if req.District != "" { updates["district"] = req.District } if req.Address != "" { updates["address"] = req.Address } if req.Status == 0 || req.Status == 1 { updates["status"] = req.Status } if req.LogisticsID > 0 { updates["logistics_id"] = req.LogisticsID } if err := databaseConn.Model(&warehouse).Updates(updates).Error; err != nil { return errors.New("更新仓库失败") } if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, warehouse.Code, utils.Ternary(req.Name == "", warehouse.Name, req.Name), "update"); err != nil { return errors.New("更新仓库映射关系失败") } return nil } // DeleteWarehouse 删除仓库 // aboutId: 用户ID // userName: 用户名称 // db: 数据库连接 func (s *WarehouseService) DeleteWarehouse(id, aboutId int64, userName string, db ...*gorm.DB) error { databaseConn := database.OptionalDB(db...) if id == 0 { return errors.New("仓库ID不能为空") } var warehouse models.Warehouse if err := databaseConn.Where("id = ? AND is_del = ?", id, 0).First(&warehouse).Error; err != nil { if err == gorm.ErrRecordNotFound { return errors.New("仓库不存在") } return errors.New("查询仓库失败") } var locationCount int64 databaseConn.Model(&models.Location{}). Where("warehouse_id = ? AND is_del = ?", id, 0). Count(&locationCount) if locationCount > 0 { return errors.New("该仓库下存在库位,无法删除") } // 执行真正的物理删除 if err := databaseConn.Delete(&warehouse).Error; err != nil { return errors.New("删除仓库失败") } if err := s.SyncUserWarehouseMapping(aboutId, warehouse.ID, userName, warehouse.Code, warehouse.Name, "delete"); err != nil { return errors.New("删除仓库映射关系失败") } return nil } // SyncUserWarehouseMapping 同步用户仓库映射关系 // aboutID: 用户关联ID // code: 仓库编码 // name: 仓库名称 // operation: 操作类型 create/update/delete func (s *WarehouseService) SyncUserWarehouseMapping(aboutID, id int64, userName, code, name, operation string) error { db := database.DB switch operation { case "create", "update": return s.upsertUserMapping(db, aboutID, id, userName, code, name) case "delete": return s.deleteUserMapping(db, aboutID, id) default: return nil } } // GetUserWarehouseMappings 获取用户的仓库映射列表 func (s *WarehouseService) GetUserWarehouseMappings() ([]models.UserMapping, error) { db := database.DB var mappings []models.UserMapping err := db.Where("is_del = ?", 0). Order("id DESC"). Find(&mappings).Error return mappings, err } // ExportLocations 导出库位数据 func (s *LocationService) ExportLocations(req systemReq.ExportLocationRequest, db ...*gorm.DB) (*systemRes.ExportLocationResponse, error) { databaseConn := database.OptionalDB(db...) query := databaseConn.Model(&models.Location{}). Where("is_del = ?", 0) if req.WarehouseID > 0 { query = query.Where("warehouse_id = ?", req.WarehouseID) } if req.Type > 0 { query = query.Where("type = ?", req.Type) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, utils.NewError("查询库位总数失败") } if total == 0 { return nil, fmt.Errorf("没有符合条件的库位数据") } var locations []models.Location if err := query.Order("warehouse_id ASC, code ASC").Find(&locations).Error; err != nil { return nil, utils.NewError("查询库位数据失败") } var warehouses []models.Warehouse warehouseIDs := make([]int64, 0) for _, loc := range locations { warehouseIDs = append(warehouseIDs, loc.WarehouseID) } if len(warehouseIDs) > 0 { databaseConn.Where("id IN ? AND is_del = ?", warehouseIDs, 0).Find(&warehouses) } warehouseMap := make(map[int64]string) for _, wh := range warehouses { warehouseMap[wh.ID] = wh.Code } f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Printf("关闭Excel文件失败: %v\n", err) } }() sheetName := "库位列表" index, _ := f.NewSheet(sheetName) f.SetActiveSheet(index) err := f.DeleteSheet("Sheet1") if err != nil { return nil, err } headers := []string{ "库位ID", "仓库ID", "仓库编码", "库位编码", "库位类型", "容量", "排序", "状态", "创建时间", "更新时间", } for i, header := range headers { cell, _ := excelize.CoordinatesToCellName(i+1, 1) err := f.SetCellValue(sheetName, cell, header) if err != nil { return nil, err } } headerStyle, _ := f.NewStyle(&excelize.Style{ Font: &excelize.Font{ Bold: true, Size: 12, }, Alignment: &excelize.Alignment{ Horizontal: "center", Vertical: "center", }, Fill: excelize.Fill{ Type: "pattern", Color: []string{"#E0E0E0"}, Pattern: 1, }, }) err = f.SetCellStyle(sheetName, "A1", "J1", headerStyle) if err != nil { return nil, err } typeMap := map[int8]string{ 1: "存储库位", 2: "拣货库位", 3: "收货库位", 4: "发货库位", 5: "退货库位", } statusMap := map[int8]string{ 0: "禁用", 1: "启用", } for idx, location := range locations { row := idx + 2 err := f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), location.ID) if err != nil { return nil, err } err = f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), location.WarehouseID) if err != nil { return nil, err } warehouseCode := "" if code, exists := warehouseMap[location.WarehouseID]; exists { warehouseCode = code } err = f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), warehouseCode) if err != nil { return nil, err } err = f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), location.Code) if err != nil { return nil, err } typeStr := "未知" if typeVal, exists := typeMap[location.Type]; exists { typeStr = typeVal } err = f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), typeStr) if err != nil { return nil, err } err = f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), location.Capacity) if err != nil { return nil, err } err = f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), location.Sort) if err != nil { return nil, err } statusStr := "未知" if statusVal, exists := statusMap[location.Status]; exists { statusStr = statusVal } err = f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), statusStr) if err != nil { return nil, err } createdAtStr := time.Unix(location.CreatedAt, 0).Format("2006-01-02 15:04:05") err = f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), createdAtStr) if err != nil { return nil, err } updatedAtStr := time.Unix(location.UpdatedAt, 0).Format("2006-01-02 15:04:05") err = f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), updatedAtStr) if err != nil { return nil, err } } colWidths := map[string]float64{ "A": 12, "B": 12, "C": 15, "D": 20, "E": 12, "F": 12, "G": 8, "H": 10, "I": 20, "J": 20, } for col, width := range colWidths { err := f.SetColWidth(sheetName, col, col, width) if err != nil { return nil, err } } now := time.Now() fileName := fmt.Sprintf("location_export_%s.xlsx", now.Format("20060102150405")) filePath := fmt.Sprintf("excel/%s", fileName) if err := f.SaveAs(filePath); err != nil { return nil, fmt.Errorf("保存Excel文件失败: %v", err) } return &systemRes.ExportLocationResponse{ Total: total, FileName: fileName, FilePath: config.AppConfig.Server.Host + filePath, }, nil } // ImportLocationsFromCSV 导入库位数据 func (s *LocationService) ImportLocationsFromCSV(req systemReq.ImportLocationRequest, filePath string, db ...*gorm.DB) (*systemRes.ImportLocationResult, error) { databaseConn := database.OptionalDB(db...) result := &systemRes.ImportLocationResult{ SuccessCodes: make([]string, 0), FailCodes: make([]string, 0), } var warehouse models.Warehouse if err := databaseConn.Where("id = ? AND is_del = ?", req.WarehouseID, 0).First(&warehouse).Error; err != nil { return result, errors.New("仓库不存在") } f, err := excelize.OpenFile(filePath) if err != nil { return result, fmt.Errorf("打开Excel文件失败: %v", err) } defer func() { if err := f.Close(); err != nil { fmt.Printf("关闭Excel文件失败: %v\n", err) } }() sheetName := f.GetSheetName(0) rows, err := f.GetRows(sheetName) if err != nil { return result, fmt.Errorf("读取表格数据失败: %v", err) } if len(rows) < 2 { return result, errors.New("文件中没有数据") } now := time.Now().Unix() successCount := 0 failCount := 0 for i, row := range rows { if i == 0 { continue } if len(row) < 6 { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("第%d行: 列数不足", i+1)) continue } code := strings.TrimSpace(row[3]) if code == "" { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("第%d行: 库位编码为空", i+1)) continue } typeStr := strings.TrimSpace(row[4]) capacityStr := strings.TrimSpace(row[5]) sortStr := "" if len(row) > 6 { sortStr = strings.TrimSpace(row[6]) } statusStr := "" if len(row) > 7 { statusStr = strings.TrimSpace(row[7]) } var locType int8 = 1 if typeStr != "" { typeMap := map[string]int8{ "存储库位": 1, "拣货库位": 2, "收货库位": 3, "发货库位": 4, "退货库位": 5, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, } if val, exists := typeMap[typeStr]; exists { locType = val } else { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 库位类型无效", code)) continue } } var capacity int64 = 0 if capacityStr != "" { if capVal, err := strconv.ParseInt(capacityStr, 10, 64); err == nil { capacity = capVal } } var sort int = 0 if sortStr != "" { if sortVal, err := strconv.Atoi(sortStr); err == nil { sort = sortVal } } var status int8 = 1 if statusStr != "" { statusMap := map[string]int8{ "启用": 1, "禁用": 0, "1": 1, "0": 0, } if val, exists := statusMap[statusStr]; exists { status = val } } var existingLocation models.Location err := databaseConn.Where("warehouse_id = ? AND code = ?", req.WarehouseID, code).First(&existingLocation).Error if err == nil { if existingLocation.IsDel == 1 { existingLocation.IsDel = 0 existingLocation.Type = locType existingLocation.Capacity = capacity existingLocation.Sort = sort existingLocation.Status = status existingLocation.UpdatedAt = now if updateErr := databaseConn.Save(&existingLocation).Error; updateErr != nil { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 恢复库位失败", code)) continue } successCount++ result.SuccessCodes = append(result.SuccessCodes, code) } else { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 已存在", code)) continue } } else if err == gorm.ErrRecordNotFound { location := models.Location{ WarehouseID: req.WarehouseID, Code: code, Type: locType, Capacity: capacity, Sort: sort, Status: status, CreatedAt: now, UpdatedAt: now, IsDel: 0, } if createErr := databaseConn.Create(&location).Error; createErr != nil { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 创建失败", code)) continue } successCount++ result.SuccessCodes = append(result.SuccessCodes, code) } else { failCount++ result.FailCodes = append(result.FailCodes, fmt.Sprintf("%s: 查询失败", code)) continue } } result.SuccessCount = successCount result.FailCount = failCount if failCount > 0 { result.Message = fmt.Sprintf("导入完成:成功%d个,失败%d个", successCount, failCount) } else { result.Message = fmt.Sprintf("导入完成:全部成功,共%d个", successCount) } return result, nil } // =============== 私有方法 =============== // upsertUserMapping 插入或更新用户映射(保证 about_id + code 唯一) // db: 数据库连接 // aboutID: 用户关联ID // id: 仓库ID // userName: 用户名称 func (s *WarehouseService) upsertUserMapping(db *gorm.DB, aboutID, id int64, userName, code, name string) error { now := time.Now().Unix() var existing models.UserMapping err := db.Where("about_id = ? AND warehouse_id = ? AND is_del = ?", aboutID, id, 0).First(&existing).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } if errors.Is(err, gorm.ErrRecordNotFound) { // 不存在则创建 mapping := models.UserMapping{ AboutID: aboutID, Username: userName, WarehouseId: id, WarehouseCode: code, WarehouseName: name, CreatedAt: now, UpdatedAt: now, IsDel: 0, } return db.Create(&mapping).Error } // 存在则更新 return db.Model(&existing).Updates(map[string]interface{}{ "warehouse_name": name, "warehouse_code": code, "updated_at": now, }).Error } // deleteUserMapping 删除用户映射(软删除) func (s *WarehouseService) deleteUserMapping(db *gorm.DB, aboutID, id int64) error { now := time.Now().Unix() return db.Model(&models.UserMapping{}). Where("about_id = ? AND warehouse_id = ? AND is_del = ?", aboutID, id, 0). Updates(map[string]interface{}{ "is_del": 1, "updated_at": now, }).Error }