daShangDao_psiServer/service/location.go
2026-06-18 13:01:56 +08:00

1128 lines
31 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}