- CreateMerchant 事务内调用 copyDefaultProducts,将自营商户全部可售商品复制给新商户 - 新增 model.MerchantCodeSelfOperated 常量,消除散落的 self-operated 魔法字符串 - tenant.go 替换魔法字符串为常量 - 实测:新建商户后自动获得 27 个商品,价格/货币/库存/关联商品均正确
164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"affiliate_dash/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// TenantService 负责商户成员关系与请求租户解析。
|
|
type TenantService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewTenantService(db *gorm.DB) *TenantService {
|
|
return &TenantService{db: db}
|
|
}
|
|
|
|
func (s *TenantService) ResolveMember(userID uint, merchantRef string) (*model.MerchantMember, error) {
|
|
if userID == 0 {
|
|
return nil, errors.New("无效的用户身份")
|
|
}
|
|
tx := s.db.Preload("Merchant").
|
|
Where("merchant_members.user_id = ? AND merchant_members.status = ?", userID, 1).
|
|
Joins("JOIN merchants ON merchants.id = merchant_members.merchant_id AND merchants.status = ?", model.MerchantStatusActive)
|
|
if merchantRef != "" {
|
|
if id, err := strconv.ParseUint(merchantRef, 10, 64); err == nil {
|
|
tx = tx.Where("merchant_members.merchant_id = ?", uint(id))
|
|
} else {
|
|
tx = tx.Where("merchants.code = ?", merchantRef)
|
|
}
|
|
}
|
|
var member model.MerchantMember
|
|
err := tx.Order("merchant_members.is_default DESC, merchant_members.id ASC").First(&member).Error
|
|
if err == nil {
|
|
return &member, nil
|
|
}
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("当前账号无权访问该商户")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (s *TenantService) EnsureSelfMember(userID uint, role string, status int) error {
|
|
var merchant model.Merchant
|
|
if err := s.db.Where("code = ?", model.MerchantCodeSelfOperated).First(&merchant).Error; err != nil {
|
|
return err
|
|
}
|
|
return s.EnsureMember(merchant.ID, userID, role, status, role == model.MemberRoleOwner)
|
|
}
|
|
|
|
func (s *TenantService) EnsureMember(merchantID, userID uint, role string, status int, isDefault bool) error {
|
|
if merchantID == 0 || userID == 0 {
|
|
return errors.New("商户和用户不能为空")
|
|
}
|
|
if !isValidMemberRole(role) {
|
|
return errors.New("无效的商户成员角色")
|
|
}
|
|
member := model.MerchantMember{
|
|
MerchantID: merchantID,
|
|
UserID: userID,
|
|
Role: role,
|
|
Status: status,
|
|
IsDefault: isDefault,
|
|
}
|
|
return s.db.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "merchant_id"}, {Name: "user_id"}},
|
|
DoUpdates: clause.Assignments(map[string]interface{}{
|
|
"role": role,
|
|
"status": status,
|
|
"is_default": isDefault,
|
|
}),
|
|
}).Create(&member).Error
|
|
}
|
|
|
|
func isValidMemberRole(role string) bool {
|
|
switch role {
|
|
case model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance, model.MemberRoleViewer:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func MemberCanManage(memberRole string) bool {
|
|
return memberRole == model.MemberRoleOwner || memberRole == model.MemberRoleOperator
|
|
}
|
|
|
|
func MemberCanManageFinance(memberRole string) bool {
|
|
return memberRole == model.MemberRoleOwner || memberRole == model.MemberRoleFinance
|
|
}
|
|
|
|
func ParseScopes(scopes string) map[string]struct{} {
|
|
out := make(map[string]struct{})
|
|
for _, scope := range strings.FieldsFunc(scopes, func(r rune) bool {
|
|
return r == ',' || r == ' ' || r == '\n' || r == '\t'
|
|
}) {
|
|
if scope != "" {
|
|
out[scope] = struct{}{}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func HasAnyScope(scopes string, wanted ...string) bool {
|
|
set := ParseScopes(scopes)
|
|
if _, ok := set["*"]; ok {
|
|
return true
|
|
}
|
|
for _, scope := range wanted {
|
|
if _, ok := set[scope]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NormalizeMerchantFeatures(features string) string {
|
|
set := ParseScopes(features)
|
|
if len(set) == 0 {
|
|
set = ParseScopes(model.DefaultMerchantFeatures)
|
|
}
|
|
valid := map[string]struct{}{
|
|
model.MerchantFeatureProducts: {},
|
|
model.MerchantFeatureOrders: {},
|
|
model.MerchantFeatureWallet: {},
|
|
model.MerchantFeatureAPI: {},
|
|
model.MerchantFeatureCallbacks: {},
|
|
}
|
|
ordered := []string{
|
|
model.MerchantFeatureProducts,
|
|
model.MerchantFeatureOrders,
|
|
model.MerchantFeatureWallet,
|
|
model.MerchantFeatureAPI,
|
|
model.MerchantFeatureCallbacks,
|
|
}
|
|
out := make([]string, 0, len(ordered))
|
|
for _, feature := range ordered {
|
|
if _, ok := set[feature]; ok {
|
|
if _, allowed := valid[feature]; allowed {
|
|
out = append(out, feature)
|
|
}
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return model.DefaultMerchantFeatures
|
|
}
|
|
return strings.Join(out, ",")
|
|
}
|
|
|
|
func MerchantHasFeature(features string, wanted ...string) bool {
|
|
set := ParseScopes(features)
|
|
for _, feature := range wanted {
|
|
if _, ok := set[feature]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|