feat: 支持顺运宝多订单号搜索 (#217)

This commit is contained in:
chengma
2026-08-13 14:58:19 +08:00
parent 2ebe6bf695
commit 8f2b3f725d
13 changed files with 372 additions and 46 deletions
+118 -19
View File
@@ -23,6 +23,8 @@ import (
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"cmautobuy/admin/config"
"cmautobuy/admin/model"
@@ -1171,13 +1173,64 @@ func sybCollectingIsStale(c repository.SybOrderContext) bool {
// SybListResult 是列表页要的全部数据。
type SybListResult struct {
Rows []SybOrderView
Total int
HasAny bool
IsFiltered bool
Stage string
Page int
TotalPages int
Rows []SybOrderView
Total int
HasAny bool
IsFiltered bool
Stage string
Page int
TotalPages int
OrderNoQuery string
OrderNoCount int
MatchedOrderCount int
UnmatchedOrderNos []string
}
const (
maxSybOrderNoQueryLen = 4000
maxSybOrderNoCount = 100
maxSybOrderNoLen = 191
)
// ParseSybOrderNos 把采购员粘贴的订单号列变成去重后的精确查询值。
// 支持 Excel 换行/Tab,以及中英文逗号和分号;订单号本身不允许包含空白。
func ParseSybOrderNos(raw string) ([]string, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, nil
}
if utf8.RuneCountInString(raw) > maxSybOrderNoQueryLen {
return nil, invalidFieldInput("order_no", "订单号输入不能超过 %d 个字符", maxSybOrderNoQueryLen)
}
parts := strings.FieldsFunc(raw, func(r rune) bool {
return unicode.IsSpace(r) || r == ',' || r == ',' || r == ';' || r == ';'
})
if len(parts) == 0 {
return nil, invalidFieldInput("order_no", "请输入至少一个有效订单号")
}
seen := make(map[string]struct{}, len(parts))
orderNos := make([]string, 0, len(parts))
for _, part := range parts {
orderNo := strings.TrimSpace(part)
if orderNo == "" {
continue
}
if utf8.RuneCountInString(orderNo) > maxSybOrderNoLen {
return nil, invalidFieldInput("order_no", "订单号 %q 超过 %d 个字符", orderNo, maxSybOrderNoLen)
}
if _, exists := seen[orderNo]; exists {
continue
}
seen[orderNo] = struct{}{}
orderNos = append(orderNos, orderNo)
if len(orderNos) > maxSybOrderNoCount {
return nil, invalidFieldInput("order_no", "一次最多搜索 %d 个不同订单号", maxSybOrderNoCount)
}
}
if len(orderNos) == 0 {
return nil, invalidFieldInput("order_no", "请输入至少一个有效订单号")
}
return orderNos, nil
}
// ListSybOrdersView 按筛选条件分页查货运单明细列表,翻成界面文字。
@@ -1187,13 +1240,24 @@ type SybListResult struct {
func ListSybOrdersView(db *sql.DB, keyword, shop, stage string, page int) (*SybListResult, error) {
shop = strings.TrimSpace(shop)
stage = ParseSybStage(stage)
orderNos, err := ParseSybOrderNos(keyword)
if err != nil {
hasAny, countErr := repository.CountSybOrdersTotal(db)
if countErr != nil {
return nil, countErr
}
return &SybListResult{
Rows: make([]SybOrderView, 0), HasAny: hasAny > 0, IsFiltered: true,
Stage: stage, Page: 1, TotalPages: 1, OrderNoQuery: keyword,
}, err
}
if stage == SybStagePddCollecting || stage == SybStagePddCollectingStale ||
stage == SybStageMappingPending || stage == SybStagePurchaseReady ||
stage == SybStageTaskCreated || stage == SybStagePurchaseCompleted ||
stage == SybStagePurchaseReview || stage == SybStagePurchaseBlocked {
return listAdvancedSybStage(db, keyword, shop, stage, page)
return listAdvancedSybStage(db, orderNos, shop, stage, page)
}
filter := repository.SybOrderFilter{Keyword: keyword, Shop: shop, Stage: stage}
filter := repository.SybOrderFilter{OrderNos: orderNos, Shop: shop, Stage: stage}
total, err := repository.CountSybOrders(db, filter)
if err != nil {
return nil, err
@@ -1213,17 +1277,24 @@ func ListSybOrdersView(db *sql.DB, keyword, shop, stage string, page int) (*SybL
}
result := &SybListResult{
Rows: make([]SybOrderView, 0, len(rows)),
Total: total,
HasAny: hasAny > 0,
IsFiltered: strings.TrimSpace(keyword) != "" || shop != "" || stage != "",
Stage: stage,
Page: page,
TotalPages: totalPages,
Rows: make([]SybOrderView, 0, len(rows)),
Total: total,
HasAny: hasAny > 0,
IsFiltered: len(orderNos) > 0 || shop != "" || stage != "",
Stage: stage,
Page: page,
TotalPages: totalPages,
OrderNoQuery: strings.Join(orderNos, ","),
OrderNoCount: len(orderNos),
}
for _, context := range rows {
result.Rows = append(result.Rows, sybOrderViewFor(context))
}
matchedOrderNos, err := repository.ListMatchedSybOrderNos(db, filter)
if err != nil {
return nil, err
}
setSybOrderMatchStats(result, orderNos, matchedOrderNos)
return result, nil
}
@@ -1231,9 +1302,9 @@ func ListSybOrdersView(db *sql.DB, keyword, shop, stage string, page int) (*SybL
// 这些阶段不能只靠 SQL 判断:同一个 option key 是否仍存在,需要走唯一的
// OptionKey 规范化逻辑。当前同步量是百到千级,先保证采购判断正确;普通列表
// 和其余阶段仍在数据库分页。
func listAdvancedSybStage(db *sql.DB, keyword, shop, stage string, page int) (*SybListResult, error) {
func listAdvancedSybStage(db *sql.DB, orderNos []string, shop, stage string, page int) (*SybListResult, error) {
contexts, err := repository.ListSybOrderContexts(db,
repository.SybOrderFilter{Keyword: keyword, Shop: shop}, -1, 0)
repository.SybOrderFilter{OrderNos: orderNos, Shop: shop}, -1, 0)
if err != nil {
return nil, err
}
@@ -1256,7 +1327,8 @@ func listAdvancedSybStage(db *sql.DB, keyword, shop, stage string, page int) (*S
end = total
}
result := &SybListResult{Total: total, Stage: stage, Page: page, TotalPages: totalPages,
IsFiltered: true, Rows: make([]SybOrderView, 0, end-start)}
IsFiltered: true, Rows: make([]SybOrderView, 0, end-start),
OrderNoQuery: strings.Join(orderNos, ","), OrderNoCount: len(orderNos)}
hasAny, err := repository.CountSybOrdersTotal(db)
if err != nil {
return nil, err
@@ -1265,9 +1337,36 @@ func listAdvancedSybStage(db *sql.DB, keyword, shop, stage string, page int) (*S
for _, context := range filtered[start:end] {
result.Rows = append(result.Rows, sybOrderViewFor(context))
}
matchedOrderNos := make([]string, 0, len(orderNos))
matchedSet := make(map[string]struct{}, len(orderNos))
for _, context := range filtered {
if _, exists := matchedSet[context.Order.OrderNo]; exists {
continue
}
matchedSet[context.Order.OrderNo] = struct{}{}
matchedOrderNos = append(matchedOrderNos, context.Order.OrderNo)
}
setSybOrderMatchStats(result, orderNos, matchedOrderNos)
return result, nil
}
func setSybOrderMatchStats(result *SybListResult, orderNos, matchedOrderNos []string) {
if len(orderNos) == 0 {
return
}
matched := make(map[string]struct{}, len(matchedOrderNos))
for _, orderNo := range matchedOrderNos {
matched[orderNo] = struct{}{}
}
result.MatchedOrderCount = len(matched)
result.UnmatchedOrderNos = make([]string, 0, len(orderNos)-len(matched))
for _, orderNo := range orderNos {
if _, exists := matched[orderNo]; !exists {
result.UnmatchedOrderNos = append(result.UnmatchedOrderNos, orderNo)
}
}
}
func sybOrderViewFor(context repository.SybOrderContext) SybOrderView {
o := context.Order
v := SybOrderView{SybID: o.SybID, OrderNo: o.OrderNo, ShopName: o.ShopName, Title: o.Title,