diff --git a/server/app/goauto/purchase/admin_query.go b/server/app/goauto/purchase/admin_query.go index d3ab64a..444d414 100644 --- a/server/app/goauto/purchase/admin_query.go +++ b/server/app/goauto/purchase/admin_query.go @@ -109,6 +109,9 @@ type AdminDetailResponse struct { Matching MatchingView `json:"matching"` } +// Query-only value: never a PurchaseTask status or a logistics writeback state. +const adminStatusSYBWritebackSucceeded = "syb_writeback_succeeded" + func (s *Service) AdminList(ctx context.Context, req AdminListRequest) (AdminListResponse, error) { if req.Page < 1 { req.Page = 1 @@ -119,7 +122,7 @@ func (s *Service) AdminList(ctx context.Context, req AdminListRequest) (AdminLis if req.PageSize > 100 { req.PageSize = 100 } - if req.Status != "" && !validPurchaseStatus(req.Status) { + if req.Status != "" && req.Status != adminStatusSYBWritebackSucceeded && !validPurchaseStatus(req.Status) { return AdminListResponse{}, fail(CodeInvalidRequest, "status 无效") } if req.ExecutionMode != "" && req.ExecutionMode != models.PurchaseExecutionModeRehearsal && req.ExecutionMode != models.PurchaseExecutionModeLive { @@ -132,7 +135,9 @@ func (s *Service) AdminList(ctx context.Context, req AdminListRequest) (AdminLis if req.TaskID > 0 { query = query.Where("id = ?", req.TaskID) } - if req.Status != "" { + if req.Status == adminStatusSYBWritebackSucceeded { + query = query.Where("id IN (?)", s.DB.WithContext(ctx).Model(&models.PurchaseOrderWriteback{}).Select("purchase_task_id").Where("status = ?", "succeeded")) + } else if req.Status != "" { query = query.Where("status = ?", req.Status) } if req.ExecutionMode != "" { diff --git a/server/app/goauto/purchase/admin_query_test.go b/server/app/goauto/purchase/admin_query_test.go index ff8c4dd..07fc272 100644 --- a/server/app/goauto/purchase/admin_query_test.go +++ b/server/app/goauto/purchase/admin_query_test.go @@ -105,6 +105,63 @@ func TestAdminQueryValidationAndNotFound(t *testing.T) { } } +func TestAdminListSYBWritebackSucceededFilter(t *testing.T) { + s, first := orderWritebackFixture(t) + db := s.DB + if err := db.Model(&models.PurchaseOrderWriteback{}).Where("purchase_task_id = ?", first.ID).Update("status", "succeeded").Error; err != nil { + t.Fatal(err) + } + var successIDs []uint64 + successIDs = append(successIDs, first.ID) + for _, state := range []string{"succeeded", "pending", "running", "failed", "unknown", "conflict", "none"} { + task := first + task.ID = 0 + task.Status = models.PurchaseTaskStatusCancelled + task.PDDOrderNo = nil + task.WritebackStatus = models.PurchaseWritebackStatusSucceeded // old logistics must not match + task.CreateRequestID = uuid.NewString() + task.UnknownResolveRequestID = nil + if err := db.Create(&task).Error; err != nil { + t.Fatal(err) + } + if state != "none" { + if err := db.Create(&models.PurchaseOrderWriteback{PurchaseTaskID: task.ID, StockID: 2, DetailID: 1, OrderNo: "FILTER-DEMO", Status: state}).Error; err != nil { + t.Fatal(err) + } + } + if state == "succeeded" { + successIDs = append(successIDs, task.ID) + } + } + for page := 1; page <= 2; page++ { + out, err := s.AdminList(context.Background(), AdminListRequest{Status: adminStatusSYBWritebackSucceeded, Page: page, PageSize: 1}) + if err != nil || out.Total != 2 || len(out.Items) != 1 || out.Items[0].ID != successIDs[2-page] { + t.Fatalf("page %d: %+v, %v", page, out, err) + } + } + for _, req := range []AdminListRequest{ + {Status: adminStatusSYBWritebackSucceeded, TaskID: first.ID}, + {Status: models.PurchaseTaskStatusOrderCreated}, + {Status: adminStatusSYBWritebackSucceeded, PDDOrderNo: *first.PDDOrderNo, ExecutionMode: "live", TaskType: "syb_order", SYBProductID: *first.SYBProductID}, + } { + out, err := s.AdminList(context.Background(), req) + if err != nil || out.Total != 1 || len(out.Items) != 1 || out.Items[0].ID != first.ID { + t.Fatalf("combined filter: %+v %v", out, err) + } + } + out, err := s.AdminList(context.Background(), AdminListRequest{}) + if err != nil || out.Total != 8 { + t.Fatalf("clear filter: %+v %v", out, err) + } + out, err = s.AdminList(context.Background(), AdminListRequest{Status: adminStatusSYBWritebackSucceeded, PDDOrderNo: "not-found"}) + if err != nil || out.Total != 0 { + t.Fatalf("empty filter: %+v %v", out, err) + } + if validPurchaseStatus(adminStatusSYBWritebackSucceeded) { + t.Fatal("filter became task state") + } +} + func TestAdminQueryHandlersRequireOperatorRole(t *testing.T) { gin.SetMode(gin.TestMode) for _, path := range []string{"/api/admin/v1/purchase-tasks", "/api/admin/v1/purchase-tasks/1"} { diff --git a/web/src/views/goauto/purchase-tasks/index.vue b/web/src/views/goauto/purchase-tasks/index.vue index 50554af..41088b8 100644 --- a/web/src/views/goauto/purchase-tasks/index.vue +++ b/web/src/views/goauto/purchase-tasks/index.vue @@ -157,7 +157,7 @@ export default { setup() { return { RefreshLeft, Search } }, data() { return { - loading: false, loadError: '', tasks: [], total: 0, statuses: statusOptions, + loading: false, loadError: '', tasks: [], total: 0, statuses: [...statusOptions, { value: 'syb_writeback_succeeded', label: '已回填 SYB' }], orderWriteback: { open: false, saving: false, items: [] }, query: { page: 1, pageSize: 20, taskId: '', taskType: '', shopeeOrderNo: '', status: '', executionMode: 'live', sybProductId: '', pddOrderNo: '' }, detail: { open: false, loading: false, task: null, attempts: [], matching: null }, diff --git a/web/tests/e2e/purchase-order-writeback.spec.ts b/web/tests/e2e/purchase-order-writeback.spec.ts index c27ccf9..3045702 100644 --- a/web/tests/e2e/purchase-order-writeback.spec.ts +++ b/web/tests/e2e/purchase-order-writeback.spec.ts @@ -82,6 +82,28 @@ test('冲突显示原因且没有覆盖按钮', async ({ page }) => { await expect(drawer.getByRole('button', { name: /回填 SYB 单号/ })).toHaveCount(0) }) +test('状态下拉框筛选已回填SYB,清空后恢复普通列表', async ({ page }) => { + const requests = await setup(page, 'succeeded') + const statuses: string[] = [] + await page.route('**/api/admin/v1/purchase-tasks?**', async route => { + const query = new URL(route.request().url()).searchParams + statuses.push(query.get('status') || '') + expect(query.get('page')).toBe('1') + return route.fulfill({ json: { code: 200, data: { items: [{ id: 11, taskType: 'syb_order', executionMode: 'live', status: 'order_created', orderWriteback: { status: 'succeeded', canSubmit: false } }], total: 1 } } }) + }) + const field = page.locator('.search-form .el-form-item').filter({ has: page.locator('label').filter({ hasText: /^状态$/ }) }) + await field.locator('.el-select').click() + await page.getByRole('option', { name: '已回填 SYB', exact: true }).click() + await page.getByRole('button', { name: '查询', exact: true }).click() + await expect.poll(() => statuses.at(-1)).toBe('syb_writeback_succeeded') + await expect(page.getByText('订单已创建', { exact: true }).first()).toBeVisible() + await field.locator('.el-select').hover() + await field.locator('.el-select__clear').click() + await page.getByRole('button', { name: '查询', exact: true }).click() + await expect.poll(() => statuses.at(-1)).toBe('') + expect(requests).toHaveLength(0) +}) + for (const amount of ['0', '13.01']) { test(`已创建订单补金额 ${amount},不重新采购或修改支付状态`, async ({ page }) => { const requests = await setup(page)