fix: IoT 卡列表补充 virtual_no 字段和查询过滤,修正设备/卡导入 API 文档描述
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

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-16 10:44:38 +08:00
parent b9c3875c08
commit 275debdd38
54 changed files with 159 additions and 6379 deletions

View File

@@ -11,6 +11,7 @@ type ListStandaloneIotCardRequest struct {
SeriesID *uint `json:"series_id" query:"series_id" description:"套餐系列ID"`
ICCID string `json:"iccid" query:"iccid" validate:"omitempty,max=20" maxLength:"20" description:"ICCID(模糊查询)"`
MSISDN string `json:"msisdn" query:"msisdn" validate:"omitempty,max=20" maxLength:"20" description:"卡接入号(模糊查询)"`
VirtualNo string `json:"virtual_no" query:"virtual_no" validate:"omitempty,max=50" maxLength:"50" description:"虚拟号(模糊查询)"`
BatchNo string `json:"batch_no" query:"batch_no" validate:"omitempty,max=100" maxLength:"100" description:"批次号"`
PackageID *uint `json:"package_id" query:"package_id" description:"套餐ID"`
IsDistributed *bool `json:"is_distributed" query:"is_distributed" description:"是否已分销 (true:已分销, false:未分销)"`
@@ -22,6 +23,7 @@ type ListStandaloneIotCardRequest struct {
type StandaloneIotCardResponse struct {
ID uint `json:"id" description:"卡ID"`
ICCID string `json:"iccid" description:"ICCID"`
VirtualNo string `json:"virtual_no,omitempty" description:"卡虚拟号(用于客服查找资产)"`
CardCategory string `json:"card_category" description:"卡业务类型 (normal:普通卡, industry:行业卡)"`
CarrierID uint `json:"carrier_id" description:"运营商ID"`
CarrierType string `json:"carrier_type,omitempty" description:"运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)"`

View File

@@ -88,7 +88,7 @@ func registerDeviceRoutes(router fiber.Router, handler *admin.DeviceHandler, imp
- 文件格式:仅支持 .xlsx (Excel 2007+)
- 必须包含列(首行为表头):
- ` + "`device_no`" + `: 设备号(必填,唯一)
- ` + "`virtual_no`" + `: 设备虚拟号(必填,全局唯一)
- ` + "`device_name`" + `: 设备名称
- ` + "`device_model`" + `: 设备型号
- ` + "`device_type`" + `: 设备类型

View File

@@ -49,9 +49,16 @@ func registerIotCardRoutes(router fiber.Router, handler *admin.IotCardHandler, i
### Excel 文件格式
- 文件格式:仅支持 .xlsx (Excel 2007+)
- 必须包含两列:` + "`ICCID`" + `, ` + "`MSISDN`" + `
- 必列:` + "`ICCID`" + `, ` + "`MSISDN`" + `
- 可选列:` + "`virtual_no`" + `虚拟号表头关键字virtual_no / VirtualNo / 虚拟号 / 设备号)
- 首行为表头(可选,但建议包含)
- 列格式:设置为文本格式(避免长数字被转为科学记数法)`,
- 列格式:设置为文本格式(避免长数字被转为科学记数法)
#### virtual_no 列导入规则(只补空白)
- 该行 virtual_no 非空 + 数据库当前值为 NULL填入新值
- 该行 virtual_no 非空 + 数据库已有值:跳过(不覆盖)
- 批次内有任意 virtual_no 与数据库现存值重复:**整批拒绝**`,
Tags: []string{"IoT卡管理"},
Input: new(dto.ImportIotCardRequest),
Output: new(dto.ImportIotCardResponse),

View File

@@ -105,6 +105,9 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
if req.MSISDN != "" {
filters["msisdn"] = req.MSISDN
}
if req.VirtualNo != "" {
filters["virtual_no"] = req.VirtualNo
}
if req.BatchNo != "" {
filters["batch_no"] = req.BatchNo
}
@@ -238,6 +241,7 @@ func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]str
resp := &dto.StandaloneIotCardResponse{
ID: card.ID,
ICCID: card.ICCID,
VirtualNo: card.VirtualNo,
CardCategory: card.CardCategory,
CarrierID: card.CarrierID,
CarrierType: card.CarrierType,

View File

@@ -234,7 +234,8 @@ func (s *IotCardStore) ListStandalone(ctx context.Context, opts *store.QueryOpti
_, hasICCID := filters["iccid"].(string)
_, hasMSISDN := filters["msisdn"].(string)
useFuzzySearch := (hasICCID && filters["iccid"] != "") || (hasMSISDN && filters["msisdn"] != "")
_, hasVirtualNo := filters["virtual_no"].(string)
useFuzzySearch := (hasICCID && filters["iccid"] != "") || (hasMSISDN && filters["msisdn"] != "") || (hasVirtualNo && filters["virtual_no"] != "")
if !useFuzzySearch {
if shopIDs, ok := filters["subordinate_shop_ids"].([]uint); ok && len(shopIDs) > 1 {
@@ -615,6 +616,9 @@ func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.D
if msisdn, ok := filters["msisdn"].(string); ok && msisdn != "" {
query = query.Where("msisdn LIKE ?", "%"+msisdn+"%")
}
if virtualNo, ok := filters["virtual_no"].(string); ok && virtualNo != "" {
query = query.Where("virtual_no LIKE ?", "%"+virtualNo+"%")
}
if batchNo, ok := filters["batch_no"].(string); ok && batchNo != "" {
query = query.Where("batch_no = ?", batchNo)
}