feat: 规划档口入库码确定性匹配 (#232)

This commit is contained in:
chengma
2026-08-15 08:51:38 +08:00
parent a4ffa27ba9
commit 82ab80d1c1
3 changed files with 539 additions and 0 deletions
+107
View File
@@ -4,10 +4,17 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"cmautobuy/admin/model"
)
// InnerCodeSybSnapshot 是本地顺运宝明细用于解析货运单 stock id 的最小快照。
type InnerCodeSybSnapshot struct {
OrderNumber string
SybData string
}
// InnerCodeImportOutcome 说明幂等导入是新增还是更新。
type InnerCodeImportOutcome string
@@ -69,6 +76,99 @@ func UpsertInnerCodeImportRow(tx *sql.Tx, row model.InnerCodeImportRow, now stri
return InnerCodeImportUpdated, nil
}
// ListInnerCodeRecordsForPlanning 返回某日允许重新规划的记录。
func ListInnerCodeRecordsForPlanning(q Execer, businessDate string) ([]model.InnerCodeRecord, error) {
rows, err := q.Query(`
SELECT id,business_date,source_row,COALESCE(print_sequence,0),order_number,
COALESCE(shop_name,''),stall,spec_raw,spec_key,inner_code,source_duplicate_count,
status,COALESCE(result_message,''),created_by_user_id,created_at,updated_at
FROM syb_inner_code_records
WHERE business_date=? AND status IN ('pending','ready','skipped','failed')
ORDER BY source_row,id`, businessDate)
if err != nil {
return nil, fmt.Errorf("查询待规划档口入库码失败: %w", err)
}
defer rows.Close()
result := make([]model.InnerCodeRecord, 0)
for rows.Next() {
var row model.InnerCodeRecord
if err := rows.Scan(&row.ID, &row.BusinessDate, &row.SourceRow, &row.PrintSequence,
&row.OrderNumber, &row.ShopName, &row.Stall, &row.SpecRaw, &row.SpecKey,
&row.InnerCode, &row.SourceDuplicateCount, &row.Status, &row.ResultMessage,
&row.CreatedByUserID, &row.CreatedAt, &row.UpdatedAt); err != nil {
return nil, fmt.Errorf("读取待规划档口入库码失败: %w", err)
}
result = append(result, row)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历待规划档口入库码失败: %w", err)
}
return result, nil
}
// ListInnerCodeSybSnapshots 查询订单号对应的本地 SYB 原始快照。
func ListInnerCodeSybSnapshots(q Execer, orderNumbers []string) ([]InnerCodeSybSnapshot, error) {
if len(orderNumbers) == 0 {
return nil, nil
}
placeholders := make([]string, len(orderNumbers))
args := make([]any, len(orderNumbers))
for index, orderNumber := range orderNumbers {
placeholders[index] = "?"
args[index] = orderNumber
}
rows, err := q.Query(`SELECT order_no,syb_data FROM syb_orders WHERE order_no IN (`+
strings.Join(placeholders, ",")+`) ORDER BY order_no,syb_id`, args...)
if err != nil {
return nil, fmt.Errorf("查询档口入库码对应顺运宝快照失败: %w", err)
}
defer rows.Close()
result := make([]InnerCodeSybSnapshot, 0)
for rows.Next() {
var row InnerCodeSybSnapshot
if err := rows.Scan(&row.OrderNumber, &row.SybData); err != nil {
return nil, fmt.Errorf("读取顺运宝快照失败: %w", err)
}
result = append(result, row)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历顺运宝快照失败: %w", err)
}
return result, nil
}
// SaveInnerCodePlans 在同一事务中保存一批只读规划结果。
func SaveInnerCodePlans(db *sql.DB, plans []model.InnerCodeRecord, plannedAt string) error {
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开始保存档口入库码规划事务失败: %w", err)
}
defer tx.Rollback()
for _, plan := range plans {
result, err := tx.Exec(`
UPDATE syb_inner_code_records
SET stock_id=?,detail_id=?,syb_spec=?,syb_sku=?,syb_variation_sku=?,
purchase_platform=?,purchase_code=?,remote_inner_code=?,status=?,
result_message=?,planned_at=?,updated_at=?
WHERE id=? AND status IN ('pending','ready','skipped','failed')`,
nullablePositiveInt64(plan.StockID), nullablePositiveInt64(plan.DetailID),
nullableString(plan.SybSpec), nullableString(plan.SybSKU), nullableString(plan.SybVariationSKU),
nullableString(plan.PurchasePlatform), nullableString(plan.PurchaseCode),
nullableString(plan.RemoteInnerCode), plan.Status, nullableString(plan.ResultMessage),
plannedAt, plannedAt, plan.ID)
if err != nil {
return fmt.Errorf("保存档口入库码记录 %d 规划失败: %w", plan.ID, err)
}
if affected, err := result.RowsAffected(); err != nil || affected != 1 {
return fmt.Errorf("档口入库码记录 %d 状态已变化,规划整体未保存", plan.ID)
}
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交档口入库码规划失败: %w", err)
}
return nil
}
func nullablePositiveInt(value int) any {
if value <= 0 {
return nil
@@ -76,6 +176,13 @@ func nullablePositiveInt(value int) any {
return value
}
func nullablePositiveInt64(value int64) any {
if value <= 0 {
return nil
}
return value
}
func nullableString(value string) any {
if value == "" {
return nil