fix: 简化真实采购创建并恢复客户端选择 (#112)
This commit is contained in:
@@ -325,7 +325,7 @@ func TestClientAssignment_一人多客户端并按采购员隔离列表(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestListLivePurchaseClients_只返回可见在线且声明Live的客户端(t *testing.T) {
|
||||
func TestListPurchaseClientOptions_返回可见客户端且离线Live仍可选(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, _ := prepareClientAssignmentUsers(t, db)
|
||||
now := time.Now().UTC()
|
||||
@@ -346,12 +346,15 @@ func TestListLivePurchaseClients_只返回可见在线且声明Live的客户端(
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := ListLivePurchaseClients(db, buyerA, time.Minute)
|
||||
options, err := ListPurchaseClientOptions(db, buyerA, time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("读取真实采购客户端失败: %v", err)
|
||||
t.Fatalf("读取采购客户端候选失败: %v", err)
|
||||
}
|
||||
if len(rows) != 1 || rows[0].ClientID != "live-online" {
|
||||
t.Fatalf("只应返回在线 live 客户端,实际 %+v", rows)
|
||||
if len(options.Rows) != 3 || options.SelectableCount != 2 {
|
||||
t.Fatalf("应显示全部可见客户端且两个 live 可选,实际 %+v", options)
|
||||
}
|
||||
if options.Rows[2].ClientID != "live-offline" || options.Rows[2].Status != "离线" {
|
||||
t.Fatalf("离线 live 客户端必须保留在候选中,实际 %+v", options.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,16 +163,11 @@ type PurchaseTaskRequest struct {
|
||||
ContextVersion string
|
||||
}
|
||||
|
||||
const LivePurchaseConfirmation = "创建未付款订单"
|
||||
|
||||
// PurchaseTaskOptions 是一次批量创建共用的执行门禁。
|
||||
// ExecutionMode 留空表示 dry_run,确保旧调用和普通操作都保持安全默认值。
|
||||
type PurchaseTaskOptions struct {
|
||||
ClientID string
|
||||
ExecutionMode model.TaskExecutionMode
|
||||
LiveAcknowledged bool
|
||||
LiveConfirmation string
|
||||
ClientSeenAfter string
|
||||
ClientID string
|
||||
ExecutionMode model.TaskExecutionMode
|
||||
}
|
||||
|
||||
// PurchaseTaskResult 同时返回已创建数量和每条无法创建的原因。
|
||||
@@ -215,15 +210,6 @@ func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []Pu
|
||||
if !visible {
|
||||
return result, fmt.Errorf("所选客户端不存在或不在当前账号可见范围")
|
||||
}
|
||||
if options.ClientSeenAfter != "" {
|
||||
online, err := repository.ClientSeenAfter(tx, clientID, options.ClientSeenAfter)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !online {
|
||||
return result, fmt.Errorf("所选客户端已离线,请刷新页面后选择在线客户端")
|
||||
}
|
||||
}
|
||||
executionMode := options.ExecutionMode
|
||||
if executionMode == "" {
|
||||
executionMode = model.TaskExecutionDryRun
|
||||
@@ -233,9 +219,6 @@ func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []Pu
|
||||
}
|
||||
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
|
||||
|
||||
@@ -389,8 +389,7 @@ func TestCreatePurchaseTasksWithOptions_正常采购员可创建真实任务(t *
|
||||
|
||||
result, err := CreatePurchaseTasksWithOptions(db, buyer,
|
||||
[]PurchaseTaskRequest{{SybID: "SYB-LIVE", MaxPriceCent: 4200}},
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive,
|
||||
LiveAcknowledged: true, LiveConfirmation: LivePurchaseConfirmation})
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive})
|
||||
if err != nil || result.Created != 1 {
|
||||
t.Fatalf("正常采购员创建真实任务失败: result=%+v err=%v", result, err)
|
||||
}
|
||||
@@ -404,7 +403,7 @@ func TestCreatePurchaseTasksWithOptions_正常采购员可创建真实任务(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePurchaseTasksWithOptions_真实模式安全门禁(t *testing.T) {
|
||||
func TestCreatePurchaseTasksWithOptions_真实模式保留账号和Client能力门禁(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
key := seedPurchasableWorkflow(t, db, "SYB-GATE")
|
||||
SaveSybMapping(db, "SYB-GATE", key, "USR-1")
|
||||
@@ -414,16 +413,9 @@ func TestCreatePurchaseTasksWithOptions_真实模式安全门禁(t *testing.T) {
|
||||
|
||||
if _, err := CreatePurchaseTasksWithOptions(db, admin, request, PurchaseTaskOptions{
|
||||
ClientID: "CLIENT-DRY", ExecutionMode: model.TaskExecutionLive,
|
||||
LiveAcknowledged: true, LiveConfirmation: LivePurchaseConfirmation,
|
||||
}); err == nil || !strings.Contains(err.Error(), "未声明 live") {
|
||||
t.Fatalf("dry_run 客户端必须被拒绝: %v", err)
|
||||
}
|
||||
if _, err := CreatePurchaseTasksWithOptions(db, admin, request, PurchaseTaskOptions{
|
||||
ClientID: "CLIENT-DRY", ExecutionMode: model.TaskExecutionLive,
|
||||
LiveAcknowledged: true, LiveConfirmation: "错误短语",
|
||||
}); err == nil || !strings.Contains(err.Error(), "必须勾选") {
|
||||
t.Fatalf("错误确认短语必须被拒绝: %v", err)
|
||||
}
|
||||
disabled := *admin
|
||||
disabled.Status = model.UserDisabled
|
||||
if _, err := CreatePurchaseTasks(db, &disabled, request, "CLIENT-DRY"); err == nil || !strings.Contains(err.Error(), "不是正常状态") {
|
||||
@@ -431,7 +423,7 @@ func TestCreatePurchaseTasksWithOptions_真实模式安全门禁(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePurchaseTasksWithOptions_页面打开后客户端离线会被拒绝(t *testing.T) {
|
||||
func TestCreatePurchaseTasksWithOptions_离线Live客户端可提前指派(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
key := seedPurchasableWorkflow(t, db, "SYB-OFFLINE")
|
||||
SaveSybMapping(db, "SYB-OFFLINE", key, "USR-1")
|
||||
@@ -441,13 +433,11 @@ func TestCreatePurchaseTasksWithOptions_页面打开后客户端离线会被拒
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := CreatePurchaseTasksWithOptions(db, admin,
|
||||
result, err := CreatePurchaseTasksWithOptions(db, admin,
|
||||
[]PurchaseTaskRequest{{SybID: "SYB-OFFLINE", MaxPriceCent: 4200}},
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-OFFLINE", ExecutionMode: model.TaskExecutionLive,
|
||||
LiveAcknowledged: true, LiveConfirmation: LivePurchaseConfirmation,
|
||||
ClientSeenAfter: "2026-08-10T09:00:00Z"})
|
||||
if err == nil || !strings.Contains(err.Error(), "已离线") {
|
||||
t.Fatalf("离线客户端必须在创建事务内被拒绝: %v", err)
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-OFFLINE", ExecutionMode: model.TaskExecutionLive})
|
||||
if err != nil || result.Created != 1 {
|
||||
t.Fatalf("离线 live 客户端应可提前指派: result=%+v err=%v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,8 +458,7 @@ func TestCreatePurchaseTasksWithOptions_拒绝弹窗打开后变化的规格映
|
||||
result, err := CreatePurchaseTasksWithOptions(db, admin,
|
||||
[]PurchaseTaskRequest{{SybID: "SYB-STALE", MaxPriceCent: 3990,
|
||||
MappingOptionKey: blackKey, ContextVersion: oldVersion}},
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive,
|
||||
LiveAcknowledged: true, LiveConfirmation: LivePurchaseConfirmation})
|
||||
PurchaseTaskOptions{ClientID: "CLIENT-LIVE", ExecutionMode: model.TaskExecutionLive})
|
||||
if err != nil || result.Created != 0 || len(result.Failures) != 1 ||
|
||||
!strings.Contains(result.Failures[0].Reason, "已变化") {
|
||||
t.Fatalf("旧弹窗必须因映射变化被拒绝: result=%+v err=%v", result, err)
|
||||
|
||||
@@ -205,20 +205,25 @@ func ListAssignableClients(db *sql.DB, actor *model.User, threshold time.Duratio
|
||||
return ListClientViewsForUser(db, actor, "", threshold)
|
||||
}
|
||||
|
||||
// ListLivePurchaseClients 返回当前用户能分配真实采购任务的在线客户端。
|
||||
//
|
||||
// 页面过滤只用于减少误选;创建任务时仍会在事务内重新校验可见范围和 live 能力。
|
||||
func ListLivePurchaseClients(db *sql.DB, actor *model.User, threshold time.Duration) ([]ClientView, error) {
|
||||
// PurchaseClientOptions 是采购弹窗的客户端候选和可选数量。
|
||||
// 非 live Client 也返回给页面显示禁用原因;创建时仍会在事务内复核能力。
|
||||
type PurchaseClientOptions struct {
|
||||
Rows []ClientView
|
||||
SelectableCount int
|
||||
}
|
||||
|
||||
// ListPurchaseClientOptions 返回当前用户可见的采购客户端。
|
||||
// live Client 即使暂时离线也可提前指派,等它上线后再领取任务。
|
||||
func ListPurchaseClientOptions(db *sql.DB, actor *model.User, threshold time.Duration) (*PurchaseClientOptions, error) {
|
||||
clients, err := ListClientViewsForUser(db, actor, "", threshold)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]ClientView, 0, len(clients))
|
||||
result := &PurchaseClientOptions{Rows: clients}
|
||||
for _, client := range clients {
|
||||
if client.Status != "在线" || client.PurchaseMode != string(model.TaskExecutionLive) {
|
||||
continue
|
||||
if client.PurchaseMode == string(model.TaskExecutionLive) {
|
||||
result.SelectableCount++
|
||||
}
|
||||
result = append(result, client)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user