254 lines
9.1 KiB
Go
254 lines
9.1 KiB
Go
package repository
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"cmautobuy/admin/model"
|
||
|
|
"github.com/go-sql-driver/mysql"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
ErrShopNameExists = errors.New("店铺名称已经存在")
|
||
|
|
ErrShopAliasExists = errors.New("渠道店铺名称已经关联")
|
||
|
|
ErrShopHasReferences = errors.New("店铺仍有关联数据")
|
||
|
|
)
|
||
|
|
|
||
|
|
// ListShops 返回管理页的一店一行汇总;首版每个渠道维护一个主别名。
|
||
|
|
func ListShops(q Execer) ([]model.Shop, error) {
|
||
|
|
rows, err := q.Query(`SELECT s.shop_id,s.display_name,s.normalized_name,s.enabled,
|
||
|
|
COALESCE(MAX(CASE WHEN a.channel='syb' THEN a.alias_name END),''),
|
||
|
|
COALESCE(MAX(CASE WHEN a.channel='syb' THEN a.enabled END),0),
|
||
|
|
COALESCE(MAX(CASE WHEN a.channel='shopee' THEN a.alias_name END),''),
|
||
|
|
COUNT(DISTINCT sp.goods_id),s.created_by_user_id,s.created_at,s.updated_at
|
||
|
|
FROM shops s
|
||
|
|
LEFT JOIN shop_channel_aliases a ON a.shop_id=s.shop_id
|
||
|
|
LEFT JOIN shopee_products sp ON sp.shop_id=s.shop_id AND sp.deleted_at IS NULL
|
||
|
|
GROUP BY s.shop_id ORDER BY s.enabled DESC,s.normalized_name`)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("查询店铺列表失败: %w", err)
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
var result []model.Shop
|
||
|
|
for rows.Next() {
|
||
|
|
var item model.Shop
|
||
|
|
var enabled, sybEnabled int
|
||
|
|
if err := rows.Scan(&item.ShopID, &item.DisplayName, &item.NormalizedName, &enabled,
|
||
|
|
&item.SybAlias, &sybEnabled, &item.ShopeeAlias, &item.ShopeeProductCount,
|
||
|
|
&item.CreatedByUserID, &item.CreatedAt, &item.UpdatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
item.Enabled = enabled == 1
|
||
|
|
item.SybSyncEnabled = sybEnabled == 1
|
||
|
|
result = append(result, item)
|
||
|
|
}
|
||
|
|
return result, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
// ListShopOptions 返回筛选器使用的全部业务店铺,停用项仍保留以便查历史数据。
|
||
|
|
func ListShopOptions(q Execer) ([]model.Shop, error) {
|
||
|
|
rows, err := q.Query(`SELECT shop_id,display_name,normalized_name,enabled,created_by_user_id,created_at,updated_at
|
||
|
|
FROM shops ORDER BY enabled DESC,normalized_name`)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("查询店铺选项失败: %w", err)
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
var result []model.Shop
|
||
|
|
for rows.Next() {
|
||
|
|
var item model.Shop
|
||
|
|
var enabled int
|
||
|
|
if err := rows.Scan(&item.ShopID, &item.DisplayName, &item.NormalizedName, &enabled,
|
||
|
|
&item.CreatedByUserID, &item.CreatedAt, &item.UpdatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
item.Enabled = enabled == 1
|
||
|
|
result = append(result, item)
|
||
|
|
}
|
||
|
|
return result, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetShop(q Execer, shopID string) (model.Shop, bool, error) {
|
||
|
|
var item model.Shop
|
||
|
|
var enabled int
|
||
|
|
err := q.QueryRow(`SELECT shop_id,display_name,normalized_name,enabled,created_by_user_id,created_at,updated_at
|
||
|
|
FROM shops WHERE shop_id=?`, shopID).Scan(&item.ShopID, &item.DisplayName,
|
||
|
|
&item.NormalizedName, &enabled, &item.CreatedByUserID, &item.CreatedAt, &item.UpdatedAt)
|
||
|
|
if errors.Is(err, sql.ErrNoRows) {
|
||
|
|
return model.Shop{}, false, nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return model.Shop{}, false, fmt.Errorf("查询店铺失败: %w", err)
|
||
|
|
}
|
||
|
|
item.Enabled = enabled == 1
|
||
|
|
return item, true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetShopAliasEnabled(q Execer, shopID, channel string) (bool, bool, error) {
|
||
|
|
var enabled int
|
||
|
|
err := q.QueryRow(`SELECT enabled FROM shop_channel_aliases WHERE shop_id=? AND channel=? LIMIT 1`,
|
||
|
|
shopID, channel).Scan(&enabled)
|
||
|
|
if errors.Is(err, sql.ErrNoRows) {
|
||
|
|
return false, false, nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return false, false, fmt.Errorf("查询渠道店铺状态失败: %w", err)
|
||
|
|
}
|
||
|
|
return enabled == 1, true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ListEnabledSybShopMappings 返回“SYB 原始精确名称 → 业务店铺 ID”。
|
||
|
|
func ListEnabledSybShopMappings(q Execer) (map[string]string, error) {
|
||
|
|
rows, err := q.Query(`SELECT a.normalized_alias,a.shop_id FROM shop_channel_aliases a
|
||
|
|
JOIN shops s ON s.shop_id=a.shop_id WHERE a.channel='syb' AND a.enabled=1 AND s.enabled=1
|
||
|
|
ORDER BY a.normalized_alias`)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("查询启用的 SYB 店铺配置失败: %w", err)
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
result := map[string]string{}
|
||
|
|
for rows.Next() {
|
||
|
|
var name, shopID string
|
||
|
|
if err := rows.Scan(&name, &shopID); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
result[name] = shopID
|
||
|
|
}
|
||
|
|
return result, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func FindShopIDByAlias(q Execer, channel, rawName string) (string, error) {
|
||
|
|
name := strings.TrimSpace(rawName)
|
||
|
|
if name == "" {
|
||
|
|
return "", nil
|
||
|
|
}
|
||
|
|
var shopID string
|
||
|
|
err := q.QueryRow(`SELECT shop_id FROM shop_channel_aliases
|
||
|
|
WHERE channel=? AND normalized_alias=?`, channel, name).Scan(&shopID)
|
||
|
|
if errors.Is(err, sql.ErrNoRows) {
|
||
|
|
return "", nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("按 %s 店铺名称查业务店铺失败: %w", channel, err)
|
||
|
|
}
|
||
|
|
return shopID, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func InsertShop(q Execer, item model.Shop) error {
|
||
|
|
_, err := q.Exec(`INSERT INTO shops(shop_id,display_name,normalized_name,enabled,created_by_user_id,created_at,updated_at)
|
||
|
|
VALUES(?,?,?,?,?,?,?)`, item.ShopID, item.DisplayName, item.NormalizedName, item.Enabled,
|
||
|
|
item.CreatedByUserID, item.CreatedAt, item.UpdatedAt)
|
||
|
|
if err != nil {
|
||
|
|
var mysqlErr *mysql.MySQLError
|
||
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||
|
|
return ErrShopNameExists
|
||
|
|
}
|
||
|
|
return fmt.Errorf("新增店铺失败: %w", err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func InsertShopAlias(q Execer, item model.ShopChannelAlias) error {
|
||
|
|
_, err := q.Exec(`INSERT INTO shop_channel_aliases(alias_id,shop_id,channel,alias_name,normalized_alias,enabled,created_at,updated_at)
|
||
|
|
VALUES(?,?,?,?,?,?,?,?)`, item.AliasID, item.ShopID, item.Channel, item.AliasName,
|
||
|
|
item.NormalizedAlias, item.Enabled, item.CreatedAt, item.UpdatedAt)
|
||
|
|
if err != nil {
|
||
|
|
var mysqlErr *mysql.MySQLError
|
||
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||
|
|
return ErrShopAliasExists
|
||
|
|
}
|
||
|
|
return fmt.Errorf("新增渠道店铺名称失败: %w", err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func UpdateShopName(q Execer, shopID, displayName, normalizedName, updatedAt string) (bool, error) {
|
||
|
|
result, err := q.Exec(`UPDATE shops SET display_name=?,normalized_name=?,updated_at=? WHERE shop_id=?`,
|
||
|
|
displayName, normalizedName, updatedAt, shopID)
|
||
|
|
if err != nil {
|
||
|
|
var mysqlErr *mysql.MySQLError
|
||
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||
|
|
return false, ErrShopNameExists
|
||
|
|
}
|
||
|
|
return false, fmt.Errorf("更新店铺名称失败: %w", err)
|
||
|
|
}
|
||
|
|
n, err := result.RowsAffected()
|
||
|
|
return n == 1, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func SetShopEnabled(q Execer, shopID string, enabled bool, updatedAt string) (bool, error) {
|
||
|
|
result, err := q.Exec(`UPDATE shops SET enabled=?,updated_at=? WHERE shop_id=?`, enabled, updatedAt, shopID)
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("更新店铺状态失败: %w", err)
|
||
|
|
}
|
||
|
|
n, err := result.RowsAffected()
|
||
|
|
return n == 1, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func SetShopSybEnabled(q Execer, shopID string, enabled bool, updatedAt string) (bool, error) {
|
||
|
|
result, err := q.Exec(`UPDATE shop_channel_aliases SET enabled=?,updated_at=?
|
||
|
|
WHERE shop_id=? AND channel='syb'`, enabled, updatedAt, shopID)
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("更新 SYB 同步状态失败: %w", err)
|
||
|
|
}
|
||
|
|
n, err := result.RowsAffected()
|
||
|
|
return n > 0, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func ReplaceShopAlias(q Execer, aliasID, shopID, channel, aliasName, updatedAt string, enabled bool) error {
|
||
|
|
if _, err := q.Exec(`DELETE FROM shop_channel_aliases WHERE shop_id=? AND channel=?`, shopID, channel); err != nil {
|
||
|
|
return fmt.Errorf("移除旧渠道店铺名称失败: %w", err)
|
||
|
|
}
|
||
|
|
if strings.TrimSpace(aliasName) == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return InsertShopAlias(q, model.ShopChannelAlias{AliasID: aliasID, ShopID: shopID, Channel: channel,
|
||
|
|
AliasName: aliasName, NormalizedAlias: strings.TrimSpace(aliasName), Enabled: enabled,
|
||
|
|
CreatedAt: updatedAt, UpdatedAt: updatedAt})
|
||
|
|
}
|
||
|
|
|
||
|
|
// RebuildShopAssociations 按渠道原始名称精确重建一个店铺的派生关联。
|
||
|
|
func RebuildShopAssociations(q Execer, shopID string) error {
|
||
|
|
if _, err := q.Exec(`UPDATE syb_orders SET shop_id=NULL WHERE shop_id=?`, shopID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, err := q.Exec(`UPDATE shopee_products SET shop_id=NULL WHERE shop_id=?`, shopID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, err := q.Exec(`UPDATE syb_orders so JOIN shop_channel_aliases a
|
||
|
|
ON a.shop_id=? AND a.channel='syb' AND a.normalized_alias=TRIM(so.shop_name) COLLATE utf8mb4_bin
|
||
|
|
SET so.shop_id=a.shop_id`, shopID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, err := q.Exec(`UPDATE shopee_products sp JOIN shop_channel_aliases a
|
||
|
|
ON a.shop_id=? AND a.channel='shopee' AND a.normalized_alias=TRIM(sp.shopee_shop_name) COLLATE utf8mb4_bin
|
||
|
|
SET sp.shop_id=a.shop_id`, shopID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func DeleteDisabledShop(q Execer, shopID string) (bool, error) {
|
||
|
|
var references int
|
||
|
|
if err := q.QueryRow(`SELECT (SELECT COUNT(*) FROM syb_orders WHERE shop_id=?)+
|
||
|
|
(SELECT COUNT(*) FROM shopee_products WHERE shop_id=?)`, shopID, shopID).Scan(&references); err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
if references > 0 {
|
||
|
|
return false, ErrShopHasReferences
|
||
|
|
}
|
||
|
|
result, err := q.Exec(`DELETE FROM shops WHERE shop_id=? AND enabled=0`, shopID)
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("删除停用店铺失败: %w", err)
|
||
|
|
}
|
||
|
|
n, err := result.RowsAffected()
|
||
|
|
return n == 1, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func CountUnlinkedShopeeShops(q Execer) (int, error) {
|
||
|
|
var count int
|
||
|
|
err := q.QueryRow(`SELECT COUNT(*) FROM shopee_products WHERE deleted_at IS NULL AND shop_id IS NULL`).Scan(&count)
|
||
|
|
return count, err
|
||
|
|
}
|