191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
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
|
|
}
|
|
}
|