统一订单单状态模型

This commit is contained in:
yml2213
2026-07-31 17:02:19 +08:00
parent 677bdbbc1c
commit 04130e5ec8
19 changed files with 300 additions and 345 deletions
+41 -43
View File
@@ -80,7 +80,7 @@ func TestFulfillmentCreateOrderDebitsWalletAndIsIdempotent(t *testing.T) {
if first.Idempotent {
t.Fatalf("first create should not be idempotent")
}
if first.Order.Amount != 400 || first.Order.PaymentStatus != model.PaymentStatusPaid || first.Order.FulfillmentStatus != model.FulfillmentStatusPending {
if first.Order.Amount != 400 || first.Order.OrderStatus != model.OrderStatusPaid {
t.Fatalf("unexpected order: %+v", first.Order)
}
@@ -202,7 +202,7 @@ func TestDashboardScopesPlatformAndMerchantData(t *testing.T) {
if merchantStats.ProductCount != 1 || merchantStats.ActiveProductCount != 1 {
t.Fatalf("merchant product stats should only include current merchant, got %+v", merchantStats)
}
if merchantStats.OrderCount != 1 || merchantStats.PendingOrderCount != 1 || merchantStats.TotalSales != 100 {
if merchantStats.OrderCount != 1 || merchantStats.PaidOrderCount != 1 || merchantStats.TotalSales != 100 {
t.Fatalf("merchant order stats should only include current merchant, got %+v", merchantStats)
}
if merchantStats.WalletAvailableBalance != 900 {
@@ -216,7 +216,7 @@ func TestDashboardScopesPlatformAndMerchantData(t *testing.T) {
if platformStats.Scope != "platform" {
t.Fatalf("expected platform scope, got %s", platformStats.Scope)
}
if platformStats.OrderCount != 2 || platformStats.PendingOrderCount != 2 || platformStats.TotalSales != 400 {
if platformStats.OrderCount != 2 || platformStats.PaidOrderCount != 2 || platformStats.TotalSales != 400 {
t.Fatalf("platform order stats should include all merchants, got %+v", platformStats)
}
if platformStats.WalletAvailableBalance < 2600 {
@@ -319,7 +319,7 @@ func TestFulfillmentCancelRefundsOnceAndRestoresStock(t *testing.T) {
if err != nil {
t.Fatalf("cancel order: %v", err)
}
if cancelled.PaymentStatus != model.PaymentStatusRefunded || cancelled.FulfillmentStatus != model.FulfillmentStatusCancelled {
if cancelled.OrderStatus != model.OrderStatusCancelled {
t.Fatalf("unexpected cancelled order: %+v", cancelled)
}
if _, err := svc.CancelOrder(merchantID, 12, created.Order.OrderNo, "重复取消"); err != nil {
@@ -343,7 +343,7 @@ func TestFulfillmentCancelRefundsOnceAndRestoresStock(t *testing.T) {
}
}
func TestFulfillmentStatusTransitions(t *testing.T) {
func TestOrderStatusTransitions(t *testing.T) {
db := newServiceTestDB(t)
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-c", 1000, -1, 100)
svc := NewFulfillmentService(db, nil)
@@ -356,37 +356,37 @@ func TestFulfillmentStatusTransitions(t *testing.T) {
if err != nil {
t.Fatalf("create order: %v", err)
}
processing, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
delivering, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
MerchantID: merchantID,
APIClientID: 13,
OrderNo: created.Order.OrderNo,
Status: model.FulfillmentStatusProcessing,
Status: model.OrderStatusDelivering,
})
if err != nil {
t.Fatalf("mark processing: %v", err)
t.Fatalf("mark delivering: %v", err)
}
if processing.FulfillmentStatus != model.FulfillmentStatusProcessing {
t.Fatalf("expected processing, got %s", processing.FulfillmentStatus)
if delivering.OrderStatus != model.OrderStatusDelivering {
t.Fatalf("expected delivering, got %s", delivering.OrderStatus)
}
succeeded, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
delivered, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
MerchantID: merchantID,
APIClientID: 13,
OrderNo: created.Order.OrderNo,
Status: model.FulfillmentStatusSucceeded,
Status: model.OrderStatusDelivered,
ProviderOrderNo: "provider-1",
ResultData: map[string]string{"ok": "true"},
})
if err != nil {
t.Fatalf("mark succeeded: %v", err)
t.Fatalf("mark delivered: %v", err)
}
if succeeded.FulfillmentStatus != model.FulfillmentStatusSucceeded || succeeded.DeliveredAt == nil {
t.Fatalf("unexpected succeeded order: %+v", succeeded)
if delivered.OrderStatus != model.OrderStatusDelivered || delivered.DeliveredAt == nil {
t.Fatalf("unexpected delivered order: %+v", delivered)
}
_, err = svc.UpdateFulfillment(FulfillmentUpdateInput{
MerchantID: merchantID,
APIClientID: 13,
OrderNo: created.Order.OrderNo,
Status: model.FulfillmentStatusFailed,
Status: model.OrderStatusShipFailed,
})
if err == nil {
t.Fatalf("should reject rollback after success")
@@ -406,19 +406,19 @@ func TestMarkProcessingTimeoutsMarksStaleOrdersFailed(t *testing.T) {
if err != nil {
t.Fatalf("create order: %v", err)
}
processing, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
delivering, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
MerchantID: merchantID,
APIClientID: 13,
OrderNo: created.Order.OrderNo,
Status: model.FulfillmentStatusProcessing,
Status: model.OrderStatusDelivering,
})
if err != nil {
t.Fatalf("mark processing: %v", err)
t.Fatalf("mark delivering: %v", err)
}
if err := db.Model(&model.FulfillmentOrder{}).
Where("id = ?", processing.ID).
Where("id = ?", delivering.ID).
Update("updated_at", time.Now().Add(-time.Hour)).Error; err != nil {
t.Fatalf("age processing order: %v", err)
t.Fatalf("age delivering order: %v", err)
}
changed, err := svc.MarkProcessingTimeouts(30*time.Minute, 10)
@@ -429,10 +429,10 @@ func TestMarkProcessingTimeoutsMarksStaleOrdersFailed(t *testing.T) {
t.Fatalf("expected one timed out order, got %d", changed)
}
var order model.FulfillmentOrder
if err := db.First(&order, processing.ID).Error; err != nil {
if err := db.First(&order, delivering.ID).Error; err != nil {
t.Fatalf("query order: %v", err)
}
if order.FulfillmentStatus != model.FulfillmentStatusFailed || !strings.Contains(order.FailureReason, "履约超时") {
if order.OrderStatus != model.OrderStatusShipFailed || !strings.Contains(order.FailureReason, "履约超时") {
t.Fatalf("expected failed timeout order, got %+v", order)
}
if !strings.Contains(order.ResultData, `"timeout":true`) {
@@ -453,28 +453,28 @@ func TestMarkProcessingTimeoutsSkipsRecentOrders(t *testing.T) {
if err != nil {
t.Fatalf("create order: %v", err)
}
processing, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
delivering, err := svc.UpdateFulfillment(FulfillmentUpdateInput{
MerchantID: merchantID,
APIClientID: 13,
OrderNo: created.Order.OrderNo,
Status: model.FulfillmentStatusProcessing,
Status: model.OrderStatusDelivering,
})
if err != nil {
t.Fatalf("mark processing: %v", err)
t.Fatalf("mark delivering: %v", err)
}
changed, err := svc.MarkProcessingTimeouts(30*time.Minute, 10)
if err != nil {
t.Fatalf("mark timeouts: %v", err)
}
if changed != 0 {
t.Fatalf("recent processing order should not time out, got %d", changed)
t.Fatalf("recent delivering order should not time out, got %d", changed)
}
var order model.FulfillmentOrder
if err := db.First(&order, processing.ID).Error; err != nil {
if err := db.First(&order, delivering.ID).Error; err != nil {
t.Fatalf("query order: %v", err)
}
if order.FulfillmentStatus != model.FulfillmentStatusProcessing {
t.Fatalf("expected processing order, got %+v", order)
if order.OrderStatus != model.OrderStatusDelivering {
t.Fatalf("expected delivering order, got %+v", order)
}
}
@@ -534,18 +534,16 @@ func TestWalletAdjustIsIdempotentPerMerchant(t *testing.T) {
func TestCanFulfill(t *testing.T) {
ok, reason := CanFulfill(&model.FulfillmentOrder{
PaymentStatus: model.PaymentStatusPaid,
FulfillmentStatus: model.FulfillmentStatusFailed,
OrderStatus: model.OrderStatusShipFailed,
})
if !ok || reason != "" {
t.Fatalf("failed paid order should be fulfillable")
}
ok, _ = CanFulfill(&model.FulfillmentOrder{
PaymentStatus: model.PaymentStatusRefunded,
FulfillmentStatus: model.FulfillmentStatusPending,
OrderStatus: model.OrderStatusCancelled,
})
if ok {
t.Fatalf("refunded order should not be fulfillable")
t.Fatalf("cancelled order should not be fulfillable")
}
}
@@ -588,7 +586,7 @@ func TestCreateTestOrderCreatesFulfillableOrderWithoutBilling(t *testing.T) {
if !strings.HasPrefix(order.OrderNo, "O") || order.ClientOrderNo != "TEST-"+order.OrderNo {
t.Fatalf("unexpected test order numbers: %+v", order)
}
if order.PaymentStatus != model.PaymentStatusPaid || order.FulfillmentStatus != model.FulfillmentStatusPending {
if order.OrderStatus != model.OrderStatusPaid {
t.Fatalf("unexpected test order status: %+v", order)
}
canShip, reason := CanFulfill(order)
@@ -631,15 +629,15 @@ func TestCreateTestOrderSupportsFailedFulfillableStatus(t *testing.T) {
svc := NewFulfillmentService(db, nil)
order, err := svc.CreateTestOrder(CreateTestOrderInput{
MerchantID: merchantID,
SKU: product.SKU,
FulfillmentStatus: model.FulfillmentStatusFailed,
Note: "等待重新发货",
MerchantID: merchantID,
SKU: product.SKU,
OrderStatus: model.OrderStatusShipFailed,
Note: "等待重新发货",
})
if err != nil {
t.Fatalf("create failed test order: %v", err)
}
if order.FulfillmentStatus != model.FulfillmentStatusFailed || order.FailureReason != "等待重新发货" {
if order.OrderStatus != model.OrderStatusShipFailed || order.FailureReason != "等待重新发货" {
t.Fatalf("unexpected failed test order: %+v", order)
}
openOrder, err := svc.QueryOpenOrder(order.OrderNo)
@@ -694,7 +692,7 @@ func TestHandleShipNotifyUpdatesOrderAndEnqueuesMerchantCallback(t *testing.T) {
if err := db.First(&order, created.Order.ID).Error; err != nil {
t.Fatalf("query order: %v", err)
}
if order.FulfillmentStatus != model.FulfillmentStatusSucceeded || order.ProviderOrderNo != "SRC-10001" || order.DeliveredAt == nil {
if order.OrderStatus != model.OrderStatusDelivered || order.ProviderOrderNo != "SRC-10001" || order.DeliveredAt == nil {
t.Fatalf("unexpected order after source notify: %+v", order)
}
var delivery model.CallbackDelivery
@@ -771,7 +769,7 @@ func TestHandleShipNotifySuccessClearsPreviousResultFailureReason(t *testing.T)
if err := db.First(&order, created.Order.ID).Error; err != nil {
t.Fatalf("query order: %v", err)
}
if order.FulfillmentStatus != model.FulfillmentStatusSucceeded || order.FailureReason != "" {
if order.OrderStatus != model.OrderStatusDelivered || order.FailureReason != "" {
t.Fatalf("success should clear order failure reason, got %+v", order)
}
if strings.Contains(order.ResultData, "fail_reason") {