feat: 实现单卡资产分配与回收功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
- 新增单卡分配/回收 API(支持 ICCID 列表、号段范围、筛选条件三种选卡方式) - 新增资产分配记录查询 API(支持多条件筛选和分页) - 新增 AssetAllocationRecord 模型、Store、Service、Handler 完整实现 - 扩展 IotCardStore 新增批量更新、号段查询、筛选查询等方法 - 修复 GORM Callback 处理 slice 类型(BatchCreate)的问题 - 新增完整的单元测试和集成测试 - 同步 OpenSpec 规范并归档 change
This commit is contained in:
127
internal/store/postgres/asset_allocation_record_store.go
Normal file
127
internal/store/postgres/asset_allocation_record_store.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AssetAllocationRecordStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewAssetAllocationRecordStore(db *gorm.DB, redis *redis.Client) *AssetAllocationRecordStore {
|
||||
return &AssetAllocationRecordStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) Create(ctx context.Context, record *model.AssetAllocationRecord) error {
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) BatchCreate(ctx context.Context, records []*model.AssetAllocationRecord) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).CreateInBatches(records, 100).Error
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) GetByID(ctx context.Context, id uint) (*model.AssetAllocationRecord, error) {
|
||||
var record model.AssetAllocationRecord
|
||||
if err := s.db.WithContext(ctx).First(&record, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) GetByAllocationNo(ctx context.Context, allocationNo string) (*model.AssetAllocationRecord, error) {
|
||||
var record model.AssetAllocationRecord
|
||||
if err := s.db.WithContext(ctx).Where("allocation_no = ?", allocationNo).First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]any) ([]*model.AssetAllocationRecord, int64, error) {
|
||||
var records []*model.AssetAllocationRecord
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.AssetAllocationRecord{})
|
||||
|
||||
if allocationType, ok := filters["allocation_type"].(string); ok && allocationType != "" {
|
||||
query = query.Where("allocation_type = ?", allocationType)
|
||||
}
|
||||
if assetType, ok := filters["asset_type"].(string); ok && assetType != "" {
|
||||
query = query.Where("asset_type = ?", assetType)
|
||||
}
|
||||
if assetIdentifier, ok := filters["asset_identifier"].(string); ok && assetIdentifier != "" {
|
||||
query = query.Where("asset_identifier LIKE ?", "%"+assetIdentifier+"%")
|
||||
}
|
||||
if allocationNo, ok := filters["allocation_no"].(string); ok && allocationNo != "" {
|
||||
query = query.Where("allocation_no = ?", allocationNo)
|
||||
}
|
||||
if fromShopID, ok := filters["from_shop_id"].(uint); ok && fromShopID > 0 {
|
||||
query = query.Where("from_owner_type = ? AND from_owner_id = ?", constants.OwnerTypeShop, fromShopID)
|
||||
}
|
||||
if toShopID, ok := filters["to_shop_id"].(uint); ok && toShopID > 0 {
|
||||
query = query.Where("to_owner_type = ? AND to_owner_id = ?", constants.OwnerTypeShop, toShopID)
|
||||
}
|
||||
if operatorID, ok := filters["operator_id"].(uint); ok && operatorID > 0 {
|
||||
query = query.Where("operator_id = ?", operatorID)
|
||||
}
|
||||
if createdAtStart, ok := filters["created_at_start"].(time.Time); ok {
|
||||
query = query.Where("created_at >= ?", createdAtStart)
|
||||
}
|
||||
if createdAtEnd, ok := filters["created_at_end"].(time.Time); ok {
|
||||
query = query.Where("created_at <= ?", createdAtEnd)
|
||||
}
|
||||
if relatedShopIDs, ok := filters["related_shop_ids"].([]uint); ok && len(relatedShopIDs) > 0 {
|
||||
query = query.Where(
|
||||
"(from_owner_type = ? AND from_owner_id IN ?) OR (to_owner_type = ? AND to_owner_id IN ?)",
|
||||
constants.OwnerTypeShop, relatedShopIDs,
|
||||
constants.OwnerTypeShop, relatedShopIDs,
|
||||
)
|
||||
}
|
||||
|
||||
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(&records).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return records, total, nil
|
||||
}
|
||||
|
||||
func (s *AssetAllocationRecordStore) GenerateAllocationNo(ctx context.Context, allocationType string) string {
|
||||
prefix := "AL"
|
||||
if allocationType == constants.AssetAllocationTypeRecall {
|
||||
prefix = "RC"
|
||||
}
|
||||
return fmt.Sprintf("%s%s%d", prefix, time.Now().Format("20060102150405"), time.Now().UnixNano()%10000)
|
||||
}
|
||||
232
internal/store/postgres/asset_allocation_record_store_test.go
Normal file
232
internal/store/postgres/asset_allocation_record_store_test.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAssetAllocationRecordStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: "AL20260124100001",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678901234",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, record)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, record.ID)
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
records := []*model.AssetAllocationRecord{
|
||||
{
|
||||
AllocationNo: "AL20260124100010",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678901001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
},
|
||||
{
|
||||
AllocationNo: "AL20260124100011",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 2,
|
||||
AssetIdentifier: "89860012345678901002",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := s.BatchCreate(ctx, records)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, record := range records {
|
||||
assert.NotZero(t, record.ID)
|
||||
}
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchCreate(ctx, []*model.AssetAllocationRecord{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: "AL20260124100003",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678903001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
Remark: "测试备注",
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, record))
|
||||
|
||||
result, err := s.GetByID(ctx, record.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, record.AllocationNo, result.AllocationNo)
|
||||
assert.Equal(t, record.AssetIdentifier, result.AssetIdentifier)
|
||||
assert.Equal(t, "测试备注", result.Remark)
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
records := []*model.AssetAllocationRecord{
|
||||
{
|
||||
AllocationNo: "AL20260124100004",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678904001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: shopID,
|
||||
OperatorID: 1,
|
||||
},
|
||||
{
|
||||
AllocationNo: "AL20260124100005",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 2,
|
||||
AssetIdentifier: "89860012345678904002",
|
||||
FromOwnerType: constants.OwnerTypeShop,
|
||||
FromOwnerID: &shopID,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 200,
|
||||
OperatorID: 2,
|
||||
},
|
||||
{
|
||||
AllocationNo: "RC20260124100001",
|
||||
AllocationType: constants.AssetAllocationTypeRecall,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 3,
|
||||
AssetIdentifier: "89860012345678904003",
|
||||
FromOwnerType: constants.OwnerTypeShop,
|
||||
FromOwnerID: &shopID,
|
||||
ToOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerID: 0,
|
||||
OperatorID: 1,
|
||||
},
|
||||
}
|
||||
require.NoError(t, s.BatchCreate(ctx, records))
|
||||
|
||||
t.Run("查询所有记录", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, result, 3)
|
||||
})
|
||||
|
||||
t.Run("按分配类型过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"allocation_type": constants.AssetAllocationTypeAllocate}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
for _, r := range result {
|
||||
assert.Equal(t, constants.AssetAllocationTypeAllocate, r.AllocationType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按分配单号过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"allocation_no": "AL20260124100004"}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, "AL20260124100004", result[0].AllocationNo)
|
||||
})
|
||||
|
||||
t.Run("按资产标识模糊查询", func(t *testing.T) {
|
||||
filters := map[string]any{"asset_identifier": "904002"}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, result[0].AssetIdentifier, "904002")
|
||||
})
|
||||
|
||||
t.Run("按目标店铺过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"to_shop_id": shopID}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, shopID, result[0].ToOwnerID)
|
||||
})
|
||||
|
||||
t.Run("按操作人过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"operator_id": uint(2)}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, uint(2), result[0].OperatorID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_GenerateAllocationNo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("分配单号前缀为AL", func(t *testing.T) {
|
||||
no := s.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate)
|
||||
assert.True(t, len(no) > 0)
|
||||
assert.Equal(t, "AL", no[:2])
|
||||
})
|
||||
|
||||
t.Run("回收单号前缀为RC", func(t *testing.T) {
|
||||
no := s.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall)
|
||||
assert.True(t, len(no) > 0)
|
||||
assert.Equal(t, "RC", no[:2])
|
||||
})
|
||||
}
|
||||
@@ -172,11 +172,50 @@ func (s *IotCardStore) ListStandalone(ctx context.Context, opts *store.QueryOpti
|
||||
return cards, total, nil
|
||||
}
|
||||
|
||||
func (s *IotCardStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]any) ([]*model.IotCard, int64, error) {
|
||||
func (s *IotCardStore) GetByICCIDs(ctx context.Context, iccids []string) ([]*model.IotCard, error) {
|
||||
if len(iccids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var cards []*model.IotCard
|
||||
var total int64
|
||||
if err := s.db.WithContext(ctx).Where("iccid IN ?", iccids).Find(&cards).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.IotCard{})
|
||||
func (s *IotCardStore) GetStandaloneByICCIDRange(ctx context.Context, iccidStart, iccidEnd string, shopID *uint) ([]*model.IotCard, error) {
|
||||
query := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id NOT IN (?)",
|
||||
s.db.Model(&model.DeviceSimBinding{}).
|
||||
Select("iot_card_id").
|
||||
Where("bind_status = ?", 1)).
|
||||
Where("iccid >= ? AND iccid <= ?", iccidStart, iccidEnd)
|
||||
|
||||
if shopID == nil {
|
||||
query = query.Where("shop_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("shop_id = ?", *shopID)
|
||||
}
|
||||
|
||||
var cards []*model.IotCard
|
||||
if err := query.Find(&cards).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (s *IotCardStore) GetStandaloneByFilters(ctx context.Context, filters map[string]any, shopID *uint) ([]*model.IotCard, error) {
|
||||
query := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id NOT IN (?)",
|
||||
s.db.Model(&model.DeviceSimBinding{}).
|
||||
Select("iot_card_id").
|
||||
Where("bind_status = ?", 1))
|
||||
|
||||
if shopID == nil {
|
||||
query = query.Where("shop_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("shop_id = ?", *shopID)
|
||||
}
|
||||
|
||||
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||
query = query.Where("status = ?", status)
|
||||
@@ -184,35 +223,38 @@ func (s *IotCardStore) List(ctx context.Context, opts *store.QueryOptions, filte
|
||||
if carrierID, ok := filters["carrier_id"].(uint); ok && carrierID > 0 {
|
||||
query = query.Where("carrier_id = ?", carrierID)
|
||||
}
|
||||
if shopID, ok := filters["shop_id"].(uint); ok && shopID > 0 {
|
||||
query = query.Where("shop_id = ?", shopID)
|
||||
}
|
||||
if iccid, ok := filters["iccid"].(string); ok && iccid != "" {
|
||||
query = query.Where("iccid LIKE ?", "%"+iccid+"%")
|
||||
}
|
||||
|
||||
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 batchNo, ok := filters["batch_no"].(string); ok && batchNo != "" {
|
||||
query = query.Where("batch_no = ?", batchNo)
|
||||
}
|
||||
|
||||
var cards []*model.IotCard
|
||||
if err := query.Find(&cards).Error; err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cards, total, nil
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (s *IotCardStore) BatchUpdateShopIDAndStatus(ctx context.Context, cardIDs []uint, shopID *uint, status int) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
updates := map[string]any{
|
||||
"shop_id": shopID,
|
||||
"status": status,
|
||||
}
|
||||
return s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id IN ?", cardIDs).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
func (s *IotCardStore) GetBoundCardIDs(ctx context.Context, cardIDs []uint) ([]uint, error) {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var boundCardIDs []uint
|
||||
err := s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
|
||||
Select("iot_card_id").
|
||||
Where("iot_card_id IN ? AND bind_status = ?", cardIDs, 1).
|
||||
Pluck("iot_card_id", &boundCardIDs).Error
|
||||
return boundCardIDs, err
|
||||
}
|
||||
|
||||
@@ -240,3 +240,174 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
|
||||
assert.Equal(t, int64(2), total)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetByICCIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678905001", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678905002", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678905003", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("查询存在的ICCID", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{"89860012345678905001", "89860012345678905002"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的ICCID", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{"89860012345678909999"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 0)
|
||||
})
|
||||
|
||||
t.Run("空列表返回nil", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetStandaloneByICCIDRange(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678906001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
{ICCID: "89860012345678906002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
{ICCID: "89860012345678906003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID},
|
||||
{ICCID: "89860012345678906004", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("平台查询未分配的卡", func(t *testing.T) {
|
||||
result, err := s.GetStandaloneByICCIDRange(ctx, "89860012345678906001", "89860012345678906004", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Nil(t, card.ShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("店铺查询自己的卡", func(t *testing.T) {
|
||||
result, err := s.GetStandaloneByICCIDRange(ctx, "89860012345678906001", "89860012345678906004", &shopID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Equal(t, shopID, *card.ShopID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetStandaloneByFilters(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678907001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH001"},
|
||||
{ICCID: "89860012345678907002", CardType: "data_card", CarrierID: 2, Status: 1, ShopID: nil, BatchNo: "BATCH002"},
|
||||
{ICCID: "89860012345678907003", CardType: "data_card", CarrierID: 1, Status: 2, ShopID: &shopID, BatchNo: "BATCH001"},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("按运营商过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"carrier_id": uint(1)}
|
||||
result, err := s.GetStandaloneByFilters(ctx, filters, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, uint(1), result[0].CarrierID)
|
||||
})
|
||||
|
||||
t.Run("按批次号过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"batch_no": "BATCH001"}
|
||||
result, err := s.GetStandaloneByFilters(ctx, filters, &shopID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "BATCH001", result[0].BatchNo)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_BatchUpdateShopIDAndStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678908001", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678908002", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
newShopID := uint(200)
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID}
|
||||
|
||||
err := s.BatchUpdateShopIDAndStatus(ctx, cardIDs, &newShopID, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedCards []*model.IotCard
|
||||
require.NoError(t, tx.Where("id IN ?", cardIDs).Find(&updatedCards).Error)
|
||||
for _, card := range updatedCards {
|
||||
assert.Equal(t, newShopID, *card.ShopID)
|
||||
assert.Equal(t, 2, card.Status)
|
||||
}
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchUpdateShopIDAndStatus(ctx, []uint{}, nil, 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetBoundCardIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678909001", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678909002", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678909003", CardType: "data_card", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
binding := &model.DeviceSimBinding{
|
||||
DeviceID: 1,
|
||||
IotCardID: cards[0].ID,
|
||||
BindStatus: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(binding).Error)
|
||||
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID, cards[2].ID}
|
||||
boundIDs, err := s.GetBoundCardIDs(ctx, cardIDs)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, boundIDs, 1)
|
||||
assert.Contains(t, boundIDs, cards[0].ID)
|
||||
|
||||
t.Run("空列表返回nil", func(t *testing.T) {
|
||||
result, err := s.GetBoundCardIDs(ctx, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user