feat: 客户关联产品并支持按产品圈选通知群体

新增客户-产品多对多关联,列表可按产品筛选并复制邮箱;发证/下单自动回填历史关联。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 21:58:35 +08:00
1 parent d0e1ca3729
commit 4c46088f4f
10 files changed
+337 -89

No files matched your search

+48 -2
View File
@@ -63,8 +63,9 @@ func migrateDatabase(db *gorm.DB) (*gorm.DB, error) {
&model.PaymentGatewayConfig{}, // 支付网关配置 // Logan.Li
// 核心授权域
&model.Product{}, // 产品 // Logan.Li
&model.Customer{}, // 客户 // Logan.Li
&model.License{}, // License // Logan.Li
&model.Customer{}, // 客户 // Logan.Li
&model.CustomerProduct{}, // 客户-产品关联 // Logan.Li
&model.License{}, // License // Logan.Li
&model.Activation{}, // 激活记录 // Logan.Li
// 定价与订单
&model.Plan{}, // 定价计划 // Logan.Li
@@ -81,5 +82,50 @@ func migrateDatabase(db *gorm.DB) (*gorm.DB, error) {
return nil, fmt.Errorf("种子数据初始化失败: %v", err)
}
if err := backfillCustomerProducts(db); err != nil {
return nil, fmt.Errorf("客户产品关联回填失败: %v", err)
}
return db, nil
}
// backfillCustomerProducts 用历史 License/权益回填客户-产品关联 // Logan.Li
func backfillCustomerProducts(db *gorm.DB) error {
type pair struct {
CustomerID uint
ProductID uint
}
var pairs []pair
if err := db.Model(&model.License{}).
Select("DISTINCT customer_id, product_id").
Where("customer_id > 0 AND product_id > 0").
Scan(&pairs).Error; err != nil {
return err
}
var entPairs []pair
_ = db.Model(&model.CustomerEntitlement{}).
Select("DISTINCT customer_id, product_id").
Where("customer_id > 0 AND product_id > 0").
Scan(&entPairs)
pairs = append(pairs, entPairs...)
seen := map[[2]uint]struct{}{}
for _, p := range pairs {
k := [2]uint{p.CustomerID, p.ProductID}
if _, ok := seen[k]; ok {
continue
}
seen[k] = struct{}{}
var n int64
db.Model(&model.CustomerProduct{}).
Where("customer_id = ? AND product_id = ?", p.CustomerID, p.ProductID).
Count(&n)
if n > 0 {
continue
}
if err := db.Create(&model.CustomerProduct{CustomerID: p.CustomerID, ProductID: p.ProductID}).Error; err != nil {
return err
}
}
return nil
}