From 3e7885b1373b5ed13d30d0bfe91f0ea6b7dc89d7 Mon Sep 17 00:00:00 2001 From: dujiao-next Date: Sun, 11 Oct 2026 13:23:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9C=AC=E5=9C=B0=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=B7=B2=E9=80=80=E6=AC=BE=E6=88=96=E5=8F=96=E6=B6=88=E6=97=B6?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=90=91=E4=B8=8A=E6=B8=B8=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=87=87=E8=B4=AD=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/procurement/application/submit.go | 5 +++ .../integrationtest/submit_test.go | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/internal/modules/procurement/application/submit.go b/internal/modules/procurement/application/submit.go index 8a544a1a..99f35709 100644 --- a/internal/modules/procurement/application/submit.go +++ b/internal/modules/procurement/application/submit.go @@ -49,6 +49,11 @@ func (s *Service) SubmitToUpstream(procurementOrderID uint) error { s.rejectProcurement(procOrder, fmt.Sprintf("local order %d not found", procOrder.LocalOrderID)) return nil // 永久性错误,不重试 } + // 本地订单已退款/取消等不再需要采购(如巡检补投的历史积压单),不能再去上游扣款 + if localOrder.Status != constants.OrderStatusPaid && localOrder.Status != constants.OrderStatusFulfilling { + s.rejectProcurement(procOrder, fmt.Sprintf("local order %d status %s no longer needs procurement", localOrder.ID, localOrder.Status)) + return nil // 永久性错误,不重试 + } if len(localOrder.Items) == 0 { s.rejectProcurement(procOrder, fmt.Sprintf("local order %d has no items", localOrder.ID)) return nil // 永久性错误,不重试 diff --git a/internal/modules/procurement/integrationtest/submit_test.go b/internal/modules/procurement/integrationtest/submit_test.go index f7f27105..34c93833 100644 --- a/internal/modules/procurement/integrationtest/submit_test.go +++ b/internal/modules/procurement/integrationtest/submit_test.go @@ -299,3 +299,36 @@ func TestSubmitToUpstream_HTTPErrorWithNonRetryableCode_Rejects(t *testing.T) { t.Fatalf("expected rejected without retries, got status=%q retry_count=%d", updated.Status, updated.RetryCount) } } + +// 本地订单已退款时(如巡检补投历史积压单),不能再去上游下单扣款。 +func TestSubmitToUpstream_LocalOrderRefunded_RejectsWithoutCallingUpstream(t *testing.T) { + db := setupProcurementTestDB(t) + order := createProcTestOrder(t, db, "PROC-REFUNDED-001", constants.OrderStatusRefunded, constants.FulfillmentTypeUpstream) + pm := &mappingdomain.Mapping{ConnectionID: 1, LocalProductID: 1, UpstreamProductID: 101, IsActive: true} + db.Create(pm) + db.Create(&mappingdomain.SKUMapping{ProductMappingID: pm.ID, LocalSKUID: 1, UpstreamSKUID: 201, UpstreamIsActive: true}) + + called := false + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{"ok": true, "order_id": 999, "order_no": "UP-999"}) + })) + defer server.Close() + + connSvc := newTestSiteConnectionService(db, "test-key", t.TempDir()) + conn, _ := connSvc.Create(siteconnectionapp.CreateInput{ + Name: "test-upstream", BaseURL: server.URL, + ApiKey: "key", ApiSecret: "secret", Protocol: constants.ConnectionProtocolDujiaoNext, + }) + proc := createTestProcurementOrder(t, db, conn.ID, order.ID, order.OrderNo, "failed") + if err := newTestProcurementService(db, connSvc).SubmitToUpstream(proc.ID); err != nil { + t.Fatalf("SubmitToUpstream: %v", err) + } + + var updated ProcurementOrder + db.First(&updated, proc.ID) + if called || updated.Status != constants.ProcurementStatusRejected { + t.Fatalf("expected rejected without upstream call, got called=%v status=%q", called, updated.Status) + } +}