feat: 增加真实采购任务安全模式 (#98)

This commit is contained in:
chengma
2026-08-10 15:11:38 +08:00
parent ba6710a2a2
commit 3aaa40cfe0
23 changed files with 642 additions and 66 deletions
+46 -2
View File
@@ -161,20 +161,42 @@ type PurchaseTaskRequest struct {
MaxPriceCent int64
}
const LivePurchaseConfirmation = "创建未付款订单"
// PurchaseTaskOptions 是一次批量创建共用的执行门禁。
// ExecutionMode 留空表示 dry_run,确保旧调用和普通操作都保持安全默认值。
type PurchaseTaskOptions struct {
ClientID string
ExecutionMode model.TaskExecutionMode
LiveAcknowledged bool
LiveConfirmation string
}
// PurchaseTaskResult 同时返回已创建数量和每条无法创建的原因。
type PurchaseTaskResult struct {
Created int
Failures []TaskCreateError
}
// CreatePurchaseTasks 校验并创建采购任务。业务校验失败按明细返回,数据库错误整体回滚。
// CreatePurchaseTasks 保留旧调用入口;未传执行模式时必须安全地创建演练任务。
func CreatePurchaseTasks(db *sql.DB, actor *model.User, requests []PurchaseTaskRequest, clientID string) (PurchaseTaskResult, error) {
return CreatePurchaseTasksWithOptions(db, actor, requests, PurchaseTaskOptions{ClientID: clientID})
}
// CreatePurchaseTasksWithOptions 校验并创建采购任务。业务校验失败按明细返回,数据库错误整体回滚。
func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []PurchaseTaskRequest, options PurchaseTaskOptions) (PurchaseTaskResult, error) {
var result PurchaseTaskResult
if actor == nil {
return result, ErrUnauthenticated
}
if actor.Status != model.UserActive {
return result, fmt.Errorf("当前账号不是正常状态,不能创建采购任务")
}
visibleUserID, err := visibleClientUserID(actor)
if err != nil {
return result, err
}
clientID = strings.TrimSpace(clientID)
clientID := strings.TrimSpace(options.ClientID)
if clientID == "" {
return result, fmt.Errorf("请选择执行采购任务的客户端")
}
@@ -190,6 +212,27 @@ func CreatePurchaseTasks(db *sql.DB, actor *model.User, requests []PurchaseTaskR
if !visible {
return result, fmt.Errorf("所选客户端不存在或不在当前账号可见范围")
}
executionMode := options.ExecutionMode
if executionMode == "" {
executionMode = model.TaskExecutionDryRun
}
if executionMode != model.TaskExecutionDryRun && executionMode != model.TaskExecutionLive {
return result, fmt.Errorf("执行模式无效,请选择采购演练或真实下单")
}
confirmedBy, confirmedAt := "", ""
if executionMode == model.TaskExecutionLive {
if !options.LiveAcknowledged || strings.TrimSpace(options.LiveConfirmation) != LivePurchaseConfirmation {
return result, fmt.Errorf("真实下单必须勾选风险确认并输入“%s”", LivePurchaseConfirmation)
}
purchaseMode, err := repository.ClientPurchaseMode(tx, clientID)
if err != nil {
return result, err
}
if purchaseMode != string(model.TaskExecutionLive) {
return result, fmt.Errorf("所选客户端未声明 live 能力,不能执行真实采购任务")
}
confirmedBy, confirmedAt = actor.UserID, model.NowISO()
}
seen := map[string]bool{}
for _, request := range requests {
@@ -211,6 +254,7 @@ func CreatePurchaseTasks(db *sql.DB, actor *model.User, requests []PurchaseTaskR
}
if err := repository.InsertPurchaseTask(tx, model.Task{
TaskID: newPurchaseTaskID(), AssignedClient: clientID,
ExecutionMode: executionMode, LiveConfirmedBy: confirmedBy, LiveConfirmedAt: confirmedAt,
SybID: context.Order.SybID, OrderNo: context.Order.OrderNo,
GoodsID: context.Order.ShopeeGoodsID,
PddGoodsURL: context.PddGoodsURL, PddGoodsID: context.PddGoodsID,