feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-14 18:27:28 +08:00
parent b5147d1acb
commit b9c3875c08
77 changed files with 5832 additions and 2393 deletions

View File

@@ -48,7 +48,7 @@ func (s *DeviceStore) GetByID(ctx context.Context, id uint) (*model.Device, erro
func (s *DeviceStore) GetByDeviceNo(ctx context.Context, deviceNo string) (*model.Device, error) {
var device model.Device
query := s.db.WithContext(ctx).Where("device_no = ?", deviceNo)
query := s.db.WithContext(ctx).Where("virtual_no = ?", deviceNo)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.First(&device).Error; err != nil {
@@ -58,10 +58,10 @@ func (s *DeviceStore) GetByDeviceNo(ctx context.Context, deviceNo string) (*mode
}
// GetByIdentifier 通过任意标识符查找设备
// 支持 device_no虚拟号、imei、sn 三个字段的自动匹配
// 支持 virtual_no虚拟号、imei、sn 三个字段的自动匹配
func (s *DeviceStore) GetByIdentifier(ctx context.Context, identifier string) (*model.Device, error) {
var device model.Device
query := s.db.WithContext(ctx).Where("device_no = ? OR imei = ? OR sn = ?", identifier, identifier, identifier)
query := s.db.WithContext(ctx).Where("virtual_no = ? OR imei = ? OR sn = ?", identifier, identifier, identifier)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.First(&device).Error; err != nil {
@@ -100,8 +100,8 @@ func (s *DeviceStore) List(ctx context.Context, opts *store.QueryOptions, filter
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if deviceNo, ok := filters["device_no"].(string); ok && deviceNo != "" {
query = query.Where("device_no LIKE ?", "%"+deviceNo+"%")
if virtualNo, ok := filters["virtual_no"].(string); ok && virtualNo != "" {
query = query.Where("virtual_no LIKE ?", "%"+virtualNo+"%")
}
if deviceName, ok := filters["device_name"].(string); ok && deviceName != "" {
query = query.Where("device_name LIKE ?", "%"+deviceName+"%")
@@ -184,17 +184,17 @@ func (s *DeviceStore) ExistsByDeviceNoBatch(ctx context.Context, deviceNos []str
}
var existingDevices []struct {
DeviceNo string
VirtualNo string
}
if err := s.db.WithContext(ctx).Model(&model.Device{}).
Select("device_no").
Where("device_no IN ?", deviceNos).
Select("virtual_no").
Where("virtual_no IN ?", deviceNos).
Find(&existingDevices).Error; err != nil {
return nil, err
}
for _, d := range existingDevices {
result[d.DeviceNo] = true
result[d.VirtualNo] = true
}
return result, nil
}
@@ -204,7 +204,7 @@ func (s *DeviceStore) GetByDeviceNos(ctx context.Context, deviceNos []string) ([
if len(deviceNos) == 0 {
return devices, nil
}
query := s.db.WithContext(ctx).Where("device_no IN ?", deviceNos)
query := s.db.WithContext(ctx).Where("virtual_no IN ?", deviceNos)
// 应用数据权限过滤NULL shop_id 对代理用户不可见)
query = middleware.ApplyShopFilter(ctx, query)
if err := query.Find(&devices).Error; err != nil {

View File

@@ -903,6 +903,26 @@ func (s *IotCardStore) BatchDelete(ctx context.Context, cardIDs []uint) error {
Delete(&model.IotCard{}).Error
}
// ExistsByVirtualNoBatch 批量检查 virtual_no 是否已存在
func (s *IotCardStore) ExistsByVirtualNoBatch(ctx context.Context, virtualNos []string) (map[string]bool, error) {
result := make(map[string]bool)
if len(virtualNos) == 0 {
return result, nil
}
var existingNos []string
if err := s.db.WithContext(ctx).Model(&model.IotCard{}).
Where("virtual_no IN ? AND virtual_no <> ''", virtualNos).
Pluck("virtual_no", &existingNos).Error; err != nil {
return nil, err
}
for _, no := range existingNos {
result[no] = true
}
return result, nil
}
// ==================== 列表计数缓存 ====================
func (s *IotCardStore) getCachedCount(ctx context.Context, table string, filters map[string]any) (int64, bool) {

View File

@@ -104,7 +104,7 @@ func (s *PersonalCustomerDeviceStore) CreateOrUpdateLastUsed(ctx context.Context
// 不存在,创建新记录
newRecord := &model.PersonalCustomerDevice{
CustomerID: customerID,
DeviceNo: deviceNo,
VirtualNo: deviceNo,
Status: 1, // 启用
}
return s.Create(ctx, newRecord)