fix: 档口入库码按货运单号直查顺运宝 (#245)

This commit is contained in:
chengma
2026-08-15 16:27:03 +08:00
parent 4d8434b499
commit b47e7ed9a9
5 changed files with 412 additions and 111 deletions
+117 -16
View File
@@ -464,10 +464,8 @@ func (c *Client) CheckSession(ctx context.Context, userID int64, username string
// ---------- 货运单列表 ----------
// listPayload 组装 /am/stock/listTotal、/am/stock/list 共用的请求体,
// 见 08 §4.1/§4.2:按日期范围查询(同步用这个),dvalue 是
// "起始日期,结束日期",YYYY-MM-DD,逗号分隔。
func listPayload(dateFrom, dateTo string, start, pageIndex, pageSize int) map[string]any {
// stockListPayload 组装 /am/stock/listTotal、/am/stock/list 共用的请求体。
func stockListPayload(query map[string]any, start, pageIndex, pageSize int) map[string]any {
return map[string]any{
"history": 0,
"length": pageSize,
@@ -476,20 +474,38 @@ func listPayload(dateFrom, dateTo string, start, pageIndex, pageSize int) map[st
"pageIndex": pageIndex,
"store": false,
"columns": columnsPayload(),
"queries": []map[string]any{
{
"dvalue": dateFrom + "," + dateTo,
"tableName": "t_stock",
"colName": "created",
"op": 0,
"type": 3,
"tableAlias": "t",
"optType": 0,
},
},
"queries": []map[string]any{query},
}
}
// listPayload 组装同步使用的日期范围条件,dvalue 是
// "起始日期,结束日期",YYYY-MM-DD,逗号分隔。
func listPayload(dateFrom, dateTo string, start, pageIndex, pageSize int) map[string]any {
return stockListPayload(map[string]any{
"dvalue": dateFrom + "," + dateTo,
"tableName": "t_stock",
"colName": "created",
"op": 0,
"type": 3,
"tableAlias": "t",
"optType": 0,
}, start, pageIndex, pageSize)
}
// orderNumberListPayload 使用顺运宝页面“全部单号”的原始 HAR 条件。
// allcode 会搜索多类单号,因此调用方还必须核对返回行的 code 与输入完全一致。
func orderNumberListPayload(orderNumber string, start, pageIndex, pageSize int) map[string]any {
return stockListPayload(map[string]any{
"dvalue": orderNumber,
"tableName": "t_stock",
"colName": "allcode",
"op": 6,
"type": 0,
"tableAlias": "t",
"optType": 1,
}, start, pageIndex, pageSize)
}
// ListTotal 查某个日期范围内的货运单总数:POST /am/stock/listTotal。
// `[必须]` data 是裸整数,不是对象,见 08 §2。
func (c *Client) ListTotal(ctx context.Context, dateFrom, dateTo string, pageSize int) (int, error) {
@@ -498,10 +514,17 @@ func (c *Client) ListTotal(ctx context.Context, dateFrom, dateTo string, pageSiz
if err != nil {
return 0, err
}
return parseListTotal(data)
}
func parseListTotal(data json.RawMessage) (int, error) {
var total int
if err := json.Unmarshal(data, &total); err != nil {
return 0, fmt.Errorf("listTotal 返回的总数格式错误: %s", string(data))
}
if total < 0 {
return 0, fmt.Errorf("listTotal 返回了负数: %d", total)
}
return total, nil
}
@@ -516,8 +539,12 @@ type StockRow struct {
// ListPage 按日期范围翻一页货运单列表:POST /am/stock/list。
// 返回响应内的 total;HAR 证明它是当前页条数,不是筛选范围总数。
func (c *Client) ListPage(ctx context.Context, dateFrom, dateTo string, start, pageIndex, pageSize int) ([]StockRow, int, error) {
return c.listPage(ctx, listPayload(dateFrom, dateTo, start, pageIndex, pageSize))
}
func (c *Client) listPage(ctx context.Context, payload map[string]any) ([]StockRow, int, error) {
data, err := c.do(ctx, http.MethodPost, "/am/stock/list", nil,
listPayload(dateFrom, dateTo, start, pageIndex, pageSize))
payload)
if err != nil {
return nil, 0, err
}
@@ -548,6 +575,80 @@ func (c *Client) ListPage(ctx context.Context, dateFrom, dateTo string, start, p
return rows, *wrap.Total, nil
}
const (
orderNumberPageSize = 20
orderNumberMaxMatches = 100
orderNumberMaxLength = 128
)
// ListByOrderNumber 使用顺运宝页面“全部单号”条件直接查询货运单。
// 返回值只包含 code 与输入完全相同的货运单;服务端返回不相关 code、重复 ID
// 或不完整分页时按协议错误处理,避免后续把档口入库码写到错误货运单。
func (c *Client) ListByOrderNumber(ctx context.Context, orderNumber string) ([]StockRow, error) {
orderNumber = strings.TrimSpace(orderNumber)
if orderNumber == "" {
return nil, fmt.Errorf("货运单号不能为空")
}
if utf8.RuneCountInString(orderNumber) > orderNumberMaxLength {
return nil, fmt.Errorf("货运单号不能超过 %d 个字符", orderNumberMaxLength)
}
for _, character := range orderNumber {
if character < 32 || character == 127 {
return nil, fmt.Errorf("货运单号不能包含控制字符")
}
}
data, err := c.do(ctx, http.MethodPost, "/am/stock/listTotal", nil,
orderNumberListPayload(orderNumber, 0, 1, orderNumberPageSize))
if err != nil {
return nil, err
}
total, err := parseListTotal(data)
if err != nil {
return nil, err
}
if total == 0 {
return []StockRow{}, nil
}
if total > orderNumberMaxMatches {
return nil, fmt.Errorf("货运单号 %q 查询返回 %d 条,超过安全上限 %d",
orderNumber, total, orderNumberMaxMatches)
}
rows := make([]StockRow, 0, total)
seenIDs := make(map[int64]bool, total)
for start := 0; start < total; start += orderNumberPageSize {
pageIndex := start/orderNumberPageSize + 1
pageRows, pageCount, pageErr := c.listPage(ctx,
orderNumberListPayload(orderNumber, start, pageIndex, orderNumberPageSize))
if pageErr != nil {
return nil, pageErr
}
expected := orderNumberPageSize
if remaining := total - start; remaining < expected {
expected = remaining
}
if pageCount != expected {
return nil, fmt.Errorf("货运单号 %q 第 %d 页不完整:预期 %d 行,实际 %d 行",
orderNumber, pageIndex, expected, pageCount)
}
for _, row := range pageRows {
if row.Code != orderNumber {
return nil, fmt.Errorf("货运单号 %q 查询返回了不一致的 code %q", orderNumber, row.Code)
}
if seenIDs[row.ID] {
return nil, fmt.Errorf("货运单号 %q 查询重复返回货运单 id=%d", orderNumber, row.ID)
}
seenIDs[row.ID] = true
rows = append(rows, row)
}
}
if len(rows) != total {
return nil, fmt.Errorf("货运单号 %q 查询总数为 %d,实际读取 %d 行", orderNumber, total, len(rows))
}
return rows, nil
}
// ---------- 货运单明细 ----------
// DetailItem 是货运单明细里的一个商品(t_stock.details[] 的一项),