+79
-15
@@ -136,6 +136,7 @@ type ShopeeFilter struct {
|
||||
Status string
|
||||
Shop string // has / missing / 空(全部)
|
||||
Image string // has / missing / 空(全部)
|
||||
Deleted bool // true 只看软删除数据;false 只看正常数据
|
||||
}
|
||||
|
||||
// shopeeFilterClause 把关键字、状态、店铺和图片筛选拼成 WHERE 子句,供 ListShopeeProducts
|
||||
@@ -146,7 +147,10 @@ type ShopeeFilter struct {
|
||||
// SKU 时 JOIN 会出重复行,DISTINCT 又会让外层 LIMIT/OFFSET 的行为难推理
|
||||
// (见工单 #43)。
|
||||
func shopeeFilterClause(filter ShopeeFilter) (string, []any) {
|
||||
var clauses []string
|
||||
clauses := []string{"sp.deleted_at IS NULL"}
|
||||
if filter.Deleted {
|
||||
clauses[0] = "sp.deleted_at IS NOT NULL"
|
||||
}
|
||||
var args []any
|
||||
|
||||
if kw := strings.TrimSpace(filter.Keyword); kw != "" {
|
||||
@@ -183,9 +187,6 @@ func shopeeFilterClause(filter ShopeeFilter) (string, []any) {
|
||||
clauses = append(clauses, `(sp.image_url IS NULL OR TRIM(sp.image_url) = '')`)
|
||||
}
|
||||
|
||||
if len(clauses) == 0 {
|
||||
return "", args
|
||||
}
|
||||
return " WHERE " + strings.Join(clauses, " AND "), args
|
||||
}
|
||||
|
||||
@@ -200,7 +201,7 @@ func ListShopeeProducts(q Execer, filter ShopeeFilter, limit, offset int) ([]Sho
|
||||
where, args := shopeeFilterClause(filter)
|
||||
sqlText := `
|
||||
SELECT sp.goods_id, sp.title, sp.shopee_status, sp.main_sku_code, sp.image_url,sp.shopee_shop_name,sp.image_source,sp.image_observed_at,sp.image_is_manual,sp.shop_name_source,sp.shop_name_observed_at,sp.shop_name_is_manual,sp.source,
|
||||
sp.pdd_goods_url, sp.pdd_goods_id, sp.created_at, sp.updated_at,
|
||||
sp.pdd_goods_url, sp.pdd_goods_id,sp.deleted_at,sp.deleted_by_user_id, sp.created_at, sp.updated_at,
|
||||
COUNT(DISTINCT CASE WHEN sk.parse_ok = 1 THEN sk.color END) AS color_count,
|
||||
COUNT(DISTINCT CASE WHEN sk.parse_ok = 1 THEN sk.size END) AS size_count,
|
||||
COUNT(sk.sku_id) AS sku_count,
|
||||
@@ -222,11 +223,11 @@ func ListShopeeProducts(q Execer, filter ShopeeFilter, limit, offset int) ([]Sho
|
||||
var list []ShopeeProductRow
|
||||
for rows.Next() {
|
||||
var r ShopeeProductRow
|
||||
var title, shopeeStatus, mainSKUCode, imageURL, shopName, imageSource, imageObserved, shopSource, shopObserved, source, pddGoodsURL, pddGoodsID sql.NullString
|
||||
var title, shopeeStatus, mainSKUCode, imageURL, shopName, imageSource, imageObserved, shopSource, shopObserved, source, pddGoodsURL, pddGoodsID, deletedAt, deletedBy sql.NullString
|
||||
var imageManual, shopManual int
|
||||
if err := rows.Scan(
|
||||
&r.GoodsID, &title, &shopeeStatus, &mainSKUCode, &imageURL, &shopName, &imageSource, &imageObserved, &imageManual, &shopSource, &shopObserved, &shopManual, &source,
|
||||
&pddGoodsURL, &pddGoodsID, &r.CreatedAt, &r.UpdatedAt,
|
||||
&pddGoodsURL, &pddGoodsID, &deletedAt, &deletedBy, &r.CreatedAt, &r.UpdatedAt,
|
||||
&r.ColorCount, &r.SizeCount, &r.SKUCount, &r.PendingCount,
|
||||
&r.CollectStatus, &r.CollectMsg,
|
||||
); err != nil {
|
||||
@@ -246,6 +247,8 @@ func ListShopeeProducts(q Execer, filter ShopeeFilter, limit, offset int) ([]Sho
|
||||
r.Source = source.String
|
||||
r.PddGoodsURL = pddGoodsURL.String
|
||||
r.PddGoodsID = pddGoodsID.String
|
||||
r.DeletedAt = deletedAt.String
|
||||
r.DeletedByUserID = deletedBy.String
|
||||
list = append(list, r)
|
||||
}
|
||||
return list, rows.Err()
|
||||
@@ -270,14 +273,14 @@ func CountShopeeProductsFiltered(q Execer, filter ShopeeFilter) (int, error) {
|
||||
// 弹窗组装商品信息时用。查不到返回 (nil, nil)。
|
||||
func GetShopeeProductByGoodsID(q Execer, goodsID string) (*model.ShopeeProduct, error) {
|
||||
var p model.ShopeeProduct
|
||||
var title, shopeeStatus, mainSKUCode, imageURL, shopName, imageSource, imageObserved, shopSource, shopObserved, source, pddGoodsURL, pddGoodsID sql.NullString
|
||||
var title, shopeeStatus, mainSKUCode, imageURL, shopName, imageSource, imageObserved, shopSource, shopObserved, source, pddGoodsURL, pddGoodsID, deletedAt, deletedBy sql.NullString
|
||||
var imageManual, shopManual int
|
||||
err := q.QueryRow(`
|
||||
SELECT goods_id, title, shopee_status, main_sku_code,image_url,shopee_shop_name,image_source,image_observed_at,image_is_manual,shop_name_source,shop_name_observed_at,shop_name_is_manual, source,
|
||||
pdd_goods_url, pdd_goods_id, created_at, updated_at
|
||||
FROM shopee_products WHERE goods_id = ?`, goodsID).Scan(
|
||||
pdd_goods_url, pdd_goods_id,deleted_at,deleted_by_user_id, created_at, updated_at
|
||||
FROM shopee_products WHERE goods_id = ? AND deleted_at IS NULL`, goodsID).Scan(
|
||||
&p.GoodsID, &title, &shopeeStatus, &mainSKUCode, &imageURL, &shopName, &imageSource, &imageObserved, &imageManual, &shopSource, &shopObserved, &shopManual, &source,
|
||||
&pddGoodsURL, &pddGoodsID, &p.CreatedAt, &p.UpdatedAt)
|
||||
&pddGoodsURL, &pddGoodsID, &deletedAt, &deletedBy, &p.CreatedAt, &p.UpdatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -298,6 +301,8 @@ func GetShopeeProductByGoodsID(q Execer, goodsID string) (*model.ShopeeProduct,
|
||||
p.Source = source.String
|
||||
p.PddGoodsURL = pddGoodsURL.String
|
||||
p.PddGoodsID = pddGoodsID.String
|
||||
p.DeletedAt = deletedAt.String
|
||||
p.DeletedByUserID = deletedBy.String
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
@@ -314,7 +319,7 @@ func ListShopeePddLinksByGoodsIDs(q Execer, goodsIDs []string) (map[string]strin
|
||||
args[i] = goodsID
|
||||
}
|
||||
rows, err := q.Query(`SELECT goods_id, COALESCE(pdd_goods_id, '')
|
||||
FROM shopee_products WHERE goods_id IN (`+placeholders+`)`, args...)
|
||||
FROM shopee_products WHERE deleted_at IS NULL AND goods_id IN (`+placeholders+`)`, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("批量查询蝦皮商品 PDD 关联失败: %w", err)
|
||||
}
|
||||
@@ -338,7 +343,7 @@ func UpdateShopeePddLink(q Execer, shopeeGoodsID, pddGoodsID, pddURL string) err
|
||||
result, err := q.Exec(`
|
||||
UPDATE shopee_products
|
||||
SET pdd_goods_id = ?, pdd_goods_url = ?, updated_at = ?
|
||||
WHERE goods_id = ?`,
|
||||
WHERE goods_id = ? AND deleted_at IS NULL`,
|
||||
pddGoodsID, pddURL, model.NowISO(), shopeeGoodsID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新蝦皮商品 %s 的 PDD 关联失败: %w", shopeeGoodsID, err)
|
||||
@@ -418,7 +423,8 @@ func GetShopeeSKUByID(q Execer, skuID string) (*model.ShopeeSKU, error) {
|
||||
err := q.QueryRow(`
|
||||
SELECT sku_id, COALESCE(shopee_sku_id,''), goods_id, spec_raw, color, size, advice, parse_ok, sku_code,
|
||||
is_manual, created_at, updated_at
|
||||
FROM shopee_skus WHERE sku_id = ?`, skuID).Scan(
|
||||
FROM shopee_skus sk WHERE sku_id = ?
|
||||
AND EXISTS (SELECT 1 FROM shopee_products sp WHERE sp.goods_id=sk.goods_id AND sp.deleted_at IS NULL)`, skuID).Scan(
|
||||
&sk.RecordID, &sk.SKUID, &sk.GoodsID, &sk.SpecRaw, &color, &size, &advice,
|
||||
&parseOK, &skuCode, &isManual, &sk.CreatedAt, &sk.UpdatedAt,
|
||||
)
|
||||
@@ -440,8 +446,66 @@ func GetShopeeSKUByID(q Execer, skuID string) (*model.ShopeeSKU, error) {
|
||||
// CountShopeeProducts 统计蝦皮商品总数,供列表页判断"是否已导入过任何数据"。
|
||||
func CountShopeeProducts(q Execer) (int, error) {
|
||||
var n int
|
||||
if err := q.QueryRow(`SELECT COUNT(*) FROM shopee_products`).Scan(&n); err != nil {
|
||||
if err := q.QueryRow(`SELECT COUNT(*) FROM shopee_products WHERE deleted_at IS NULL`).Scan(&n); err != nil {
|
||||
return 0, fmt.Errorf("统计蝦皮商品数量失败: %w", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ShopeeProductsWithActiveTasks 返回仍有采集或采购任务执行中的商品编号。
|
||||
func ShopeeProductsWithActiveTasks(q Execer, goodsIDs []string) ([]string, error) {
|
||||
if len(goodsIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(goodsIDs)), ",")
|
||||
args := make([]any, len(goodsIDs))
|
||||
for i, id := range goodsIDs {
|
||||
args[i] = id
|
||||
}
|
||||
rows, err := q.Query(`SELECT DISTINCT sp.goods_id
|
||||
FROM shopee_products sp JOIN tasks t
|
||||
ON (t.task_type='purchase' AND t.goods_id=sp.goods_id)
|
||||
OR (t.task_type='collect' AND t.goods_id=sp.pdd_goods_id)
|
||||
WHERE sp.goods_id IN (`+placeholders+`)
|
||||
AND t.status IN ('pending','assigned','claimed') ORDER BY sp.goods_id`, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查蝦皮商品进行中任务失败: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
// SetShopeeProductsDeleted 批量设置或清除软删除标记。
|
||||
func SetShopeeProductsDeleted(q Execer, goodsIDs []string, actorUserID, deletedAt string) (int64, error) {
|
||||
if len(goodsIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(goodsIDs)), ",")
|
||||
args := make([]any, 0, len(goodsIDs)+3)
|
||||
var sqlText string
|
||||
if deletedAt != "" {
|
||||
sqlText = `UPDATE shopee_products SET deleted_at=?,deleted_by_user_id=?,updated_at=?
|
||||
WHERE deleted_at IS NULL AND goods_id IN (` + placeholders + `)`
|
||||
args = append(args, deletedAt, actorUserID, deletedAt)
|
||||
} else {
|
||||
sqlText = `UPDATE shopee_products SET deleted_at=NULL,deleted_by_user_id=NULL,updated_at=?
|
||||
WHERE deleted_at IS NOT NULL AND goods_id IN (` + placeholders + `)`
|
||||
args = append(args, model.NowISO())
|
||||
}
|
||||
for _, id := range goodsIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
result, err := q.Exec(sqlText, args...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("更新蝦皮商品删除状态失败: %w", err)
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user