// Package shop 提供店铺创建与业务员归属的简单写事务脚本。 package shop import ( "context" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // CreateService 收口平台与代理创建店铺的完整事务。 type CreateService struct { db *gorm.DB } // NewCreateService 创建店铺创建事务脚本。 func NewCreateService(db *gorm.DB) *CreateService { return &CreateService{db: db} } // Create 按操作者类型执行平台显式归属或代理安全继承。 func (s *CreateService) Create(ctx context.Context, request *dto.CreateShopRequest) (*dto.ShopResponse, error) { userType := middleware.GetUserTypeFromContext(ctx) operatorID := middleware.GetUserIDFromContext(ctx) if operatorID == 0 { return nil, errors.New(errors.CodeUnauthorized) } resolver := resolvePlatformBusinessOwner switch userType { case constants.UserTypeSuperAdmin, constants.UserTypePlatform: case constants.UserTypeAgent: if request.BusinessOwnerAccountIDSet { return nil, errors.New(errors.CodeForbidden, "无权限设置店铺业务员") } if request.ParentID == nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } if err := middleware.CanManageShop(ctx, *request.ParentID); err != nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } resolver = resolveInheritedBusinessOwner default: return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.InitPassword), bcrypt.DefaultCost) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "密码哈希失败") } var response *dto.ShopResponse err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { created, createErr := createShop(tx, request, operatorID, string(hashedPassword), resolver) if createErr != nil { return createErr } response = created return nil }) if err != nil { return nil, err } return response, nil } type businessOwnerResolver func(*gorm.DB, *dto.CreateShopRequest, *model.Shop) (*uint, error) func createShop(tx *gorm.DB, request *dto.CreateShopRequest, operatorID uint, hashedPassword string, resolveOwner businessOwnerResolver) (*dto.ShopResponse, error) { if exists, err := recordExists(tx, &model.Shop{}, "shop_code = ?", request.ShopCode); err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "校验店铺编号失败") } else if exists { return nil, errors.New(errors.CodeShopCodeExists, "店铺编号已存在") } if exists, err := recordExists(tx, &model.Account{}, "username = ?", request.InitUsername); err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "校验初始账号用户名失败") } else if exists { return nil, errors.New(errors.CodeUsernameExists, "初始账号用户名已存在") } if exists, err := recordExists(tx, &model.Account{}, "phone = ?", request.InitPhone); err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "校验初始账号手机号失败") } else if exists { return nil, errors.New(errors.CodePhoneExists, "初始账号手机号已存在") } parent, level, err := resolveParent(tx, request.ParentID) if err != nil { return nil, err } ownerID, err := resolveOwner(tx, request, parent) if err != nil { return nil, err } var role model.Role if err := tx.Where("id = ? AND role_type = ? AND status = ?", request.DefaultRoleID, constants.RoleTypeCustomer, constants.StatusEnabled).First(&role).Error; err != nil { return nil, errors.New(errors.CodeInvalidParam, "请选择启用的客户角色") } shop := &model.Shop{ ShopName: request.ShopName, ShopCode: request.ShopCode, ParentID: request.ParentID, BusinessOwnerAccountID: ownerID, Level: level, ContactName: request.ContactName, ContactPhone: request.ContactPhone, Province: request.Province, City: request.City, District: request.District, Address: request.Address, Status: constants.ShopStatusEnabled, } shop.Creator = operatorID shop.Updater = operatorID if err := tx.Create(shop).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建店铺失败") } account := &model.Account{ Username: request.InitUsername, Phone: request.InitPhone, Password: hashedPassword, UserType: constants.UserTypeAgent, ShopID: &shop.ID, Status: constants.StatusEnabled, IsPrimary: true, } account.Creator = operatorID account.Updater = operatorID if err := tx.Create(account).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建初始账号失败") } if err := tx.Create(&model.AccountRole{ AccountID: account.ID, RoleID: request.DefaultRoleID, Status: constants.StatusEnabled, Creator: operatorID, Updater: operatorID, }).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "为初始账号分配角色失败") } if err := tx.Create(&model.ShopRole{ ShopID: shop.ID, RoleID: request.DefaultRoleID, Status: constants.StatusEnabled, Creator: operatorID, Updater: operatorID, }).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "设置店铺默认角色失败") } if err := tx.Create([]*model.AgentWallet{ { ShopID: shop.ID, WalletType: constants.AgentWalletTypeMain, CreditEnabled: role.DefaultCreditEnabled, CreditLimit: role.DefaultCreditLimit, Currency: "CNY", Status: constants.AgentWalletStatusNormal, ShopIDTag: shop.ID, }, { ShopID: shop.ID, WalletType: constants.AgentWalletTypeCommission, CreditEnabled: false, CreditLimit: 0, Currency: "CNY", Status: constants.AgentWalletStatusNormal, ShopIDTag: shop.ID, }, }).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "初始化店铺钱包失败") } parentName := "" if parent != nil { parentName = parent.ShopName } response := newShopResponse(shop, parentName) if err := fillBusinessOwnerResponse(tx, shop, response); err != nil { return nil, err } return response, nil } func resolveParent(tx *gorm.DB, parentID *uint) (*model.Shop, int, error) { if parentID == nil { return nil, 1, nil } var parent model.Shop if err := tx.First(&parent, *parentID).Error; err != nil { return nil, 0, errors.New(errors.CodeInvalidParentID, "上级店铺不存在或无效") } level := parent.Level + 1 if level > constants.ShopMaxLevel { return nil, 0, errors.New(errors.CodeShopLevelExceeded, "店铺层级不能超过 7 级") } return &parent, level, nil } func resolvePlatformBusinessOwner(tx *gorm.DB, request *dto.CreateShopRequest, parent *model.Shop) (*uint, error) { if !request.BusinessOwnerAccountIDSet { if parent == nil || parent.BusinessOwnerAccountID == nil { return nil, nil } ownerID := *parent.BusinessOwnerAccountID return &ownerID, nil } if request.BusinessOwnerAccountID == nil { return nil, nil } if *request.BusinessOwnerAccountID == 0 { return nil, errors.New(errors.CodeInvalidParam, "业务员账号无效") } var account model.Account if err := tx.Where("id = ? AND user_type = ? AND status = ?", *request.BusinessOwnerAccountID, constants.UserTypePlatform, constants.StatusEnabled). First(&account).Error; err != nil { return nil, errors.New(errors.CodeInvalidParam, "业务员账号无效或不可用") } ownerID := account.ID return &ownerID, nil } func resolveInheritedBusinessOwner(_ *gorm.DB, _ *dto.CreateShopRequest, parent *model.Shop) (*uint, error) { if parent == nil || parent.BusinessOwnerAccountID == nil { return nil, nil } ownerID := *parent.BusinessOwnerAccountID return &ownerID, nil } func recordExists(tx *gorm.DB, target any, query string, value any) (bool, error) { var count int64 err := tx.Model(target).Where(query, value).Count(&count).Error return count > 0, err } func newShopResponse(shop *model.Shop, parentName string) *dto.ShopResponse { return &dto.ShopResponse{ ID: shop.ID, ShopName: shop.ShopName, ShopCode: shop.ShopCode, ParentID: shop.ParentID, BusinessOwnerAccountID: shop.BusinessOwnerAccountID, ParentShopName: parentName, Level: shop.Level, ContactName: shop.ContactName, ContactPhone: shop.ContactPhone, Province: shop.Province, City: shop.City, District: shop.District, Address: shop.Address, Status: shop.Status, StatusName: constants.GetStatusName(shop.Status), CreatedAt: shop.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: shop.UpdatedAt.Format("2006-01-02 15:04:05"), } } func fillBusinessOwnerResponse(tx *gorm.DB, shop *model.Shop, response *dto.ShopResponse) error { if shop.BusinessOwnerAccountID == nil { return nil } var account model.Account err := tx.Unscoped().Where("id = ?", *shop.BusinessOwnerAccountID).First(&account).Error if err == gorm.ErrRecordNotFound { return nil } if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询业务员摘要失败") } response.BusinessOwnerUsername = account.Username response.BusinessOwnerPhoneSummary = maskBusinessOwnerPhone(account.Phone) response.BusinessOwnerAvailable = account.UserType == constants.UserTypePlatform && account.Status == constants.StatusEnabled && !account.DeletedAt.Valid return nil } func maskBusinessOwnerPhone(phone string) string { phone = strings.TrimSpace(phone) if len(phone) < 7 { return "" } return phone[:3] + "****" + phone[len(phone)-4:] }