feat: 支持顺运宝多订单号搜索 (#217)
This commit is contained in:
+118
-19
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user