1065 lines
39 KiB
Go
1065 lines
39 KiB
Go
// Package gen
|
||
// @Link https://github.com/xiujiecn/xiu-admin
|
||
// @Copyright Copyright (c) 2026 LiXiujie
|
||
// @Author Lxj <xxx@example.com>
|
||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||
// @AutoGenerate Version
|
||
package iot
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/text"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/exportcenter"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/cardflow"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/trafficcutoffconfig"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||
"strings"
|
||
|
||
"github.com/gogf/gf/v2/database/gdb"
|
||
"github.com/gogf/gf/v2/encoding/gjson"
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
)
|
||
|
||
type sIotTSimCardInfo struct{}
|
||
|
||
const (
|
||
simFlowInternalErrorCode = 9999
|
||
simFlowUpstreamUnavailableMessage = "同步卡流量失败,请稍后重试!"
|
||
|
||
// 移动 OneLink 在自身繁忙时会对逐卡查询随机返回该状态,实测占比接近 50%,
|
||
// 属上游侧瞬时状态,与我方系统无关,因此对用户展示为上游服务的提示。
|
||
simUpstreamBusyCode = 13000
|
||
simUpstreamBusyMessage = "上游系统服务忙,请稍后再试"
|
||
simUpstreamBusyForbiddenMessage = "上游系统禁止访问,请稍后再试"
|
||
)
|
||
|
||
func NewIotTSimCardInfo() *sIotTSimCardInfo {
|
||
return &sIotTSimCardInfo{}
|
||
}
|
||
|
||
func init() {
|
||
service.RegisterIotTSimCardInfo(NewIotTSimCardInfo())
|
||
}
|
||
|
||
// Model 卡信息表ORM模型
|
||
func (s *sIotTSimCardInfo) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||
if len(option) == 0 {
|
||
option = append(option, &handler.Option{
|
||
FilterTenant: true,
|
||
FilterAuth: true,
|
||
})
|
||
}
|
||
return handler.Model(dao.TSimCardInfo.Ctx(ctx), option...)
|
||
}
|
||
|
||
// List 获取卡信息表列表
|
||
func (s *sIotTSimCardInfo) List(ctx context.Context, in *model.TSimCardInfoListParam) (list []*model.TSimCardInfoListModel, totalCount int, err error) {
|
||
var batchCondition string
|
||
var batchArgs []interface{}
|
||
if in.BatchInput != nil {
|
||
batchCondition, batchArgs, err = simCardBatchCondition(ctx, *in.BatchInput)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
}
|
||
mod := s.Model(ctx)
|
||
cardInfoTable := dao.TSimCardInfo.Table()
|
||
simChannels := dao.TSimChannels.Table()
|
||
sysDept := dao.SysDept.Table()
|
||
flowPackage := dao.TSimFlowPackageInfo.Table()
|
||
BatchMain := dao.BatchMain.Table()
|
||
|
||
// 字段过滤
|
||
mod = mod.FieldsPrefix(cardInfoTable, model.TSimCardInfoListModel{})
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"COALESCE(NULLIF(%s.%s, ''), %s.%s) as chanName, %s.%s as chanAlias",
|
||
simChannels,
|
||
dao.TSimChannels.Columns().ChanName,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().ChanKey,
|
||
simChannels,
|
||
dao.TSimChannels.Columns().ChanAlias,
|
||
))
|
||
// 部门名称改用标量子查询,不再 JOIN sys_dept。
|
||
// 原因:sys_dept 带 deleted_at 软删除字段,LEFT JOIN 后框架会把 `sys_dept.deleted_at IS NULL`
|
||
// 追加到 WHERE,使优化器对 sys_dept 选用 hash join;hash join 不保证输出行序,
|
||
// 导致 `ORDER BY id DESC` 无法借索引序提前终止,只能对全表 19 万行 filesort(生产实测 7.3s)。
|
||
// 标量子查询只对 LIMIT 后的 20 行求值,开销可忽略。
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"(SELECT d.%s FROM %s AS d WHERE d.%s = %s.%s AND d.%s IS NULL) as deptName",
|
||
dao.SysDept.Columns().DeptName,
|
||
sysDept,
|
||
dao.SysDept.Columns().DeptId,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().DeptId,
|
||
dao.SysDept.Columns().DeletedAt,
|
||
))
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"COALESCE(NULLIF(%s.%s, ''), %s.%s) as packageName",
|
||
flowPackage,
|
||
dao.TSimFlowPackageInfo.Columns().PackageName,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().CurrentPackageCode,
|
||
))
|
||
mod = mod.Fields(cardflow.RemainFlowExpr(cardInfoTable, flowPackage) + " as reminFlowMb")
|
||
if in.DeptId != 0 {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().DeptId, in.DeptId)
|
||
}
|
||
|
||
if in.CardValue != "" {
|
||
cardValueCondition := "(" + dao.TSimCardInfo.Columns().Msisdn + " = ? OR " +
|
||
dao.TSimCardInfo.Columns().Iccid + " = ? OR " +
|
||
dao.TSimCardInfo.Columns().Imsi + " = ?)"
|
||
mod = mod.Where(cardValueCondition, in.CardValue, in.CardValue, in.CardValue)
|
||
}
|
||
if batchCondition != "" {
|
||
mod = mod.Where(batchCondition, batchArgs...)
|
||
}
|
||
if in.Remark != "" {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Remark+" LIKE ?", "%"+in.Remark+"%")
|
||
}
|
||
if in.Imei != "" {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Imei, in.Imei)
|
||
}
|
||
if len(in.TrafficCutoffStatus) > 0 {
|
||
mod = mod.WhereIn(dao.TSimCardInfo.Columns().TrafficCutoffStatus, in.TrafficCutoffStatus)
|
||
}
|
||
|
||
// 查询运营商卡状态
|
||
if in.BossCardStatus != nil {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().BossCardStatus, in.BossCardStatus)
|
||
}
|
||
|
||
// 查询时间类型
|
||
if in.TimeType != nil {
|
||
switch *in.TimeType {
|
||
case 0: // 开卡时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().BossOpenAt, in.Time[0], in.Time[1])
|
||
case 1: // 激活时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().BossActivateAt, in.Time[0], in.Time[1])
|
||
case 2: //入库时间
|
||
mod = mod.LeftJoin(BatchMain, BatchMain+"."+dao.BatchMain.Columns().SourceBatchNo+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().InboundBatchNo)
|
||
mod = mod.WhereBetween(BatchMain+"."+dao.BatchMain.Columns().StartTime, in.Time[0], in.Time[1])
|
||
case 3: //出库时间
|
||
mod = mod.LeftJoin(BatchMain, BatchMain+"."+dao.BatchMain.Columns().SourceBatchNo+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().OutboundBatchNo)
|
||
mod = mod.WhereBetween(BatchMain+"."+dao.BatchMain.Columns().StartTime, in.Time[0], in.Time[1])
|
||
case 4: //到期时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().FlowPkgEndAt, in.Time[0], in.Time[1])
|
||
case 5: //创建时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().CreatedAt, in.Time[0], in.Time[1])
|
||
case 6: //流量同步时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().FlowSyncTime, in.Time[0], in.Time[1])
|
||
case 7: //状态同步时间
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().StatusSyncTime, in.Time[0], in.Time[1])
|
||
default:
|
||
mod = mod.WhereBetween(dao.TSimCardInfo.Columns().BossOpenAt, in.Time[0], in.Time[1])
|
||
}
|
||
}
|
||
// 查询查询条件
|
||
if in.Condition != nil {
|
||
switch *in.Condition {
|
||
case "msisdn": // MSISDN
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Msisdn+" >= ?", in.StartNum).
|
||
Where(dao.TSimCardInfo.Columns().Msisdn+" <= ?", in.EndNum)
|
||
case "iccid": // ICCID
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Iccid+" >= ?", in.StartNum).
|
||
Where(dao.TSimCardInfo.Columns().Iccid+" <= ?", in.EndNum)
|
||
case "imis": // IMSI
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Imsi+" >= ?", in.StartNum).
|
||
Where(dao.TSimCardInfo.Columns().Imsi+" <= ?", in.EndNum)
|
||
case "imei": // IMEI
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Imei+" >= ?", in.StartNum).
|
||
Where(dao.TSimCardInfo.Columns().Imei+" <= ?", in.EndNum)
|
||
}
|
||
}
|
||
|
||
// 查询维度
|
||
if in.Dimension != nil && in.DimensionStatus != nil && in.DimensionValue != nil {
|
||
switch *in.Dimension {
|
||
case 1: // 使用流量
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().UsedFlowMb + *in.DimensionStatus + *in.DimensionValue)
|
||
case 2: // 剩余流量
|
||
mod = mod.Where(cardflow.RemainFlowExpr(cardInfoTable, flowPackage) + *in.DimensionStatus + *in.DimensionValue)
|
||
}
|
||
}
|
||
|
||
// 查询排序值
|
||
if in.SortValue != nil && in.SortType != nil {
|
||
var orderStr string
|
||
switch *in.SortValue {
|
||
case 1: // iccid
|
||
orderStr = dao.TSimCardInfo.Columns().Iccid
|
||
case 2: // msisdn
|
||
orderStr = dao.TSimCardInfo.Columns().Msisdn
|
||
case 3: // imsi
|
||
orderStr = dao.TSimCardInfo.Columns().Imsi
|
||
case 4: // 流量用量
|
||
orderStr = dao.TSimCardInfo.Columns().UsedFlowMb
|
||
case 5: // 激活日期
|
||
orderStr = dao.TSimCardInfo.Columns().BossActivateAt
|
||
case 6: // 流量到期日期
|
||
orderStr = dao.TSimCardInfo.Columns().FlowPkgEndAt
|
||
case 7: // 状态同步日期
|
||
orderStr = dao.TSimCardInfo.Columns().StatusSyncTime
|
||
case 8: // 出库时间
|
||
orderStr = dao.TSimCardInfo.Columns().OutStockTime
|
||
}
|
||
if *in.SortType == 0 {
|
||
mod = mod.Order(orderStr + " ASC")
|
||
} else {
|
||
mod = mod.Order(orderStr + " DESC")
|
||
}
|
||
}
|
||
|
||
// 查询通道编号
|
||
if in.ChanKey != nil {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().ChanKey, *in.ChanKey)
|
||
}
|
||
|
||
// 查询当前套餐编码
|
||
if in.CurrentPackageCode != nil {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().CurrentPackageCode, *in.CurrentPackageCode)
|
||
}
|
||
|
||
// 查询IMEI
|
||
if in.Imei != "" {
|
||
mod = mod.Where(dao.TSimCardInfo.Columns().Imei, in.Imei)
|
||
}
|
||
|
||
// 分页
|
||
mod = mod.Page(in.Page, in.PageSize)
|
||
|
||
// 排序
|
||
mod = mod.OrderDesc(dao.TSimCardInfo.Columns().Id)
|
||
// 部门软删除过滤改用反连接(NOT EXISTS),不再 JOIN sys_dept。
|
||
// 语义与原 LEFT JOIN + WHERE dept.deleted_at IS NULL 完全等价(已被软删除的部门下的卡同样不返回),
|
||
// 但不会引入 hash join,优化器得以按主键逆序扫描并在取满 20 行后立即终止。
|
||
mod = mod.Where(fmt.Sprintf(
|
||
"NOT EXISTS (SELECT 1 FROM %s AS d WHERE d.%s = %s.%s AND d.%s IS NOT NULL)",
|
||
sysDept,
|
||
dao.SysDept.Columns().DeptId,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().DeptId,
|
||
dao.SysDept.Columns().DeletedAt,
|
||
))
|
||
mod = mod.LeftJoin(simChannels, simChannels+"."+dao.TSimChannels.Columns().ChanKey+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey+" AND "+simChannels+"."+dao.TSimChannels.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
mod = mod.LeftJoin(flowPackage, flowPackage+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+" AND "+flowPackage+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
|
||
// 操作人摘要信息
|
||
mod = mod.Hook(hook.MemberSummary)
|
||
|
||
// 查询数据
|
||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||
err = gerror.Wrap(err, "获取卡信息表列表失败,请稍后重试!")
|
||
return
|
||
}
|
||
applyCardChannelDisplayNames(ctx, list)
|
||
if resolver, resolverErr := newCardDeptDisplayResolver(ctx); resolverErr != nil {
|
||
logCardDeptDisplayResolverError(ctx, resolverErr)
|
||
} else {
|
||
for _, item := range list {
|
||
if item != nil {
|
||
item.DeptName = resolver(item.DeptId, item.DeptName)
|
||
}
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// exportColumns 取前端传入的导出列。
|
||
// 优先解析 JSON 字符串形式(数组绑定可能丢失内容),失败再退回对象数组。
|
||
func (s *sIotTSimCardInfo) exportColumns(in *model.TSimCardInfoListParam) []model.ExportColumn {
|
||
if raw := strings.TrimSpace(in.ExportColumnsJson); raw != "" {
|
||
cols := make([]model.ExportColumn, 0)
|
||
if err := gjson.New(raw).Scan(&cols); err == nil && len(cols) > 0 {
|
||
return cols
|
||
}
|
||
}
|
||
return in.ExportColumns
|
||
}
|
||
|
||
const simCardBatchMaxRows = 1000
|
||
|
||
// simCardBatchCondition 将批量查询输入编译为查询条件。
|
||
// 每行支持 1 至 3 个号码值,同一行的多个值表示同一张卡的多个字段值。
|
||
// 同一行的值互不相同,必然落在不同字段上,因此“该行每个值都命中这张卡的某个字段”
|
||
// 已等价于“这些值都属于这张卡”,无需判断某个值具体是 ICCID、MSISDN 还是 IMSI。
|
||
func simCardBatchCondition(ctx context.Context, input string) (string, []interface{}, error) {
|
||
lines, err := text.ParseWhitespaceFields(ctx, input)
|
||
if errors.Is(err, text.ErrEmptyInput) {
|
||
return "", nil, gerror.New("批量查询卡号不能为空")
|
||
}
|
||
if err != nil {
|
||
return "", nil, err
|
||
}
|
||
if len(lines) > simCardBatchMaxRows {
|
||
return "", nil, gerror.New("批量查询最多支持1000行")
|
||
}
|
||
|
||
columns := dao.TSimCardInfo.Columns()
|
||
// 单值行之间相互独立,合并成 IN 列表后语义不变,可显著降低条件求值成本。
|
||
singleValues := make([]string, 0, len(lines))
|
||
singleIndex := make(map[string]struct{}, len(lines))
|
||
// 多值行需要保留同一行内的配对关系,不能跨行合并。
|
||
multiConditions := make([]string, 0, len(lines))
|
||
multiArgs := make([]interface{}, 0, len(lines)*9)
|
||
multiIndex := make(map[string]struct{}, len(lines))
|
||
|
||
for _, line := range lines {
|
||
switch len(line.Fields) {
|
||
case 1:
|
||
value := line.Fields[0]
|
||
if _, exists := singleIndex[value]; exists {
|
||
continue
|
||
}
|
||
singleIndex[value] = struct{}{}
|
||
singleValues = append(singleValues, value)
|
||
case 2, 3:
|
||
key := strings.Join(line.Fields, "\x00")
|
||
if _, exists := multiIndex[key]; exists {
|
||
continue
|
||
}
|
||
multiIndex[key] = struct{}{}
|
||
groups := make([]string, 0, len(line.Fields))
|
||
for _, value := range line.Fields {
|
||
groups = append(groups, "("+columns.Iccid+" = ? OR "+
|
||
columns.Msisdn+" = ? OR "+
|
||
columns.Imsi+" = ?)")
|
||
multiArgs = append(multiArgs, value, value, value)
|
||
}
|
||
multiConditions = append(multiConditions, "("+strings.Join(groups, " AND ")+")")
|
||
default:
|
||
return "", nil, gerror.Newf("第%d行格式错误,每行支持 1 至 3 个号码值", line.RowNo)
|
||
}
|
||
}
|
||
|
||
parts := make([]string, 0, 2)
|
||
args := make([]interface{}, 0, len(singleValues)*3+len(multiArgs))
|
||
if len(singleValues) > 0 {
|
||
placeholders := "(" + strings.TrimSuffix(strings.Repeat("?,", len(singleValues)), ",") + ")"
|
||
parts = append(parts, "("+columns.Iccid+" IN "+placeholders+" OR "+
|
||
columns.Msisdn+" IN "+placeholders+" OR "+
|
||
columns.Imsi+" IN "+placeholders+")")
|
||
for repeat := 0; repeat < 3; repeat++ {
|
||
for _, value := range singleValues {
|
||
args = append(args, value)
|
||
}
|
||
}
|
||
}
|
||
if len(multiConditions) > 0 {
|
||
parts = append(parts, "("+strings.Join(multiConditions, " OR ")+")")
|
||
args = append(args, multiArgs...)
|
||
}
|
||
|
||
return "(" + strings.Join(parts, " OR ") + ")", args, nil
|
||
}
|
||
|
||
// Export 导出卡信息(异步:提交任务后到下载中心取文件)
|
||
// 导出的列由前端按当前列表显示列传入,未传时按列表模型字段顺序全量导出。
|
||
func (s *sIotTSimCardInfo) Export(ctx context.Context, in *model.TSimCardInfoListParam) (err error) {
|
||
if in.BatchInput != nil {
|
||
if _, _, err = simCardBatchCondition(ctx, *in.BatchInput); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||
BizKey: "tSimCardInfo",
|
||
BizName: "卡信息",
|
||
PermCode: "cpm:iot:tSimCardInfo:export",
|
||
QueryParam: in,
|
||
Columns: s.exportColumns(in),
|
||
Tags: func() ([]string, error) {
|
||
return convert.GetEntityDescTags(model.TSimCardInfoListModel{})
|
||
},
|
||
Fetch: func(ctx context.Context, page, pageSize int) (rows []interface{}, total int, err error) {
|
||
query := *in
|
||
query.Page = page
|
||
query.PageSize = pageSize
|
||
list, count, err := s.List(ctx, &query)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
rows = make([]interface{}, 0, len(list))
|
||
for _, item := range list {
|
||
rows = append(rows, item)
|
||
}
|
||
return rows, count, nil
|
||
},
|
||
})
|
||
return err
|
||
}
|
||
|
||
// Edit 修改/新增卡信息表
|
||
func (s *sIotTSimCardInfo) Edit(ctx context.Context, in *model.TSimCardInfoEditParam) (err error) {
|
||
|
||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||
if err = s.validateTrafficCutoffConfig(ctx, tx, in); err != nil {
|
||
return
|
||
}
|
||
|
||
// 修改
|
||
if in.Id > 0 {
|
||
userId := contexts.GetUserId(ctx)
|
||
in.UpdatedBy = &userId
|
||
if in.TrafficCutoffStatus != nil && *in.TrafficCutoffStatus != 1 {
|
||
if _, err = tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
|
||
Where(dao.TSimCardInfo.Columns().Id, in.Id).
|
||
Data(do.TSimCardTrafficCutoffConfig{
|
||
TrafficCutoffCrossMonthHold: 0,
|
||
TrafficCutoffTriggeredAt: gdb.Raw("NULL"),
|
||
UpdatedBy: userId,
|
||
}).Update(); err != nil {
|
||
return
|
||
}
|
||
}
|
||
if _, err = s.Model(ctx).
|
||
Fields(model.TSimCardInfoUpdateFields{}).
|
||
WherePri(in.Id).
|
||
Data(in).
|
||
OmitNil().
|
||
Update(); err != nil {
|
||
g.Log().Error(ctx, "修改卡信息表失败", "id", in.Id, "error", err)
|
||
err = gerror.Wrap(err, "修改卡信息表失败,请稍后重试!")
|
||
}
|
||
return
|
||
}
|
||
|
||
// 新增
|
||
// 验证'Iccid'唯一
|
||
if err = xgorm.IsUnique(ctx, &dao.TSimCardInfo, g.Map{dao.TSimCardInfo.Columns().Iccid: in.Iccid}, "ICCID已存在", in.Id); err != nil {
|
||
return
|
||
}
|
||
// 验证'Msisdn'唯一
|
||
if err = xgorm.IsUnique(ctx, &dao.TSimCardInfo, g.Map{dao.TSimCardInfo.Columns().Msisdn: in.Msisdn}, "MSISDN/卡号已存在", in.Id); err != nil {
|
||
}
|
||
|
||
deptId := contexts.GetDeptId(ctx)
|
||
in.CreatedDept = &deptId
|
||
userId := contexts.GetUserId(ctx)
|
||
in.CreatedBy = &userId
|
||
in.CreatedAt = gtime.Now()
|
||
tenantId := contexts.GetTenantId(ctx)
|
||
in.TenantId = &tenantId
|
||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||
Fields(model.TSimCardInfoInsertFields{}).
|
||
Data(in).Insert(); err != nil {
|
||
err = gerror.Wrap(err, "新增卡信息表失败,请稍后重试!")
|
||
}
|
||
return
|
||
})
|
||
}
|
||
|
||
func (s *sIotTSimCardInfo) validateTrafficCutoffConfig(ctx context.Context, tx gdb.TX, in *model.TSimCardInfoEditParam) error {
|
||
if in == nil || (in.TrafficCutoffStatus == nil && in.TrafficCutoffThreshold == nil) {
|
||
return nil
|
||
}
|
||
if in.TrafficCutoffStatus != nil && *in.TrafficCutoffStatus == 0 && in.TrafficCutoffThreshold == nil {
|
||
threshold := float64(0)
|
||
in.TrafficCutoffThreshold = &threshold
|
||
}
|
||
var current struct {
|
||
Status int `orm:"traffic_cutoff_status"`
|
||
Threshold float64 `orm:"traffic_cutoff_threshold"`
|
||
}
|
||
if in.Id > 0 {
|
||
columns := dao.TSimCardInfo.Columns()
|
||
if err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
|
||
Fields(columns.TrafficCutoffStatus, columns.TrafficCutoffThreshold).
|
||
Where(columns.Id, in.Id).
|
||
Scan(¤t); err != nil {
|
||
return gerror.Wrap(err, "查询卡达量断网配置失败")
|
||
}
|
||
}
|
||
if in.TrafficCutoffStatus != nil {
|
||
current.Status = *in.TrafficCutoffStatus
|
||
}
|
||
if in.TrafficCutoffThreshold != nil {
|
||
current.Threshold = *in.TrafficCutoffThreshold
|
||
}
|
||
return trafficcutoffconfig.ValidateConfig(current.Status, current.Threshold)
|
||
}
|
||
|
||
// Delete 删除卡信息表
|
||
func (s *sIotTSimCardInfo) Delete(ctx context.Context, in *model.TSimCardInfoDeleteParam) (err error) {
|
||
|
||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||
err = gerror.Wrap(err, "删除卡信息表失败,请稍后重试!")
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// View 获取卡信息表指定信息
|
||
func (s *sIotTSimCardInfo) View(ctx context.Context, in *model.TSimCardInfoViewParam) (res *model.TSimCardInfoViewModel, err error) {
|
||
mod := s.Model(ctx)
|
||
cardInfoTable := dao.TSimCardInfo.Table()
|
||
simChannels := dao.TSimChannels.Table()
|
||
sysDept := dao.SysDept.Table()
|
||
flowPackage := dao.TSimFlowPackageInfo.Table()
|
||
// BatchMain := dao.BatchMain.Table()
|
||
mod = mod.LeftJoin(simChannels, simChannels+"."+dao.TSimChannels.Columns().ChanKey+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey+" AND "+simChannels+"."+dao.TSimChannels.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
mod = mod.LeftJoin(sysDept, sysDept+"."+dao.SysDept.Columns().DeptId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().DeptId)
|
||
mod = mod.LeftJoin(flowPackage, flowPackage+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+" AND "+flowPackage+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
|
||
// 字段过滤
|
||
mod = mod.FieldsPrefix(dao.TSimCardInfo.Table(), model.TSimCardInfoListModel{})
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"COALESCE(NULLIF(%s.%s, ''), %s.%s) as chanName",
|
||
simChannels,
|
||
dao.TSimChannels.Columns().ChanName,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().ChanKey,
|
||
))
|
||
mod = mod.Fields(simChannels + ".chan_alias as chanAlias")
|
||
mod = mod.Fields(sysDept + ".dept_name as deptName")
|
||
mod = mod.Fields(flowPackage + ".package_name as packageName")
|
||
mod = mod.Fields(cardflow.RemainFlowExpr(cardInfoTable, flowPackage) + " as reminFlowMb")
|
||
|
||
mod = mod.WherePri(in.Id)
|
||
|
||
err = mod.Hook(hook.MemberSummary).Scan(&res)
|
||
|
||
if err != nil {
|
||
err = gerror.Wrap(err, "获取卡信息表信息,请稍后重试!")
|
||
return
|
||
}
|
||
if res != nil {
|
||
res.ChanName = channelDisplayName(canViewChannelFullName(ctx), res.ChanName, res.ChanAlias)
|
||
if resolver, resolverErr := newCardDeptDisplayResolver(ctx); resolverErr != nil {
|
||
logCardDeptDisplayResolverError(ctx, resolverErr)
|
||
} else {
|
||
res.DeptName = resolver(res.DeptId, res.DeptName)
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// SyncSimCardFlow 同步SIM卡流量
|
||
func (s *sIotTSimCardInfo) SyncSimCardFlow(ctx context.Context, input *model.SyncSimCardFlowInput) (err error) {
|
||
in := model.SimDataUsageInput{
|
||
SimKeys: []*model.SimKey{
|
||
{
|
||
ICCID: input.Iccid,
|
||
},
|
||
},
|
||
}
|
||
out, err := service.UpstreamChan().QuerySimDatausage(ctx, &in)
|
||
if err != nil {
|
||
g.Log().Errorf(ctx, "SyncSimCardFlow query upstream failed, iccid:%s, err:%+v", input.Iccid, err)
|
||
return gerror.New(simFlowUpstreamUnavailableMessage)
|
||
}
|
||
if len(out) > 0 && out[0].Code != 0 {
|
||
g.Log().Errorf(ctx, "SyncSimCardFlow upstream business failure, iccid:%s, code:%d, message:%s", input.Iccid, out[0].Code, out[0].Message)
|
||
}
|
||
usage, err := validateSimDataUsageResult(out)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err = s.Model(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, input.Iccid).
|
||
Update(do.TSimCardInfo{
|
||
UsedFlowMb: usage.ResData.DataUsage,
|
||
FlowSyncTime: gtime.Now(),
|
||
}); err != nil {
|
||
g.Log().Errorf(ctx, "SyncSimCardFlow update card usage failed, iccid:%s, err:%+v", input.Iccid, err)
|
||
err = gerror.New("同步卡流量数据保存失败,请稍后重试!")
|
||
return
|
||
}
|
||
return nil
|
||
|
||
}
|
||
|
||
func validateSimDataUsageResult(out []model.SimBaseRes[model.SimDataUsageItem]) (model.SimBaseRes[model.SimDataUsageItem], error) {
|
||
if len(out) == 0 {
|
||
return model.SimBaseRes[model.SimDataUsageItem]{}, gerror.New(simFlowUpstreamUnavailableMessage)
|
||
}
|
||
if out[0].Code != 0 {
|
||
return model.SimBaseRes[model.SimDataUsageItem]{}, newSimDataUsageError(out[0].Code, out[0].Message)
|
||
}
|
||
return out[0], nil
|
||
}
|
||
|
||
func newSimDataUsageError(code int, message string) error {
|
||
message = strings.TrimSpace(message)
|
||
if code == simFlowInternalErrorCode || isInternalUpstreamError(message) {
|
||
return gerror.New(simFlowUpstreamUnavailableMessage)
|
||
}
|
||
// 上游返回的业务状态不直接暴露给用户,避免用户把上游问题误认为本平台故障。
|
||
if display := upstreamStatusDisplayMessage(code, message); display != "" {
|
||
return gerror.New(display)
|
||
}
|
||
if message == "" {
|
||
return gerror.Newf("同步卡流量失败(错误码:%d),请稍后重试!", code)
|
||
}
|
||
return gerror.Newf("同步卡流量失败(错误码:%d):%s", code, message)
|
||
}
|
||
|
||
// upstreamStatusDisplayMessage 把上游业务状态翻译成面向用户的提示。
|
||
// 返回空字符串表示该状态需要保留原始信息(调用方自行决定如何展示)。
|
||
//
|
||
// 目前只覆盖上游自身繁忙/受限这类「与用户操作无关、重试即可」的状态:
|
||
// 这类状态若把原始错误码抛给用户,会被误认为本平台出错。
|
||
func upstreamStatusDisplayMessage(code int, message string) string {
|
||
switch code {
|
||
case simUpstreamBusyCode:
|
||
return simUpstreamBusyMessage
|
||
}
|
||
// 上游可能用不同状态码返回同一种语义,用文案兜底识别。
|
||
normalized := strings.TrimSpace(message)
|
||
if normalized != "" && isUpstreamBusyMessage(normalized) {
|
||
return simUpstreamBusyMessage
|
||
}
|
||
if normalized != "" && isUpstreamForbiddenMessage(normalized) {
|
||
return simUpstreamBusyForbiddenMessage
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func isUpstreamBusyMessage(message string) bool {
|
||
return strings.Contains(message, "系统忙")
|
||
}
|
||
|
||
func isUpstreamForbiddenMessage(message string) bool {
|
||
return strings.Contains(message, "禁止访问")
|
||
}
|
||
|
||
func isInternalUpstreamError(message string) bool {
|
||
lower := strings.ToLower(strings.TrimSpace(message))
|
||
if lower == "" {
|
||
return false
|
||
}
|
||
for _, marker := range []string{
|
||
"request failed",
|
||
"no host in request url",
|
||
"unsupported protocol scheme",
|
||
"context deadline exceeded",
|
||
"client.timeout",
|
||
"connection refused",
|
||
"dial tcp",
|
||
"http://",
|
||
"https://",
|
||
"timeout",
|
||
"unexpected eof",
|
||
} {
|
||
if strings.Contains(lower, marker) {
|
||
return true
|
||
}
|
||
}
|
||
return strings.Contains(message, "通道未实例化") || strings.Contains(message, "上游服务")
|
||
}
|
||
|
||
// SyncSimCardStatus 同步SIM卡状态
|
||
func (s *sIotTSimCardInfo) SyncSimCardStatus(ctx context.Context, input *model.SyncSimCardStatusInput) (err error) {
|
||
in := model.SimCardStatusInput{
|
||
SimKeys: []*model.SimKey{
|
||
{
|
||
ICCID: input.Iccid,
|
||
},
|
||
},
|
||
}
|
||
out, err := service.UpstreamChan().QuerySimCardStatus(ctx, &in)
|
||
if err != nil {
|
||
return gerror.Wrap(err, "同步卡状态失败,请稍后重试!")
|
||
}
|
||
status, err := validateSimCardStatusResult(out, "同步卡状态")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err = s.Model(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, input.Iccid).
|
||
Update(do.TSimCardInfo{
|
||
BossCardStatus: status.ResData.CardStatus,
|
||
StatusSyncTime: gtime.Now(),
|
||
}); err != nil {
|
||
err = gerror.Wrap(err, "同步卡状态失败,请稍后重试!")
|
||
return
|
||
}
|
||
s.syncBossActivateAtIfNeeded(ctx, input.Iccid, status.ResData.CardStatus)
|
||
return nil
|
||
|
||
}
|
||
|
||
func validateSimCardStatusResult(out []model.SimBaseRes[model.SimCardStatusItem], operation string) (model.SimBaseRes[model.SimCardStatusItem], error) {
|
||
if len(out) == 0 {
|
||
return model.SimBaseRes[model.SimCardStatusItem]{}, gerror.Newf("%s失败,请稍后重试!", operation)
|
||
}
|
||
if out[0].Code != 0 {
|
||
if display := upstreamStatusDisplayMessage(out[0].Code, out[0].Message); display != "" {
|
||
return model.SimBaseRes[model.SimCardStatusItem]{}, gerror.New(display)
|
||
}
|
||
return model.SimBaseRes[model.SimCardStatusItem]{}, gerror.Newf("%s失败,错误码:%d,错误信息:%s", operation, out[0].Code, out[0].Message)
|
||
}
|
||
if out[0].ResData.CardStatus != 0 {
|
||
if _, ok := consts.V3ApiSimBossCardStatusNameMap[out[0].ResData.CardStatus]; !ok {
|
||
return model.SimBaseRes[model.SimCardStatusItem]{}, gerror.Newf("%s失败,上游返回了不支持的卡状态:%d", operation, out[0].ResData.CardStatus)
|
||
}
|
||
}
|
||
return out[0], nil
|
||
}
|
||
|
||
func validateSimSessionResult(out []model.SimBaseRes[model.SimSessionItem]) (model.SimBaseRes[model.SimSessionItem], error) {
|
||
if len(out) == 0 {
|
||
return model.SimBaseRes[model.SimSessionItem]{}, gerror.New("查询SIM卡会话失败,请稍后重试!")
|
||
}
|
||
if out[0].Code != 0 {
|
||
if display := upstreamStatusDisplayMessage(out[0].Code, out[0].Message); display != "" {
|
||
return model.SimBaseRes[model.SimSessionItem]{}, gerror.New(display)
|
||
}
|
||
return model.SimBaseRes[model.SimSessionItem]{}, gerror.Newf("查询SIM卡会话失败,错误码:%d,错误信息:%s", out[0].Code, out[0].Message)
|
||
}
|
||
return out[0], nil
|
||
}
|
||
|
||
// resolveUpstreamSimKey loads the local channel binding before calling upstream.
|
||
func (s *sIotTSimCardInfo) resolveUpstreamSimKey(ctx context.Context, iccid string) (*model.SimKey, error) {
|
||
var card struct {
|
||
ChanKey string
|
||
Iccid string
|
||
Imsi string
|
||
Msisdn string
|
||
}
|
||
if err := s.Model(ctx).
|
||
Fields(
|
||
dao.TSimCardInfo.Columns().ChanKey,
|
||
dao.TSimCardInfo.Columns().Iccid,
|
||
dao.TSimCardInfo.Columns().Imsi,
|
||
dao.TSimCardInfo.Columns().Msisdn,
|
||
).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, iccid).
|
||
Scan(&card); err != nil {
|
||
return nil, gerror.Wrap(err, `query SIM card channel failed`)
|
||
}
|
||
return &model.SimKey{
|
||
ChanKey: card.ChanKey,
|
||
ICCID: iccid,
|
||
IMSI: card.Imsi,
|
||
MSISDN: card.Msisdn,
|
||
}, nil
|
||
}
|
||
|
||
// QuerySimStatus 查询SIM卡状态
|
||
func (s *sIotTSimCardInfo) QuerySimStatus(ctx context.Context, input *model.QuerySimStatusInput) (res *model.QuerySimStatusOutput, err error) {
|
||
simKey, err := s.resolveUpstreamSimKey(ctx, input.ICCID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
in := model.SimCardStatusInput{
|
||
SimKeys: []*model.SimKey{
|
||
simKey,
|
||
},
|
||
}
|
||
out, err := service.UpstreamChan().QuerySimCardStatus(ctx, &in)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
status, err := validateSimCardStatusResult(out, "查询SIM卡状态")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
res = &status.ResData
|
||
if _, err = s.Model(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, input.ICCID).
|
||
Data(do.TSimCardInfo{
|
||
BossCardStatus: res.CardStatus,
|
||
StatusSyncTime: gtime.Now(),
|
||
}).Update(); err != nil {
|
||
err = gerror.Wrap(err, "更新SIM卡状态失败,请稍后重试!")
|
||
return
|
||
}
|
||
s.syncBossActivateAtIfNeeded(ctx, input.ICCID, res.CardStatus)
|
||
return res, nil
|
||
}
|
||
|
||
// syncBossActivateAtIfNeeded records the operator activation date once the
|
||
// operator confirms that a card is activated. It intentionally never replaces
|
||
// an existing value because activation time is historical source data.
|
||
func (s *sIotTSimCardInfo) syncBossActivateAtIfNeeded(ctx context.Context, iccid string, cardStatus int) {
|
||
if cardStatus != consts.V3ApiSimBossCardStatusActivated {
|
||
return
|
||
}
|
||
|
||
missingCount, err := s.Model(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, iccid).
|
||
WhereNull(dao.TSimCardInfo.Columns().BossActivateAt).
|
||
Count()
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "check missing SIM activation date failed, iccid:%s, err:%v", iccid, err)
|
||
return
|
||
}
|
||
if missingCount == 0 {
|
||
return
|
||
}
|
||
|
||
output, err := service.UpstreamChan().QuerySimBasicInfo(ctx, &model.SimBasicInfoInput{
|
||
SimKeys: []*model.SimKey{{ICCID: iccid}},
|
||
})
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "query SIM activation date failed, iccid:%s, err:%v", iccid, err)
|
||
return
|
||
}
|
||
if len(output) == 0 || output[0].Code != 0 {
|
||
message := "empty response"
|
||
code := 0
|
||
if len(output) > 0 {
|
||
code = output[0].Code
|
||
message = output[0].Message
|
||
}
|
||
g.Log().Warningf(ctx, "query SIM activation date returned no usable data, iccid:%s, code:%d, message:%s", iccid, code, message)
|
||
return
|
||
}
|
||
|
||
activeDate := strings.TrimSpace(output[0].ResData.ActiveDate)
|
||
if activeDate == "" {
|
||
g.Log().Warningf(ctx, "query SIM activation date returned empty activeDate, iccid:%s", iccid)
|
||
return
|
||
}
|
||
activateAt, err := gtime.StrToTime(activeDate)
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "parse SIM activation date failed, iccid:%s, activeDate:%s, err:%v", iccid, activeDate, err)
|
||
return
|
||
}
|
||
if _, err = s.Model(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, iccid).
|
||
WhereNull(dao.TSimCardInfo.Columns().BossActivateAt).
|
||
Update(do.TSimCardInfo{BossActivateAt: activateAt}); err != nil {
|
||
g.Log().Warningf(ctx, "save SIM activation date failed, iccid:%s, err:%v", iccid, err)
|
||
}
|
||
}
|
||
|
||
// QuerySimSession 查询SIM卡会话
|
||
func (s *sIotTSimCardInfo) QuerySimSession(ctx context.Context, input *model.QuerySimSessionInput) (res *model.QuerySimSessionOutput, err error) {
|
||
simKey, err := s.resolveUpstreamSimKey(ctx, input.ICCID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
in := model.SimSessionInput{
|
||
SimKeys: []*model.SimKey{
|
||
simKey,
|
||
},
|
||
}
|
||
out, err := service.UpstreamChan().QuerySimSession(ctx, &in)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
session, err := validateSimSessionResult(out)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
res = &session.ResData
|
||
if imeiList, imeiErr := service.UpstreamChan().QuerySimImei(ctx, &model.SimImeiInput{
|
||
SimKeys: []*model.SimKey{
|
||
simKey,
|
||
},
|
||
}); imeiErr == nil && len(imeiList) > 0 {
|
||
res.Imei = imeiList[0].ResData.Imei
|
||
} else if imeiErr != nil {
|
||
g.Log().Warningf(ctx, "查询SIM卡实时IMEI失败,继续返回会话信息,iccid:%s, err:%v", input.ICCID, imeiErr)
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
// GetCardPackageOptions 获取卡套餐下拉选项(去重后的套餐编码列表,套餐状态为生效中)
|
||
func (s *sIotTSimCardInfo) GetCardPackageOptions(ctx context.Context, deptId int64) (res []*model.GetCardPackageOptionsRes, err error) {
|
||
mod := dao.TSimCardInfo.Ctx(ctx)
|
||
err = mod.Fields("DISTINCT "+dao.TSimCardInfo.Columns().CurrentPackageCode).
|
||
Where(dao.TSimCardInfo.Columns().PackageStatus, 2).
|
||
Where(dao.TSimCardInfo.Columns().DeptId, deptId).
|
||
Scan(&res)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "获取卡套餐选项失败,请稍后重试!")
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
// CalculatePoolStatistics 计算流量池统计数据
|
||
func (s *sIotTSimCardInfo) CalculatePoolStatistics(ctx context.Context, req *model.CalculatePoolStatisticsReq) (res *model.CalculatePoolStatisticsRes, err error) {
|
||
g.Log().Info(ctx, "计算流量池统计数据req:", req)
|
||
deptId := req.DeptId
|
||
res = &model.CalculatePoolStatisticsRes{}
|
||
|
||
// 容错处理:如果传入的 102 套餐列表为空,则直接返回空结果
|
||
if len(req.CardCodes102) == 0 {
|
||
return res, nil
|
||
}
|
||
|
||
// 1. 查询该流量池下所有 102 卡的总数量和总已用流量
|
||
type CardInfoStats struct {
|
||
TotalCardCount int64 `gconv:"total_card_count"`
|
||
TotalUsedFlowMb float64 `gconv:"total_used_flow_mb"`
|
||
}
|
||
|
||
var cardStats CardInfoStats
|
||
|
||
err = s.Model(ctx).
|
||
Where("dept_id", deptId).
|
||
WhereIn("current_package_code", req.CardCodes102).
|
||
Fields("COUNT(1) AS total_card_count, IFNULL(SUM(used_flow_mb), 0) AS total_used_flow_mb").
|
||
Scan(&cardStats)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
res.TotalCardCount = cardStats.TotalCardCount
|
||
res.TotalUsedFlowMb = cardStats.TotalUsedFlowMb
|
||
|
||
// 2. 查询单卡流量 MB(仅查第一个套餐,从流程套餐表查询)
|
||
type CardFlowInfo struct {
|
||
FlowMb float64 `gconv:"flow_mb"`
|
||
}
|
||
var cardFlowInfo CardFlowInfo
|
||
err = dao.TSimFlowPackageInfo.Ctx(ctx).
|
||
Where("package_code", req.CardCodes102[0]).
|
||
Fields("flow_mb").
|
||
Scan(&cardFlowInfo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
res.CardFlowMb = cardFlowInfo.FlowMb
|
||
|
||
// 3. 计算套内总流量 PoolPackageFlowMb
|
||
res.PoolPackageFlowMb = float64(res.TotalCardCount) * res.CardFlowMb
|
||
|
||
// 4. 计算加油包流量 AddOnFlowMb (104 套餐)
|
||
if len(req.CardCodes104) > 0 {
|
||
g.Log().Info(ctx, "部门编号:", deptId)
|
||
g.Log().Info(ctx, "流量池编码:", req.PoolPackageCode)
|
||
g.Log().Info(ctx, "104套餐编码:", req.CardCodes104)
|
||
|
||
type FlowPackageInfo struct {
|
||
FlowMb float64 `gconv:"flow_mb"`
|
||
}
|
||
var flowPackageInfo FlowPackageInfo
|
||
bindTable := dao.TSimPoolBindPackage.Table()
|
||
packageTable := dao.TSimFlowPackageInfo.Table()
|
||
bindColumns := dao.TSimPoolBindPackage.Columns()
|
||
packageColumns := dao.TSimFlowPackageInfo.Columns()
|
||
now := gtime.Now()
|
||
err = dao.TSimPoolBindPackage.Ctx(ctx).
|
||
LeftJoin(packageTable, fmt.Sprintf("%s.%s = %s.%s AND %s.%s = %s.%s", bindTable, bindColumns.PackageCode, packageTable, packageColumns.PackageCode, bindTable, bindColumns.TenantId, packageTable, packageColumns.TenantId)).
|
||
Where(bindTable+"."+bindColumns.DeptId, deptId).
|
||
Where(bindTable+"."+bindColumns.PoolNo, req.PoolPackageCode).
|
||
Where(bindTable+"."+bindColumns.PackageType, 104).
|
||
Where(bindTable+"."+bindColumns.EffectiveStatus, consts.EffectiveStatusEffective).
|
||
WhereIn(bindTable+"."+bindColumns.PackageCode, req.CardCodes104).
|
||
Where(fmt.Sprintf("(%s.%s IS NULL OR %s.%s <= ?)", bindTable, bindColumns.EffectiveTime, bindTable, bindColumns.EffectiveTime), now).
|
||
Where(fmt.Sprintf("(%s.%s IS NULL OR %s.%s > ?)", bindTable, bindColumns.ExpectedExpireTime, bindTable, bindColumns.ExpectedExpireTime), now).
|
||
Fields(fmt.Sprintf("IFNULL(SUM(%s.%s), 0) AS flow_mb", packageTable, packageColumns.FlowMb)).
|
||
Scan(&flowPackageInfo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
res.AddOnFlowMb = flowPackageInfo.FlowMb
|
||
}
|
||
|
||
// 5. 核心公式计算 & 状态判定
|
||
// 总流量MB = 套内总流量MB + 加油包流量MB
|
||
res.TotalFlowMb = res.PoolPackageFlowMb + res.AddOnFlowMb
|
||
|
||
// 剩余流量MB = 总流量MB - 总已用流量MB
|
||
res.RemainFlowMb = res.TotalFlowMb - res.TotalUsedFlowMb
|
||
|
||
// 状态判断:大于0正常(0),小于等于0停用(1)
|
||
if res.RemainFlowMb > 0 {
|
||
res.PoolStatus = 0
|
||
} else {
|
||
res.PoolStatus = 1
|
||
}
|
||
|
||
return res, nil
|
||
}
|
||
|
||
// GetCardsByPackageCodes 根据套餐编码列表获取卡列表
|
||
func (s *sIotTSimCardInfo) GetCardsByPackageCodes(ctx context.Context, req *model.GetCardsByPackageCodesParam) (list []*model.TSimCardInfoListModel, totalCount int, err error) {
|
||
mod := dao.TSimCardInfo.Ctx(ctx)
|
||
cardInfoTable := dao.TSimCardInfo.Table()
|
||
simChannels := dao.TSimChannels.Table()
|
||
sysDept := dao.SysDept.Table()
|
||
flowPackage := dao.TSimFlowPackageInfo.Table()
|
||
|
||
// 字段过滤
|
||
mod = mod.FieldsPrefix(cardInfoTable, model.TSimCardInfoListModel{})
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"COALESCE(NULLIF(%s.%s, ''), %s.%s) as chanName, %s.%s as chanAlias",
|
||
simChannels,
|
||
dao.TSimChannels.Columns().ChanName,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().ChanKey,
|
||
simChannels,
|
||
dao.TSimChannels.Columns().ChanAlias,
|
||
))
|
||
mod = mod.Fields(sysDept + ".dept_name as deptName")
|
||
mod = mod.Fields(fmt.Sprintf(
|
||
"COALESCE(NULLIF(%s.%s, ''), %s.%s) as packageName",
|
||
flowPackage,
|
||
dao.TSimFlowPackageInfo.Columns().PackageName,
|
||
cardInfoTable,
|
||
dao.TSimCardInfo.Columns().CurrentPackageCode,
|
||
))
|
||
mod = mod.Fields(cardflow.RemainFlowExpr(cardInfoTable, flowPackage) + " as reminFlowMb")
|
||
|
||
// 部门ID条件
|
||
if req.DeptId > 0 {
|
||
mod = mod.Where(cardInfoTable+"."+dao.TSimCardInfo.Columns().DeptId, req.DeptId)
|
||
}
|
||
|
||
// ICCID条件
|
||
if req.Iccid != "" {
|
||
mod = mod.Where(cardInfoTable+"."+dao.TSimCardInfo.Columns().Iccid, req.Iccid)
|
||
}
|
||
|
||
// 通道条件
|
||
if req.ChanKey != "" {
|
||
mod = mod.Where(cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey, req.ChanKey)
|
||
}
|
||
|
||
// 卡状态条件
|
||
if req.BossCardStatus != nil {
|
||
mod = mod.Where(cardInfoTable+"."+dao.TSimCardInfo.Columns().BossCardStatus, *req.BossCardStatus)
|
||
}
|
||
|
||
if len(req.PackageCodes) > 0 {
|
||
mod = mod.WhereIn(cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode, req.PackageCodes)
|
||
}
|
||
|
||
// LeftJoin
|
||
mod = mod.LeftJoin(simChannels, simChannels+"."+dao.TSimChannels.Columns().ChanKey+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey+" AND "+simChannels+"."+dao.TSimChannels.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
mod = mod.LeftJoin(sysDept, sysDept+"."+dao.SysDept.Columns().DeptId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().DeptId)
|
||
mod = mod.LeftJoin(flowPackage, flowPackage+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+" AND "+flowPackage+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId)
|
||
|
||
// 分页
|
||
mod = mod.Page(req.Page, req.PageSize)
|
||
|
||
// 排序
|
||
mod = mod.OrderDesc(cardInfoTable + "." + dao.TSimCardInfo.Columns().Id)
|
||
|
||
// 查询数据
|
||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||
err = gerror.Wrap(err, "获取卡列表失败,请稍后重试!")
|
||
return
|
||
}
|
||
applyCardChannelDisplayNames(ctx, list)
|
||
if resolver, resolverErr := newCardDeptDisplayResolver(ctx); resolverErr != nil {
|
||
logCardDeptDisplayResolverError(ctx, resolverErr)
|
||
} else {
|
||
for _, item := range list {
|
||
if item != nil {
|
||
item.DeptName = resolver(item.DeptId, item.DeptName)
|
||
}
|
||
}
|
||
}
|
||
return
|
||
}
|