feat: 支持顺运宝批量创建采集任务 (#153)
This commit is contained in:
@@ -157,3 +157,31 @@ func CreateSybPddCollectTaskForUser(db *sql.DB, actor *model.User, sybID string)
|
||||
}
|
||||
return CreateShopeePddCollectTaskForUser(db, actor, context.Order.ShopeeGoodsID)
|
||||
}
|
||||
|
||||
// CreateSybPddCollectTasksForUser 把勾选的顺运宝明细转换成 PDD 商品后批量建采集任务。
|
||||
// 同一 PDD 商品可能对应多条货运单明细,因此先按 goods_id 去重;任何明细不存在或
|
||||
// 已不处于可采集阶段时整批拒绝,避免浏览器过期或参数篡改造成部分创建。
|
||||
func CreateSybPddCollectTasksForUser(db *sql.DB, actor *model.User, sybIDs []string, clientID string) (CollectTaskResult, error) {
|
||||
sybIDs = dedupe(sybIDs)
|
||||
if len(sybIDs) == 0 {
|
||||
return CollectTaskResult{}, invalidInput("没有选择可采集的顺运宝明细")
|
||||
}
|
||||
|
||||
goodsIDs := make([]string, 0, len(sybIDs))
|
||||
for _, sybID := range sybIDs {
|
||||
context, err := repository.GetSybOrderContext(db, sybID)
|
||||
if err != nil {
|
||||
return CollectTaskResult{}, err
|
||||
}
|
||||
if context == nil {
|
||||
return CollectTaskResult{}, invalidInput("顺运宝明细 %s 不存在,请刷新页面后重新选择", sybID)
|
||||
}
|
||||
stage, _, _, _ := sybStageFor(*context)
|
||||
if stage != SybStagePddPending && stage != SybStagePddFailed {
|
||||
return CollectTaskResult{}, invalidInput("顺运宝明细 %s 已不处于 PDD 待采集或采集失败状态,请刷新页面后重新选择", sybID)
|
||||
}
|
||||
goodsIDs = append(goodsIDs, context.PddGoodsID)
|
||||
}
|
||||
|
||||
return CreatePddCollectTasksForUser(db, actor, dedupe(goodsIDs), clientID)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
"cmautobuy/admin/repository"
|
||||
)
|
||||
|
||||
@@ -116,3 +119,92 @@ func TestCreateShopeePddCollectTaskForUser_记录当前用户(t *testing.T) {
|
||||
t.Fatalf("蝦皮入口任务创建人=%q err=%v", creator, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSybPddCollectTasksForUser_同一PDD商品去重(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, _ := insertTaskUsers(t, db)
|
||||
seedShopeeProduct(t, db, "1001", "商品一")
|
||||
seedShopeeProduct(t, db, "1002", "商品二")
|
||||
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := AssociateShopeePdd(db, "1002", pddURLA, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seedSybCollectOrder(t, db, "SYB-1", "1001")
|
||||
seedSybCollectOrder(t, db, "SYB-2", "1002")
|
||||
|
||||
result, err := CreateSybPddCollectTasksForUser(
|
||||
db, admin, []string{"SYB-1", "SYB-2", "SYB-1"}, "")
|
||||
if err != nil || result.Created != 1 {
|
||||
t.Fatalf("批量创建结果=%+v err=%v", result, err)
|
||||
}
|
||||
var taskCount int
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount); err != nil || taskCount != 1 {
|
||||
t.Fatalf("同一 PDD 商品应只建一条任务,count=%d err=%v", taskCount, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSybPddCollectTasksForUser_不可采集明细整批拒绝(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, _ := insertTaskUsers(t, db)
|
||||
seedShopeeProduct(t, db, "1001", "商品一")
|
||||
seedShopeeProduct(t, db, "1002", "商品二")
|
||||
AssociateShopeePdd(db, "1001", pddURLA, false)
|
||||
AssociateShopeePdd(db, "1002", pddURLB, false)
|
||||
seedSybCollectOrder(t, db, "SYB-PENDING", "1001")
|
||||
seedSybCollectOrder(t, db, "SYB-COLLECTED", "1002")
|
||||
if _, err := db.Exec(`UPDATE pdd_products SET collect_status='collected' WHERE goods_id='937122477375'`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := CreateSybPddCollectTasksForUser(
|
||||
db, admin, []string{"SYB-PENDING", "SYB-COLLECTED"}, "")
|
||||
if !IsValidationError(err) {
|
||||
t.Fatalf("不可采集明细应返回校验错误,实际 %v", err)
|
||||
}
|
||||
var taskCount int
|
||||
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
|
||||
if taskCount != 0 {
|
||||
t.Fatalf("整批拒绝后不应产生部分任务,实际 %d", taskCount)
|
||||
}
|
||||
pending, _ := repository.GetPddProductByGoodsID(db, "737116531267")
|
||||
if pending.CollectStatus != model.CollectPending {
|
||||
t.Fatalf("整批拒绝后待采集商品状态被改变为 %s", pending.CollectStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSybPddCollectTasksForUser_拒绝不可见客户端(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, buyerB := prepareClientAssignmentUsers(t, db)
|
||||
if err := RegisterClient(db, model.Client{ClientID: "client-b", Name: "B 的机器"}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := AssignClient(db, admin, "client-b", buyerB.UserID,
|
||||
time.Date(2026, 8, 11, 1, 0, 0, 0, time.UTC)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seedShopeeProduct(t, db, "1001", "商品一")
|
||||
AssociateShopeePdd(db, "1001", pddURLA, false)
|
||||
seedSybCollectOrder(t, db, "SYB-1", "1001")
|
||||
|
||||
_, err := CreateSybPddCollectTasksForUser(db, buyerA, []string{"SYB-1"}, "client-b")
|
||||
if !IsValidationError(err) {
|
||||
t.Fatalf("不可见客户端应返回校验错误,实际 %v", err)
|
||||
}
|
||||
var taskCount int
|
||||
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
|
||||
if taskCount != 0 {
|
||||
t.Fatalf("客户端权限校验失败后不应建任务,实际 %d", taskCount)
|
||||
}
|
||||
}
|
||||
|
||||
func seedSybCollectOrder(t *testing.T, db *sql.DB, sybID, shopeeGoodsID string) {
|
||||
t.Helper()
|
||||
if _, err := repository.UpsertSybOrder(db, model.SybOrder{
|
||||
SybID: sybID, OrderNo: "ORDER-" + sybID, Title: "测试商品", ProductSpec: "黑色,M",
|
||||
ShopeeGoodsID: shopeeGoodsID, Quantity: 1, SybData: `{}`,
|
||||
}); err != nil {
|
||||
t.Fatalf("插入顺运宝测试明细失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +939,9 @@ type SybOrderView struct {
|
||||
StageHelp string
|
||||
ActionText string
|
||||
NeedsAttention bool
|
||||
CanCollect bool
|
||||
CanPurchase bool
|
||||
SelectionHint string
|
||||
DefaultMaxPrice string
|
||||
MappedPddChoice string
|
||||
MappingOptionKey string
|
||||
@@ -1129,7 +1131,16 @@ func sybOrderViewFor(context repository.SybOrderContext) SybOrderView {
|
||||
Quantity: o.Quantity, ImageURL: o.ImageURL,
|
||||
UpdatedAt: formatLocalTime(o.UpdatedAt)}
|
||||
v.Stage, v.StageText, v.StageHelp, v.ActionText = sybStageFor(context)
|
||||
v.CanCollect = v.Stage == SybStagePddPending || v.Stage == SybStagePddFailed
|
||||
v.CanPurchase = v.Stage == SybStagePurchaseReady
|
||||
switch {
|
||||
case v.CanCollect:
|
||||
v.SelectionHint = "可批量创建 PDD 采集任务"
|
||||
case v.CanPurchase:
|
||||
v.SelectionHint = "可批量创建采购任务"
|
||||
default:
|
||||
v.SelectionHint = "当前阶段不能批量操作:" + v.StageHelp
|
||||
}
|
||||
if v.CanPurchase {
|
||||
v.MappingOptionKey = context.MappingOptionKey
|
||||
v.ContextVersion = mappingContextVersion(context)
|
||||
|
||||
@@ -130,3 +130,44 @@ func TestSybStageFor_全部筛选阶段与逐行推导一致(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSybOrderViewFor_批量动作能力按阶段隔离(t *testing.T) {
|
||||
base := repository.SybOrderContext{
|
||||
Order: model.SybOrder{SybID: "SYB-1", SpecKey: "黑色,M", Quantity: 1},
|
||||
PddGoodsID: "737116531267",
|
||||
}
|
||||
cases := []struct {
|
||||
status model.CollectStatus
|
||||
mappingKey string
|
||||
wantCollect, wantPurchase bool
|
||||
}{
|
||||
{status: model.CollectPending, wantCollect: true},
|
||||
{status: model.CollectFailed, wantCollect: true},
|
||||
{status: model.CollectCollecting},
|
||||
{status: model.CollectCollected},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
context := base
|
||||
context.PddCollectStatus = string(tc.status)
|
||||
context.MappingOptionKey = tc.mappingKey
|
||||
view := sybOrderViewFor(context)
|
||||
if view.CanCollect != tc.wantCollect || view.CanPurchase != tc.wantPurchase {
|
||||
t.Errorf("status=%s: CanCollect=%v CanPurchase=%v", tc.status, view.CanCollect, view.CanPurchase)
|
||||
}
|
||||
if view.SelectionHint == "" {
|
||||
t.Errorf("status=%s: 应给出选择能力说明", tc.status)
|
||||
}
|
||||
}
|
||||
key, err := OptionKey(map[string]string{"color": "黑色", "size": "M码", "style": "常规"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ready := base
|
||||
ready.PddCollectStatus = string(model.CollectCollected)
|
||||
ready.PddSkusJSON = collectedThreeDimensions
|
||||
ready.MappingOptionKey = key
|
||||
view := sybOrderViewFor(ready)
|
||||
if view.CanCollect || !view.CanPurchase {
|
||||
t.Fatalf("采购就绪行应只允许采购:CanCollect=%v CanPurchase=%v", view.CanCollect, view.CanPurchase)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user