From 9f5c5c36809f150eb21f182652e72d98091c1a78 Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 19 May 2026 12:06:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=8D=A1=E5=88=97=E8=A1=A8):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20has=5Factive=5Fpackage=20=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=AD=9B=E9=80=89=E7=94=9F=E6=95=88=E4=B8=AD?= =?UTF-8?q?=E5=A5=97=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DTO ListStandaloneIotCardRequest 增加 HasActivePackage *bool 字段, query/json 名 has_active_package,description 中文 - Service ListStandalone 将 HasActivePackage 映射为 filters["has_active_package"] - Store applyStandaloneFilters 新增 EXISTS/NOT EXISTS 子查询, 使用 constants.PackageUsageStatusActive,限制 master_usage_id IS NULL 且 deleted_at IS NULL,所有查询路径(默认/两阶段/并行)均复用此函数 Co-Authored-By: Claude Sonnet 4.6 --- internal/model/dto/iot_card_dto.go | 3 ++- internal/service/iot_card/service.go | 3 +++ internal/store/postgres/iot_card_store.go | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/model/dto/iot_card_dto.go b/internal/model/dto/iot_card_dto.go index a3435ec..1293a82 100644 --- a/internal/model/dto/iot_card_dto.go +++ b/internal/model/dto/iot_card_dto.go @@ -17,7 +17,8 @@ type ListStandaloneIotCardRequest struct { BatchNo string `json:"batch_no" query:"batch_no" validate:"omitempty,max=100" maxLength:"100" description:"批次号"` PackageID *uint `json:"package_id" query:"package_id" description:"套餐ID"` IsDistributed *bool `json:"is_distributed" query:"is_distributed" description:"是否已分销 (true:已分销, false:未分销)"` - IsReplaced *bool `json:"is_replaced" query:"is_replaced" description:"是否有换卡记录 (true:有换卡记录, false:无换卡记录)"` + IsReplaced *bool `json:"is_replaced" query:"is_replaced" description:"是否有换卡记录 (true:有换卡记录, false:无换卡记录)"` + HasActivePackage *bool `json:"has_active_package" query:"has_active_package" description:"是否有生效中的套餐 (true:有生效中套餐, false:无生效中套餐, 不传:不过滤)"` ICCIDStart string `json:"iccid_start" query:"iccid_start" validate:"omitempty,max=20" maxLength:"20" description:"ICCID起始号"` ICCIDEnd string `json:"iccid_end" query:"iccid_end" validate:"omitempty,max=20" maxLength:"20" description:"ICCID结束号"` CarrierName string `json:"carrier_name" query:"carrier_name" validate:"omitempty,max=100" maxLength:"100" description:"运营商名称(模糊查询)"` diff --git a/internal/service/iot_card/service.go b/internal/service/iot_card/service.go index b0dc946..64658f8 100644 --- a/internal/service/iot_card/service.go +++ b/internal/service/iot_card/service.go @@ -203,6 +203,9 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot if req.CarrierName != "" { filters["carrier_name"] = req.CarrierName } + if req.HasActivePackage != nil { + filters["has_active_package"] = *req.HasActivePackage + } // 代理用户注入 subordinate_shop_ids,让 Store 层走并行查询路径 // 避免 PG 对 shop_id IN (...) + ORDER BY 选择全表扫描 diff --git a/internal/store/postgres/iot_card_store.go b/internal/store/postgres/iot_card_store.go index 190197e..d3b9a95 100644 --- a/internal/store/postgres/iot_card_store.go +++ b/internal/store/postgres/iot_card_store.go @@ -895,6 +895,16 @@ func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.D if carrierName, ok := filters["carrier_name"].(string); ok && carrierName != "" { query = query.Where("carrier_name LIKE ?", "%"+carrierName+"%") } + // has_active_package 过滤:是否存在生效中的主套餐记录 + if hasActivePackage, ok := filters["has_active_package"].(bool); ok { + subQuery := s.db.WithContext(ctx).Table("tb_package_usage pu").Select("1"). + Where("pu.iot_card_id = tb_iot_card.id AND pu.status = ? AND pu.master_usage_id IS NULL AND pu.deleted_at IS NULL", constants.PackageUsageStatusActive) + if hasActivePackage { + query = query.Where("EXISTS (?)", subQuery) + } else { + query = query.Where("NOT EXISTS (?)", subQuery) + } + } return query }