144 lines
6.9 KiB
Go
144 lines
6.9 KiB
Go
package repository
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/go-sql-driver/mysql"
|
|
|
|
"cmautobuy/admin/model"
|
|
)
|
|
|
|
var (
|
|
// ErrPurchaseSpecResolutionExists 表示解析编号或业务身份已经存在,调用方应读取旧记录复查。
|
|
ErrPurchaseSpecResolutionExists = errors.New("采购运行时规格解析记录已存在")
|
|
// ErrPurchaseSpecResolutionConflict 表示相同业务身份对应了不同请求内容。
|
|
ErrPurchaseSpecResolutionConflict = errors.New("采购运行时规格解析请求冲突")
|
|
// ErrPurchaseSpecResolutionNotFound 表示要完成的解析记录不存在。
|
|
ErrPurchaseSpecResolutionNotFound = errors.New("采购运行时规格解析记录不存在")
|
|
// ErrPurchaseSpecResolutionCompleted 表示解析记录已经完成,不能覆盖第一次决策。
|
|
ErrPurchaseSpecResolutionCompleted = errors.New("采购运行时规格解析记录已经完成")
|
|
)
|
|
|
|
// InsertPurchaseSpecResolution 保存一次候选观察。匹配逻辑不在 Repository 中执行。
|
|
func InsertPurchaseSpecResolution(q Execer, resolution model.PurchaseSpecResolution) error {
|
|
if resolution.Outcome == "" {
|
|
resolution.Outcome = model.PurchaseSpecResolutionPending
|
|
}
|
|
_, err := q.Exec(`INSERT INTO purchase_spec_resolutions
|
|
(resolution_id,task_id,attempt_id,client_id,task_version,pdd_goods_id,
|
|
original_options_json,selected_color,target_size,candidates_json,candidate_snapshot_hash,
|
|
request_hash,observed_at,outcome,created_at)
|
|
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
resolution.ResolutionID, resolution.TaskID, resolution.AttemptID, resolution.ClientID,
|
|
resolution.TaskVersion, resolution.PddGoodsID, resolution.OriginalOptionsJSON,
|
|
resolution.SelectedColor, resolution.TargetSize, resolution.CandidatesJSON,
|
|
resolution.CandidateSnapshotHash, resolution.RequestHash, resolution.ObservedAt,
|
|
resolution.Outcome, resolution.CreatedAt)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
var mysqlErr *mysql.MySQLError
|
|
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
|
return ErrPurchaseSpecResolutionExists
|
|
}
|
|
return fmt.Errorf("写入采购运行时规格解析观察失败: %w", err)
|
|
}
|
|
|
|
// GetPurchaseSpecResolutionByID 按公开解析编号读取候选观察和最终决策。
|
|
func GetPurchaseSpecResolutionByID(q Execer, resolutionID string) (*model.PurchaseSpecResolution, error) {
|
|
return scanPurchaseSpecResolution(q.QueryRow(purchaseSpecResolutionSelect+` WHERE resolution_id=?`, resolutionID))
|
|
}
|
|
|
|
// GetPurchaseSpecResolutionByIdentity 按任务、执行尝试和候选快照读取唯一解析记录。
|
|
func GetPurchaseSpecResolutionByIdentity(q Execer, taskID, attemptID, candidateSnapshotHash string) (*model.PurchaseSpecResolution, error) {
|
|
return scanPurchaseSpecResolution(q.QueryRow(purchaseSpecResolutionSelect+
|
|
` WHERE task_id=? AND attempt_id=? AND candidate_snapshot_hash=?`,
|
|
taskID, attemptID, candidateSnapshotHash))
|
|
}
|
|
|
|
// GetPurchaseSpecResolutionForReplay 复查幂等重放。相同业务身份但请求哈希不同必须冲突。
|
|
func GetPurchaseSpecResolutionForReplay(q Execer, taskID, attemptID, candidateSnapshotHash, requestHash string) (*model.PurchaseSpecResolution, error) {
|
|
resolution, err := GetPurchaseSpecResolutionByIdentity(q, taskID, attemptID, candidateSnapshotHash)
|
|
if err != nil || resolution == nil {
|
|
return resolution, err
|
|
}
|
|
if resolution.RequestHash != requestHash {
|
|
return nil, ErrPurchaseSpecResolutionConflict
|
|
}
|
|
return resolution, nil
|
|
}
|
|
|
|
// CompletePurchaseSpecResolution 只允许把 pending 记录完成一次,保留第一次决策审计。
|
|
func CompletePurchaseSpecResolution(q Execer, decision model.PurchaseSpecResolutionDecision) error {
|
|
result, err := q.Exec(`UPDATE purchase_spec_resolutions SET
|
|
outcome=?,decision_source=?,chosen_candidate_id=?,resolved_options_json=?,confidence_bps=?,
|
|
reason=?,provider_id=?,source_model=?,config_fingerprint=?,rules_version=?,prompt_version=?,decided_at=?
|
|
WHERE resolution_id=? AND outcome='pending'`,
|
|
decision.Outcome, nullableText(string(decision.DecisionSource)), nullableText(decision.ChosenCandidateID),
|
|
nullableText(decision.ResolvedOptionsJSON), nullableInt(decision.ConfidenceBPS, decision.ConfidenceSet),
|
|
nullableText(decision.Reason), nullableText(decision.ProviderID), nullableText(decision.SourceModel),
|
|
nullableText(decision.ConfigFingerprint), nullableText(decision.RulesVersion), nullableText(decision.PromptVersion),
|
|
decision.DecidedAt, decision.ResolutionID)
|
|
if err != nil {
|
|
return fmt.Errorf("完成采购运行时规格解析失败: %w", err)
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("读取采购运行时规格解析更新结果失败: %w", err)
|
|
}
|
|
if affected == 1 {
|
|
return nil
|
|
}
|
|
existing, err := GetPurchaseSpecResolutionByID(q, decision.ResolutionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing == nil {
|
|
return ErrPurchaseSpecResolutionNotFound
|
|
}
|
|
return ErrPurchaseSpecResolutionCompleted
|
|
}
|
|
|
|
const purchaseSpecResolutionSelect = `SELECT
|
|
resolution_id,task_id,attempt_id,client_id,task_version,pdd_goods_id,
|
|
original_options_json,selected_color,target_size,candidates_json,candidate_snapshot_hash,
|
|
request_hash,observed_at,outcome,decision_source,chosen_candidate_id,resolved_options_json,
|
|
confidence_bps,reason,provider_id,source_model,config_fingerprint,rules_version,prompt_version,
|
|
created_at,decided_at FROM purchase_spec_resolutions`
|
|
|
|
func scanPurchaseSpecResolution(row *sql.Row) (*model.PurchaseSpecResolution, error) {
|
|
var resolution model.PurchaseSpecResolution
|
|
var source, candidateID, resolvedOptions, reason, providerID sql.NullString
|
|
var sourceModel, configFingerprint, rulesVersion, promptVersion, decidedAt sql.NullString
|
|
var confidence sql.NullInt64
|
|
err := row.Scan(
|
|
&resolution.ResolutionID, &resolution.TaskID, &resolution.AttemptID, &resolution.ClientID,
|
|
&resolution.TaskVersion, &resolution.PddGoodsID, &resolution.OriginalOptionsJSON,
|
|
&resolution.SelectedColor, &resolution.TargetSize, &resolution.CandidatesJSON,
|
|
&resolution.CandidateSnapshotHash, &resolution.RequestHash, &resolution.ObservedAt,
|
|
&resolution.Outcome, &source, &candidateID, &resolvedOptions, &confidence, &reason,
|
|
&providerID, &sourceModel, &configFingerprint, &rulesVersion, &promptVersion,
|
|
&resolution.CreatedAt, &decidedAt)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取采购运行时规格解析失败: %w", err)
|
|
}
|
|
resolution.DecisionSource = model.PurchaseSpecResolutionSource(source.String)
|
|
resolution.ChosenCandidateID = candidateID.String
|
|
resolution.ResolvedOptionsJSON = resolvedOptions.String
|
|
resolution.ConfidenceBPS = int(confidence.Int64)
|
|
resolution.ConfidenceSet = confidence.Valid
|
|
resolution.Reason = reason.String
|
|
resolution.ProviderID = providerID.String
|
|
resolution.SourceModel = sourceModel.String
|
|
resolution.ConfigFingerprint = configFingerprint.String
|
|
resolution.RulesVersion = rulesVersion.String
|
|
resolution.PromptVersion = promptVersion.String
|
|
resolution.DecidedAt = decidedAt.String
|
|
return &resolution, nil
|
|
}
|