package service import ( "fmt" "strings" "time" "github.com/xuri/excelize/v2" "gorm.io/gorm" "psi/database" "psi/models" "psi/models/request" ) // LocationImportService 库位导入服务 type LocationImportService struct{} // LOCATION_IMPORT_COLUMNS 库位导入表格列(仅1列) var LOCATION_IMPORT_COLUMNS = []string{"库位编码"} // LocationImportResult 导入结果 type LocationImportResult struct { SuccessCount int FailCount int FailList []string Message string } // AddFail 添加失败记录 func (r *LocationImportResult) AddFail(reason string) { r.FailCount++ r.FailList = append(r.FailList, reason) } // ParseLocationImportExcel 解析Excel文件为库位行数据 func (s *LocationImportService) ParseLocationImportExcel(fileBytes []byte) ([]request.LocationImportRow, error) { f, err := excelize.OpenReader(strings.NewReader(string(fileBytes))) if err != nil { return nil, fmt.Errorf("无法打开Excel文件: %v", err) } defer f.Close() rows, err := f.GetRows(f.GetSheetName(0)) if err != nil { return nil, fmt.Errorf("读取Sheet失败: %v", err) } if len(rows) < 2 { return nil, fmt.Errorf("Excel至少需要表头+1行数据") } // 校验表头 header := rows[0] if len(header) < len(LOCATION_IMPORT_COLUMNS) { return nil, fmt.Errorf("表头列数不足,期望%d列(库位编码),实际%d列", len(LOCATION_IMPORT_COLUMNS), len(header)) } var result []request.LocationImportRow for i := 1; i < len(rows); i++ { row := rows[i] code := strings.TrimSpace(getCell(row, 0)) if code == "" { continue } result = append(result, request.LocationImportRow{Code: code}) } return result, nil } // getCell 安全获取单元格值 func getCell(row []string, idx int) string { if idx < len(row) { return row[idx] } return "" } // ImportLocationsFromExcel 从Excel导入库位(事务内批量创建) func (s *LocationImportService) ImportLocationsFromExcel(userID, warehouseID int64, rows []request.LocationImportRow) (*LocationImportResult, error) { databaseConn, err := database.GetTenantDB(userID) if err != nil { return nil, fmt.Errorf("获取数据库连接失败: %v", err) } result := &LocationImportResult{} err = databaseConn.Transaction(func(tx *gorm.DB) error { now := time.Now().Unix() for _, row := range rows { code := strings.TrimSpace(row.Code) if code == "" { result.AddFail("库位编码为空") continue } // 检查同仓库下是否已存在 var existing models.Location if err := tx.Where("warehouse_id = ? AND code = ? AND is_del = ?", warehouseID, code, 0).First(&existing).Error; err == nil { result.AddFail(fmt.Sprintf("%s: 库位已存在", code)) continue } loc := models.Location{ WarehouseID: warehouseID, Code: code, Type: 1, // 存储库位 Capacity: 255, Sort: 0, Status: 1, // 启用 CreatedAt: now, UpdatedAt: now, IsDel: 0, } if err := tx.Create(&loc).Error; err != nil { result.AddFail(fmt.Sprintf("%s: 创建失败: %v", code, err)) continue } result.SuccessCount++ } return nil }) if err != nil { return nil, fmt.Errorf("导入库位事务失败: %v", err) } if result.FailCount > 0 { result.Message = fmt.Sprintf("成功 %d 条,失败 %d 条。失败明细: %s", result.SuccessCount, result.FailCount, strings.Join(result.FailList, "; ")) } else { result.Message = fmt.Sprintf("全部成功,共 %d 条", result.SuccessCount) } return result, nil }