package service import ( "database/sql" "errors" "fmt" "strings" "time" "cmautobuy/admin/model" "cmautobuy/admin/repository" ) const maxShopNameLength = 500 type ShopListResult struct { Items []model.Shop UnlinkedShopeeCount int } func ListShops(db *sql.DB, actor *model.User) (ShopListResult, error) { if actor == nil || !actor.IsAdmin() { return ShopListResult{}, ErrAdminRequired } items, err := repository.ListShops(db) if err != nil { return ShopListResult{}, err } unlinked, err := repository.CountUnlinkedShopeeShops(db) if err != nil { return ShopListResult{}, err } return ShopListResult{Items: items, UnlinkedShopeeCount: unlinked}, nil } func CreateShop(db *sql.DB, actor *model.User, shopName string, now time.Time) error { if actor == nil || !actor.IsAdmin() { return ErrAdminRequired } shopName, err := validateShopName(shopName) if err != nil { return err } shopID, err := randomID("SHOP-", 16) if err != nil { return fmt.Errorf("生成店铺编号失败: %w", err) } at := now.UTC().Format(model.TimeLayout) tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() if err := repository.InsertShop(tx, model.Shop{ShopID: shopID, DisplayName: shopName, NormalizedName: shopName, Enabled: true, CreatedByUserID: actor.UserID, CreatedAt: at, UpdatedAt: at}); err != nil { return shopValidationError(err) } if err := insertShopAlias(tx, shopID, "syb", shopName, true, at); err != nil { return shopValidationError(err) } if err := insertShopAlias(tx, shopID, "shopee", shopName, true, at); err != nil { return shopValidationError(err) } if err := repository.RebuildShopAssociations(tx, shopID); err != nil { return err } return tx.Commit() } func UpdateShop(db *sql.DB, actor *model.User, shopID, shopName string, now time.Time) error { if actor == nil || !actor.IsAdmin() { return ErrAdminRequired } shopID = strings.TrimSpace(shopID) if shopID == "" { return &validationError{field: "shop_id", message: "店铺编号不能为空"} } shopName, err := validateShopName(shopName) if err != nil { return err } at := now.UTC().Format(model.TimeLayout) tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() shop, found, err := repository.GetShop(tx, shopID) if err != nil { return err } else if !found { return &validationError{field: "shop_id", message: "店铺不存在,请刷新页面后重试"} } if _, err := repository.UpdateShopName(tx, shopID, shopName, shopName, at); err != nil { return shopValidationError(err) } if err := replaceShopAlias(tx, shopID, "syb", shopName, shop.Enabled, at); err != nil { return shopValidationError(err) } if err := replaceShopAlias(tx, shopID, "shopee", shopName, shop.Enabled, at); err != nil { return shopValidationError(err) } if err := repository.RebuildShopAssociations(tx, shopID); err != nil { return err } return tx.Commit() } func SetShopEnabled(db *sql.DB, actor *model.User, shopID string, enabled bool, now time.Time) error { if actor == nil || !actor.IsAdmin() { return ErrAdminRequired } shopID = strings.TrimSpace(shopID) at := now.UTC().Format(model.TimeLayout) tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() found, err := repository.SetShopEnabled(tx, shopID, enabled, at) if err != nil { return err } if !found { return &validationError{field: "shop_id", message: "店铺不存在,请刷新页面后重试"} } if err := repository.SetShopCompatibilityAliasesEnabled(tx, shopID, enabled, at); err != nil { return err } return tx.Commit() } func DeleteShop(db *sql.DB, actor *model.User, shopID string) error { if actor == nil || !actor.IsAdmin() { return ErrAdminRequired } deleted, err := repository.DeleteDisabledShop(db, strings.TrimSpace(shopID)) if errors.Is(err, repository.ErrShopHasReferences) { return &validationError{field: "shop_id", message: "店铺仍有关联数据,只能保留为停用状态"} } if err != nil { return err } if !deleted { return &validationError{field: "shop_id", message: "店铺不存在或仍在启用,请先停用"} } return nil } func validateShopName(shopName string) (string, error) { shopName = strings.TrimSpace(shopName) if shopName == "" { return "", &validationError{field: "shop_name", message: "店铺名称不能为空"} } if len([]rune(shopName)) > maxShopNameLength { return "", &validationError{field: "shop_name", message: "店铺名称最多 500 个字符"} } return shopName, nil } func insertShopAlias(q repository.Execer, shopID, channel, alias string, enabled bool, at string) error { if alias == "" { return nil } aliasID, err := randomID("ALIAS-", 16) if err != nil { return err } return repository.InsertShopAlias(q, model.ShopChannelAlias{AliasID: aliasID, ShopID: shopID, Channel: channel, AliasName: alias, NormalizedAlias: alias, Enabled: enabled, CreatedAt: at, UpdatedAt: at}) } func replaceShopAlias(q repository.Execer, shopID, channel, alias string, enabled bool, at string) error { aliasID, err := randomID("ALIAS-", 16) if err != nil { return err } return repository.ReplaceShopAlias(q, aliasID, shopID, channel, alias, at, enabled) } func shopValidationError(err error) error { switch { case errors.Is(err, repository.ErrShopNameExists): return &validationError{field: "shop_name", message: "店铺名称已经存在"} case errors.Is(err, repository.ErrShopAliasExists): return &validationError{field: "shop_name", message: "店铺名称已经存在"} default: return err } }