Files
junhong_cmp_fiber/docs/7月迭代/需求03-07-11-12-13-简单改动.md
2026-07-13 12:01:18 +09:00

11 KiB
Raw Blame History

需求03/07/11/12/13简单改动合集


需求03店铺列表搜索新增联系电话

后端

Shop 表已有 contact_phone 字段。仅需在列表查询接口新增过滤条件。

文件internal/store/postgres/shop_store.go(列表查询 Store 方法)

// 现有过滤条件基础上追加
if req.ContactPhone != "" {
    query = query.Where("contact_phone = ?", req.ContactPhone)
}

DTO 变更internal/model/dto/shop_dto.goShopListRequest 新增:

ContactPhone string `json:"contact_phone" query:"contact_phone" validate:"omitempty,len=11" minLength:"11" maxLength:"11" description:"联系人电话精确匹配11位"`

前端

店铺列表搜索栏新增"联系电话"输入框,填入后带入 contact_phone 参数请求。


需求07IoT卡/设备管理新增已实名/未实名筛选

IoT 卡

IotCard.real_name_status 已有0=未实名, 1=已实名),ListStandaloneIotCardRequest 无该过滤字段,需新增。

DTO 变更internal/model/dto/iot_card_dto.goListStandaloneIotCardRequest 新增):

RealNameStatus *int `json:"real_name_status" query:"real_name_status" validate:"omitempty,oneof=0 1" description:"实名状态 (0:未实名, 1:已实名)"`

Store 追加internal/store/postgres/iot_card_store.go

if req.RealNameStatus != nil {
    query = query.Where("real_name_status = ?", *req.RealNameStatus)
}

设备

设备本身目前无 real_name_status 字段。语义为:任意一张绑定卡已实名 = 设备已实名。

为避免列表查询时走 EXISTS 子查询,改为快照方案:在 Device 表落盘,轮询时维护。

迁移

tb_devices 新增字段:

ALTER TABLE tb_devices ADD COLUMN real_name_status int NOT NULL DEFAULT 0
    COMMENT '实名状态快照(0=未实名,1=已实名)任意绑定卡已实名则为1由轮询异步维护';

Modelinternal/model/device.go

RealNameStatus int `gorm:"column:real_name_status;type:int;default:0;not null;comment:实名状态快照(0=未实名,1=已实名)任意绑定卡已实名则为1" json:"real_name_status"`

快照更新时机

以下两处卡实名状态变化时,需同步更新所属设备的快照:

1. 轮询实名处理internal/task/polling_realname_handler.go

卡状态变化后,已有 triggerDeviceRealnameActivation 查出 deviceID,在此同步更新设备快照:

// statusChanged 时,如果卡属于某设备,重新计算并写入设备快照
if statusChanged {
    if binding, err := h.deviceSimBindingStore.GetActiveBindingByCardID(ctx, cardID); err == nil {
        h.deviceStore.RefreshRealnameSnapshot(ctx, binding.DeviceID)
    }
}

2. 管理员手动修改卡实名状态internal/service/iot_card/service.go:ManualUpdateRealnameStatus

更新卡状态成功后,查所属设备并更新快照(同上逻辑)。

快照计算

DeviceStore.RefreshRealnameSnapshot

// RefreshRealnameSnapshot 重新计算并写入设备实名状态快照
func (s *DeviceStore) RefreshRealnameSnapshot(ctx context.Context, deviceID uint) error {
    var count int64
    s.db.WithContext(ctx).Raw(`
        SELECT COUNT(*) FROM tb_device_sim_binding dsb
        JOIN tb_iot_card ic ON ic.id = dsb.iot_card_id
        WHERE dsb.device_id = ? AND dsb.deleted_at IS NULL
          AND ic.real_name_status = 1 AND ic.deleted_at IS NULL
    `, deviceID).Scan(&count)
    status := 0
    if count > 0 {
        status = 1
    }
    return s.db.WithContext(ctx).Model(&model.Device{}).
        Where("id = ?", deviceID).
        Update("real_name_status", status).Error
}

DTO 变更

请求internal/model/dto/device_dto.goListDeviceRequest 新增):

RealNameStatus *int `json:"real_name_status" query:"real_name_status" validate:"omitempty,oneof=0 1" description:"实名状态 (0:未实名, 1:已实名)"`

响应DeviceResponse 新增):

RealNameStatus     int    `json:"real_name_status" description:"实名状态 (0:未实名, 1:已实名)"`
RealNameStatusName string `json:"real_name_status_name" description:"实名状态名称(中文)"`

Store 过滤(直接 WHERE无需 EXISTS

if req.RealNameStatus != nil {
    query = query.Where("real_name_status = ?", *req.RealNameStatus)
}

前端

IoT卡管理筛选栏新增"实名状态"下拉(全部/已实名/未实名)→ 传 real_name_status=0|1
设备管理同上,列表展示 real_name_status_name 字段。


需求11资产详情-套餐到期时间字段 + 15天高亮

后端

无需改动。

后台资产详情页实际调用的是:

GET /api/admin/assets/resolve/:identifier

该接口的 AssetResolveResponseinternal/model/dto/asset_dto.go)已包含:

CurrentPackage            string     `json:"current_package"`             // 当前套餐名称
CurrentPackageActivatedAt *time.Time `json:"current_package_activated_at"` // 开始时间
CurrentPackageExpiresAt   *time.Time `json:"current_package_expires_at"`   // 到期时间(无套餐为 null

到期时间字段已有,前端直接读 current_package_expires_at 即可,不需要新增后端字段。

前端

资产详情页"套餐信息"板块展示到期时间,并在剩余 ≤15 天时高亮:

  • 读取 resolve 接口返回的 current_package_expires_at
  • 若为 null:展示"暂无套餐"
  • 剩余天数由前端计算:Math.ceil((expiresAt - now) / 86400000)
  • 剩余 ≤15 天:红色/高亮展示(建议红色文字 + 标签)

需求12换货管理显示修复

背景

换货单表 tb_exchange_order

  • old_asset_identifier — 旧资产标识符快照
  • new_asset_identifier — 新资产标识符快照
  • old_asset_id / new_asset_id — 旧/新资产主键

EXC-001/EXC-002旧/新资产标识显示不一致

根本原因:后端创建换货单时快照逻辑有误(internal/service/exchange/service.go)。

  • 卡的旧资产:快照了 card.VirtualNo(虚拟号),应为 card.ICCID
  • 卡的新资产:快照了操作员输入的 identifier 原值,未规范化,应统一为 card.ICCID
  • 设备:快照 VirtualNo 优先,没有则 IMEI逻辑正确,无需改动

修复internal/service/exchange/service.go

resolveAssetByIdentifierWithTx 及锁定资产路径中,卡的 Identifier 改为 card.ICCID

// 修复前
return &resolvedExchangeAsset{..., Identifier: card.VirtualNo, ...}

// 修复后
return &resolvedExchangeAsset{..., Identifier: card.ICCID, ...}

历史数据不回填,仅修正后续新建换货单的快照行为。

EXC-003/EXC-004旧/新资产搜索支持 ICCID/接入号/虚拟号

方案:拆分为独立的旧资产和新资产搜索,搜索逻辑用两步查询,不用 JOIN。

DTO 变更internal/model/dto/exchange_dto.goExchangeListRequest

废弃原有 Identifier 字段,改为:

OldAssetKeyword string `json:"old_asset_keyword" query:"old_asset_keyword" validate:"omitempty,max=100" description:"旧资产搜索ICCID/接入号/虚拟号)"`
NewAssetKeyword string `json:"new_asset_keyword" query:"new_asset_keyword" validate:"omitempty,max=100" description:"新资产搜索ICCID/接入号/虚拟号)"`

Store 修改internal/store/postgres/exchange_order_store.go

两步查询——先在资产表搜出 ID再过滤换货表

// 步骤1旧资产关键词搜索
if req.OldAssetKeyword != "" {
    kw := "%" + req.OldAssetKeyword + "%"
    var cardIDs []uint
    s.db.WithContext(ctx).Table("tb_iot_card").
        Where("(iccid LIKE ? OR virtual_no LIKE ? OR msisdn LIKE ?) AND deleted_at IS NULL", kw, kw, kw).
        Pluck("id", &cardIDs)
    var deviceIDs []uint
    s.db.WithContext(ctx).Table("tb_device").
        Where("(virtual_no LIKE ? OR imei LIKE ?) AND deleted_at IS NULL", kw, kw).
        Pluck("id", &deviceIDs)

    if len(cardIDs) == 0 && len(deviceIDs) == 0 {
        return &ExchangeListResult{}, nil // 无匹配,直接返回空
    }
    query = query.Where(
        "(old_asset_type = 'iot_card' AND old_asset_id IN ?) OR (old_asset_type = 'device' AND old_asset_id IN ?)",
        cardIDs, deviceIDs,
    )
}
// new_asset_keyword 同理,过滤 new_asset_id

前端

  • EXC-001/002后端修复后old_asset_identifiernew_asset_identifier 均为 ICCID或设备号设备展示直接读这两个字段即可
  • EXC-003/004搜索栏拆分为"旧资产"和"新资产"两个独立输入框,分别传 old_asset_keywordnew_asset_keyword

需求13列表字段新增

核心原则

提交人、审批人均在写入时快照到单据本身,读取时直接返回,不做额外查询。


COL-003换货管理列表新增提交人待建

需求文档原写"换号管理",确认为"换货管理"(系统无"换号"概念)。

迁移tb_exchange_order 新增字段:

ALTER TABLE tb_exchange_order ADD COLUMN submitter_name varchar(50) NOT NULL DEFAULT '';

Modelinternal/model/exchange_order.go

SubmitterName string `gorm:"column:submitter_name;type:varchar(50);not null;default:'';comment:提交人账号名快照" json:"submitter_name"`

创建换货单时internal/service/exchange/service.go)快照当前操作人 username

SubmitterName: middleware.GetUsername(ctx), // 从 ctx 取当前登录账号的 username

响应 DTOinternal/model/dto/exchange_dto.goExchangeOrderResponse 新增):

SubmitterName string `json:"submitter_name" description:"提交人账号名"`

COL-001退款管理列表新增提交人、审批人依赖审批流

迁移tb_refund 新增字段:

ALTER TABLE tb_refund ADD COLUMN submitter_name varchar(50) NOT NULL DEFAULT '';
ALTER TABLE tb_refund ADD COLUMN dept_leader_name varchar(50) NOT NULL DEFAULT '';
ALTER TABLE tb_refund ADD COLUMN finance_approver_name varchar(50) NOT NULL DEFAULT '';
  • submitter_name:创建退款单时快照操作人 username
  • dept_leader_name / finance_approver_name:审批流各环节通过时快照对应审批人 username

实施依赖dept_leader_name / finance_approver_name 在审批流需求20实现后才能写入未实现前返回空字符串。submitter_name 本迭代即可实现。

响应 DTO(退款列表响应新增):

SubmitterName       string `json:"submitter_name" description:"提交人账号名"`
DeptLeaderName      string `json:"dept_leader_name" description:"部门领导审批人账号名"`
FinanceApproverName string `json:"finance_approver_name" description:"财务审批人账号名"`

COL-002代理充值列表新增提交人、审批人依赖审批流

与 COL-001 同理,tb_agent_recharge(充值单表)新增同样三个字段,快照逻辑一致。

实施依赖submitter_name 本迭代可实现审批人字段依赖需求21充值审批流


前端

各列表页新增对应字段列,展示即可,无额外交互。