fix: 明确拦截档口入库码唯一冲突 (#231)
This commit is contained in:
@@ -6,9 +6,14 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
)
|
||||
|
||||
// ErrInnerCodeUniqueConflict 表示同一日期的入库码已属于另一个业务键。
|
||||
var ErrInnerCodeUniqueConflict = errors.New("同一业务日期的档口入库码已被其他记录使用")
|
||||
|
||||
// InnerCodeSybSnapshot 是本地顺运宝明细用于解析货运单 stock id 的最小快照。
|
||||
type InnerCodeSybSnapshot struct {
|
||||
OrderNumber string
|
||||
@@ -40,6 +45,17 @@ const (
|
||||
// UpsertInnerCodeImportRow 按已确认业务键写入一行。
|
||||
// 必须在事务中调用;先锁定业务键,避免另一个唯一键冲突时更新错行。
|
||||
func UpsertInnerCodeImportRow(tx *sql.Tx, row model.InnerCodeImportRow, now string) (InnerCodeImportOutcome, error) {
|
||||
var codeRecordID int64
|
||||
var codeOrder, codeStall, codeSpecKey string
|
||||
codeErr := tx.QueryRow(`SELECT id,order_number,stall,spec_key FROM syb_inner_code_records
|
||||
WHERE business_date=? AND inner_code=? FOR UPDATE`, row.BusinessDate, row.InnerCode).
|
||||
Scan(&codeRecordID, &codeOrder, &codeStall, &codeSpecKey)
|
||||
if codeErr != nil && !errors.Is(codeErr, sql.ErrNoRows) {
|
||||
return "", fmt.Errorf("核对档口入库码唯一性失败: %w", codeErr)
|
||||
}
|
||||
if codeErr == nil && (codeOrder != row.OrderNumber || codeStall != row.Stall || codeSpecKey != row.SpecKey) {
|
||||
return "", fmt.Errorf("%w(记录 %d)", ErrInnerCodeUniqueConflict, codeRecordID)
|
||||
}
|
||||
var id int64
|
||||
err := tx.QueryRow(`
|
||||
SELECT id FROM syb_inner_code_records
|
||||
@@ -59,7 +75,7 @@ func UpsertInnerCodeImportRow(tx *sql.Tx, row model.InnerCodeImportRow, now stri
|
||||
nullableString(row.ShopName), row.Stall, row.SpecRaw, row.SpecKey, row.InnerCode,
|
||||
row.SourceDuplicateCount, row.CreatedByUserID, now, now)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("新增档口入库码记录失败: %w", err)
|
||||
return "", innerCodeImportWriteError("新增档口入库码记录失败", err)
|
||||
}
|
||||
return InnerCodeImportCreated, nil
|
||||
}
|
||||
@@ -85,11 +101,21 @@ func UpsertInnerCodeImportRow(tx *sql.Tx, row model.InnerCodeImportRow, now stri
|
||||
row.SourceRow, nullablePositiveInt(row.PrintSequence), nullableString(row.ShopName), row.SpecRaw,
|
||||
row.InnerCode, row.SourceDuplicateCount, now, id)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("更新档口入库码导入记录失败: %w", err)
|
||||
return "", innerCodeImportWriteError("更新档口入库码导入记录失败", err)
|
||||
}
|
||||
return InnerCodeImportUpdated, nil
|
||||
}
|
||||
|
||||
func innerCodeImportWriteError(action string, err error) error {
|
||||
// 并发导入可能都在 SELECT 时看不到对方,最终仍由唯一索引裁决。
|
||||
// 不把 MySQL 索引名或 SQL 原文显示给操作员。
|
||||
var mysqlError *mysql.MySQLError
|
||||
if errors.As(err, &mysqlError) && mysqlError.Number == 1062 {
|
||||
return fmt.Errorf("%s: %w", action, ErrInnerCodeUniqueConflict)
|
||||
}
|
||||
return fmt.Errorf("%s: %w", action, err)
|
||||
}
|
||||
|
||||
// ListInnerCodeRecordsForPlanning 返回某日允许重新规划的记录。
|
||||
func ListInnerCodeRecordsForPlanning(q Execer, businessDate string) ([]model.InnerCodeRecord, error) {
|
||||
rows, err := q.Query(`
|
||||
|
||||
Reference in New Issue
Block a user