feat(merchant): 新建商户自动复制自营商户的 27 个默认商品

- CreateMerchant 事务内调用 copyDefaultProducts,将自营商户全部可售商品复制给新商户
- 新增 model.MerchantCodeSelfOperated 常量,消除散落的 self-operated 魔法字符串
- tenant.go 替换魔法字符串为常量
- 实测:新建商户后自动获得 27 个商品,价格/货币/库存/关联商品均正确
This commit is contained in:
yml2213
2026-07-30 15:12:40 +08:00
parent 108fd834aa
commit 7cff2dffed
3 changed files with 46 additions and 1 deletions
+3
View File
@@ -10,6 +10,9 @@ const (
MerchantStatusActive = "active" MerchantStatusActive = "active"
MerchantStatusDisabled = "disabled" MerchantStatusDisabled = "disabled"
// MerchantCodeSelfOperated 是平台自营商户编码,作为默认商品目录的模板来源。
MerchantCodeSelfOperated = "self-operated"
MerchantFeatureProducts = "products" MerchantFeatureProducts = "products"
MerchantFeatureOrders = "orders" MerchantFeatureOrders = "orders"
MerchantFeatureWallet = "wallet" MerchantFeatureWallet = "wallet"
+42
View File
@@ -103,6 +103,9 @@ func (s *MerchantService) CreateMerchant(in CreateMerchantInput, actorUserID uin
}).Error; err != nil { }).Error; err != nil {
return err return err
} }
if err := copyDefaultProducts(tx, merchant.ID); err != nil {
return err
}
return writeAudit(tx, &merchant.ID, &actorUserID, nil, "merchant.create", "merchant", fmt.Sprint(merchant.ID), map[string]string{"code": merchant.Code}) return writeAudit(tx, &merchant.ID, &actorUserID, nil, "merchant.create", "merchant", fmt.Sprint(merchant.ID), map[string]string{"code": merchant.Code})
}) })
if err != nil { if err != nil {
@@ -532,6 +535,45 @@ func (s *MerchantService) UpdateAPIClientStatus(merchantID, id uint, status stri
}) })
} }
// copyDefaultProducts 将自营商户的全部可售商品复制给新建商户,作为默认商品目录。
// 自营商户(self-operated)充当平台默认商品模板,新商户开箱即用。
// 使用 OnConflict DoNothing 保证幂等:即使重复调用也不会报唯一索引冲突。
func copyDefaultProducts(tx *gorm.DB, merchantID uint) error {
var selfMerchant model.Merchant
if err := tx.Where("code = ?", model.MerchantCodeSelfOperated).First(&selfMerchant).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil // 自营商户不存在时跳过,不阻断建商户
}
return err
}
if selfMerchant.ID == merchantID {
return nil // 自营商户自身无需复制
}
var templates []model.MerchantProduct
if err := tx.Where("merchant_id = ?", selfMerchant.ID).Find(&templates).Error; err != nil {
return err
}
if len(templates) == 0 {
return nil
}
products := make([]model.MerchantProduct, 0, len(templates))
for _, t := range templates {
products = append(products, model.MerchantProduct{
MerchantID: merchantID,
ProductID: t.ProductID,
SKU: t.SKU,
DisplayName: t.DisplayName,
PriceAmount: t.PriceAmount,
CostAmount: t.CostAmount,
Currency: t.Currency,
Stock: t.Stock,
Status: t.Status,
FulfillmentConfig: t.FulfillmentConfig,
})
}
return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&products).Error
}
func ensureProduct(tx *gorm.DB, in CreateMerchantProductInput) (*model.Product, error) { func ensureProduct(tx *gorm.DB, in CreateMerchantProductInput) (*model.Product, error) {
if in.ProductCode != "" { if in.ProductCode != "" {
var product model.Product var product model.Product
+1 -1
View File
@@ -47,7 +47,7 @@ func (s *TenantService) ResolveMember(userID uint, merchantRef string) (*model.M
func (s *TenantService) EnsureSelfMember(userID uint, role string, status int) error { func (s *TenantService) EnsureSelfMember(userID uint, role string, status int) error {
var merchant model.Merchant var merchant model.Merchant
if err := s.db.Where("code = ?", "self-operated").First(&merchant).Error; err != nil { if err := s.db.Where("code = ?", model.MerchantCodeSelfOperated).First(&merchant).Error; err != nil {
return err return err
} }
return s.EnsureMember(merchant.ID, userID, role, status, role == model.MemberRoleOwner) return s.EnsureMember(merchant.ID, userID, role, status, role == model.MemberRoleOwner)