feat: 完成规格映射与采购任务创建 (#69)
This commit is contained in:
@@ -184,6 +184,26 @@ func ListClientsForUser(db *sql.DB, keyword, visibleUserID string) ([]ClientWith
|
||||
return listClientsForUserPage(db, keyword, visibleUserID, -1, 0)
|
||||
}
|
||||
|
||||
// ClientVisibleToUser 校验客户端是否存在且位于当前账号可见范围。
|
||||
// visibleUserID 为空表示管理员,可选择任意已登记客户端。
|
||||
func ClientVisibleToUser(q Execer, clientID, visibleUserID string) (bool, error) {
|
||||
query := `SELECT COUNT(*) FROM clients c`
|
||||
args := []any{}
|
||||
if visibleUserID != "" {
|
||||
query += ` JOIN client_user_assignments a
|
||||
ON a.client_id = c.client_id AND a.ended_at IS NULL
|
||||
AND a.user_id = ?`
|
||||
args = append(args, visibleUserID)
|
||||
}
|
||||
query += ` WHERE c.client_id = ?`
|
||||
args = append(args, clientID)
|
||||
var count int
|
||||
if err := q.QueryRow(query, args...).Scan(&count); err != nil {
|
||||
return false, fmt.Errorf("校验客户端可见范围失败: %w", err)
|
||||
}
|
||||
return count == 1, nil
|
||||
}
|
||||
|
||||
// ListClientsForUserPage 返回一页客户端和同一可见范围内的总数。
|
||||
func ListClientsForUserPage(db *sql.DB, keyword, visibleUserID string, limit, offset int) ([]ClientWithAssignee, int, error) {
|
||||
where, args := clientListFilter(keyword, visibleUserID)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
)
|
||||
|
||||
// GetSKUMapping 读取某个蝦皮 SKU 在指定 PDD 商品下的映射。
|
||||
func GetSKUMapping(q Execer, shopeeSKUID, pddGoodsID string) (*model.SKUMapping, error) {
|
||||
var m model.SKUMapping
|
||||
var mappedBy sql.NullString
|
||||
err := q.QueryRow(`
|
||||
SELECT shopee_sku_id, pdd_goods_id, pdd_option_key, pdd_options,
|
||||
goods_id, mapped_at, mapped_by
|
||||
FROM sku_mappings
|
||||
WHERE shopee_sku_id = ? AND pdd_goods_id = ?`,
|
||||
shopeeSKUID, pddGoodsID).Scan(
|
||||
&m.ShopeeSKUID, &m.PddGoodsID, &m.PddOptionKey, &m.PddOptions,
|
||||
&m.GoodsID, &m.MappedAt, &mappedBy)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询规格映射失败: %w", err)
|
||||
}
|
||||
m.MappedBy = mappedBy.String
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// UpsertSKUMapping 保存可复用规格映射,唯一键包含当前 PDD 商品。
|
||||
func UpsertSKUMapping(q Execer, m model.SKUMapping) error {
|
||||
_, err := q.Exec(`
|
||||
INSERT INTO sku_mappings
|
||||
(shopee_sku_id, pdd_goods_id, pdd_option_key, pdd_options,
|
||||
goods_id, mapped_at, mapped_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(shopee_sku_id, pdd_goods_id) DO UPDATE SET
|
||||
pdd_option_key = excluded.pdd_option_key,
|
||||
pdd_options = excluded.pdd_options,
|
||||
goods_id = excluded.goods_id,
|
||||
mapped_at = excluded.mapped_at,
|
||||
mapped_by = excluded.mapped_by`,
|
||||
m.ShopeeSKUID, m.PddGoodsID, m.PddOptionKey, m.PddOptions,
|
||||
m.GoodsID, m.MappedAt, nullableText(m.MappedBy))
|
||||
if err != nil {
|
||||
return fmt.Errorf("保存规格映射失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+26
-5
@@ -320,7 +320,9 @@ func sybOrderFilterClause(filter SybOrderFilter) (string, []any) {
|
||||
const sybOrderContextFrom = `
|
||||
FROM syb_orders so
|
||||
LEFT JOIN shopee_products sp ON sp.goods_id = so.shopee_goods_id
|
||||
LEFT JOIN pdd_products pp ON pp.goods_id = sp.pdd_goods_id AND pp.deleted_at IS NULL`
|
||||
LEFT JOIN pdd_products pp ON pp.goods_id = sp.pdd_goods_id AND pp.deleted_at IS NULL
|
||||
LEFT JOIN sku_mappings sm
|
||||
ON sm.shopee_sku_id = so.shopee_sku_id AND sm.pdd_goods_id = sp.pdd_goods_id`
|
||||
|
||||
// SybOrderContext 是顺运宝明细及其当前蝦皮/PDD 处理上下文。
|
||||
// 处理阶段由 service 计算,Repository 只提供数据库事实。
|
||||
@@ -331,6 +333,10 @@ type SybOrderContext struct {
|
||||
PddGoodsURL string
|
||||
PddCollectStatus string
|
||||
PddCollectMsg string
|
||||
PddSkusJSON string
|
||||
MappingOptionKey string
|
||||
MappingOptions string
|
||||
HasActiveTask bool
|
||||
}
|
||||
|
||||
func scanSybOrderContext(s rowScanner) (SybOrderContext, error) {
|
||||
@@ -338,12 +344,15 @@ func scanSybOrderContext(s rowScanner) (SybOrderContext, error) {
|
||||
var title, productSpec, shopeeGoodsID, shopeeSKUID, imageURL sql.NullString
|
||||
var priceCent sql.NullInt64
|
||||
var shopeeExists int
|
||||
var pddGoodsID, pddGoodsURL, collectStatus, collectMsg sql.NullString
|
||||
var pddGoodsID, pddGoodsURL, collectStatus, collectMsg, skusJSON sql.NullString
|
||||
var mappingKey, mappingOptions sql.NullString
|
||||
var hasActiveTask int
|
||||
err := s.Scan(
|
||||
&c.Order.SybID, &c.Order.OrderNo, &title, &productSpec, &shopeeGoodsID, &shopeeSKUID,
|
||||
&c.Order.Quantity, &priceCent, &imageURL, &c.Order.SybData,
|
||||
&c.Order.CreatedAt, &c.Order.UpdatedAt, &shopeeExists,
|
||||
&pddGoodsID, &pddGoodsURL, &collectStatus, &collectMsg,
|
||||
&pddGoodsID, &pddGoodsURL, &collectStatus, &collectMsg, &skusJSON,
|
||||
&mappingKey, &mappingOptions, &hasActiveTask,
|
||||
)
|
||||
c.Order.Title = title.String
|
||||
c.Order.ProductSpec = productSpec.String
|
||||
@@ -356,6 +365,10 @@ func scanSybOrderContext(s rowScanner) (SybOrderContext, error) {
|
||||
c.PddGoodsURL = pddGoodsURL.String
|
||||
c.PddCollectStatus = collectStatus.String
|
||||
c.PddCollectMsg = collectMsg.String
|
||||
c.PddSkusJSON = skusJSON.String
|
||||
c.MappingOptionKey = mappingKey.String
|
||||
c.MappingOptions = mappingOptions.String
|
||||
c.HasActiveTask = hasActiveTask != 0
|
||||
return c, err
|
||||
}
|
||||
|
||||
@@ -367,7 +380,11 @@ func ListSybOrderContexts(q Execer, filter SybOrderFilter, limit, offset int) ([
|
||||
so.shopee_goods_id, so.shopee_sku_id, so.quantity,
|
||||
so.price_twd_cent, so.image_url, so.syb_data, so.created_at, so.updated_at,
|
||||
CASE WHEN sp.goods_id IS NULL THEN 0 ELSE 1 END,
|
||||
sp.pdd_goods_id, sp.pdd_goods_url, pp.collect_status, pp.collect_msg` +
|
||||
sp.pdd_goods_id, sp.pdd_goods_url, pp.collect_status, pp.collect_msg,
|
||||
pp.skus_json, sm.pdd_option_key, sm.pdd_options,
|
||||
EXISTS(SELECT 1 FROM tasks t WHERE t.task_type = 'purchase'
|
||||
AND t.syb_id = so.syb_id
|
||||
AND t.status IN ('pending', 'assigned', 'claimed'))` +
|
||||
sybOrderContextFrom + where + `
|
||||
ORDER BY so.updated_at DESC, so.syb_id DESC LIMIT ? OFFSET ?`
|
||||
args = append(args, limit, offset)
|
||||
@@ -394,7 +411,11 @@ func GetSybOrderContext(q Execer, sybID string) (*SybOrderContext, error) {
|
||||
so.shopee_goods_id, so.shopee_sku_id, so.quantity,
|
||||
so.price_twd_cent, so.image_url, so.syb_data, so.created_at, so.updated_at,
|
||||
CASE WHEN sp.goods_id IS NULL THEN 0 ELSE 1 END,
|
||||
sp.pdd_goods_id, sp.pdd_goods_url, pp.collect_status, pp.collect_msg`+
|
||||
sp.pdd_goods_id, sp.pdd_goods_url, pp.collect_status, pp.collect_msg,
|
||||
pp.skus_json, sm.pdd_option_key, sm.pdd_options,
|
||||
EXISTS(SELECT 1 FROM tasks t WHERE t.task_type = 'purchase'
|
||||
AND t.syb_id = so.syb_id
|
||||
AND t.status IN ('pending', 'assigned', 'claimed'))`+
|
||||
sybOrderContextFrom+` WHERE so.syb_id = ?`, sybID))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
|
||||
@@ -445,3 +445,40 @@ func InsertCollectTask(q Execer, taskID, goodsID, goodsURL string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasActivePurchaseTask 判断顺运宝明细是否已有尚未结束的采购任务。
|
||||
func HasActivePurchaseTask(q Execer, sybID string) (bool, error) {
|
||||
var count int
|
||||
err := q.QueryRow(`
|
||||
SELECT COUNT(*) FROM tasks
|
||||
WHERE task_type = 'purchase' AND syb_id = ?
|
||||
AND status IN ('pending', 'assigned', 'claimed')`, sybID).Scan(&count)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("检查顺运宝明细 %s 的进行中采购任务失败: %w", sybID, err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// InsertPurchaseTask 插入一条已分配、等待指定客户端领取的采购任务。
|
||||
func InsertPurchaseTask(q Execer, task model.Task) error {
|
||||
if task.TaskID == "" || task.AssignedClient == "" || task.SybID == "" ||
|
||||
task.PddGoodsURL == "" || task.PddGoodsID == "" || task.PddOptions == "" ||
|
||||
task.Quantity <= 0 || task.MaxPriceCent <= 0 {
|
||||
return fmt.Errorf("采购任务缺少客户端、商品、规格、数量或人民币价格上限")
|
||||
}
|
||||
now := model.NowISO()
|
||||
_, err := q.Exec(`
|
||||
INSERT INTO tasks
|
||||
(task_id, task_type, status, assigned_client,
|
||||
syb_id, order_no, goods_id, shopee_sku_id,
|
||||
pdd_goods_url, pdd_goods_id, pdd_options,
|
||||
quantity, max_price_cent, created_at, updated_at)
|
||||
VALUES (?, 'purchase', 'assigned', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
task.TaskID, task.AssignedClient, task.SybID, task.OrderNo,
|
||||
task.GoodsID, task.ShopeeSKUID, task.PddGoodsURL, task.PddGoodsID,
|
||||
task.PddOptions, task.Quantity, task.MaxPriceCent, now, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建顺运宝明细 %s 的采购任务失败: %w", task.SybID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user