feat: filter purchase tasks by successful SYB order writeback (#327)

This commit is contained in:
QiuSW
2026-09-19 15:38:42 +08:00
parent f66952f640
commit 368f2c2357
4 changed files with 87 additions and 3 deletions
+7 -2
View File
@@ -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 != "" {
@@ -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"} {
@@ -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 },
@@ -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)