feat: 增加真实采购任务安全模式 (#98)
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user