feat: 按允许店铺筛选顺运宝同步 (#196)
This commit is contained in:
+55
-12
@@ -213,9 +213,12 @@ type SkipNote struct {
|
||||
// SyncReport 是一次同步的结果,供状态条显示。
|
||||
type SyncReport struct {
|
||||
From, To string
|
||||
Specified bool // true 表示操作员发起的指定日期补同步
|
||||
StockCount int // 拉到的货运单数
|
||||
DetailCount int // 落库的商品明细行数(不含跳过的)
|
||||
Specified bool // true 表示操作员发起的指定日期补同步
|
||||
StockCount int // 拉到的货运单数
|
||||
AcceptedCount int // 店铺准入后接受的货运单数
|
||||
ShopSkipped int // 店铺不在允许列表或为空而跳过的货运单数
|
||||
ShopFilterHash string // 本次固定店铺快照的 SHA-256,不保存敏感凭据
|
||||
DetailCount int // 落库的商品明细行数(不含跳过的)
|
||||
Created int
|
||||
Updated int
|
||||
SkippedZero int // quantity <= 0 被跳过的条数
|
||||
@@ -289,7 +292,9 @@ func FinishSybSyncRun(db *sql.DB, runID string, report SyncReport) error {
|
||||
}
|
||||
return repository.FinishSybSyncRun(db, model.SybSyncRun{
|
||||
RunID: runID, Status: status, StockCount: report.StockCount,
|
||||
DetailCount: report.DetailCount, Created: report.Created, Updated: report.Updated,
|
||||
AcceptedCount: report.AcceptedCount, ShopSkipped: report.ShopSkipped,
|
||||
ShopFilterHash: report.ShopFilterHash,
|
||||
DetailCount: report.DetailCount, Created: report.Created, Updated: report.Updated,
|
||||
Skipped: report.SkippedZero, ErrorMessage: errorMessage,
|
||||
CursorAdvanced: report.CursorAdvanced, FinishedAt: finishedAt.UTC().Format(model.TimeLayout),
|
||||
})
|
||||
@@ -324,8 +329,8 @@ func ListSybSyncHistory(db *sql.DB, page int) (*SybSyncHistoryResult, error) {
|
||||
RunID: run.RunID, Username: run.Username,
|
||||
DateRange: run.DateFrom + " ~ " + run.DateTo,
|
||||
StatusText: statusText, StatusClass: statusClass,
|
||||
Summary: fmt.Sprintf("货运单 %d,明细 %d(新增 %d,更新 %d,跳过 %d)",
|
||||
run.StockCount, run.DetailCount, run.Created, run.Updated, run.Skipped),
|
||||
Summary: fmt.Sprintf("原始货运单 %d,接受 %d,店铺跳过 %d;明细 %d(新增 %d,更新 %d,数量跳过 %d)",
|
||||
run.StockCount, run.AcceptedCount, run.ShopSkipped, run.DetailCount, run.Created, run.Updated, run.Skipped),
|
||||
ErrorMessage: run.ErrorMessage, StartedAt: formatLocalTime(run.StartedAt),
|
||||
FinishedAt: formatLocalTime(run.FinishedAt),
|
||||
}
|
||||
@@ -362,8 +367,8 @@ func (r SyncReport) Summary() string {
|
||||
if r.Err != nil {
|
||||
return "同步失败:" + r.Err.Error()
|
||||
}
|
||||
msg := fmt.Sprintf("同步完成:日期范围 %s ~ %s,货运单 %d 张,商品明细 %d 条(新增 %d,更新 %d,跳过 %d)",
|
||||
r.From, r.To, r.StockCount, r.DetailCount, r.Created, r.Updated, r.SkippedZero)
|
||||
msg := fmt.Sprintf("同步完成:日期范围 %s ~ %s,原始货运单 %d 张,接受 %d 张,店铺跳过 %d 张;商品明细 %d 条(新增 %d,更新 %d,数量跳过 %d)",
|
||||
r.From, r.To, r.StockCount, r.AcceptedCount, r.ShopSkipped, r.DetailCount, r.Created, r.Updated, r.SkippedZero)
|
||||
if len(r.Notes) > 0 {
|
||||
var reasons []string
|
||||
for _, n := range r.Notes {
|
||||
@@ -548,6 +553,22 @@ func RunSybSync(ctx context.Context, db *sql.DB, client *syb.Client, cfg config.
|
||||
// 局部历史补拉只 upsert 数据、不动游标,否则会让未覆盖的订单永久漏掉。
|
||||
func RunSybSyncWithOptions(ctx context.Context, db *sql.DB, client *syb.Client, cfg config.SybConfig, now time.Time, options SybSyncOptions) SyncReport {
|
||||
report := SyncReport{StartedAt: now, Specified: options.IsSpecified()}
|
||||
allowedNames, err := repository.ListEnabledSybShopNames(db)
|
||||
if err != nil {
|
||||
report.Err = fmt.Errorf("读取顺运宝允许店铺失败: %w", err)
|
||||
report.FinishedAt = time.Now().UTC()
|
||||
return report
|
||||
}
|
||||
if len(allowedNames) == 0 {
|
||||
report.Err = fmt.Errorf("没有启用的顺运宝同步店铺,请先由管理员在“同步店铺”中配置并启用至少一个店铺")
|
||||
report.FinishedAt = time.Now().UTC()
|
||||
return report
|
||||
}
|
||||
allowedShops := make(map[string]struct{}, len(allowedNames))
|
||||
for _, name := range allowedNames {
|
||||
allowedShops[name] = struct{}{}
|
||||
}
|
||||
report.ShopFilterHash = fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(allowedNames, "\x00"))))
|
||||
|
||||
pageSize := cfg.PageSize
|
||||
if pageSize <= 0 {
|
||||
@@ -729,8 +750,17 @@ func RunSybSyncWithOptions(ctx context.Context, db *sql.DB, client *syb.Client,
|
||||
}
|
||||
|
||||
stockByID := listResult.stockByID
|
||||
orderedIDs := listResult.orderedIDs
|
||||
report.StockCount += len(orderedIDs)
|
||||
rawIDs := listResult.orderedIDs
|
||||
report.StockCount += len(rawIDs)
|
||||
orderedIDs := make([]int64, 0, len(rawIDs))
|
||||
for _, id := range rawIDs {
|
||||
if !sybShopAllowed(allowedShops, stockByID[id].Raw, nil) {
|
||||
report.ShopSkipped++
|
||||
continue
|
||||
}
|
||||
orderedIDs = append(orderedIDs, id)
|
||||
}
|
||||
report.AcceptedCount += len(orderedIDs)
|
||||
|
||||
for i := 0; i < len(orderedIDs); i += detailBatch {
|
||||
end := i + detailBatch
|
||||
@@ -752,6 +782,11 @@ func RunSybSyncWithOptions(ctx context.Context, db *sql.DB, client *syb.Client,
|
||||
}
|
||||
for _, d := range details {
|
||||
stockRow := stockByID[d.ID]
|
||||
if !sybShopAllowed(allowedShops, stockRow.Raw, d.Raw) {
|
||||
report.AcceptedCount--
|
||||
report.ShopSkipped++
|
||||
continue
|
||||
}
|
||||
if err := writeStockDetail(db, cfg.BaseURL, stockRow, d, &report); err != nil {
|
||||
report.Err = fmt.Errorf("写入货运单 %s(id=%d)失败(本次同步整体作废,"+
|
||||
"已写入的数据保留): %w", d.Code, d.ID, err)
|
||||
@@ -762,9 +797,9 @@ func RunSybSyncWithOptions(ctx context.Context, db *sql.DB, client *syb.Client,
|
||||
}
|
||||
if unstableTodayErr != nil {
|
||||
report.Err = fmt.Errorf(
|
||||
"%s 当天货运单在连续 %d 次分页期间仍有变化;已保存最后一次取得的 %d 张货运单完整明细,"+
|
||||
"%s 当天货运单在连续 %d 次分页期间仍有变化;最后一次取得原始货运单 %d 张,已保存其中允许店铺 %d 张的完整明细,"+
|
||||
"本次未形成稳定快照且不推进游标,下次同步将继续覆盖当天:%w",
|
||||
plan.date, sybTodayListMaxAttempts, len(orderedIDs), unstableTodayErr)
|
||||
plan.date, sybTodayListMaxAttempts, len(rawIDs), len(orderedIDs), unstableTodayErr)
|
||||
report.FinishedAt = time.Now().UTC()
|
||||
return report
|
||||
}
|
||||
@@ -783,6 +818,14 @@ func RunSybSyncWithOptions(ctx context.Context, db *sql.DB, client *syb.Client,
|
||||
return report
|
||||
}
|
||||
|
||||
// sybShopAllowed 用明细字段覆盖列表字段后再核对,防止列表通过但明细在同步
|
||||
// 期间已变成其他店铺。detail 为空时只检查列表快照。
|
||||
func sybShopAllowed(allowed map[string]struct{}, listRaw, detailRaw map[string]any) bool {
|
||||
name := trimmedStringField(mergeRaw(listRaw, detailRaw), "shopName")
|
||||
_, ok := allowed[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// validateDetailBatch 确认批量明细响应与请求 ID 一一对应。任何缺失、重复、
|
||||
// 意外 ID 或空商品明细都会让同步失败,避免在数据不完整时推进游标。
|
||||
func validateDetailBatch(requested []int64, details []syb.StockDetail) error {
|
||||
|
||||
Reference in New Issue
Block a user