This commit is contained in:
@@ -1021,7 +1021,14 @@ func (s *Service) buildRecallRecords(devices []*model.Device, successDeviceIDs [
|
||||
|
||||
// BatchSetSeriesBinding 批量设置设备的套餐系列绑定
|
||||
func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDeviceSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetDeviceSeriesBindngResponse, error) {
|
||||
devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
|
||||
selectionType, err := normalizeDeviceSeriesBindingSelection(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devices, err := s.getDevicesForSeriesBinding(ctx, req, selectionType)
|
||||
batchTotal := deviceSeriesBindingBatchTotal(req, selectionType, devices)
|
||||
auditData := deviceSeriesBindingAuditData(req, selectionType, operatorShopID)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
@@ -1030,41 +1037,37 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
"operator_shop_id": operatorShopID,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSeriesBinding,
|
||||
"设备系列绑定被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
"reason": "设备不存在",
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
errors.New(errors.CodeNotFound, "设备不存在"),
|
||||
)
|
||||
failedItems := []dto.DeviceSeriesBindngFailedItem{}
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
failedItems = s.buildDeviceNotFoundFailedItems(req.DeviceIDs)
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSeriesBinding,
|
||||
"设备系列绑定被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
nil,
|
||||
nil,
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
len(failedItems),
|
||||
errors.New(errors.CodeNotFound, "设备不存在"),
|
||||
)
|
||||
}
|
||||
return &dto.BatchSetDeviceSeriesBindngResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: len(req.DeviceIDs),
|
||||
FailedItems: s.buildDeviceNotFoundFailedItems(req.DeviceIDs),
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1087,13 +1090,10 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
constants.AssetAuditResultDenied,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
@@ -1105,13 +1105,10 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
@@ -1125,13 +1122,10 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
constants.AssetAuditResultDenied,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
@@ -1140,72 +1134,75 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
|
||||
var successDeviceIDs []uint
|
||||
var failedItems []dto.DeviceSeriesBindngFailedItem
|
||||
successDeviceIDSet := make(map[uint]struct{})
|
||||
|
||||
for _, deviceID := range req.DeviceIDs {
|
||||
device, exists := deviceMap[deviceID]
|
||||
if !exists {
|
||||
hasSeriesAllocation := true
|
||||
if operatorShopID != nil && req.SeriesID > 0 {
|
||||
hasSeriesAllocation, err = s.hasAvailableSeriesAllocation(ctx, *operatorShopID, req.SeriesID)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSeriesBinding,
|
||||
"设备系列绑定失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
auditData,
|
||||
batchTotal,
|
||||
0,
|
||||
batchTotal,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
addDevice := func(device *model.Device, requestedDeviceID uint) {
|
||||
if device == nil {
|
||||
failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
|
||||
DeviceID: deviceID,
|
||||
DeviceID: requestedDeviceID,
|
||||
VirtualNo: "",
|
||||
Reason: "设备不存在",
|
||||
})
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// 验证操作者权限(仅代理用户)- 检查是否有该系列的套餐分配
|
||||
if operatorShopID != nil && req.SeriesID > 0 {
|
||||
seriesAllocations, err := s.shopSeriesAllocationStore.GetByShopID(ctx, *operatorShopID)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSeriesBinding,
|
||||
"设备系列绑定失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"device_ids": req.DeviceIDs,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
0,
|
||||
len(req.DeviceIDs),
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
hasSeriesAllocation := false
|
||||
for _, alloc := range seriesAllocations {
|
||||
if alloc.SeriesID == req.SeriesID && alloc.Status == 1 {
|
||||
hasSeriesAllocation = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasSeriesAllocation {
|
||||
failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
|
||||
DeviceID: deviceID,
|
||||
VirtualNo: device.VirtualNo,
|
||||
Reason: "您没有权限分配该套餐系列",
|
||||
})
|
||||
continue
|
||||
}
|
||||
if !hasSeriesAllocation {
|
||||
failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
|
||||
DeviceID: device.ID,
|
||||
VirtualNo: device.VirtualNo,
|
||||
Reason: "您没有权限分配该套餐系列",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证设备权限(基于 device.ShopID)
|
||||
if operatorShopID != nil {
|
||||
if device.ShopID == nil || *device.ShopID != *operatorShopID {
|
||||
failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
|
||||
DeviceID: device.ID,
|
||||
VirtualNo: device.VirtualNo,
|
||||
Reason: "无权操作此设备",
|
||||
})
|
||||
continue
|
||||
}
|
||||
// 代理只能操作自己店铺名下的设备,保持旧接口权限语义不变。
|
||||
if operatorShopID != nil && (device.ShopID == nil || *device.ShopID != *operatorShopID) {
|
||||
failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
|
||||
DeviceID: device.ID,
|
||||
VirtualNo: device.VirtualNo,
|
||||
Reason: "无权操作此设备",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, exists := successDeviceIDSet[device.ID]; exists {
|
||||
return
|
||||
}
|
||||
successDeviceIDSet[device.ID] = struct{}{}
|
||||
successDeviceIDs = append(successDeviceIDs, device.ID)
|
||||
}
|
||||
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
for _, deviceID := range req.DeviceIDs {
|
||||
addDevice(deviceMap[deviceID], deviceID)
|
||||
}
|
||||
} else {
|
||||
for _, device := range devices {
|
||||
addDevice(device, device.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(successDeviceIDs) > 0 {
|
||||
var seriesIDPtr *uint
|
||||
if req.SeriesID > 0 {
|
||||
@@ -1221,10 +1218,11 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
nil,
|
||||
map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"selection_type": selectionType,
|
||||
"success_device_ids": successDeviceIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
len(successDeviceIDs),
|
||||
len(failedItems),
|
||||
err,
|
||||
@@ -1253,11 +1251,12 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
"devices": beforeData,
|
||||
},
|
||||
map[string]any{
|
||||
"selection_type": selectionType,
|
||||
"series_id": req.SeriesID,
|
||||
"success_device_ids": successDeviceIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
len(req.DeviceIDs),
|
||||
batchTotal,
|
||||
len(successDeviceIDs),
|
||||
len(failedItems),
|
||||
resultErr,
|
||||
@@ -1270,6 +1269,161 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeDeviceSeriesBindingSelection(req *dto.BatchSetDeviceSeriesBindngRequest) (string, error) {
|
||||
switch req.SelectionType {
|
||||
case dto.SelectionTypeList:
|
||||
if len(req.DeviceIDs) == 0 {
|
||||
return "", errors.New(errors.CodeInvalidParam, "selection_type=list时device_ids不能为空")
|
||||
}
|
||||
return dto.SelectionTypeList, nil
|
||||
case dto.SelectionTypeRange:
|
||||
if req.VirtualNoStart == "" || req.VirtualNoEnd == "" {
|
||||
return "", errors.New(errors.CodeInvalidParam, "selection_type=range时virtual_no_start和virtual_no_end不能为空")
|
||||
}
|
||||
if req.VirtualNoStart > req.VirtualNoEnd {
|
||||
return "", errors.New(errors.CodeInvalidParam, "设备虚拟号起始值不能大于结束值")
|
||||
}
|
||||
return dto.SelectionTypeRange, nil
|
||||
case dto.SelectionTypeFilter:
|
||||
return dto.SelectionTypeFilter, nil
|
||||
case "":
|
||||
if len(req.DeviceIDs) > 0 {
|
||||
return dto.SelectionTypeList, nil
|
||||
}
|
||||
if req.VirtualNoStart != "" || req.VirtualNoEnd != "" {
|
||||
if req.VirtualNoStart == "" || req.VirtualNoEnd == "" {
|
||||
return "", errors.New(errors.CodeInvalidParam, "virtual_no_start和virtual_no_end必须同时传入")
|
||||
}
|
||||
if req.VirtualNoStart > req.VirtualNoEnd {
|
||||
return "", errors.New(errors.CodeInvalidParam, "设备虚拟号起始值不能大于结束值")
|
||||
}
|
||||
return dto.SelectionTypeRange, nil
|
||||
}
|
||||
if hasDeviceSeriesBindingFilters(req) {
|
||||
return dto.SelectionTypeFilter, nil
|
||||
}
|
||||
return "", errors.New(errors.CodeInvalidParam, "请选择要设置套餐系列的设备")
|
||||
default:
|
||||
return "", errors.New(errors.CodeInvalidParam, "无效的选设备方式")
|
||||
}
|
||||
}
|
||||
|
||||
func hasDeviceSeriesBindingFilters(req *dto.BatchSetDeviceSeriesBindngRequest) bool {
|
||||
return req.VirtualNo != "" ||
|
||||
req.DeviceName != "" ||
|
||||
req.Status != nil ||
|
||||
req.ShopID != nil ||
|
||||
len(req.ShopIDs) > 0 ||
|
||||
req.FilterSeriesID != nil ||
|
||||
req.BatchNo != "" ||
|
||||
req.DeviceType != "" ||
|
||||
req.Manufacturer != "" ||
|
||||
req.CreatedAtStart != nil ||
|
||||
req.CreatedAtEnd != nil
|
||||
}
|
||||
|
||||
func (s *Service) getDevicesForSeriesBinding(ctx context.Context, req *dto.BatchSetDeviceSeriesBindngRequest, selectionType string) ([]*model.Device, error) {
|
||||
switch selectionType {
|
||||
case dto.SelectionTypeList:
|
||||
return s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
|
||||
case dto.SelectionTypeRange:
|
||||
return s.deviceStore.GetByFilters(ctx, map[string]any{
|
||||
"virtual_no_start": req.VirtualNoStart,
|
||||
"virtual_no_end": req.VirtualNoEnd,
|
||||
})
|
||||
case dto.SelectionTypeFilter:
|
||||
return s.deviceStore.GetByFilters(ctx, buildDeviceSeriesBindingFilters(req))
|
||||
default:
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的选设备方式")
|
||||
}
|
||||
}
|
||||
|
||||
func buildDeviceSeriesBindingFilters(req *dto.BatchSetDeviceSeriesBindngRequest) map[string]any {
|
||||
filters := make(map[string]any)
|
||||
if req.VirtualNo != "" {
|
||||
filters["virtual_no"] = req.VirtualNo
|
||||
}
|
||||
if req.VirtualNoStart != "" {
|
||||
filters["virtual_no_start"] = req.VirtualNoStart
|
||||
}
|
||||
if req.VirtualNoEnd != "" {
|
||||
filters["virtual_no_end"] = req.VirtualNoEnd
|
||||
}
|
||||
if req.DeviceName != "" {
|
||||
filters["device_name"] = req.DeviceName
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
shopIDs, hasShopIDs := normalizeShopIDs(req.ShopIDs)
|
||||
if hasShopIDs {
|
||||
filters["shop_ids"] = shopIDs
|
||||
} else if req.ShopID != nil {
|
||||
if *req.ShopID == 0 {
|
||||
filters["shop_ids"] = []uint{}
|
||||
} else {
|
||||
filters["shop_id"] = req.ShopID
|
||||
}
|
||||
}
|
||||
if req.FilterSeriesID != nil {
|
||||
filters["series_id"] = *req.FilterSeriesID
|
||||
}
|
||||
if req.BatchNo != "" {
|
||||
filters["batch_no"] = req.BatchNo
|
||||
}
|
||||
if req.DeviceType != "" {
|
||||
filters["device_type"] = req.DeviceType
|
||||
}
|
||||
if req.Manufacturer != "" {
|
||||
filters["manufacturer"] = req.Manufacturer
|
||||
}
|
||||
if req.CreatedAtStart != nil {
|
||||
filters["created_at_start"] = *req.CreatedAtStart
|
||||
}
|
||||
if req.CreatedAtEnd != nil {
|
||||
filters["created_at_end"] = *req.CreatedAtEnd
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
func (s *Service) hasAvailableSeriesAllocation(ctx context.Context, shopID uint, seriesID uint) (bool, error) {
|
||||
seriesAllocations, err := s.shopSeriesAllocationStore.GetByShopID(ctx, shopID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, alloc := range seriesAllocations {
|
||||
if alloc.SeriesID == seriesID && alloc.Status == 1 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func deviceSeriesBindingBatchTotal(req *dto.BatchSetDeviceSeriesBindngRequest, selectionType string, devices []*model.Device) int {
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
return len(req.DeviceIDs)
|
||||
}
|
||||
return len(devices)
|
||||
}
|
||||
|
||||
func deviceSeriesBindingAuditData(req *dto.BatchSetDeviceSeriesBindngRequest, selectionType string, operatorShopID *uint) map[string]any {
|
||||
data := map[string]any{
|
||||
"selection_type": selectionType,
|
||||
"series_id": req.SeriesID,
|
||||
"operator_shop_id": operatorShopID,
|
||||
}
|
||||
switch selectionType {
|
||||
case dto.SelectionTypeList:
|
||||
data["device_ids"] = req.DeviceIDs
|
||||
case dto.SelectionTypeRange:
|
||||
data["virtual_no_start"] = req.VirtualNoStart
|
||||
data["virtual_no_end"] = req.VirtualNoEnd
|
||||
case dto.SelectionTypeFilter:
|
||||
data["filters"] = buildDeviceSeriesBindingFilters(req)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *Service) buildDeviceNotFoundFailedItems(deviceIDs []uint) []dto.DeviceSeriesBindngFailedItem {
|
||||
items := make([]dto.DeviceSeriesBindngFailedItem, len(deviceIDs))
|
||||
for i, id := range deviceIDs {
|
||||
|
||||
Reference in New Issue
Block a user