This commit is contained in:
@@ -145,8 +145,23 @@ type RecallDevicesResponse struct {
|
||||
|
||||
// BatchSetDeviceSeriesBindngRequest 批量设置设备的套餐系列绑定请求
|
||||
type BatchSetDeviceSeriesBindngRequest struct {
|
||||
DeviceIDs []uint `json:"device_ids" validate:"required,min=1,max=500,dive,required" required:"true" minItems:"1" maxItems:"500" description:"设备ID列表"`
|
||||
SeriesID uint `json:"series_id" validate:"required,min=0" required:"true" minimum:"0" description:"套餐系列ID(0表示清除关联)"`
|
||||
SelectionType string `json:"selection_type" validate:"omitempty,oneof=list range filter" enum:"list,range,filter" description:"选设备方式 (list:设备ID列表, range:设备虚拟号范围, filter:筛选条件;不传时兼容旧版设备ID列表)"`
|
||||
DeviceIDs []uint `json:"device_ids" validate:"omitempty,max=1000,dive,required" maxItems:"1000" description:"设备ID列表(selection_type=list时使用,最多1000个)"`
|
||||
VirtualNoStart string `json:"virtual_no_start" validate:"omitempty,max=100" maxLength:"100" description:"起始设备虚拟号(selection_type=range时使用)"`
|
||||
VirtualNoEnd string `json:"virtual_no_end" validate:"omitempty,max=100" maxLength:"100" description:"结束设备虚拟号(selection_type=range时使用)"`
|
||||
VirtualNo string `json:"virtual_no" validate:"omitempty,max=100" maxLength:"100" description:"设备虚拟号模糊查询(selection_type=filter时可选)"`
|
||||
DeviceName string `json:"device_name" validate:"omitempty,max=255" maxLength:"255" description:"设备名称模糊查询(selection_type=filter时可选)"`
|
||||
Status *int `json:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)(selection_type=filter时可选)"`
|
||||
ShopID *uint `json:"shop_id" description:"店铺ID(selection_type=filter时可选,兼容旧参数,单选)"`
|
||||
ShopIDs []uint `json:"shop_ids" validate:"omitempty,dive,min=1" description:"店铺ID列表(selection_type=filter时可选,多选)"`
|
||||
FilterSeriesID *uint `json:"filter_series_id" description:"当前绑定套餐系列ID(selection_type=filter时可选;目标套餐系列仍使用series_id)"`
|
||||
BatchNo string `json:"batch_no" validate:"omitempty,max=100" maxLength:"100" description:"批次号(selection_type=filter时可选)"`
|
||||
DeviceType string `json:"device_type" validate:"omitempty,max=50" maxLength:"50" description:"设备类型(selection_type=filter时可选)"`
|
||||
Manufacturer string `json:"manufacturer" validate:"omitempty,max=255" maxLength:"255" description:"制造商模糊查询(selection_type=filter时可选)"`
|
||||
CreatedAtStart *time.Time `json:"created_at_start" description:"创建时间起始(selection_type=filter时可选)"`
|
||||
CreatedAtEnd *time.Time `json:"created_at_end" description:"创建时间结束(selection_type=filter时可选)"`
|
||||
|
||||
SeriesID uint `json:"series_id" validate:"required,min=0" required:"true" minimum:"0" description:"目标套餐系列ID(0表示清除关联)"`
|
||||
}
|
||||
|
||||
// DeviceSeriesBindngFailedItem 设备系列绑定失败项
|
||||
|
||||
@@ -148,8 +148,26 @@ type IotCardDetailResponse struct {
|
||||
|
||||
// BatchSetCardSeriesBindngRequest 批量设置卡的套餐系列绑定请求
|
||||
type BatchSetCardSeriesBindngRequest struct {
|
||||
ICCIDs []string `json:"iccids" validate:"required,min=1,max=500,dive,required" required:"true" minItems:"1" maxItems:"500" description:"ICCID列表"`
|
||||
SeriesID uint `json:"series_id" validate:"required,min=0" required:"true" minimum:"0" description:"套餐系列ID(0表示清除关联)"`
|
||||
SelectionType string `json:"selection_type" validate:"omitempty,oneof=list range filter" enum:"list,range,filter" description:"选卡方式 (list:ICCID列表, range:号段范围, filter:筛选条件;不传时兼容旧版ICCID列表)"`
|
||||
ICCIDs []string `json:"iccids" validate:"omitempty,max=1000,dive,required,max=20" maxItems:"1000" description:"ICCID列表(selection_type=list时使用,最多1000个)"`
|
||||
ICCIDStart string `json:"iccid_start" validate:"omitempty,max=20" maxLength:"20" description:"起始ICCID(selection_type=range时使用)"`
|
||||
ICCIDEnd string `json:"iccid_end" validate:"omitempty,max=20" maxLength:"20" description:"结束ICCID(selection_type=range时使用)"`
|
||||
|
||||
Status *int `json:"status" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)(selection_type=filter时可选)"`
|
||||
CarrierID *uint `json:"carrier_id" description:"运营商ID(selection_type=filter时可选)"`
|
||||
ShopID *uint `json:"shop_id" description:"分销商ID(selection_type=filter时可选,兼容旧参数,单选)"`
|
||||
ShopIDs []uint `json:"shop_ids" validate:"omitempty,dive,min=1" description:"分销商ID列表(selection_type=filter时可选,多选)"`
|
||||
FilterSeriesID *uint `json:"filter_series_id" description:"当前绑定套餐系列ID(selection_type=filter时可选;目标套餐系列仍使用series_id)"`
|
||||
ICCID string `json:"iccid" validate:"omitempty,max=20" maxLength:"20" description:"ICCID模糊查询(selection_type=filter时可选)"`
|
||||
MSISDN string `json:"msisdn" validate:"omitempty,max=20" maxLength:"20" description:"卡接入号模糊查询(selection_type=filter时可选)"`
|
||||
IsStandalone *bool `json:"is_standalone" description:"是否为独立卡(selection_type=filter时可选)"`
|
||||
BatchNo string `json:"batch_no" validate:"omitempty,max=100" maxLength:"100" description:"批次号(selection_type=filter时可选)"`
|
||||
PackageID *uint `json:"package_id" description:"套餐ID(selection_type=filter时可选)"`
|
||||
IsDistributed *bool `json:"is_distributed" description:"是否已分销(selection_type=filter时可选)"`
|
||||
IsReplaced *bool `json:"is_replaced" description:"是否有换卡记录(selection_type=filter时可选)"`
|
||||
CarrierName string `json:"carrier_name" validate:"omitempty,max=100" maxLength:"100" description:"运营商名称模糊查询(selection_type=filter时可选)"`
|
||||
|
||||
SeriesID uint `json:"series_id" validate:"required,min=0" required:"true" minimum:"0" description:"目标套餐系列ID(0表示清除关联)"`
|
||||
}
|
||||
|
||||
// CardSeriesBindngFailedItem 卡系列绑定失败项
|
||||
|
||||
@@ -126,7 +126,7 @@ func registerDeviceRoutes(router fiber.Router, handler *admin.DeviceHandler, imp
|
||||
|
||||
Register(devices, doc, groupPath, "PATCH", "/series-binding", handler.BatchSetSeriesBinding, RouteSpec{
|
||||
Summary: "批量设置设备的套餐系列绑定",
|
||||
Description: "批量设置或清除设备与套餐系列分配的关联关系。参数:series_id(套餐系列ID,0表示清除关联)。",
|
||||
Description: "批量设置或清除设备与套餐系列分配的关联关系。series_id 为目标套餐系列ID,0表示清除关联。支持 selection_type=list/range/filter:list 传 device_ids(兼容旧请求,不传 selection_type 时默认按 device_ids 处理),range 传 virtual_no_start/virtual_no_end,filter 按筛选条件选择完整结果,不受列表分页限制。",
|
||||
Tags: []string{"设备管理"},
|
||||
Input: new(dto.BatchSetDeviceSeriesBindngRequest),
|
||||
Output: new(dto.BatchSetDeviceSeriesBindngResponse),
|
||||
|
||||
@@ -103,7 +103,7 @@ func registerIotCardRoutes(router fiber.Router, handler *admin.IotCardHandler, i
|
||||
|
||||
Register(iotCards, doc, groupPath, "PATCH", "/series-binding", handler.BatchSetSeriesBinding, RouteSpec{
|
||||
Summary: "批量设置卡的套餐系列绑定",
|
||||
Description: "批量设置或清除卡与套餐系列分配的关联关系。参数:series_id(套餐系列ID,0表示清除关联)。",
|
||||
Description: "批量设置或清除卡与套餐系列分配的关联关系。series_id 为目标套餐系列ID,0表示清除关联。支持 selection_type=list/range/filter:list 传 iccids(兼容旧请求,不传 selection_type 时默认按 iccids 处理),range 传 iccid_start/iccid_end,filter 按筛选条件选择完整结果,不受列表分页限制。",
|
||||
Tags: []string{"IoT卡管理"},
|
||||
Input: new(dto.BatchSetCardSeriesBindngRequest),
|
||||
Output: new(dto.BatchSetCardSeriesBindngResponse),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -982,7 +982,14 @@ func (s *Service) buildRecallRecords(successCards []*model.IotCard, toShopID *ui
|
||||
|
||||
// BatchSetSeriesBinding 批量设置卡的套餐系列绑定
|
||||
func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCardSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetCardSeriesBindngResponse, error) {
|
||||
cards, err := s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
|
||||
selectionType, err := normalizeCardSeriesBindingSelection(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := s.getCardsForSeriesBinding(ctx, req, selectionType)
|
||||
batchTotal := cardSeriesBindingBatchTotal(req, selectionType, cards)
|
||||
auditData := cardSeriesBindingAuditData(req, selectionType)
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
@@ -991,44 +998,38 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
BatchTotal: batchTotal,
|
||||
AfterData: auditData,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cards) == 0 {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorMsg: "卡不存在",
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
FailCount: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
failedItems := []dto.CardSeriesBindngFailedItem{}
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
failedItems = s.buildCardNotFoundFailedItems(req.ICCIDs)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorMsg: "卡不存在",
|
||||
BatchTotal: batchTotal,
|
||||
FailCount: len(failedItems),
|
||||
AfterData: auditData,
|
||||
})
|
||||
}
|
||||
return &dto.BatchSetCardSeriesBindngResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: len(req.ICCIDs),
|
||||
FailedItems: s.buildCardNotFoundFailedItems(req.ICCIDs),
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
cardMap := make(map[string]*model.IotCard)
|
||||
for _, card := range cards {
|
||||
cardMap[card.ICCID] = card
|
||||
}
|
||||
cardMap := indexCardsByICCID(cards)
|
||||
|
||||
// 验证系列存在(仅当 SeriesID > 0 时)
|
||||
var packageSeries *model.PackageSeries
|
||||
if req.SeriesID > 0 {
|
||||
packageSeries, err = s.packageSeriesStore.GetByID(ctx, req.SeriesID)
|
||||
packageSeries, err := s.packageSeriesStore.GetByID(ctx, req.SeriesID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
denyErr := errors.New(errors.CodeNotFound, "套餐系列不存在或已禁用")
|
||||
@@ -1039,11 +1040,8 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
BatchTotal: batchTotal,
|
||||
AfterData: auditData,
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
@@ -1054,11 +1052,8 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
BatchTotal: batchTotal,
|
||||
AfterData: auditData,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
@@ -1071,11 +1066,8 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
BatchTotal: batchTotal,
|
||||
AfterData: auditData,
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
@@ -1083,53 +1075,59 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
|
||||
var successCardIDs []uint
|
||||
var failedItems []dto.CardSeriesBindngFailedItem
|
||||
successCardIDSet := make(map[uint]struct{})
|
||||
|
||||
for _, iccid := range req.ICCIDs {
|
||||
card, exists := cardMap[iccid]
|
||||
if !exists {
|
||||
hasSeriesAllocation := true
|
||||
if operatorShopID != nil && req.SeriesID > 0 {
|
||||
hasSeriesAllocation, err = s.hasAvailableSeriesAllocation(ctx, *operatorShopID, req.SeriesID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
addCard := func(card *model.IotCard, requestedICCID string) {
|
||||
if card == nil {
|
||||
failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
|
||||
ICCID: iccid,
|
||||
ICCID: requestedICCID,
|
||||
Reason: "卡不存在",
|
||||
})
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// 验证操作者权限(仅代理用户)
|
||||
if operatorShopID != nil && req.SeriesID > 0 {
|
||||
seriesAllocations, err := s.shopSeriesAllocationStore.GetByShopID(ctx, *operatorShopID)
|
||||
if err != nil {
|
||||
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.CardSeriesBindngFailedItem{
|
||||
ICCID: iccid,
|
||||
Reason: "您没有权限分配该套餐系列",
|
||||
})
|
||||
continue
|
||||
}
|
||||
if !hasSeriesAllocation {
|
||||
failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
|
||||
ICCID: card.ICCID,
|
||||
Reason: "您没有权限分配该套餐系列",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证卡权限(基于 card.ShopID)
|
||||
if operatorShopID != nil {
|
||||
if card.ShopID == nil || *card.ShopID != *operatorShopID {
|
||||
failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
|
||||
ICCID: iccid,
|
||||
Reason: "无权操作此卡",
|
||||
})
|
||||
continue
|
||||
}
|
||||
// 代理只能操作自己店铺名下的卡,保持旧接口权限语义不变。
|
||||
if operatorShopID != nil && (card.ShopID == nil || *card.ShopID != *operatorShopID) {
|
||||
failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
|
||||
ICCID: card.ICCID,
|
||||
Reason: "无权操作此卡",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, exists := successCardIDSet[card.ID]; exists {
|
||||
return
|
||||
}
|
||||
successCardIDSet[card.ID] = struct{}{}
|
||||
successCardIDs = append(successCardIDs, card.ID)
|
||||
}
|
||||
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
for _, iccid := range req.ICCIDs {
|
||||
addCard(cardMap[iccid], iccid)
|
||||
}
|
||||
} else {
|
||||
for _, card := range cards {
|
||||
addCard(card, card.ICCID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(successCardIDs) > 0 {
|
||||
var seriesIDPtr *uint
|
||||
if req.SeriesID > 0 {
|
||||
@@ -1143,13 +1141,14 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
BatchTotal: batchTotal,
|
||||
SuccessCount: len(successCardIDs),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
"selection_type": selectionType,
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
@@ -1184,16 +1183,17 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
OperationDesc: operationDesc,
|
||||
ResultStatus: resultStatus,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
BatchTotal: batchTotal,
|
||||
SuccessCount: len(successCardIDs),
|
||||
FailCount: len(failedItems),
|
||||
BeforeData: map[string]any{
|
||||
"cards": beforeCards,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
"selection_type": selectionType,
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1204,6 +1204,185 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeCardSeriesBindingSelection(req *dto.BatchSetCardSeriesBindngRequest) (string, error) {
|
||||
switch req.SelectionType {
|
||||
case dto.SelectionTypeList:
|
||||
if len(req.ICCIDs) == 0 {
|
||||
return "", errors.New(errors.CodeInvalidParam, "selection_type=list时iccids不能为空")
|
||||
}
|
||||
return dto.SelectionTypeList, nil
|
||||
case dto.SelectionTypeRange:
|
||||
if req.ICCIDStart == "" || req.ICCIDEnd == "" {
|
||||
return "", errors.New(errors.CodeInvalidParam, "selection_type=range时iccid_start和iccid_end不能为空")
|
||||
}
|
||||
if req.ICCIDStart > req.ICCIDEnd {
|
||||
return "", errors.New(errors.CodeInvalidParam, "ICCID起始号不能大于结束号")
|
||||
}
|
||||
return dto.SelectionTypeRange, nil
|
||||
case dto.SelectionTypeFilter:
|
||||
return dto.SelectionTypeFilter, nil
|
||||
case "":
|
||||
if len(req.ICCIDs) > 0 {
|
||||
return dto.SelectionTypeList, nil
|
||||
}
|
||||
if req.ICCIDStart != "" || req.ICCIDEnd != "" {
|
||||
if req.ICCIDStart == "" || req.ICCIDEnd == "" {
|
||||
return "", errors.New(errors.CodeInvalidParam, "iccid_start和iccid_end必须同时传入")
|
||||
}
|
||||
if req.ICCIDStart > req.ICCIDEnd {
|
||||
return "", errors.New(errors.CodeInvalidParam, "ICCID起始号不能大于结束号")
|
||||
}
|
||||
return dto.SelectionTypeRange, nil
|
||||
}
|
||||
if hasCardSeriesBindingFilters(req) {
|
||||
return dto.SelectionTypeFilter, nil
|
||||
}
|
||||
return "", errors.New(errors.CodeInvalidParam, "请选择要设置套餐系列的卡")
|
||||
default:
|
||||
return "", errors.New(errors.CodeInvalidParam, "无效的选卡方式")
|
||||
}
|
||||
}
|
||||
|
||||
func hasCardSeriesBindingFilters(req *dto.BatchSetCardSeriesBindngRequest) bool {
|
||||
return req.Status != nil ||
|
||||
req.CarrierID != nil ||
|
||||
req.ShopID != nil ||
|
||||
len(req.ShopIDs) > 0 ||
|
||||
req.FilterSeriesID != nil ||
|
||||
req.ICCID != "" ||
|
||||
req.MSISDN != "" ||
|
||||
req.IsStandalone != nil ||
|
||||
req.BatchNo != "" ||
|
||||
req.PackageID != nil ||
|
||||
req.IsDistributed != nil ||
|
||||
req.IsReplaced != nil ||
|
||||
req.CarrierName != ""
|
||||
}
|
||||
|
||||
func (s *Service) getCardsForSeriesBinding(ctx context.Context, req *dto.BatchSetCardSeriesBindngRequest, selectionType string) ([]*model.IotCard, error) {
|
||||
switch selectionType {
|
||||
case dto.SelectionTypeList:
|
||||
return s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
|
||||
case dto.SelectionTypeRange:
|
||||
filters := map[string]any{
|
||||
"iccid_start": req.ICCIDStart,
|
||||
"iccid_end": req.ICCIDEnd,
|
||||
}
|
||||
return s.iotCardStore.GetBySeriesBindingFilters(ctx, filters)
|
||||
case dto.SelectionTypeFilter:
|
||||
return s.iotCardStore.GetBySeriesBindingFilters(ctx, buildCardSeriesBindingFilters(req))
|
||||
default:
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的选卡方式")
|
||||
}
|
||||
}
|
||||
|
||||
func buildCardSeriesBindingFilters(req *dto.BatchSetCardSeriesBindngRequest) map[string]any {
|
||||
filters := make(map[string]any)
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
if req.CarrierID != nil {
|
||||
filters["carrier_id"] = *req.CarrierID
|
||||
}
|
||||
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.ICCID != "" {
|
||||
filters["iccid"] = req.ICCID
|
||||
}
|
||||
if req.MSISDN != "" {
|
||||
filters["msisdn"] = req.MSISDN
|
||||
}
|
||||
if req.IsStandalone != nil {
|
||||
filters["is_standalone"] = req.IsStandalone
|
||||
}
|
||||
if req.BatchNo != "" {
|
||||
filters["batch_no"] = req.BatchNo
|
||||
}
|
||||
if req.PackageID != nil {
|
||||
filters["package_id"] = *req.PackageID
|
||||
}
|
||||
if req.IsDistributed != nil {
|
||||
filters["is_distributed"] = *req.IsDistributed
|
||||
}
|
||||
if req.ICCIDStart != "" {
|
||||
filters["iccid_start"] = req.ICCIDStart
|
||||
}
|
||||
if req.ICCIDEnd != "" {
|
||||
filters["iccid_end"] = req.ICCIDEnd
|
||||
}
|
||||
if req.IsReplaced != nil {
|
||||
filters["is_replaced"] = *req.IsReplaced
|
||||
}
|
||||
if req.CarrierName != "" {
|
||||
filters["carrier_name"] = req.CarrierName
|
||||
}
|
||||
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 indexCardsByICCID(cards []*model.IotCard) map[string]*model.IotCard {
|
||||
cardMap := make(map[string]*model.IotCard, len(cards))
|
||||
for _, card := range cards {
|
||||
if card.ICCID != "" {
|
||||
cardMap[card.ICCID] = card
|
||||
}
|
||||
if card.ICCID19 != "" {
|
||||
cardMap[card.ICCID19] = card
|
||||
}
|
||||
if card.ICCID20 != nil && *card.ICCID20 != "" {
|
||||
cardMap[*card.ICCID20] = card
|
||||
}
|
||||
}
|
||||
return cardMap
|
||||
}
|
||||
|
||||
func cardSeriesBindingBatchTotal(req *dto.BatchSetCardSeriesBindngRequest, selectionType string, cards []*model.IotCard) int {
|
||||
if selectionType == dto.SelectionTypeList {
|
||||
return len(req.ICCIDs)
|
||||
}
|
||||
return len(cards)
|
||||
}
|
||||
|
||||
func cardSeriesBindingAuditData(req *dto.BatchSetCardSeriesBindngRequest, selectionType string) map[string]any {
|
||||
data := map[string]any{
|
||||
"selection_type": selectionType,
|
||||
"series_id": req.SeriesID,
|
||||
}
|
||||
switch selectionType {
|
||||
case dto.SelectionTypeList:
|
||||
data["iccids"] = req.ICCIDs
|
||||
case dto.SelectionTypeRange:
|
||||
data["iccid_start"] = req.ICCIDStart
|
||||
data["iccid_end"] = req.ICCIDEnd
|
||||
case dto.SelectionTypeFilter:
|
||||
data["filters"] = buildCardSeriesBindingFilters(req)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *Service) buildCardNotFoundFailedItems(iccids []string) []dto.CardSeriesBindngFailedItem {
|
||||
items := make([]dto.CardSeriesBindngFailedItem, len(iccids))
|
||||
for i, iccid := range iccids {
|
||||
|
||||
@@ -99,10 +99,44 @@ func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filter
|
||||
query := s.db.WithContext(ctx).Model(&model.Device{})
|
||||
// 应用数据权限过滤(NULL shop_id 对代理用户不可见)
|
||||
query = middleware.ApplyShopFilter(ctx, query)
|
||||
query = s.applyDeviceFilters(query, filters)
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = &store.QueryOptions{
|
||||
Page: 1,
|
||||
PageSize: constants.DefaultPageSize,
|
||||
}
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return devices, total, nil
|
||||
}
|
||||
|
||||
func (s *DeviceStore) applyDeviceFilters(query *gorm.DB, filters map[string]any) *gorm.DB {
|
||||
if virtualNo, ok := filters["virtual_no"].(string); ok && virtualNo != "" {
|
||||
query = query.Where("virtual_no LIKE ?", "%"+virtualNo+"%")
|
||||
}
|
||||
if virtualNoStart, ok := filters["virtual_no_start"].(string); ok && virtualNoStart != "" {
|
||||
query = query.Where("virtual_no >= ?", virtualNoStart)
|
||||
}
|
||||
if virtualNoEnd, ok := filters["virtual_no_end"].(string); ok && virtualNoEnd != "" {
|
||||
query = query.Where("virtual_no <= ?", virtualNoEnd)
|
||||
}
|
||||
if deviceName, ok := filters["device_name"].(string); ok && deviceName != "" {
|
||||
query = query.Where("device_name LIKE ?", "%"+deviceName+"%")
|
||||
}
|
||||
@@ -140,31 +174,20 @@ func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filter
|
||||
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
|
||||
query = query.Where("series_id = ?", seriesID)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = &store.QueryOptions{
|
||||
Page: 1,
|
||||
PageSize: constants.DefaultPageSize,
|
||||
}
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// GetByFilters 根据筛选条件查询设备
|
||||
// 用于批量设置套餐系列时选择完整筛选结果,不受列表分页限制。
|
||||
func (s *DeviceStore) GetByFilters(ctx context.Context, filters map[string]any) ([]*model.Device, error) {
|
||||
var devices []*model.Device
|
||||
query := s.db.WithContext(ctx).Model(&model.Device{})
|
||||
query = middleware.ApplyShopFilter(ctx, query)
|
||||
query = s.applyDeviceFilters(query, filters)
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return devices, total, nil
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (s *DeviceStore) UpdateShopID(ctx context.Context, id uint, shopID *uint) error {
|
||||
|
||||
@@ -1036,6 +1036,20 @@ func (s *IotCardStore) GetDistributedStandaloneByFilters(ctx context.Context, fi
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
// GetBySeriesBindingFilters 根据系列绑定筛选条件查询卡
|
||||
// 用于批量设置套餐系列时选择完整筛选结果,不受列表分页限制。
|
||||
func (s *IotCardStore) GetBySeriesBindingFilters(ctx context.Context, filters map[string]any) ([]*model.IotCard, error) {
|
||||
query := s.db.WithContext(ctx).Model(&model.IotCard{})
|
||||
query = middleware.ApplyShopFilter(ctx, query)
|
||||
query = s.applyStandaloneFilters(ctx, query, filters)
|
||||
|
||||
var cards []*model.IotCard
|
||||
if err := query.Find(&cards).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (s *IotCardStore) BatchUpdateShopIDAndStatus(ctx context.Context, cardIDs []uint, shopID *uint, status int) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user