feat: 移除 Client 真实采购手工授权 (#114)
This commit is contained in:
@@ -88,6 +88,8 @@
|
||||
|
||||
## 数据与接口
|
||||
|
||||
- Admin 新建采购任务固定为真实下单(不支付),当前账号可见的全部 Client 都允许被指派;创建时不得按 Client 的 `purchase_mode` 禁用候选或拒绝任务。`purchase_mode` 只用于领取期判断 Client 的自动运行就绪能力。
|
||||
|
||||
- 表结构以 [03 数据模型](../docs/admin/03-data-model.md) 为准。
|
||||
- 给 Client 的接口以 [docs/client/04-admin-api-contract.md](../docs/client/04-admin-api-contract.md) 为准。
|
||||
- `[必须]` **金额一律用整数存**,人民币用分、台币用分,字段名带单位后缀(`_cent`)。
|
||||
|
||||
@@ -181,10 +181,10 @@ func (h *Handler) renderSybListWithLoginReason(c *gin.Context, keyword, pageRaw,
|
||||
keyword, dateFrom, dateTo),
|
||||
"HistoryFetchPagination": sybHistoryPartialPagination(history.Page, history.TotalPages,
|
||||
keyword, dateFrom, dateTo),
|
||||
"Pagination": service.NewPaginationView(result.Page, result.TotalPages, values.Encode()),
|
||||
"DetailURL": "/syb/detail?" + detailValues.Encode(),
|
||||
"AssignableClients": purchaseClients.Rows,
|
||||
"LivePurchaseClientCount": purchaseClients.SelectableCount,
|
||||
"Pagination": service.NewPaginationView(result.Page, result.TotalPages, values.Encode()),
|
||||
"DetailURL": "/syb/detail?" + detailValues.Encode(),
|
||||
"AssignableClients": purchaseClients.Rows,
|
||||
"PurchaseClientCount": purchaseClients.SelectableCount,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -159,7 +159,7 @@ func TestMainPagesReturnOK(t *testing.T) {
|
||||
}
|
||||
for _, client := range []model.Client{
|
||||
{ClientID: "CLIENT-LIVE", Name: "真实采购机", Capabilities: `{"purchase_mode":"live"}`},
|
||||
{ClientID: "CLIENT-DRY", Name: "未授权机", Capabilities: `{"purchase_mode":"dry_run"}`},
|
||||
{ClientID: "CLIENT-DRY", Name: "未就绪机", Capabilities: `{"purchase_mode":"dry_run"}`},
|
||||
} {
|
||||
if err := service.RegisterClient(db, client, true); err != nil {
|
||||
t.Fatalf("准备采购客户端失败: %v", err)
|
||||
@@ -180,17 +180,17 @@ func TestMainPagesReturnOK(t *testing.T) {
|
||||
`name="date_from"`, `name="date_to"`,
|
||||
`name="stage"`, "处理阶段", "下一步", `data-detail-url=`,
|
||||
"按顺运宝货运单创建日期(UTC+8)同步",
|
||||
"创建真实采购任务", "未启用真实采购",
|
||||
"创建真实采购任务", "所有当前账号可见的客户端均可指派",
|
||||
} {
|
||||
if !strings.Contains(sybResponse.Body.String(), want) {
|
||||
t.Errorf("顺运宝页面缺少 %q", want)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{
|
||||
`value="CLIENT-LIVE" selected`,
|
||||
`value="CLIENT-DRY" disabled`,
|
||||
"未授权机(CLIENT-DRY) — 未启用真实采购",
|
||||
"离线客户端也可以提前指派",
|
||||
`value="CLIENT-LIVE"`,
|
||||
`value="CLIENT-DRY"`,
|
||||
"未就绪机(CLIENT-DRY) — 在线",
|
||||
"未就绪或离线客户端会等待其就绪后领取",
|
||||
} {
|
||||
if !strings.Contains(sybResponse.Body.String(), want) {
|
||||
t.Errorf("采购客户端候选缺少 %q", want)
|
||||
|
||||
@@ -325,7 +325,7 @@ func TestClientAssignment_一人多客户端并按采购员隔离列表(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPurchaseClientOptions_返回可见客户端且离线Live仍可选(t *testing.T) {
|
||||
func TestListPurchaseClientOptions_全部可见客户端均可选(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, _ := prepareClientAssignmentUsers(t, db)
|
||||
now := time.Now().UTC()
|
||||
@@ -350,11 +350,11 @@ func TestListPurchaseClientOptions_返回可见客户端且离线Live仍可选(t
|
||||
if err != nil {
|
||||
t.Fatalf("读取采购客户端候选失败: %v", err)
|
||||
}
|
||||
if len(options.Rows) != 3 || options.SelectableCount != 2 {
|
||||
t.Fatalf("应显示全部可见客户端且两个 live 可选,实际 %+v", options)
|
||||
if len(options.Rows) != 3 || options.SelectableCount != 3 {
|
||||
t.Fatalf("应显示全部可见客户端且全部可选,实际 %+v", options)
|
||||
}
|
||||
if options.Rows[2].ClientID != "live-offline" || options.Rows[2].Status != "离线" {
|
||||
t.Fatalf("离线 live 客户端必须保留在候选中,实际 %+v", options.Rows)
|
||||
t.Fatalf("离线客户端必须保留在候选中,实际 %+v", options.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,13 +219,6 @@ func CreatePurchaseTasksWithOptions(db *sql.DB, actor *model.User, requests []Pu
|
||||
}
|
||||
confirmedBy, confirmedAt := "", ""
|
||||
if executionMode == model.TaskExecutionLive {
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -403,7 +403,7 @@ func TestCreatePurchaseTasksWithOptions_正常采购员可创建真实任务(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePurchaseTasksWithOptions_真实模式保留账号和Client能力门禁(t *testing.T) {
|
||||
func TestCreatePurchaseTasksWithOptions_真实模式不要求Client预先声明Live(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
key := seedPurchasableWorkflow(t, db, "SYB-GATE")
|
||||
SaveSybMapping(db, "SYB-GATE", key, "USR-1")
|
||||
@@ -411,10 +411,11 @@ func TestCreatePurchaseTasksWithOptions_真实模式保留账号和Client能力
|
||||
RegisterClient(db, model.Client{ClientID: "CLIENT-DRY", Capabilities: `{"purchase_mode":"dry_run"}`}, true)
|
||||
request := []PurchaseTaskRequest{{SybID: "SYB-GATE", MaxPriceCent: 4200}}
|
||||
|
||||
if _, err := CreatePurchaseTasksWithOptions(db, admin, request, PurchaseTaskOptions{
|
||||
result, err := CreatePurchaseTasksWithOptions(db, admin, request, PurchaseTaskOptions{
|
||||
ClientID: "CLIENT-DRY", ExecutionMode: model.TaskExecutionLive,
|
||||
}); err == nil || !strings.Contains(err.Error(), "未声明 live") {
|
||||
t.Fatalf("dry_run 客户端必须被拒绝: %v", err)
|
||||
})
|
||||
if err != nil || result.Created != 1 {
|
||||
t.Fatalf("当前账号可见客户端不应被历史能力状态阻止: result=%+v err=%v", result, err)
|
||||
}
|
||||
disabled := *admin
|
||||
disabled.Status = model.UserDisabled
|
||||
|
||||
@@ -220,11 +220,7 @@ func ListPurchaseClientOptions(db *sql.DB, actor *model.User, threshold time.Dur
|
||||
return nil, err
|
||||
}
|
||||
result := &PurchaseClientOptions{Rows: clients}
|
||||
for _, client := range clients {
|
||||
if client.PurchaseMode == string(model.TaskExecutionLive) {
|
||||
result.SelectableCount++
|
||||
}
|
||||
}
|
||||
result.SelectableCount = len(clients)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -171,10 +171,10 @@
|
||||
<div class="field">
|
||||
<label for="purchase-client">执行客户端</label>
|
||||
<select id="purchase-client" name="client_id" required autofocus>
|
||||
<option value="">请选择可执行真实采购的客户端</option>
|
||||
{{range .AssignableClients}}<option value="{{.ClientID}}" {{if ne .PurchaseMode "live"}}disabled{{else if eq $.LivePurchaseClientCount 1}}selected{{end}}>{{.Name}}({{.ClientID}}){{if ne .PurchaseMode "live"}} — 未启用真实采购{{end}}</option>{{end}}
|
||||
</select>
|
||||
{{if eq .LivePurchaseClientCount 0}}<small class="missing">当前没有可执行真实采购的客户端。请先在 Client 设置页启用真实采购并重新登记。</small>{{else}}<small>离线客户端也可以提前指派,待其上线后领取。</small>{{end}}
|
||||
<option value="">请选择执行客户端</option>
|
||||
{{range .AssignableClients}}<option value="{{.ClientID}}" {{if eq $.PurchaseClientCount 1}}selected{{end}}>{{.Name}}({{.ClientID}}) — {{.Status}}</option>{{end}}
|
||||
</select>
|
||||
{{if eq .PurchaseClientCount 0}}<small class="missing">当前账号没有可见的客户端,请先联系管理员绑定客户端。</small>{{else}}<small>所有当前账号可见的客户端均可指派;未就绪或离线客户端会等待其就绪后领取。</small>{{end}}
|
||||
</div>
|
||||
<p class="hint">价格上限单位是人民币元,默认取已映射 PDD 规格的采集价;不会使用顺运宝的台币售价。</p>
|
||||
{{range .Rows}}
|
||||
@@ -194,7 +194,7 @@
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button type="button" data-modal-close>取消</button>
|
||||
<button type="submit" class="primary" data-purchase-submit {{if eq .LivePurchaseClientCount 0}}disabled{{end}}>创建真实采购任务</button>
|
||||
<button type="submit" class="primary" data-purchase-submit {{if eq .PurchaseClientCount 0}}disabled{{end}}>创建真实采购任务</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user