修改
This commit is contained in:
@@ -3610,6 +3610,14 @@ components:
|
|||||||
description: 本次开机运行时间(秒)
|
description: 本次开机运行时间(秒)
|
||||||
nullable: true
|
nullable: true
|
||||||
type: string
|
type: string
|
||||||
|
signal_bad_reason:
|
||||||
|
description: 信号较弱时的怀疑原因提示
|
||||||
|
nullable: true
|
||||||
|
type: string
|
||||||
|
signal_quality:
|
||||||
|
description: 信号综合判断(信号很好/信号正常/信号较弱/信号很差/暂无数据)
|
||||||
|
nullable: true
|
||||||
|
type: string
|
||||||
sinr:
|
sinr:
|
||||||
description: 信噪比(dB)
|
description: 信噪比(dB)
|
||||||
nullable: true
|
nullable: true
|
||||||
@@ -3906,6 +3914,14 @@ components:
|
|||||||
description: 设备本次开机运行时间(秒)
|
description: 设备本次开机运行时间(秒)
|
||||||
nullable: true
|
nullable: true
|
||||||
type: string
|
type: string
|
||||||
|
signal_bad_reason:
|
||||||
|
description: 信号较弱时的怀疑原因提示
|
||||||
|
nullable: true
|
||||||
|
type: string
|
||||||
|
signal_quality:
|
||||||
|
description: 信号综合判断(信号很好/信号正常/信号较弱/信号很差/暂无数据)
|
||||||
|
nullable: true
|
||||||
|
type: string
|
||||||
sinr:
|
sinr:
|
||||||
description: 信噪比(dB)
|
description: 信噪比(dB)
|
||||||
nullable: true
|
nullable: true
|
||||||
@@ -13578,9 +13594,11 @@ paths:
|
|||||||
- `device_name`: 设备名称
|
- `device_name`: 设备名称
|
||||||
- `device_model`: 设备型号
|
- `device_model`: 设备型号
|
||||||
- `device_type`: 设备类型
|
- `device_type`: 设备类型
|
||||||
- `max_sim_slots`: 最大插槽数(默认4)
|
- `imei`: 设备 IMEI
|
||||||
- `manufacturer`: 制造商
|
- `manufacturer`: 制造商
|
||||||
- `iccid_1` ~ `iccid_4`: 绑定的卡 ICCID(卡必须已存在且未绑定)
|
- `max_sim_slots`: 最大插槽数(默认4,范围1-4)
|
||||||
|
- `iccid_1` ~ `iccid_4`: 固定槽位的卡 ICCID(`iccid_1`=槽1,`iccid_2`=槽2,`iccid_3`=槽3,`iccid_4`=槽4;卡必须已存在且未绑定)
|
||||||
|
- 槽位规则:中间留空不会触发前移补位;如果 `max_sim_slots=2`,则 `iccid_3`、`iccid_4` 必须留空,否则该行失败
|
||||||
- 列格式:设置为文本格式(避免长数字被转为科学记数法)
|
- 列格式:设置为文本格式(避免长数字被转为科学记数法)
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
|
|||||||
@@ -641,6 +641,8 @@ func mapDeviceGatewayInfoToClientInfo(g *dto.DeviceGatewayInfo) *dto.DeviceRealt
|
|||||||
v := int64(*g.Sinr)
|
v := int64(*g.Sinr)
|
||||||
info.Sinr = &v
|
info.Sinr = &v
|
||||||
}
|
}
|
||||||
|
info.SignalQuality = g.SignalQuality
|
||||||
|
info.SignalBadReason = g.SignalBadReason
|
||||||
info.SSID = g.SSID
|
info.SSID = g.SSID
|
||||||
info.WifiEnabled = g.WifiEnabled
|
info.WifiEnabled = g.WifiEnabled
|
||||||
info.WifiPassword = g.WifiPassword
|
info.WifiPassword = g.WifiPassword
|
||||||
|
|||||||
@@ -19,17 +19,17 @@ type AssetResolveResponse struct {
|
|||||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||||
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
||||||
// 状态聚合字段
|
// 状态聚合字段
|
||||||
RealNameStatus int `json:"real_name_status" description:"实名状态:0未实名 1已实名"`
|
RealNameStatus int `json:"real_name_status" description:"实名状态:0未实名 1已实名"`
|
||||||
RealNameAt *time.Time `json:"real_name_at" description:"最近一次完成实名的时间,未实名时为 null"`
|
RealNameAt *time.Time `json:"real_name_at" description:"最近一次完成实名的时间,未实名时为 null"`
|
||||||
RealnamePolicy string `json:"realname_policy" description:"实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名)"`
|
RealnamePolicy string `json:"realname_policy" description:"实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名)"`
|
||||||
CurrentPackage string `json:"current_package" description:"当前套餐名称(无套餐时为空)"`
|
CurrentPackage string `json:"current_package" description:"当前套餐名称(无套餐时为空)"`
|
||||||
CurrentPackageUsageID *uint `json:"current_package_usage_id" description:"当前主套餐的套餐使用记录ID,无主套餐时为 null"`
|
CurrentPackageUsageID *uint `json:"current_package_usage_id" description:"当前主套餐的套餐使用记录ID,无主套餐时为 null"`
|
||||||
RealTotalMB int64 `json:"real_total_mb" description:"当前主套餐真实总量(MB)"`
|
RealTotalMB int64 `json:"real_total_mb" description:"当前主套餐真实总量(MB)"`
|
||||||
RealUsedMB int64 `json:"real_used_mb" description:"当前主套餐真实已用量(MB)"`
|
RealUsedMB int64 `json:"real_used_mb" description:"当前主套餐真实已用量(MB)"`
|
||||||
VirtualTotalMB int64 `json:"virtual_total_mb" description:"当前主套餐业务停机阈值(MB)"`
|
VirtualTotalMB int64 `json:"virtual_total_mb" description:"当前主套餐业务停机阈值(MB)"`
|
||||||
VirtualUsedMB float64 `json:"virtual_used_mb" description:"当前主套餐展示已用量(MB)"`
|
VirtualUsedMB float64 `json:"virtual_used_mb" description:"当前主套餐展示已用量(MB)"`
|
||||||
ReductionPct float64 `json:"reduction_pct" description:"展示增幅比例,公式为(real_total_mb / virtual_total_mb) - 1"`
|
ReductionPct float64 `json:"reduction_pct" description:"展示增幅比例,公式为(real_total_mb / virtual_total_mb) - 1"`
|
||||||
DeviceProtectStatus string `json:"device_protect_status,omitempty" description:"设备保护期状态:none/stop/start(仅asset_type=device时有效)"`
|
DeviceProtectStatus string `json:"device_protect_status,omitempty" description:"设备保护期状态:none/stop/start(仅asset_type=device时有效)"`
|
||||||
// 绑定关系字段
|
// 绑定关系字段
|
||||||
ICCID string `json:"iccid,omitempty" description:"卡ICCID(asset_type=card时有效)"`
|
ICCID string `json:"iccid,omitempty" description:"卡ICCID(asset_type=card时有效)"`
|
||||||
BoundDeviceID *uint `json:"bound_device_id,omitempty" description:"绑定的设备ID(asset_type=card时有效)"`
|
BoundDeviceID *uint `json:"bound_device_id,omitempty" description:"绑定的设备ID(asset_type=card时有效)"`
|
||||||
@@ -97,28 +97,28 @@ type AssetRealtimeStatusResponse struct {
|
|||||||
|
|
||||||
// AssetPackageResponse 资产套餐信息
|
// AssetPackageResponse 资产套餐信息
|
||||||
type AssetPackageResponse struct {
|
type AssetPackageResponse struct {
|
||||||
PackageUsageID uint `json:"package_usage_id" description:"套餐使用记录ID"`
|
PackageUsageID uint `json:"package_usage_id" description:"套餐使用记录ID"`
|
||||||
PackageID uint `json:"package_id" description:"套餐ID"`
|
PackageID uint `json:"package_id" description:"套餐ID"`
|
||||||
PackageName string `json:"package_name" description:"套餐名称"`
|
PackageName string `json:"package_name" description:"套餐名称"`
|
||||||
PackageType string `json:"package_type" description:"套餐类型:formal/addon"`
|
PackageType string `json:"package_type" description:"套餐类型:formal/addon"`
|
||||||
OrderID uint `json:"order_id" description:"关联订单ID(无订单分配时为0)"`
|
OrderID uint `json:"order_id" description:"关联订单ID(无订单分配时为0)"`
|
||||||
OrderNo string `json:"order_no,omitempty" description:"订单号快照(无订单分配时为空)"`
|
OrderNo string `json:"order_no,omitempty" description:"订单号快照(无订单分配时为空)"`
|
||||||
RefundID *uint `json:"refund_id,omitempty" description:"退款主键ID快照(未退款时为空)"`
|
RefundID *uint `json:"refund_id,omitempty" description:"退款主键ID快照(未退款时为空)"`
|
||||||
RefundNo string `json:"refund_no,omitempty" description:"退款单号快照(未退款时为空)"`
|
RefundNo string `json:"refund_no,omitempty" description:"退款单号快照(未退款时为空)"`
|
||||||
UsageType string `json:"usage_type" description:"使用类型:single_card/device"`
|
UsageType string `json:"usage_type" description:"使用类型:single_card/device"`
|
||||||
Status int `json:"status" description:"状态:0待生效 1生效中 2已用完 3已过期 4已失效"`
|
Status int `json:"status" description:"状态:0待生效 1生效中 2已用完 3已过期 4已失效"`
|
||||||
StatusName string `json:"status_name" description:"状态名称"`
|
StatusName string `json:"status_name" description:"状态名称"`
|
||||||
RealTotalMB int64 `json:"real_total_mb" description:"套餐真实总量(MB)"`
|
RealTotalMB int64 `json:"real_total_mb" description:"套餐真实总量(MB)"`
|
||||||
RealUsedMB int64 `json:"real_used_mb" description:"套餐真实已用量(MB)"`
|
RealUsedMB int64 `json:"real_used_mb" description:"套餐真实已用量(MB)"`
|
||||||
VirtualTotalMB int64 `json:"virtual_total_mb" description:"套餐业务停机阈值(MB)"`
|
VirtualTotalMB int64 `json:"virtual_total_mb" description:"套餐业务停机阈值(MB)"`
|
||||||
VirtualUsedMB float64 `json:"virtual_used_mb" description:"套餐展示已用量(MB)"`
|
VirtualUsedMB float64 `json:"virtual_used_mb" description:"套餐展示已用量(MB)"`
|
||||||
ReductionPct float64 `json:"reduction_pct" description:"展示增幅比例,公式为(real_total_mb / virtual_total_mb) - 1"`
|
ReductionPct float64 `json:"reduction_pct" description:"展示增幅比例,公式为(real_total_mb / virtual_total_mb) - 1"`
|
||||||
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间(待生效套餐为空)"`
|
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间(待生效套餐为空)"`
|
||||||
ExpiresAt *time.Time `json:"expires_at,omitempty" description:"到期时间(待生效套餐为空)"`
|
ExpiresAt *time.Time `json:"expires_at,omitempty" description:"到期时间(待生效套餐为空)"`
|
||||||
MasterUsageID *uint `json:"master_usage_id,omitempty" description:"主套餐ID(加油包时有值)"`
|
MasterUsageID *uint `json:"master_usage_id,omitempty" description:"主套餐ID(加油包时有值)"`
|
||||||
Priority int `json:"priority" description:"优先级"`
|
Priority int `json:"priority" description:"优先级"`
|
||||||
PaidAmount *int64 `json:"paid_amount,omitempty" description:"购买实付金额(分),线下支付或无订单分配时为空"`
|
PaidAmount *int64 `json:"paid_amount,omitempty" description:"购买实付金额(分),线下支付或无订单分配时为空"`
|
||||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetPackagesResult 套餐列表分页结果
|
// AssetPackagesResult 套餐列表分页结果
|
||||||
@@ -180,10 +180,12 @@ type DeviceGatewayInfo struct {
|
|||||||
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
|
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
|
||||||
|
|
||||||
// 信号相关
|
// 信号相关
|
||||||
Rsrp *int `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
|
Rsrp *int `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
|
||||||
Rsrq *int `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
|
Rsrq *int `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
|
||||||
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
|
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
|
||||||
Sinr *int `json:"sinr,omitempty" description:"信噪比(dB)"`
|
Sinr *int `json:"sinr,omitempty" description:"信噪比(dB)"`
|
||||||
|
SignalQuality *string `json:"signal_quality,omitempty" description:"信号综合判断(信号很好/信号正常/信号较弱/信号很差/暂无数据)"`
|
||||||
|
SignalBadReason *string `json:"signal_bad_reason,omitempty" description:"信号较弱时的怀疑原因提示"`
|
||||||
|
|
||||||
// WiFi 相关
|
// WiFi 相关
|
||||||
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
|
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
|
||||||
|
|||||||
@@ -89,10 +89,12 @@ type DeviceRealtimeInfo struct {
|
|||||||
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
|
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
|
||||||
|
|
||||||
// === 信号相关 ===
|
// === 信号相关 ===
|
||||||
Rsrp *int64 `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
|
Rsrp *int64 `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
|
||||||
Rsrq *int64 `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
|
Rsrq *int64 `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
|
||||||
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
|
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
|
||||||
Sinr *int64 `json:"sinr,omitempty" description:"信噪比(dB)"`
|
Sinr *int64 `json:"sinr,omitempty" description:"信噪比(dB)"`
|
||||||
|
SignalQuality *string `json:"signal_quality,omitempty" description:"信号综合判断(信号很好/信号正常/信号较弱/信号很差/暂无数据)"`
|
||||||
|
SignalBadReason *string `json:"signal_bad_reason,omitempty" description:"信号较弱时的怀疑原因提示"`
|
||||||
|
|
||||||
// === WiFi 相关 ===
|
// === WiFi 相关 ===
|
||||||
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
|
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
|
||||||
|
|||||||
@@ -481,6 +481,9 @@ func mapSyncRespToDeviceGatewayInfo(r *gateway.SyncDeviceInfoResp) *dto.DeviceGa
|
|||||||
IMSI: flexStrPtr(r.IMSI),
|
IMSI: flexStrPtr(r.IMSI),
|
||||||
Status: flexIntPtr(r.Status),
|
Status: flexIntPtr(r.Status),
|
||||||
}
|
}
|
||||||
|
signalQuality, signalBadReason := buildSignalSummary(info.Rsrp, info.Rsrq, info.Rssi, info.Sinr)
|
||||||
|
info.SignalQuality = strPtr(signalQuality)
|
||||||
|
info.SignalBadReason = strPtr(signalBadReason)
|
||||||
if r.BatteryLevel != nil {
|
if r.BatteryLevel != nil {
|
||||||
n := int(*r.BatteryLevel)
|
n := int(*r.BatteryLevel)
|
||||||
info.BatteryLevel = &n
|
info.BatteryLevel = &n
|
||||||
@@ -713,28 +716,28 @@ func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, pa
|
|||||||
pkgType = pkg.PackageType
|
pkgType = pkg.PackageType
|
||||||
}
|
}
|
||||||
item := &dto.AssetPackageResponse{
|
item := &dto.AssetPackageResponse{
|
||||||
PackageUsageID: u.ID,
|
PackageUsageID: u.ID,
|
||||||
PackageID: u.PackageID,
|
PackageID: u.PackageID,
|
||||||
PackageName: pkgName,
|
PackageName: pkgName,
|
||||||
PackageType: pkgType,
|
PackageType: pkgType,
|
||||||
OrderID: u.OrderID,
|
OrderID: u.OrderID,
|
||||||
OrderNo: u.OrderNo,
|
OrderNo: u.OrderNo,
|
||||||
RefundID: u.RefundID,
|
RefundID: u.RefundID,
|
||||||
RefundNo: u.RefundNo,
|
RefundNo: u.RefundNo,
|
||||||
UsageType: u.UsageType,
|
UsageType: u.UsageType,
|
||||||
Status: u.Status,
|
Status: u.Status,
|
||||||
StatusName: packageStatusName(u.Status),
|
StatusName: packageStatusName(u.Status),
|
||||||
RealTotalMB: metrics.RealTotalMB,
|
RealTotalMB: metrics.RealTotalMB,
|
||||||
RealUsedMB: metrics.RealUsedMB,
|
RealUsedMB: metrics.RealUsedMB,
|
||||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||||
ReductionPct: metrics.ReductionPct,
|
ReductionPct: metrics.ReductionPct,
|
||||||
ActivatedAt: u.ActivatedAt,
|
ActivatedAt: u.ActivatedAt,
|
||||||
ExpiresAt: u.ExpiresAt,
|
ExpiresAt: u.ExpiresAt,
|
||||||
MasterUsageID: u.MasterUsageID,
|
MasterUsageID: u.MasterUsageID,
|
||||||
Priority: u.Priority,
|
Priority: u.Priority,
|
||||||
PaidAmount: u.PaidAmount,
|
PaidAmount: u.PaidAmount,
|
||||||
CreatedAt: u.CreatedAt,
|
CreatedAt: u.CreatedAt,
|
||||||
}
|
}
|
||||||
all = append(all, item)
|
all = append(all, item)
|
||||||
}
|
}
|
||||||
@@ -791,28 +794,28 @@ func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id ui
|
|||||||
pkgType = pkg.PackageType
|
pkgType = pkg.PackageType
|
||||||
}
|
}
|
||||||
return &dto.AssetPackageResponse{
|
return &dto.AssetPackageResponse{
|
||||||
PackageUsageID: usage.ID,
|
PackageUsageID: usage.ID,
|
||||||
PackageID: usage.PackageID,
|
PackageID: usage.PackageID,
|
||||||
PackageName: pkgName,
|
PackageName: pkgName,
|
||||||
PackageType: pkgType,
|
PackageType: pkgType,
|
||||||
OrderID: usage.OrderID,
|
OrderID: usage.OrderID,
|
||||||
OrderNo: usage.OrderNo,
|
OrderNo: usage.OrderNo,
|
||||||
RefundID: usage.RefundID,
|
RefundID: usage.RefundID,
|
||||||
RefundNo: usage.RefundNo,
|
RefundNo: usage.RefundNo,
|
||||||
UsageType: usage.UsageType,
|
UsageType: usage.UsageType,
|
||||||
Status: usage.Status,
|
Status: usage.Status,
|
||||||
StatusName: packageStatusName(usage.Status),
|
StatusName: packageStatusName(usage.Status),
|
||||||
RealTotalMB: metrics.RealTotalMB,
|
RealTotalMB: metrics.RealTotalMB,
|
||||||
RealUsedMB: metrics.RealUsedMB,
|
RealUsedMB: metrics.RealUsedMB,
|
||||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||||
ReductionPct: metrics.ReductionPct,
|
ReductionPct: metrics.ReductionPct,
|
||||||
ActivatedAt: usage.ActivatedAt,
|
ActivatedAt: usage.ActivatedAt,
|
||||||
ExpiresAt: usage.ExpiresAt,
|
ExpiresAt: usage.ExpiresAt,
|
||||||
MasterUsageID: usage.MasterUsageID,
|
MasterUsageID: usage.MasterUsageID,
|
||||||
Priority: usage.Priority,
|
Priority: usage.Priority,
|
||||||
PaidAmount: usage.PaidAmount,
|
PaidAmount: usage.PaidAmount,
|
||||||
CreatedAt: usage.CreatedAt,
|
CreatedAt: usage.CreatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
159
internal/service/asset/signal_summary.go
Normal file
159
internal/service/asset/signal_summary.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package asset
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
signalQualityExcellent = "信号很好"
|
||||||
|
signalQualityNormal = "信号正常"
|
||||||
|
signalQualityWeak = "信号较弱"
|
||||||
|
signalQualityPoor = "信号很差"
|
||||||
|
signalQualityNoData = "暂无数据"
|
||||||
|
|
||||||
|
signalReasonWeakCoverage = "怀疑当前位置信号覆盖较弱"
|
||||||
|
signalReasonInterference = "怀疑周围干扰较多"
|
||||||
|
signalReasonBlocked = "怀疑设备所处位置遮挡较强"
|
||||||
|
signalReasonUnstable = "怀疑网络环境不稳定"
|
||||||
|
signalReasonUnknown = "暂时无法判断"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildSignalSummary 根据四个原始信号指标输出统一的信号摘要。
|
||||||
|
func buildSignalSummary(rsrp, rsrq *int, rssi *string, sinr *int) (string, string) {
|
||||||
|
score := 0
|
||||||
|
seen := 0
|
||||||
|
coverageBad := false
|
||||||
|
interferenceBad := false
|
||||||
|
blockedBad := false
|
||||||
|
unstableBad := false
|
||||||
|
|
||||||
|
if rsrp != nil {
|
||||||
|
seen++
|
||||||
|
switch {
|
||||||
|
case *rsrp >= -90:
|
||||||
|
score += 2
|
||||||
|
case *rsrp >= -105:
|
||||||
|
score += 1
|
||||||
|
case *rsrp >= -115:
|
||||||
|
score -= 1
|
||||||
|
coverageBad = true
|
||||||
|
default:
|
||||||
|
score -= 2
|
||||||
|
coverageBad = true
|
||||||
|
blockedBad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsrq != nil {
|
||||||
|
seen++
|
||||||
|
switch {
|
||||||
|
case *rsrq >= -10:
|
||||||
|
score += 2
|
||||||
|
case *rsrq >= -15:
|
||||||
|
score += 1
|
||||||
|
case *rsrq >= -18:
|
||||||
|
score -= 1
|
||||||
|
interferenceBad = true
|
||||||
|
default:
|
||||||
|
score -= 2
|
||||||
|
interferenceBad = true
|
||||||
|
unstableBad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sinr != nil {
|
||||||
|
seen++
|
||||||
|
switch {
|
||||||
|
case *sinr >= 20:
|
||||||
|
score += 2
|
||||||
|
case *sinr >= 13:
|
||||||
|
score += 1
|
||||||
|
case *sinr >= 5:
|
||||||
|
score -= 1
|
||||||
|
interferenceBad = true
|
||||||
|
default:
|
||||||
|
score -= 2
|
||||||
|
interferenceBad = true
|
||||||
|
unstableBad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rssiValue, ok := parseRSSI(rssi); ok {
|
||||||
|
seen++
|
||||||
|
switch {
|
||||||
|
case rssiValue >= -65:
|
||||||
|
score += 2
|
||||||
|
case rssiValue >= -75:
|
||||||
|
score += 1
|
||||||
|
case rssiValue >= -85:
|
||||||
|
score -= 1
|
||||||
|
coverageBad = true
|
||||||
|
default:
|
||||||
|
score -= 2
|
||||||
|
coverageBad = true
|
||||||
|
blockedBad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen == 0 {
|
||||||
|
return signalQualityNoData, signalReasonUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
quality := signalQualityNormal
|
||||||
|
switch {
|
||||||
|
case score >= 6:
|
||||||
|
quality = signalQualityExcellent
|
||||||
|
case score >= 2:
|
||||||
|
quality = signalQualityNormal
|
||||||
|
case score >= -2:
|
||||||
|
quality = signalQualityWeak
|
||||||
|
default:
|
||||||
|
quality = signalQualityPoor
|
||||||
|
}
|
||||||
|
|
||||||
|
if quality == signalQualityExcellent || quality == signalQualityNormal {
|
||||||
|
return quality, signalReasonUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case blockedBad:
|
||||||
|
return quality, signalReasonBlocked
|
||||||
|
case interferenceBad:
|
||||||
|
return quality, signalReasonInterference
|
||||||
|
case coverageBad:
|
||||||
|
return quality, signalReasonWeakCoverage
|
||||||
|
case unstableBad:
|
||||||
|
return quality, signalReasonUnstable
|
||||||
|
default:
|
||||||
|
return quality, signalReasonUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRSSI(rssi *string) (int, bool) {
|
||||||
|
if rssi == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
value := strings.TrimSpace(*rssi)
|
||||||
|
if value == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
sign := 1
|
||||||
|
number := 0
|
||||||
|
started := false
|
||||||
|
for i, ch := range value {
|
||||||
|
if ch == '-' && i == 0 {
|
||||||
|
sign = -1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch >= '0' && ch <= '9' {
|
||||||
|
started = true
|
||||||
|
number = number*10 + int(ch-'0')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if started {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !started {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return sign * number, true
|
||||||
|
}
|
||||||
@@ -5,22 +5,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var snapshotFieldSet = map[string]struct{}{
|
var snapshotFieldSet = map[string]struct{}{
|
||||||
"id": {},
|
"id": {},
|
||||||
"virtual_no": {},
|
"virtual_no": {},
|
||||||
"iccid": {},
|
"device_virtual_no": {},
|
||||||
"imei": {},
|
"iccid": {},
|
||||||
"sn": {},
|
"imei": {},
|
||||||
"msisdn": {},
|
"sn": {},
|
||||||
"shop_id": {},
|
"msisdn": {},
|
||||||
"status": {},
|
"shop_id": {},
|
||||||
"series_id": {},
|
"status": {},
|
||||||
"enable_polling": {},
|
"series_id": {},
|
||||||
"realname_policy": {},
|
"enable_polling": {},
|
||||||
"real_name_status": {},
|
"realname_policy": {},
|
||||||
"network_status": {},
|
"real_name_status": {},
|
||||||
"stop_reason": {},
|
"network_status": {},
|
||||||
"deleted": {},
|
"stop_reason": {},
|
||||||
"asset_status": {},
|
"deleted": {},
|
||||||
|
"asset_status": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeOperationContent 将原始 before/after 数据归一化为“操作内容”。
|
// normalizeOperationContent 将原始 before/after 数据归一化为“操作内容”。
|
||||||
@@ -250,34 +251,49 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
|||||||
switch strings.TrimSpace(operationType) {
|
switch strings.TrimSpace(operationType) {
|
||||||
case "card_allocate", "device_allocate":
|
case "card_allocate", "device_allocate":
|
||||||
return mergeFieldDesc(common, map[string]string{
|
return mergeFieldDesc(common, map[string]string{
|
||||||
"target_shop_id": "目标店铺ID",
|
"target_shop_id": "目标店铺ID",
|
||||||
"to_shop_id": "目标店铺ID",
|
"target_shop_name": "目标店铺名称",
|
||||||
"device_ids": "设备ID列表",
|
"to_shop_id": "目标店铺ID",
|
||||||
"card_ids": "卡ID列表",
|
"to_shop_name": "目标店铺名称",
|
||||||
"selection_type": "选择方式",
|
"device_ids": "设备ID列表",
|
||||||
"new_status": "变更后状态",
|
"device_virtual_nos": "设备虚拟号列表",
|
||||||
|
"card_ids": "卡ID列表",
|
||||||
|
"iccids": "卡ICCID列表",
|
||||||
|
"selection_type": "选择方式",
|
||||||
|
"new_status": "变更后状态",
|
||||||
})
|
})
|
||||||
case "card_recall", "device_recall":
|
case "card_recall", "device_recall":
|
||||||
return mergeFieldDesc(common, map[string]string{
|
return mergeFieldDesc(common, map[string]string{
|
||||||
"source_shop_id": "来源店铺ID",
|
"source_shop_id": "来源店铺ID",
|
||||||
"target_shop": "回收后归属店铺(空表示平台库存)",
|
"source_shop_name": "来源店铺名称",
|
||||||
"device_ids": "设备ID列表",
|
"target_shop": "回收后归属店铺ID(空表示平台库存)",
|
||||||
"card_ids": "卡ID列表",
|
"target_shop_name": "回收后归属店铺名称",
|
||||||
"selection_type": "选择方式",
|
"device_ids": "设备ID列表",
|
||||||
"new_status": "变更后状态",
|
"device_virtual_nos": "设备虚拟号列表",
|
||||||
|
"card_ids": "卡ID列表",
|
||||||
|
"iccids": "卡ICCID列表",
|
||||||
|
"selection_type": "选择方式",
|
||||||
|
"new_status": "变更后状态",
|
||||||
})
|
})
|
||||||
case "device_bind_card":
|
case "device_bind_card":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"binding_id": "绑定记录ID",
|
"binding_id": "绑定记录ID",
|
||||||
"slot_position": "插槽位置",
|
"slot_position": "插槽位置",
|
||||||
"iot_card_id": "卡ID",
|
"device_id": "设备ID",
|
||||||
"iccid": "卡ICCID",
|
"device_virtual_no": "设备虚拟号",
|
||||||
|
"device_imei": "设备IMEI",
|
||||||
|
"iot_card_id": "卡ID",
|
||||||
|
"iccid": "卡ICCID",
|
||||||
}
|
}
|
||||||
case "device_unbind_card":
|
case "device_unbind_card":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"binding_id": "绑定记录ID",
|
"binding_id": "绑定记录ID",
|
||||||
"iot_card_id": "卡ID",
|
"device_id": "设备ID",
|
||||||
"unbind": "是否执行解绑",
|
"device_virtual_no": "设备虚拟号",
|
||||||
|
"device_imei": "设备IMEI",
|
||||||
|
"iot_card_id": "卡ID",
|
||||||
|
"iccid": "卡ICCID",
|
||||||
|
"unbind": "是否执行解绑",
|
||||||
}
|
}
|
||||||
case "device_stop", "device_start":
|
case "device_stop", "device_start":
|
||||||
return mergeFieldDesc(common, map[string]string{
|
return mergeFieldDesc(common, map[string]string{
|
||||||
@@ -297,7 +313,9 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
|||||||
}
|
}
|
||||||
case "device_switch_card":
|
case "device_switch_card":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"target_iccid": "目标ICCID",
|
"device_virtual_no": "设备虚拟号",
|
||||||
|
"device_imei": "设备IMEI",
|
||||||
|
"target_iccid": "目标ICCID",
|
||||||
}
|
}
|
||||||
case "device_switch_mode":
|
case "device_switch_mode":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
@@ -312,9 +330,11 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
|||||||
"action": "设备恢复出厂动作",
|
"action": "设备恢复出厂动作",
|
||||||
}
|
}
|
||||||
case "asset_polling_status", "card_polling_status":
|
case "asset_polling_status", "card_polling_status":
|
||||||
return map[string]string{
|
return mergeFieldDesc(common, map[string]string{
|
||||||
"enable_polling": "轮询开关(true启用/false禁用)",
|
"enable_polling": "轮询开关(true启用/false禁用)",
|
||||||
}
|
"card_ids": "卡ID列表",
|
||||||
|
"iccids": "卡ICCID列表",
|
||||||
|
})
|
||||||
case "asset_realname_policy", "card_realname_policy":
|
case "asset_realname_policy", "card_realname_policy":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"realname_policy": "实名策略(none/before_order/after_order)",
|
"realname_policy": "实名策略(none/before_order/after_order)",
|
||||||
@@ -324,10 +344,13 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
|||||||
"real_name_status": "实名状态(0未实名/1已实名)",
|
"real_name_status": "实名状态(0未实名/1已实名)",
|
||||||
}
|
}
|
||||||
case "asset_deactivate", "card_delete", "card_batch_delete", "device_delete":
|
case "asset_deactivate", "card_delete", "card_batch_delete", "device_delete":
|
||||||
return map[string]string{
|
return mergeFieldDesc(common, map[string]string{
|
||||||
"asset_status": "资产状态",
|
"asset_status": "资产状态",
|
||||||
"deleted": "是否删除",
|
"deleted": "是否删除",
|
||||||
}
|
"iccids": "卡ICCID列表",
|
||||||
|
"device_virtual_nos": "设备虚拟号列表",
|
||||||
|
"device_imei": "设备IMEI",
|
||||||
|
})
|
||||||
case "device_import_task_create", "iot_card_import_task_create":
|
case "device_import_task_create", "iot_card_import_task_create":
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"batch_no": "批次号",
|
"batch_no": "批次号",
|
||||||
@@ -337,7 +360,19 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
|||||||
"card_category": "卡类型(仅卡导入)",
|
"card_category": "卡类型(仅卡导入)",
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return common
|
return mergeFieldDesc(common, map[string]string{
|
||||||
|
"card_id": "卡ID",
|
||||||
|
"iccid": "卡ICCID",
|
||||||
|
"iccids": "卡ICCID列表",
|
||||||
|
"device_id": "设备ID",
|
||||||
|
"device_virtual_no": "设备虚拟号",
|
||||||
|
"device_virtual_nos": "设备虚拟号列表",
|
||||||
|
"device_imei": "设备IMEI",
|
||||||
|
"device_sn": "设备SN",
|
||||||
|
"shop_name": "店铺名称",
|
||||||
|
"target_shop_name": "目标店铺名称",
|
||||||
|
"source_shop_name": "来源店铺名称",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package device
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
@@ -62,6 +63,8 @@ func (s *Service) logDeviceOperation(
|
|||||||
snapshot := deviceSnapshot(device)
|
snapshot := deviceSnapshot(device)
|
||||||
beforeContent := stripDeviceOperationMeta(beforeData)
|
beforeContent := stripDeviceOperationMeta(beforeData)
|
||||||
afterContent := stripDeviceOperationMeta(afterData)
|
afterContent := stripDeviceOperationMeta(afterData)
|
||||||
|
enrichDeviceOperationContent(beforeContent, beforeData)
|
||||||
|
enrichDeviceOperationContent(afterContent, afterData)
|
||||||
params.BeforeData, params.AfterData = assetAuditSvc.WrapOperationContent(beforeContent, afterContent, snapshot)
|
params.BeforeData, params.AfterData = assetAuditSvc.WrapOperationContent(beforeContent, afterContent, snapshot)
|
||||||
|
|
||||||
if device != nil {
|
if device != nil {
|
||||||
@@ -81,7 +84,7 @@ func stripDeviceOperationMeta(data map[string]any) map[string]any {
|
|||||||
}
|
}
|
||||||
out := make(map[string]any, len(data))
|
out := make(map[string]any, len(data))
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
if k == "device" || k == "card" || k == "cards" || k == "asset_snapshot" || k == "operation_content" {
|
if k == "device" || k == "devices" || k == "card" || k == "cards" || k == "asset_snapshot" || k == "operation_content" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out[k] = v
|
out[k] = v
|
||||||
@@ -92,6 +95,116 @@ func stripDeviceOperationMeta(data map[string]any) map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enrichDeviceOperationContent(content map[string]any, raw map[string]any) {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if deviceRaw, ok := raw["device"].(map[string]any); ok {
|
||||||
|
mergeReadableDeviceFields(content, deviceRaw)
|
||||||
|
}
|
||||||
|
if cardRaw, ok := raw["card"].(map[string]any); ok {
|
||||||
|
mergeReadableCardFields(content, cardRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch devicesRaw := raw["devices"].(type) {
|
||||||
|
case []map[string]any:
|
||||||
|
deviceIDs, virtualNos := collectDeviceReadableLists(devicesRaw)
|
||||||
|
if len(deviceIDs) > 0 && content["device_ids"] == nil {
|
||||||
|
content["device_ids"] = deviceIDs
|
||||||
|
}
|
||||||
|
if len(virtualNos) > 0 && content["device_virtual_nos"] == nil {
|
||||||
|
content["device_virtual_nos"] = virtualNos
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
devices := make([]map[string]any, 0, len(devicesRaw))
|
||||||
|
for _, item := range devicesRaw {
|
||||||
|
deviceMap, ok := item.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
devices = append(devices, deviceMap)
|
||||||
|
}
|
||||||
|
deviceIDs, virtualNos := collectDeviceReadableLists(devices)
|
||||||
|
if len(deviceIDs) > 0 && content["device_ids"] == nil {
|
||||||
|
content["device_ids"] = deviceIDs
|
||||||
|
}
|
||||||
|
if len(virtualNos) > 0 && content["device_virtual_nos"] == nil {
|
||||||
|
content["device_virtual_nos"] = virtualNos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeReadableDeviceFields(content map[string]any, device map[string]any) {
|
||||||
|
if len(device) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := content["device_id"]; !ok {
|
||||||
|
if v, exists := device["id"]; exists {
|
||||||
|
content["device_id"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_virtual_no"]; !ok {
|
||||||
|
if v := stringifyDeviceAuditValue(device["virtual_no"]); v != "" {
|
||||||
|
content["device_virtual_no"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_imei"]; !ok {
|
||||||
|
if v := stringifyDeviceAuditValue(device["imei"]); v != "" {
|
||||||
|
content["device_imei"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_sn"]; !ok {
|
||||||
|
if v := stringifyDeviceAuditValue(device["sn"]); v != "" {
|
||||||
|
content["device_sn"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeReadableCardFields(content map[string]any, card map[string]any) {
|
||||||
|
if len(card) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := content["iot_card_id"]; !ok {
|
||||||
|
if v, exists := card["id"]; exists {
|
||||||
|
content["iot_card_id"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["iccid"]; !ok {
|
||||||
|
if v := stringifyDeviceAuditValue(card["iccid"]); v != "" {
|
||||||
|
content["iccid"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectDeviceReadableLists(devices []map[string]any) ([]any, []string) {
|
||||||
|
deviceIDs := make([]any, 0, len(devices))
|
||||||
|
virtualNos := make([]string, 0, len(devices))
|
||||||
|
for _, device := range devices {
|
||||||
|
if id, ok := device["id"]; ok {
|
||||||
|
deviceIDs = append(deviceIDs, id)
|
||||||
|
}
|
||||||
|
if virtualNo := stringifyDeviceAuditValue(device["virtual_no"]); virtualNo != "" {
|
||||||
|
virtualNos = append(virtualNos, virtualNo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceIDs, virtualNos
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyDeviceAuditValue(v any) string {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case string:
|
||||||
|
return vv
|
||||||
|
case fmt.Stringer:
|
||||||
|
return vv.String()
|
||||||
|
default:
|
||||||
|
if vv == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprint(vv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func deviceSnapshot(device *model.Device) map[string]any {
|
func deviceSnapshot(device *model.Device) map[string]any {
|
||||||
if device == nil {
|
if device == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -99,6 +212,8 @@ func deviceSnapshot(device *model.Device) map[string]any {
|
|||||||
return map[string]any{
|
return map[string]any{
|
||||||
"id": device.ID,
|
"id": device.ID,
|
||||||
"virtual_no": device.VirtualNo,
|
"virtual_no": device.VirtualNo,
|
||||||
|
"imei": device.IMEI,
|
||||||
|
"sn": device.SN,
|
||||||
"shop_id": device.ShopID,
|
"shop_id": device.ShopID,
|
||||||
"status": device.Status,
|
"status": device.Status,
|
||||||
"series_id": device.SeriesID,
|
"series_id": device.SeriesID,
|
||||||
|
|||||||
@@ -450,13 +450,16 @@ func (s *Service) AllocateDevices(ctx context.Context, req *dto.AllocateDevicesR
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeData := make([]map[string]any, 0, len(devices))
|
|
||||||
for _, device := range devices {
|
|
||||||
beforeData = append(beforeData, deviceSnapshot(device))
|
|
||||||
}
|
|
||||||
|
|
||||||
newStatus := constants.DeviceStatusDistributed
|
newStatus := constants.DeviceStatusDistributed
|
||||||
targetShopID := req.TargetShopID
|
targetShopID := req.TargetShopID
|
||||||
|
targetShopName := s.getAuditShopName(ctx, &targetShopID)
|
||||||
|
shopMap := s.loadShopData(ctx, devices)
|
||||||
|
beforeData := make([]map[string]any, 0, len(devices))
|
||||||
|
for _, device := range devices {
|
||||||
|
snapshot := deviceSnapshot(device)
|
||||||
|
snapshot["shop_name"] = deviceShopMapValue(shopMap, device.ShopID)
|
||||||
|
beforeData = append(beforeData, snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||||
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
||||||
@@ -492,8 +495,9 @@ func (s *Service) AllocateDevices(ctx context.Context, req *dto.AllocateDevicesR
|
|||||||
nil,
|
nil,
|
||||||
map[string]any{"devices": beforeData},
|
map[string]any{"devices": beforeData},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"target_shop_id": req.TargetShopID,
|
"target_shop_id": req.TargetShopID,
|
||||||
"failed_items": failedItems,
|
"target_shop_name": targetShopName,
|
||||||
|
"failed_items": failedItems,
|
||||||
},
|
},
|
||||||
len(devices),
|
len(devices),
|
||||||
len(deviceIDs),
|
len(deviceIDs),
|
||||||
@@ -511,9 +515,10 @@ func (s *Service) AllocateDevices(ctx context.Context, req *dto.AllocateDevicesR
|
|||||||
nil,
|
nil,
|
||||||
map[string]any{"devices": beforeData},
|
map[string]any{"devices": beforeData},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"target_shop_id": req.TargetShopID,
|
"target_shop_id": req.TargetShopID,
|
||||||
"device_ids": deviceIDs,
|
"target_shop_name": targetShopName,
|
||||||
"failed_items": failedItems,
|
"device_ids": deviceIDs,
|
||||||
|
"failed_items": failedItems,
|
||||||
},
|
},
|
||||||
len(devices),
|
len(devices),
|
||||||
len(deviceIDs),
|
len(deviceIDs),
|
||||||
@@ -629,11 +634,6 @@ func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesReque
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeData := make([]map[string]any, 0, len(devices))
|
|
||||||
for _, device := range devices {
|
|
||||||
beforeData = append(beforeData, deviceSnapshot(device))
|
|
||||||
}
|
|
||||||
|
|
||||||
var newShopID *uint
|
var newShopID *uint
|
||||||
var newStatus int
|
var newStatus int
|
||||||
if isPlatform {
|
if isPlatform {
|
||||||
@@ -643,6 +643,14 @@ func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesReque
|
|||||||
newShopID = operatorShopID
|
newShopID = operatorShopID
|
||||||
newStatus = constants.DeviceStatusDistributed
|
newStatus = constants.DeviceStatusDistributed
|
||||||
}
|
}
|
||||||
|
targetShopName := s.getAuditRecallTargetShopName(ctx, newShopID)
|
||||||
|
shopMap := s.loadShopData(ctx, devices)
|
||||||
|
beforeData := make([]map[string]any, 0, len(devices))
|
||||||
|
for _, device := range devices {
|
||||||
|
snapshot := deviceSnapshot(device)
|
||||||
|
snapshot["shop_name"] = deviceShopMapValue(shopMap, device.ShopID)
|
||||||
|
beforeData = append(beforeData, snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||||
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
txDeviceStore := postgres.NewDeviceStore(tx, nil)
|
||||||
@@ -684,8 +692,9 @@ func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesReque
|
|||||||
nil,
|
nil,
|
||||||
map[string]any{"devices": beforeData},
|
map[string]any{"devices": beforeData},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"device_ids": deviceIDs,
|
"device_ids": deviceIDs,
|
||||||
"failed_items": failedItems,
|
"failed_items": failedItems,
|
||||||
|
"target_shop_name": targetShopName,
|
||||||
},
|
},
|
||||||
len(devices),
|
len(devices),
|
||||||
len(deviceIDs),
|
len(deviceIDs),
|
||||||
@@ -703,9 +712,10 @@ func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesReque
|
|||||||
nil,
|
nil,
|
||||||
map[string]any{"devices": beforeData},
|
map[string]any{"devices": beforeData},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"device_ids": deviceIDs,
|
"device_ids": deviceIDs,
|
||||||
"failed_items": failedItems,
|
"failed_items": failedItems,
|
||||||
"target_shop": newShopID,
|
"target_shop": newShopID,
|
||||||
|
"target_shop_name": targetShopName,
|
||||||
},
|
},
|
||||||
len(devices),
|
len(devices),
|
||||||
len(deviceIDs),
|
len(deviceIDs),
|
||||||
@@ -748,6 +758,31 @@ func (s *Service) validateDirectSubordinate(ctx context.Context, operatorShopID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) getAuditShopName(ctx context.Context, shopID *uint) string {
|
||||||
|
if shopID == nil || *shopID == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var shop model.Shop
|
||||||
|
if err := s.db.WithContext(ctx).Unscoped().First(&shop, *shopID).Error; err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return shop.ShopName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getAuditRecallTargetShopName(ctx context.Context, shopID *uint) string {
|
||||||
|
if shopID == nil {
|
||||||
|
return "平台库存"
|
||||||
|
}
|
||||||
|
return s.getAuditShopName(ctx, shopID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deviceShopMapValue(shopMap map[uint]string, shopID *uint) string {
|
||||||
|
if shopID == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return shopMap[*shopID]
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) loadShopData(ctx context.Context, devices []*model.Device) map[uint]string {
|
func (s *Service) loadShopData(ctx context.Context, devices []*model.Device) map[uint]string {
|
||||||
shopIDs := make([]uint, 0)
|
shopIDs := make([]uint, 0)
|
||||||
shopIDSet := make(map[uint]bool)
|
shopIDSet := make(map[uint]bool)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package iot_card
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||||
@@ -55,7 +56,11 @@ func normalizeCardAuditPayload(beforeData, afterData map[string]any) (map[string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assetAuditSvc.WrapOperationContent(stripCardOperationMeta(beforeData), stripCardOperationMeta(afterData), snapshot)
|
beforeContent := stripCardOperationMeta(beforeData)
|
||||||
|
afterContent := stripCardOperationMeta(afterData)
|
||||||
|
enrichCardOperationContent(beforeContent, beforeData)
|
||||||
|
enrichCardOperationContent(afterContent, afterData)
|
||||||
|
return assetAuditSvc.WrapOperationContent(beforeContent, afterContent, snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripCardOperationMeta(data map[string]any) map[string]any {
|
func stripCardOperationMeta(data map[string]any) map[string]any {
|
||||||
@@ -75,20 +80,136 @@ func stripCardOperationMeta(data map[string]any) map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enrichCardOperationContent(content map[string]any, raw map[string]any) {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cardRaw, ok := raw["card"].(map[string]any); ok {
|
||||||
|
mergeReadableCardFields(content, cardRaw)
|
||||||
|
}
|
||||||
|
if deviceRaw, ok := raw["device"].(map[string]any); ok {
|
||||||
|
mergeReadableDeviceFields(content, deviceRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cardsRaw := raw["cards"].(type) {
|
||||||
|
case []map[string]any:
|
||||||
|
cardIDs, iccids := collectCardReadableLists(cardsRaw)
|
||||||
|
if len(cardIDs) > 0 && content["card_ids"] == nil {
|
||||||
|
content["card_ids"] = cardIDs
|
||||||
|
}
|
||||||
|
if len(iccids) > 0 && content["iccids"] == nil {
|
||||||
|
content["iccids"] = iccids
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
cards := make([]map[string]any, 0, len(cardsRaw))
|
||||||
|
for _, item := range cardsRaw {
|
||||||
|
cardMap, ok := item.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cards = append(cards, cardMap)
|
||||||
|
}
|
||||||
|
cardIDs, iccids := collectCardReadableLists(cards)
|
||||||
|
if len(cardIDs) > 0 && content["card_ids"] == nil {
|
||||||
|
content["card_ids"] = cardIDs
|
||||||
|
}
|
||||||
|
if len(iccids) > 0 && content["iccids"] == nil {
|
||||||
|
content["iccids"] = iccids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeReadableCardFields(content map[string]any, card map[string]any) {
|
||||||
|
if len(card) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := content["card_id"]; !ok {
|
||||||
|
if v, exists := card["id"]; exists {
|
||||||
|
content["card_id"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["iccid"]; !ok {
|
||||||
|
if v := stringifyCardAuditValue(card["iccid"]); v != "" {
|
||||||
|
content["iccid"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_virtual_no"]; !ok {
|
||||||
|
if v := stringifyCardAuditValue(card["device_virtual_no"]); v != "" {
|
||||||
|
content["device_virtual_no"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeReadableDeviceFields(content map[string]any, device map[string]any) {
|
||||||
|
if len(device) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := content["device_id"]; !ok {
|
||||||
|
if v, exists := device["id"]; exists {
|
||||||
|
content["device_id"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_virtual_no"]; !ok {
|
||||||
|
if v := stringifyCardAuditValue(device["virtual_no"]); v != "" {
|
||||||
|
content["device_virtual_no"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_imei"]; !ok {
|
||||||
|
if v := stringifyCardAuditValue(device["imei"]); v != "" {
|
||||||
|
content["device_imei"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := content["device_sn"]; !ok {
|
||||||
|
if v := stringifyCardAuditValue(device["sn"]); v != "" {
|
||||||
|
content["device_sn"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectCardReadableLists(cards []map[string]any) ([]any, []string) {
|
||||||
|
cardIDs := make([]any, 0, len(cards))
|
||||||
|
iccids := make([]string, 0, len(cards))
|
||||||
|
for _, card := range cards {
|
||||||
|
if id, ok := card["id"]; ok {
|
||||||
|
cardIDs = append(cardIDs, id)
|
||||||
|
}
|
||||||
|
if iccid := stringifyCardAuditValue(card["iccid"]); iccid != "" {
|
||||||
|
iccids = append(iccids, iccid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cardIDs, iccids
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyCardAuditValue(v any) string {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case string:
|
||||||
|
return vv
|
||||||
|
case fmt.Stringer:
|
||||||
|
return vv.String()
|
||||||
|
default:
|
||||||
|
if vv == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprint(vv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cardSnapshot(card *model.IotCard) map[string]any {
|
func cardSnapshot(card *model.IotCard) map[string]any {
|
||||||
if card == nil {
|
if card == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"id": card.ID,
|
"id": card.ID,
|
||||||
"iccid": card.ICCID,
|
"iccid": card.ICCID,
|
||||||
"shop_id": card.ShopID,
|
"device_virtual_no": card.DeviceVirtualNo,
|
||||||
"status": card.Status,
|
"shop_id": card.ShopID,
|
||||||
"series_id": card.SeriesID,
|
"status": card.Status,
|
||||||
"enable_polling": card.EnablePolling,
|
"series_id": card.SeriesID,
|
||||||
"realname_policy": card.RealnamePolicy,
|
"enable_polling": card.EnablePolling,
|
||||||
"real_name_status": card.RealNameStatus,
|
"realname_policy": card.RealnamePolicy,
|
||||||
"network_status": card.NetworkStatus,
|
"real_name_status": card.RealNameStatus,
|
||||||
"stop_reason": card.StopReason,
|
"network_status": card.NetworkStatus,
|
||||||
|
"stop_reason": card.StopReason,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,13 +494,16 @@ func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandalone
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shopMap := s.loadShopNames(ctx, cards)
|
||||||
|
targetShopName := s.getAuditShopName(ctx, &req.ToShopID)
|
||||||
beforeData := make([]map[string]any, 0, len(cards))
|
beforeData := make([]map[string]any, 0, len(cards))
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
beforeData = append(beforeData, map[string]any{
|
beforeData = append(beforeData, map[string]any{
|
||||||
"id": card.ID,
|
"id": card.ID,
|
||||||
"iccid": card.ICCID,
|
"iccid": card.ICCID,
|
||||||
"shop_id": card.ShopID,
|
"shop_id": card.ShopID,
|
||||||
"status": card.Status,
|
"shop_name": shopMapValue(shopMap, card.ShopID),
|
||||||
|
"status": card.Status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||||
@@ -515,6 +518,7 @@ func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandalone
|
|||||||
},
|
},
|
||||||
AfterData: map[string]any{
|
AfterData: map[string]any{
|
||||||
"to_shop_id": req.ToShopID,
|
"to_shop_id": req.ToShopID,
|
||||||
|
"to_shop_name": targetShopName,
|
||||||
"new_status": newStatus,
|
"new_status": newStatus,
|
||||||
"failed_items": failedItems,
|
"failed_items": failedItems,
|
||||||
},
|
},
|
||||||
@@ -702,13 +706,16 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shopMap := s.loadShopNames(ctx, successCards)
|
||||||
|
targetShopName := s.getAuditRecallTargetShopName(ctx, newShopID)
|
||||||
beforeData := make([]map[string]any, 0, len(successCards))
|
beforeData := make([]map[string]any, 0, len(successCards))
|
||||||
for _, card := range successCards {
|
for _, card := range successCards {
|
||||||
beforeData = append(beforeData, map[string]any{
|
beforeData = append(beforeData, map[string]any{
|
||||||
"id": card.ID,
|
"id": card.ID,
|
||||||
"iccid": card.ICCID,
|
"iccid": card.ICCID,
|
||||||
"shop_id": card.ShopID,
|
"shop_id": card.ShopID,
|
||||||
"status": card.Status,
|
"shop_name": shopMapValue(shopMap, card.ShopID),
|
||||||
|
"status": card.Status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||||
@@ -722,9 +729,10 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
|||||||
"cards": beforeData,
|
"cards": beforeData,
|
||||||
},
|
},
|
||||||
AfterData: map[string]any{
|
AfterData: map[string]any{
|
||||||
"to_shop_id": newShopID,
|
"to_shop_id": newShopID,
|
||||||
"new_status": newStatus,
|
"target_shop_name": targetShopName,
|
||||||
"failed_items": failedItems,
|
"new_status": newStatus,
|
||||||
|
"failed_items": failedItems,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -774,6 +782,31 @@ func (s *Service) validateDirectSubordinate(ctx context.Context, operatorShopID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) getAuditShopName(ctx context.Context, shopID *uint) string {
|
||||||
|
if shopID == nil || *shopID == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var shop model.Shop
|
||||||
|
if err := s.db.WithContext(ctx).Unscoped().First(&shop, *shopID).Error; err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return shop.ShopName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getAuditRecallTargetShopName(ctx context.Context, shopID *uint) string {
|
||||||
|
if shopID == nil {
|
||||||
|
return "平台库存"
|
||||||
|
}
|
||||||
|
return s.getAuditShopName(ctx, shopID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shopMapValue(shopMap map[uint]string, shopID *uint) string {
|
||||||
|
if shopID == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return shopMap[*shopID]
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) getCardsForAllocation(ctx context.Context, req *dto.AllocateStandaloneCardsRequest, operatorShopID *uint) ([]*model.IotCard, error) {
|
func (s *Service) getCardsForAllocation(ctx context.Context, req *dto.AllocateStandaloneCardsRequest, operatorShopID *uint) ([]*model.IotCard, error) {
|
||||||
switch req.SelectionType {
|
switch req.SelectionType {
|
||||||
case dto.SelectionTypeList:
|
case dto.SelectionTypeList:
|
||||||
@@ -1443,9 +1476,9 @@ func (s *Service) BatchUpdatePollingStatus(ctx context.Context, cardIDs []uint,
|
|||||||
BatchTotal: len(cardIDs),
|
BatchTotal: len(cardIDs),
|
||||||
FailCount: len(cardIDs),
|
FailCount: len(cardIDs),
|
||||||
AfterData: map[string]any{
|
AfterData: map[string]any{
|
||||||
"card_ids": cardIDs,
|
"card_ids": cardIDs,
|
||||||
"enable_polling": enablePolling,
|
"enable_polling": enablePolling,
|
||||||
"trigger_source": "batch",
|
"trigger_source": "batch",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
@@ -1857,11 +1890,11 @@ func (s *Service) ManualUpdateRealnameStatus(ctx context.Context, cardID uint, r
|
|||||||
AssetID: freshCard.ID,
|
AssetID: freshCard.ID,
|
||||||
AssetIdentifier: freshCard.ICCID,
|
AssetIdentifier: freshCard.ICCID,
|
||||||
BeforeData: map[string]any{
|
BeforeData: map[string]any{
|
||||||
"real_name_status": oldStatus,
|
"real_name_status": oldStatus,
|
||||||
"first_realname_at": card.FirstRealnameAt,
|
"first_realname_at": card.FirstRealnameAt,
|
||||||
},
|
},
|
||||||
AfterData: map[string]any{
|
AfterData: map[string]any{
|
||||||
"real_name_status": freshCard.RealNameStatus,
|
"real_name_status": freshCard.RealNameStatus,
|
||||||
"first_realname_at": freshCard.FirstRealnameAt,
|
"first_realname_at": freshCard.FirstRealnameAt,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user