feat: 实现设备管理和设备导入功能,修复测试问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m30s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m30s
主要变更: - 实现设备管理模块(创建、查询、列表、更新状态、删除) - 实现设备批量导入功能(CSV 解析、ICCID 绑定、异步任务处理) - 添加设备-SIM 卡绑定约束(部分唯一索引防止并发问题) - 修复 fee_rate 数据库字段类型(numeric -> bigint) - 修复测试数据隔离问题(基于增量断言) - 修复集成测试中间件顺序问题 - 清理无用测试文件(PersonalCustomer、Email 相关) - 归档 enterprise-card-authorization 变更
This commit is contained in:
172
internal/service/device/binding.go
Normal file
172
internal/service/device/binding.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *Service) ListBindings(ctx context.Context, deviceID uint) (*dto.ListDeviceCardsResponse, error) {
|
||||
device, err := s.deviceStore.GetByID(ctx, deviceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bindings, err := s.deviceSimBindingStore.ListByDeviceID(ctx, device.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cardIDs := make([]uint, 0, len(bindings))
|
||||
for _, binding := range bindings {
|
||||
cardIDs = append(cardIDs, binding.IotCardID)
|
||||
}
|
||||
|
||||
var cards []*model.IotCard
|
||||
if len(cardIDs) > 0 {
|
||||
cards, err = s.iotCardStore.GetByIDs(ctx, cardIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cardMap := make(map[uint]*model.IotCard)
|
||||
for _, card := range cards {
|
||||
cardMap[card.ID] = card
|
||||
}
|
||||
|
||||
carrierMap := s.loadCarrierData(ctx, cards)
|
||||
|
||||
responses := make([]*dto.DeviceCardBindingResponse, 0, len(bindings))
|
||||
for _, binding := range bindings {
|
||||
card := cardMap[binding.IotCardID]
|
||||
if card == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resp := &dto.DeviceCardBindingResponse{
|
||||
ID: binding.ID,
|
||||
SlotPosition: binding.SlotPosition,
|
||||
IotCardID: binding.IotCardID,
|
||||
ICCID: card.ICCID,
|
||||
MSISDN: card.MSISDN,
|
||||
CarrierName: carrierMap[card.CarrierID],
|
||||
Status: card.Status,
|
||||
BindTime: binding.BindTime,
|
||||
}
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
|
||||
return &dto.ListDeviceCardsResponse{
|
||||
Bindings: responses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCardToDeviceRequest) (*dto.BindCardToDeviceResponse, error) {
|
||||
device, err := s.deviceStore.GetByID(ctx, deviceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.SlotPosition > device.MaxSimSlots {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "插槽位置超出设备最大插槽数")
|
||||
}
|
||||
|
||||
existingBinding, err := s.deviceSimBindingStore.GetByDeviceAndSlot(ctx, device.ID, req.SlotPosition)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
if existingBinding != nil {
|
||||
return nil, errors.New(errors.CodeConflict, "该插槽已有绑定的卡")
|
||||
}
|
||||
|
||||
card, err := s.iotCardStore.GetByID(ctx, req.IotCardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeIotCardNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
activeBinding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
if activeBinding != nil {
|
||||
return nil, errors.New(errors.CodeIotCardBoundToDevice, "该卡已绑定到其他设备")
|
||||
}
|
||||
|
||||
binding := &model.DeviceSimBinding{
|
||||
DeviceID: device.ID,
|
||||
IotCardID: card.ID,
|
||||
SlotPosition: req.SlotPosition,
|
||||
BindStatus: 1,
|
||||
}
|
||||
|
||||
if err := s.deviceSimBindingStore.Create(ctx, binding); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.BindCardToDeviceResponse{
|
||||
BindingID: binding.ID,
|
||||
Message: "绑定成功",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*dto.UnbindCardFromDeviceResponse, error) {
|
||||
device, err := s.deviceStore.GetByID(ctx, deviceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
binding, err := s.deviceSimBindingStore.GetByDeviceAndCard(ctx, device.ID, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "该卡未绑定到此设备")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceSimBindingStore.Unbind(ctx, binding.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.UnbindCardFromDeviceResponse{
|
||||
Message: "解绑成功",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) loadCarrierData(ctx context.Context, cards []*model.IotCard) map[uint]string {
|
||||
carrierIDs := make([]uint, 0)
|
||||
carrierIDSet := make(map[uint]bool)
|
||||
|
||||
for _, card := range cards {
|
||||
if card.CarrierID > 0 && !carrierIDSet[card.CarrierID] {
|
||||
carrierIDs = append(carrierIDs, card.CarrierID)
|
||||
carrierIDSet[card.CarrierID] = true
|
||||
}
|
||||
}
|
||||
|
||||
carrierMap := make(map[uint]string)
|
||||
if len(carrierIDs) > 0 {
|
||||
var carriers []model.Carrier
|
||||
s.db.WithContext(ctx).Where("id IN ?", carrierIDs).Find(&carriers)
|
||||
for _, c := range carriers {
|
||||
carrierMap[c.ID] = c.CarrierName
|
||||
}
|
||||
}
|
||||
|
||||
return carrierMap
|
||||
}
|
||||
551
internal/service/device/service.go
Normal file
551
internal/service/device/service.go
Normal file
@@ -0,0 +1,551 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
deviceStore *postgres.DeviceStore
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
shopStore *postgres.ShopStore
|
||||
assetAllocationRecordStore *postgres.AssetAllocationRecordStore
|
||||
}
|
||||
|
||||
func New(
|
||||
db *gorm.DB,
|
||||
deviceStore *postgres.DeviceStore,
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
shopStore *postgres.ShopStore,
|
||||
assetAllocationRecordStore *postgres.AssetAllocationRecordStore,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
deviceStore: deviceStore,
|
||||
deviceSimBindingStore: deviceSimBindingStore,
|
||||
iotCardStore: iotCardStore,
|
||||
shopStore: shopStore,
|
||||
assetAllocationRecordStore: assetAllocationRecordStore,
|
||||
}
|
||||
}
|
||||
|
||||
// List 获取设备列表
|
||||
func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.ListDeviceResponse, error) {
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
opts := &store.QueryOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.DeviceNo != "" {
|
||||
filters["device_no"] = req.DeviceNo
|
||||
}
|
||||
if req.DeviceName != "" {
|
||||
filters["device_name"] = req.DeviceName
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
if req.ShopID != nil {
|
||||
filters["shop_id"] = req.ShopID
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
devices, total, err := s.deviceStore.List(ctx, opts, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
shopMap := s.loadShopData(ctx, devices)
|
||||
bindingCounts, err := s.getBindingCounts(ctx, s.extractDeviceIDs(devices))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]*dto.DeviceResponse, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
item := s.toDeviceResponse(device, shopMap, bindingCounts)
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListDeviceResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get 获取设备详情
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error) {
|
||||
device, err := s.deviceStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
shopMap := s.loadShopData(ctx, []*model.Device{device})
|
||||
bindingCounts, err := s.getBindingCounts(ctx, []uint{device.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toDeviceResponse(device, shopMap, bindingCounts), nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
device, err := s.deviceStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.deviceSimBindingStore.UnbindByDeviceID(ctx, device.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deviceStore.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// AllocateDevices 批量分配设备
|
||||
func (s *Service) AllocateDevices(ctx context.Context, req *dto.AllocateDevicesRequest, operatorID uint, operatorShopID *uint) (*dto.AllocateDevicesResponse, error) {
|
||||
// 验证目标店铺是否为直属下级
|
||||
if err := s.validateDirectSubordinate(ctx, operatorShopID, req.TargetShopID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
return &dto.AllocateDevicesResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: 0,
|
||||
FailedItems: []dto.AllocationDeviceFailedItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var deviceIDs []uint
|
||||
var failedItems []dto.AllocationDeviceFailedItem
|
||||
|
||||
isPlatform := operatorShopID == nil
|
||||
|
||||
for _, device := range devices {
|
||||
// 平台只能分配 shop_id=NULL 的设备
|
||||
if isPlatform && device.ShopID != nil {
|
||||
failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
|
||||
DeviceID: device.ID,
|
||||
DeviceNo: device.DeviceNo,
|
||||
Reason: "平台只能分配库存设备",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 代理只能分配自己店铺的设备
|
||||
if !isPlatform && (device.ShopID == nil || *device.ShopID != *operatorShopID) {
|
||||
failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
|
||||
DeviceID: device.ID,
|
||||
DeviceNo: device.DeviceNo,
|
||||
Reason: "设备不属于当前店铺",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
deviceIDs = append(deviceIDs, device.ID)
|
||||
}
|
||||
|
||||
if len(deviceIDs) == 0 {
|
||||
return &dto.AllocateDevicesResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
newStatus := constants.DeviceStatusDistributed
|
||||
targetShopID := req.TargetShopID
|
||||
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
||||
txCardStore := postgres.NewIotCardStore(tx, nil)
|
||||
txRecordStore := postgres.NewAssetAllocationRecordStore(tx, nil)
|
||||
|
||||
if err := txDeviceStore.BatchUpdateShopIDAndStatus(ctx, deviceIDs, &targetShopID, newStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
boundCardIDs, err := s.deviceSimBindingStore.GetBoundCardIDsByDeviceIDs(ctx, deviceIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(boundCardIDs) > 0 {
|
||||
if err := txCardStore.BatchUpdateShopIDAndStatus(ctx, boundCardIDs, &targetShopID, constants.IotCardStatusDistributed); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate)
|
||||
records := s.buildAllocationRecords(devices, deviceIDs, operatorShopID, targetShopID, operatorID, allocationNo, req.Remark)
|
||||
return txRecordStore.BatchCreate(ctx, records)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.AllocateDevicesResponse{
|
||||
SuccessCount: len(deviceIDs),
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RecallDevices 批量回收设备
|
||||
func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesRequest, operatorID uint, operatorShopID *uint) (*dto.RecallDevicesResponse, error) {
|
||||
devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
return &dto.RecallDevicesResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: 0,
|
||||
FailedItems: []dto.AllocationDeviceFailedItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var deviceIDs []uint
|
||||
var failedItems []dto.AllocationDeviceFailedItem
|
||||
|
||||
isPlatform := operatorShopID == nil
|
||||
|
||||
for _, device := range devices {
|
||||
// 验证设备所属店铺是否为直属下级
|
||||
if device.ShopID == nil {
|
||||
failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
|
||||
DeviceID: device.ID,
|
||||
DeviceNo: device.DeviceNo,
|
||||
Reason: "设备已在平台库存中",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 验证直属下级关系
|
||||
if err := s.validateDirectSubordinate(ctx, operatorShopID, *device.ShopID); err != nil {
|
||||
failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
|
||||
DeviceID: device.ID,
|
||||
DeviceNo: device.DeviceNo,
|
||||
Reason: "只能回收直属下级店铺的设备",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
deviceIDs = append(deviceIDs, device.ID)
|
||||
}
|
||||
|
||||
if len(deviceIDs) == 0 {
|
||||
return &dto.RecallDevicesResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var newShopID *uint
|
||||
var newStatus int
|
||||
if isPlatform {
|
||||
newShopID = nil
|
||||
newStatus = constants.DeviceStatusInStock
|
||||
} else {
|
||||
newShopID = operatorShopID
|
||||
newStatus = constants.DeviceStatusDistributed
|
||||
}
|
||||
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
||||
txCardStore := postgres.NewIotCardStore(tx, nil)
|
||||
txRecordStore := postgres.NewAssetAllocationRecordStore(tx, nil)
|
||||
|
||||
if err := txDeviceStore.BatchUpdateShopIDAndStatus(ctx, deviceIDs, newShopID, newStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
boundCardIDs, err := s.deviceSimBindingStore.GetBoundCardIDsByDeviceIDs(ctx, deviceIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(boundCardIDs) > 0 {
|
||||
var cardStatus int
|
||||
if isPlatform {
|
||||
cardStatus = constants.IotCardStatusInStock
|
||||
} else {
|
||||
cardStatus = constants.IotCardStatusDistributed
|
||||
}
|
||||
if err := txCardStore.BatchUpdateShopIDAndStatus(ctx, boundCardIDs, newShopID, cardStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall)
|
||||
records := s.buildRecallRecords(devices, deviceIDs, operatorShopID, newShopID, operatorID, allocationNo, req.Remark)
|
||||
return txRecordStore.BatchCreate(ctx, records)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.RecallDevicesResponse{
|
||||
SuccessCount: len(deviceIDs),
|
||||
FailCount: len(failedItems),
|
||||
FailedItems: failedItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
|
||||
func (s *Service) validateDirectSubordinate(ctx context.Context, operatorShopID *uint, targetShopID uint) error {
|
||||
if operatorShopID != nil && *operatorShopID == targetShopID {
|
||||
return errors.ErrCannotAllocateToSelf
|
||||
}
|
||||
|
||||
targetShop, err := s.shopStore.GetByID(ctx, targetShopID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeShopNotFound)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if operatorShopID == nil {
|
||||
if targetShop.ParentID != nil {
|
||||
return errors.ErrNotDirectSubordinate
|
||||
}
|
||||
} else {
|
||||
if targetShop.ParentID == nil || *targetShop.ParentID != *operatorShopID {
|
||||
return errors.ErrNotDirectSubordinate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) loadShopData(ctx context.Context, devices []*model.Device) map[uint]string {
|
||||
shopIDs := make([]uint, 0)
|
||||
shopIDSet := make(map[uint]bool)
|
||||
|
||||
for _, device := range devices {
|
||||
if device.ShopID != nil && *device.ShopID > 0 && !shopIDSet[*device.ShopID] {
|
||||
shopIDs = append(shopIDs, *device.ShopID)
|
||||
shopIDSet[*device.ShopID] = true
|
||||
}
|
||||
}
|
||||
|
||||
shopMap := make(map[uint]string)
|
||||
if len(shopIDs) > 0 {
|
||||
var shops []model.Shop
|
||||
s.db.WithContext(ctx).Where("id IN ?", shopIDs).Find(&shops)
|
||||
for _, shop := range shops {
|
||||
shopMap[shop.ID] = shop.ShopName
|
||||
}
|
||||
}
|
||||
|
||||
return shopMap
|
||||
}
|
||||
|
||||
func (s *Service) getBindingCounts(ctx context.Context, deviceIDs []uint) (map[uint]int64, error) {
|
||||
result := make(map[uint]int64)
|
||||
if len(deviceIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
bindings, err := s.deviceSimBindingStore.ListByDeviceIDs(ctx, deviceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, binding := range bindings {
|
||||
result[binding.DeviceID]++
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) extractDeviceIDs(devices []*model.Device) []uint {
|
||||
ids := make([]uint, len(devices))
|
||||
for i, device := range devices {
|
||||
ids[i] = device.ID
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (s *Service) toDeviceResponse(device *model.Device, shopMap map[uint]string, bindingCounts map[uint]int64) *dto.DeviceResponse {
|
||||
resp := &dto.DeviceResponse{
|
||||
ID: device.ID,
|
||||
DeviceNo: device.DeviceNo,
|
||||
DeviceName: device.DeviceName,
|
||||
DeviceModel: device.DeviceModel,
|
||||
DeviceType: device.DeviceType,
|
||||
MaxSimSlots: device.MaxSimSlots,
|
||||
Manufacturer: device.Manufacturer,
|
||||
BatchNo: device.BatchNo,
|
||||
ShopID: device.ShopID,
|
||||
Status: device.Status,
|
||||
StatusName: s.getDeviceStatusName(device.Status),
|
||||
BoundCardCount: int(bindingCounts[device.ID]),
|
||||
ActivatedAt: device.ActivatedAt,
|
||||
CreatedAt: device.CreatedAt,
|
||||
UpdatedAt: device.UpdatedAt,
|
||||
}
|
||||
|
||||
if device.ShopID != nil && *device.ShopID > 0 {
|
||||
resp.ShopName = shopMap[*device.ShopID]
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Service) getDeviceStatusName(status int) string {
|
||||
switch status {
|
||||
case constants.DeviceStatusInStock:
|
||||
return "在库"
|
||||
case constants.DeviceStatusDistributed:
|
||||
return "已分销"
|
||||
case constants.DeviceStatusActivated:
|
||||
return "已激活"
|
||||
case constants.DeviceStatusSuspended:
|
||||
return "已停用"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) buildAllocationRecords(devices []*model.Device, successDeviceIDs []uint, fromShopID *uint, toShopID uint, operatorID uint, allocationNo, remark string) []*model.AssetAllocationRecord {
|
||||
successIDSet := make(map[uint]bool)
|
||||
for _, id := range successDeviceIDs {
|
||||
successIDSet[id] = true
|
||||
}
|
||||
|
||||
var records []*model.AssetAllocationRecord
|
||||
for _, device := range devices {
|
||||
if !successIDSet[device.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: allocationNo,
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.DeviceNo,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: toShopID,
|
||||
OperatorID: operatorID,
|
||||
Remark: remark,
|
||||
}
|
||||
|
||||
if fromShopID == nil {
|
||||
record.FromOwnerType = constants.OwnerTypePlatform
|
||||
record.FromOwnerID = nil
|
||||
} else {
|
||||
record.FromOwnerType = constants.OwnerTypeShop
|
||||
record.FromOwnerID = fromShopID
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func (s *Service) buildRecallRecords(devices []*model.Device, successDeviceIDs []uint, fromShopID *uint, toShopID *uint, operatorID uint, allocationNo, remark string) []*model.AssetAllocationRecord {
|
||||
successIDSet := make(map[uint]bool)
|
||||
for _, id := range successDeviceIDs {
|
||||
successIDSet[id] = true
|
||||
}
|
||||
|
||||
var records []*model.AssetAllocationRecord
|
||||
for _, device := range devices {
|
||||
if !successIDSet[device.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: allocationNo,
|
||||
AllocationType: constants.AssetAllocationTypeRecall,
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.DeviceNo,
|
||||
OperatorID: operatorID,
|
||||
Remark: remark,
|
||||
}
|
||||
|
||||
if fromShopID == nil {
|
||||
record.FromOwnerType = constants.OwnerTypePlatform
|
||||
record.FromOwnerID = nil
|
||||
} else {
|
||||
record.FromOwnerType = constants.OwnerTypeShop
|
||||
record.FromOwnerID = fromShopID
|
||||
}
|
||||
|
||||
if toShopID == nil {
|
||||
record.ToOwnerType = constants.OwnerTypePlatform
|
||||
record.ToOwnerID = 0
|
||||
} else {
|
||||
record.ToOwnerType = constants.OwnerTypeShop
|
||||
record.ToOwnerID = *toShopID
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
Reference in New Issue
Block a user