package service import ( "encoding/json" "fmt" "gorm.io/datatypes" "gorm.io/gorm" "strconv" "strings" "time" "github.com/pkg/errors" "psi/database" "psi/models" systemReq "psi/models/request" systemRes "psi/models/response" ) type LocationService struct{} // GetLocationList 获取库位列表 func (s *LocationService) GetLocationList(req systemReq.QueryLocationRequest, db ...*gorm.DB) ([]systemRes.LocationResponse, int64, error) { databaseConn := database.OptionalDB(db...) if req.WarehouseID == 0 { return nil, 0, errors.New("仓库ID不能为空") } if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } query := databaseConn.Model(&models.Location{}).Where("warehouse_id = ? AND is_del = ?", req.WarehouseID, 0) if req.Code != "" { query = query.Where("code LIKE ?", "%"+req.Code+"%") } 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 locations []models.Location offset := (req.Page - 1) * req.PageSize if err := query.Order("sort ASC,code ASC").Offset(offset).Limit(req.PageSize).Find(&locations).Error; err != nil { return nil, 0, errors.New("查询库位列表失败") } responses := make([]systemRes.LocationResponse, 0, len(locations)) for _, loc := range locations { resp := systemRes.ConvertLocationToResponse(loc) responses = append(responses, resp) } return responses, total, nil } // GetAllLocationList 查询所有库位列表(仓库ID可选) func (s *LocationService) GetAllLocationList(req systemReq.QueryAllLocationRequest, db ...*gorm.DB) ([]systemRes.LocationResponse, 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.Location{}).Where("is_del = ?", 0) if req.WarehouseID != nil && *req.WarehouseID > 0 { query = query.Where("warehouse_id = ?", *req.WarehouseID) } if req.Code != "" { query = query.Where("code LIKE ?", "%"+req.Code+"%") } 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 locations []models.Location offset := (req.Page - 1) * req.PageSize if err := query.Order("sort ASC,code ASC").Offset(offset).Limit(req.PageSize).Find(&locations).Error; err != nil { return nil, 0, errors.New("查询库位列表失败") } responses := make([]systemRes.LocationResponse, 0, len(locations)) for _, loc := range locations { resp := systemRes.ConvertLocationToResponse(loc) responses = append(responses, resp) } return responses, total, nil } // GetLocationDetail 获取库位详情 func (s *LocationService) GetLocationDetail(id int64, db ...*gorm.DB) (*systemRes.LocationResponse, error) { databaseConn := database.OptionalDB(db...) var location models.Location if err := databaseConn.Where("id = ? AND is_del = ?", id, 0).First(&location).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New("库位不存在") } return nil, errors.New("查询库位失败") } resp := systemRes.ConvertLocationToResponse(location) return &resp, nil } // GetLocationInfo 获取库位信息 func (s *LocationService) GetLocationInfo(code, warehouseCode string, db ...*gorm.DB) (*systemRes.LocationResponse, error) { databaseConn := database.OptionalDB(db...) var warehouse models.Warehouse if err := databaseConn.Where("code = ? AND is_del = ?", warehouseCode, 0).First(&warehouse).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New("仓库不存在") } return nil, errors.New("查询仓库失败") } var location models.Location if err := databaseConn.Where("code = ? AND warehouse_id = ? AND is_del = ?", code, warehouse.ID, 0).First(&location).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New("库位不存在") } return nil, errors.New("查询库位失败") } resp := systemRes.ConvertLocationToResponse(location) return &resp, nil } // CreateLocation 创建库位 func (s *LocationService) CreateLocation(req systemReq.CreateLocationRequest, db ...*gorm.DB) (int64, error) { databaseConn := database.OptionalDB(db...) var warehouse models.Warehouse if err := databaseConn.Where("id = ? AND is_del = ?", req.WarehouseID, 0).First(&warehouse).Error; err != nil { return 0, errors.New("仓库不存在") } if len(req.Code) > 50 { return 0, errors.New("库位编码长度不能超过50") } var existingLocation models.Location err := databaseConn.Where("warehouse_id = ? AND code = ?", req.WarehouseID, req.Code).First(&existingLocation).Error if err == nil { if existingLocation.IsDel == 1 { now := time.Now().Unix() existingLocation.IsDel = 0 existingLocation.Type = req.Type existingLocation.Capacity = req.Capacity existingLocation.Status = req.Status existingLocation.UpdatedAt = now if updateErr := databaseConn.Save(&existingLocation).Error; updateErr != nil { return 0, errors.New("恢复库位失败") } return existingLocation.ID, nil } else { return 0, errors.New("库位编码已存在") } } else if err == gorm.ErrRecordNotFound { location := models.Location{ WarehouseID: req.WarehouseID, Code: req.Code, Type: req.Type, Capacity: req.Capacity, Status: req.Status, CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), IsDel: 0, } if createErr := databaseConn.Create(&location).Error; createErr != nil { return 0, errors.New("创建库位失败") } return location.ID, nil } else { return 0, errors.New("查询库位失败") } } // UpdateLocation 修改库位 func (s *LocationService) BatchGenerateLocations(req systemReq.BatchGenerateLocationRequest, db ...*gorm.DB) (*systemRes.BatchGenerateResult, error) { databaseConn := database.OptionalDB(db...) result := &systemRes.BatchGenerateResult{ SuccessIDs: make([]int64, 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("仓库不存在") } if len(req.Groups) > 5 { return result, errors.New("最多支持5组") } now := time.Now().Unix() allCodes := make([]string, 0) locations := make([]models.Location, 0) failedCodes := make([]string, 0) existingCodes := make(map[string]bool) groupValues := make([][]string, len(req.Groups)) for i, group := range req.Groups { values, err := generateLevelValues(systemReq.GroupConfig{ FormatType: group.FormatType, StartValue: group.StartValue, EndValue: group.EndValue, PaddingLen: group.PaddingLen, }) if err != nil { return result, errors.Wrapf(err, "第%d组配置错误", i+1) } groupValues[i] = values } codes := make([]string, 0) var generate func(groupIndex int, prefix string) generate = func(groupIndex int, prefix string) { if groupIndex == len(groupValues) { codes = append(codes, prefix) return } separator := req.Groups[groupIndex].Separator for _, value := range groupValues[groupIndex] { newPrefix := prefix + value // separator 放在本组值后面,只有下一组存在时才生效 if groupIndex+1 < len(groupValues) { newPrefix += separator } generate(groupIndex+1, newPrefix) } } generate(0, "") for _, code := range codes { if existingCodes[code] { continue } var existingLocation models.Location err := databaseConn.Where("warehouse_id = ? AND code = ?", req.WarehouseID, code).First(&existingLocation).Error if err == nil { existingCodes[code] = true if existingLocation.IsDel == 1 { failedCodes = append(failedCodes, fmt.Sprintf("%s:已删除可恢复", code)) } else { failedCodes = append(failedCodes, fmt.Sprintf("%s:已存在", code)) } continue } else if !errors.Is(err, gorm.ErrRecordNotFound) { return result, errors.New("批量生成库位失败") } existingCodes[code] = true allCodes = append(allCodes, code) location := models.Location{ WarehouseID: req.WarehouseID, Code: code, Type: req.Type, Capacity: req.Capacity, Status: req.Status, CreatedAt: now, UpdatedAt: now, IsDel: 0, } locations = append(locations, location) } result.TotalCount = len(allCodes) result.GeneratedCode = allCodes if len(allCodes) > 10000 { return result, errors.New(fmt.Sprintf("生成的库位数量过多(%d个),请调整范围", len(allCodes))) } if len(locations) > 0 { if err := databaseConn.Create(&locations).Error; err != nil { return result, errors.New("批量创建库位失败") } for _, loc := range locations { result.SuccessIDs = append(result.SuccessIDs, loc.ID) } } result.SuccessCount = len(result.SuccessIDs) result.FailCount = len(failedCodes) result.FailCodes = failedCodes if len(failedCodes) > 0 { result.Message = fmt.Sprintf("成功创建%d个,失败%d个", result.SuccessCount, result.FailCount) } else { result.Message = fmt.Sprintf("成功创建%d个库位", result.SuccessCount) } return result, nil } // UpdateLocation 修改库位 func (s *LocationService) UpdateLocation(req systemReq.UpdateLocationRequest, db ...*gorm.DB) error { databaseConn := database.OptionalDB(db...) if len(req.IDs) == 0 { return errors.New("请选择要更新的库位") } isSingleUpdate := len(req.IDs) == 1 updates := make(map[string]interface{}) updates["updated_at"] = time.Now().Unix() if isSingleUpdate && req.Code != "" { if len(req.Code) > 50 { return errors.New("库位编码长度不能超过50") } var count int64 databaseConn.Model(&models.Location{}). Where("warehouse_id = ? AND code = ? AND id != ? AND is_del = ?", req.WarehouseID, req.Code, req.IDs[0], 0). Count(&count) if count > 0 { return errors.New("库位编码已存在") } updates["code"] = req.Code } if req.Type != nil { if *req.Type < 1 || *req.Type > 5 { return errors.New("库位类型无效") } updates["type"] = *req.Type } if req.Capacity != nil { if *req.Capacity < 0 { return errors.New("库位容量不能为负数") } var totalQuantity int64 databaseConn.Model(&models.InventoryDetail{}). Where("location_id IN (?) AND is_del = ?", req.IDs, 0). Select("COALESCE(SUM(quantity), 0)"). Scan(&totalQuantity) if *req.Capacity < totalQuantity { return errors.New(fmt.Sprintf("库位容量不能小于当前库存数量(%d)", totalQuantity)) } updates["capacity"] = *req.Capacity } if req.Status != nil { if *req.Status != 0 && *req.Status != 1 { return errors.New("库位状态无效") } updates["status"] = *req.Status } if req.Sort != nil { updates["sort"] = *req.Sort } if len(updates) <= 1 { return errors.New("没有需要更新的字段") } if err := databaseConn.Model(&models.Location{}). Where("id IN (?) AND is_del = ?", req.IDs, 0). Updates(updates).Error; err != nil { return errors.New("更新库位失败") } return nil } // DeleteLocation 删除库位 func (s *LocationService) DeleteLocation(ids []int64, db ...*gorm.DB) error { databaseConn := database.OptionalDB(db...) if len(ids) == 0 { return errors.New("请选择要删除的库位") } var count int64 databaseConn.Model(&models.InventoryDetail{}). Where("location_id IN ? AND is_del = 0 AND available_quantity > 0", ids). Count(&count) if count > 0 { return errors.New("库位下仍有库存商品,无法删除") } // 执行真正的物理删除 if err := databaseConn.Where("id IN ? AND is_del = ?", ids, 0).Delete(&models.Location{}).Error; err != nil { return errors.New("删除库位失败") } return nil } // GetLocationList 获取库位列表 // SyncLocations 同步库位数据(包含物流模板、仓库、货区) func (s *LocationService) SyncLocations(req systemReq.SyncLocationRequest) (*systemRes.SyncLocationResponse, error) { databaseConn, err := database.GetTenantDB(req.UserID) if err != nil { return nil, fmt.Errorf("获取数据库连接失败: %v", err) } result := &systemRes.SyncLocationResponse{ SuccessCodes: make([]string, 0), FailCodes: make([]string, 0), } if len(req.Data) == 0 { return result, errors.New("数据不能为空") } now := time.Now().Unix() tx := databaseConn.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() successCount := 0 failCount := 0 var failMessages []string for _, area := range req.Data { areaCode := area.Code if areaCode == "" { failCount++ failMessages = append(failMessages, "区域编码为空") continue } logisticsID, err := s.createOrUpdateLogistics(tx, area.Logistics, req.UserID, now) if err != nil { failCount++ failMessages = append(failMessages, fmt.Sprintf("%s: 创建物流模板失败-%v", areaCode, err)) continue } warehouseID, err := s.createOrUpdateWarehouse(tx, area, logisticsID, req.UserID, now) if err != nil { failCount++ failMessages = append(failMessages, fmt.Sprintf("%s: 创建仓库失败-%v", areaCode, err)) continue } locationResult, err := s.batchCreateLocations(tx, warehouseID, area.Data, now) if err != nil { failCount++ failMessages = append(failMessages, fmt.Sprintf("%s: 创建货区失败-%v", areaCode, err)) continue } successCount += locationResult.SuccessCount failCount += locationResult.FailCount if len(locationResult.FailCodes) > 0 { for _, failCode := range locationResult.FailCodes { failMessages = append(failMessages, fmt.Sprintf("%s-%s", areaCode, failCode)) } } result.SuccessCodes = append(result.SuccessCodes, locationResult.SuccessCodes...) } if failCount > 0 || len(failMessages) > 0 { if err := tx.Commit().Error; err != nil { return nil, fmt.Errorf("提交事务失败: %v", err) } result.FailCodes = failMessages result.SuccessCount = successCount result.FailCount = failCount if failCount > 0 { result.Message = fmt.Sprintf("同步完成:成功%d个,失败%d个", successCount, failCount) } else { result.Message = fmt.Sprintf("同步完成:全部成功,共%d个", successCount) } } else { if err := tx.Commit().Error; err != nil { return nil, fmt.Errorf("提交事务失败: %v", err) } result.SuccessCount = successCount result.FailCount = failCount result.Message = fmt.Sprintf("同步完成:全部成功,共%d个", successCount) } return result, nil } // createOrUpdateLogistics 创建或更新物流模板 func (s *LocationService) createOrUpdateLogistics(tx *gorm.DB, logisticsReq systemReq.SyncLogisticsRequest, userID int64, now int64) (int64, error) { var logistics models.Logistics err := tx.Where("id = ?", logisticsReq.Id).First(&logistics).Error if err == gorm.ErrRecordNotFound { createTime, _ := time.Parse(time.RFC3339, logisticsReq.CreateTime) updateTime, _ := time.Parse(time.RFC3339, logisticsReq.UpdateTime) newLogistics := models.Logistics{ Id: uint64(logisticsReq.Id), TemplateName: logisticsReq.TemplateName, DeliveryProvince: logisticsReq.DeliveryProvince, DeliveryCity: logisticsReq.DeliveryCity, DeliveryArea: logisticsReq.DeliveryArea, DeliveryAddress: logisticsReq.DeliveryAddress, PricingMethod: logisticsReq.PricingMethod, Shipping: logisticsReq.Shipping, FirWbv: logisticsReq.FirWbv, FirPrice: logisticsReq.FirPrice, ContinueWbv: logisticsReq.ContinueWbv, ContinuePrice: logisticsReq.ContinuePrice, CreateBy: uint64(userID), CreateTime: &createTime, UpdateBy: uint64(userID), UpdateTime: &updateTime, Status: logisticsReq.Status, DelFlag: "0", TenantId: logisticsReq.TenantId, CreateDept: uint64(logisticsReq.CreateDept), ShippingRange: logisticsReq.ShippingRange, WarehouseId: 0, Remark: logisticsReq.Remark, PhoneNumber: uint64(logisticsReq.PhoneNumber), Contact: logisticsReq.Contact, FullAddress: logisticsReq.FullAddress, } if err := tx.Create(&newLogistics).Error; err != nil { return 0, errors.New("创建物流模板失败") } return int64(newLogistics.Id), nil } else if err != nil { return 0, errors.New("查询物流模板失败") } else { updateTime, _ := time.Parse(time.RFC3339, logisticsReq.UpdateTime) logistics.TemplateName = logisticsReq.TemplateName logistics.DeliveryProvince = logisticsReq.DeliveryProvince logistics.DeliveryCity = logisticsReq.DeliveryCity logistics.DeliveryArea = logisticsReq.DeliveryArea logistics.DeliveryAddress = logisticsReq.DeliveryAddress logistics.PricingMethod = logisticsReq.PricingMethod logistics.Shipping = logisticsReq.Shipping logistics.FirWbv = logisticsReq.FirWbv logistics.FirPrice = logisticsReq.FirPrice logistics.ContinueWbv = logisticsReq.ContinueWbv logistics.ContinuePrice = logisticsReq.ContinuePrice logistics.UpdateBy = uint64(userID) logistics.UpdateTime = &updateTime logistics.Status = logisticsReq.Status logistics.ShippingRange = logisticsReq.ShippingRange logistics.Remark = logisticsReq.Remark logistics.PhoneNumber = uint64(logisticsReq.PhoneNumber) logistics.Contact = logisticsReq.Contact logistics.FullAddress = logisticsReq.FullAddress if err := tx.Save(&logistics).Error; err != nil { return 0, errors.New("更新物流模板失败") } return int64(logistics.Id), nil } } // createOrUpdateWarehouse 创建或更新仓库 func (s *LocationService) createOrUpdateWarehouse(tx *gorm.DB, area systemReq.SyncLocationAreaRequest, logisticsID int64, userID int64, now int64) (int64, error) { var warehouse models.Warehouse err := tx.Where("code = ? AND is_del = ?", area.Code, 0).First(&warehouse).Error if err == gorm.ErrRecordNotFound { newWarehouse := models.Warehouse{ LogisticsID: logisticsID, Code: area.Code, Name: area.Name, Type: 1, ContactPerson: area.Logistics.Contact, ContactPhone: fmt.Sprintf("%d", area.Logistics.PhoneNumber), Province: area.Logistics.DeliveryProvince, City: area.Logistics.DeliveryCity, District: area.Logistics.DeliveryArea, Address: area.Logistics.FullAddress, Status: 1, CreatedAt: now, UpdatedAt: now, IsDel: 0, } if err := tx.Create(&newWarehouse).Error; err != nil { return 0, errors.New("创建仓库失败") } return newWarehouse.ID, nil } else if err != nil { return 0, errors.New("查询仓库失败") } else { warehouse.LogisticsID = logisticsID warehouse.Name = area.Name warehouse.ContactPerson = area.Logistics.Contact warehouse.ContactPhone = fmt.Sprintf("%d", area.Logistics.PhoneNumber) warehouse.Province = area.Logistics.DeliveryProvince warehouse.City = area.Logistics.DeliveryCity warehouse.District = area.Logistics.DeliveryArea warehouse.Address = area.Logistics.FullAddress warehouse.UpdatedAt = now if err := tx.Save(&warehouse).Error; err != nil { return 0, errors.New("更新仓库失败") } return warehouse.ID, nil } } // batchCreateLocations 批量创建库位 func (s *LocationService) batchCreateLocations(tx *gorm.DB, warehouseID int64, items []systemReq.SyncLocationItemRequest, now int64) (*systemRes.SyncLocationResponse, error) { result := &systemRes.SyncLocationResponse{ SuccessCodes: make([]string, 0), FailCodes: make([]string, 0), } if len(items) == 0 { return result, nil } codes := make([]string, 0, len(items)) codeMap := make(map[string]systemReq.SyncLocationItemRequest) for _, item := range items { if item.Code == "" { result.FailCodes = append(result.FailCodes, "编码为空") continue } codes = append(codes, item.Code) codeMap[item.Code] = item } if len(codes) == 0 { result.SuccessCount = 0 result.FailCount = len(result.FailCodes) return result, nil } var existingLocations []models.Location if err := tx.Where("warehouse_id = ? AND code IN ?", warehouseID, codes).Find(&existingLocations).Error; err != nil { return result, errors.New("查询库位失败") } existingCodeMap := make(map[string]models.Location) for _, loc := range existingLocations { existingCodeMap[loc.Code] = loc } newLocations := make([]models.Location, 0) updateLocations := make([]models.Location, 0) successCodes := make([]string, 0) for _, code := range codes { item := codeMap[code] if existingLoc, exists := existingCodeMap[code]; exists { existingLoc.Capacity = item.SheQuantityMax existingLoc.UpdatedAt = now updateLocations = append(updateLocations, existingLoc) successCodes = append(successCodes, code) } else { newLocation := models.Location{ WarehouseID: warehouseID, Code: item.Code, Type: 1, Capacity: item.SheQuantityMax, Status: 1, CreatedAt: now, UpdatedAt: now, IsDel: 0, } newLocations = append(newLocations, newLocation) successCodes = append(successCodes, code) } } if len(newLocations) > 0 { if err := tx.Create(&newLocations).Error; err != nil { return result, errors.New("批量创建库位失败") } } if len(updateLocations) > 0 { for i := range updateLocations { if err := tx.Save(&updateLocations[i]).Error; err != nil { result.FailCodes = append(result.FailCodes, updateLocations[i].Code+":更新失败") for j, code := range successCodes { if code == updateLocations[i].Code { successCodes = append(successCodes[:j], successCodes[j+1:]...) break } } } } } result.SuccessCodes = successCodes result.SuccessCount = len(successCodes) result.FailCount = len(result.FailCodes) return result, nil } // createOrUpdateLocation 创建或更新库位 // SyncGoods 同步商品数据并创建库存 func (s *LocationService) SyncGoods(req systemReq.SyncGoodsRequest) (*systemRes.SyncGoodsResponse, error) { databaseConn, err := database.GetTenantDB(req.UserID) if err != nil { return nil, fmt.Errorf("获取数据库连接失败: %v", err) } result := &systemRes.SyncGoodsResponse{ SuccessCount: 0, FailCount: 0, Message: "", } if len(req.Data) == 0 { result.Message = "数据为空" return result, nil } now := time.Now().Unix() successCount := 0 failCount := 0 var failMessages []string tx := databaseConn.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() locationCodes := make([]string, 0, len(req.Data)) for _, item := range req.Data { locationCodes = append(locationCodes, item.LocationCode) } var locations []models.Location if err := tx.Where("code IN ? AND is_del = ?", locationCodes, 0).Find(&locations).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("查询库位失败: %v", err) } locationMap := make(map[string]models.Location) for _, loc := range locations { locationMap[loc.Code] = loc } newProducts := make([]models.Product, 0, len(req.Data)) for _, item := range req.Data { _, exists := locationMap[item.LocationCode] if !exists { failCount++ failMessages = append(failMessages, fmt.Sprintf("%s: 库位%s不存在", item.GoodsName, item.LocationCode)) continue } var liveImage datatypes.JSON if len(item.LiveImage) > 0 { jsonBytes, _ := json.Marshal(item.LiveImage) liveImage = jsonBytes } else { liveImage = datatypes.JSON("[]") } product := models.Product{ CategoryID: 1, StandardProductID: 1, Name: item.GoodsName, Appearance: s.parseAppearance(item.Appearance), Barcode: item.ISBN, Price: 0, SalePrice: item.Price, Cost: 0, LiveImage: liveImage, IsBatchManaged: 0, IsShelfLifeManaged: 0, Status: 1, CreatedAt: now, UpdatedAt: now, IsDel: 0, } newProducts = append(newProducts, product) } if len(newProducts) > 0 { if err := tx.Create(&newProducts).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("批量创建商品失败: %v", err) } } newInventories := make([]models.Inventory, 0, len(newProducts)) newInventoryDetails := make([]models.InventoryDetail, 0, len(newProducts)) for i, item := range req.Data { loc, exists := locationMap[item.LocationCode] if !exists { failCount++ continue } if i >= len(newProducts) { failCount++ continue } product := newProducts[i] quantity := item.Inventory inventory := models.Inventory{ WarehouseID: loc.WarehouseID, ProductID: product.ID, BatchNo: "", ProductionDate: 0, ExpiryDate: 0, Quantity: quantity, LockedQuantity: 0, AvailableQuantity: quantity, CreatedAt: now, UpdatedAt: now, IsDel: 0, } newInventories = append(newInventories, inventory) inventoryDetail := models.InventoryDetail{ WarehouseID: loc.WarehouseID, LocationID: loc.ID, ProductID: product.ID, BatchNo: "", ProductionDate: 0, ExpiryDate: 0, Quantity: quantity, LockedQuantity: 0, AvailableQuantity: quantity, CreatedAt: now, UpdatedAt: now, IsDel: 0, } newInventoryDetails = append(newInventoryDetails, inventoryDetail) successCount++ } if len(newInventories) > 0 { if err := tx.Create(&newInventories).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("批量创建库存汇总失败: %v", err) } } if len(newInventoryDetails) > 0 { if err := tx.Create(&newInventoryDetails).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("批量创建库存明细失败: %v", err) } } if err := tx.Commit().Error; err != nil { return nil, fmt.Errorf("提交事务失败: %v", err) } result.SuccessCount = successCount result.FailCount = failCount if failCount > 0 { result.Message = fmt.Sprintf("同步完成:成功%d个,失败%d个", successCount, failCount) if len(failMessages) <= 5 { result.Message += fmt.Sprintf(",失败详情:%v", failMessages) } } else { result.Message = fmt.Sprintf("同步完成:全部成功,共%d个", successCount) } return result, nil } // parseAppearance 解析品相字符串为整数 func (s *LocationService) parseAppearance(appearance string) int64 { var result int64 fmt.Sscanf(appearance, "%d", &result) return result } // generateLevelValues 生成层级值 func generateLevelValues(config systemReq.GroupConfig) ([]string, error) { values := make([]string, 0) switch config.FormatType { case 1: startChar := strings.ToUpper(config.StartValue) endChar := strings.ToUpper(config.EndValue) if len(startChar) != 1 || len(endChar) != 1 { return nil, errors.New("字母格式起始值和结束值必须是单个字母") } startNum := charToNum(startChar[0]) endNum := charToNum(endChar[0]) if startNum == -1 || endNum == -1 { return nil, errors.New("无效的字母范围") } if startNum > endNum { return nil, errors.New("起始字母不能大于结束字母") } for i := startNum; i <= endNum; i++ { val := string(numToChar(i)) paddingLen := config.PaddingLen if paddingLen > 0 { val = fmt.Sprintf("%0"+strconv.Itoa(paddingLen)+"s", val) } values = append(values, val) } case 2: startNum, err := strconv.Atoi(config.StartValue) if err != nil { return nil, errors.New("数字格式起始值必须是数字") } endNum, err := strconv.Atoi(config.EndValue) if err != nil { return nil, errors.New("数字格式结束值必须是数字") } if startNum > endNum { return nil, errors.New("起始数字不能大于结束数字") } for i := startNum; i <= endNum; i++ { val := strconv.Itoa(i) paddingLen := config.PaddingLen if paddingLen > 0 { val = fmt.Sprintf("%0"+strconv.Itoa(paddingLen)+"d", i) } values = append(values, val) } case 3: parts := strings.Split(config.StartValue, ",") if len(parts) != 2 { return nil, errors.New("字母+数字格式起始值格式错误,应为:字母,数字") } startChar := strings.ToUpper(strings.TrimSpace(parts[0])) startNumStr := strings.TrimSpace(parts[1]) parts = strings.Split(config.EndValue, ",") if len(parts) != 2 { return nil, errors.New("字母+数字格式结束值格式错误,应为:字母,数字") } endChar := strings.ToUpper(strings.TrimSpace(parts[0])) endNumStr := strings.TrimSpace(parts[1]) if len(startChar) != 1 || len(endChar) != 1 { return nil, errors.New("字母部分必须是单个字母") } startCharNum := charToNum(startChar[0]) endCharNum := charToNum(endChar[0]) if startCharNum == -1 || endCharNum == -1 { return nil, errors.New("无效的字母") } startNum, err := strconv.Atoi(startNumStr) if err != nil { return nil, errors.New("起始数字部分必须是数字") } endNum, err := strconv.Atoi(endNumStr) if err != nil { return nil, errors.New("结束数字部分必须是数字") } if startCharNum > endCharNum { return nil, errors.New("起始字母不能大于结束字母") } for c := startCharNum; c <= endCharNum; c++ { for n := startNum; n <= endNum; n++ { charVal := string(numToChar(c)) numVal := strconv.Itoa(n) if config.PaddingLen > 0 { numVal = fmt.Sprintf("%0"+strconv.Itoa(config.PaddingLen)+"d", n) } values = append(values, charVal+numVal) } } case 4: parts := strings.Split(config.StartValue, ",") if len(parts) != 2 { return nil, errors.New("数字+字母格式起始值格式错误,应为:数字,字母") } startNumStr := strings.TrimSpace(parts[0]) startChar := strings.ToUpper(strings.TrimSpace(parts[1])) parts = strings.Split(config.EndValue, ",") if len(parts) != 2 { return nil, errors.New("数字+字母格式结束值格式错误,应为:数字,字母") } endNumStr := strings.TrimSpace(parts[0]) endChar := strings.ToUpper(strings.TrimSpace(parts[1])) if len(startChar) != 1 || len(endChar) != 1 { return nil, errors.New("字母部分必须是单个字母") } startNum, err := strconv.Atoi(startNumStr) if err != nil { return nil, errors.New("起始数字部分必须是数字") } endNum, err := strconv.Atoi(endNumStr) if err != nil { return nil, errors.New("结束数字部分必须是数字") } startCharNum := charToNum(startChar[0]) endCharNum := charToNum(endChar[0]) if startCharNum == -1 || endCharNum == -1 { return nil, errors.New("无效的字母") } if startNum > endNum { return nil, errors.New("起始数字不能大于结束数字") } for n := startNum; n <= endNum; n++ { for c := startCharNum; c <= endCharNum; c++ { numVal := strconv.Itoa(n) if config.PaddingLen > 0 { numVal = fmt.Sprintf("%0"+strconv.Itoa(config.PaddingLen)+"d", n) } charVal := string(numToChar(c)) values = append(values, numVal+charVal) } } default: return nil, errors.New("不支持的格式类型") } if len(values) == 0 { return nil, errors.New("生成的值为空") } return values, nil } // charToNum 将字符转换为数字 func charToNum(c byte) int { if c >= 'A' && c <= 'Z' { return int(c - 'A') } if c >= 'a' && c <= 'z' { return int(c - 'a') } return -1 } // numToChar 将数字转换为字符 func numToChar(n int) byte { if n >= 0 && n < 26 { return byte('A' + n) } return 0 }