作废链接和取消订单

This commit is contained in:
yml2213
2026-08-14 12:58:20 +08:00
parent 33a30fcd99
commit ee3e427ab7
11 changed files with 364 additions and 39 deletions
@@ -404,6 +404,103 @@ func TestFulfillmentCancelRefundsOnceAndRestoresStock(t *testing.T) {
}
}
func TestFulfillmentCancelByUserRefundsAndAuditsActor(t *testing.T) {
db := newServiceTestDB(t)
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-bu", 1000, 2, 300)
svc := NewFulfillmentService(db, nil)
created, err := svc.CreateOrder(CreateFulfillmentOrderInput{
MerchantID: merchantID,
APIClientID: 12,
ClientOrderNo: "client-cancel-byuser",
SKU: product.SKU,
Quantity: 1,
})
if err != nil {
t.Fatalf("create order: %v", err)
}
cancelled, err := svc.CancelOrderByUser(merchantID, 99, created.Order.OrderNo, "商户后台取消")
if err != nil {
t.Fatalf("cancel by user: %v", err)
}
if cancelled.OrderStatus != model.OrderStatusCancelled {
t.Fatalf("unexpected cancelled order: %+v", cancelled)
}
var wallet model.WalletAccount
_ = db.Where("merchant_id = ?", merchantID).First(&wallet).Error
if wallet.AvailableBalance != 1000 {
t.Fatalf("wallet should refund full amount, got %d", wallet.AvailableBalance)
}
var audit model.AuditLog
if err := db.Where("action = ? AND entity_id = ?", "merchant_order.cancel", created.Order.OrderNo).First(&audit).Error; err != nil {
t.Fatalf("cancel audit missing: %v", err)
}
if audit.ActorUserID == nil || *audit.ActorUserID != 99 {
t.Fatalf("cancel audit should record actor user 99, got %v", audit.ActorUserID)
}
if audit.APIClientID != nil {
t.Fatalf("cancel audit should not record api client for backend path, got %v", *audit.APIClientID)
}
}
func TestFulfillmentCancelRejectedWhenSubmittedUpstream(t *testing.T) {
db := newServiceTestDB(t)
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-bd", 1000, 2, 300)
svc := NewFulfillmentService(db, nil)
create := func(clientOrderNo string) *model.FulfillmentOrder {
t.Helper()
created, err := svc.CreateOrder(CreateFulfillmentOrderInput{
MerchantID: merchantID,
APIClientID: 12,
ClientOrderNo: clientOrderNo,
SKU: product.SKU,
Quantity: 1,
})
if err != nil {
t.Fatalf("create order: %v", err)
}
return created.Order
}
// 已提交上游(provider_order_no 非空)的失败订单禁止取消,避免退款后上游仍发货。
submitted := create("client-cancel-submitted")
if err := db.Model(&model.FulfillmentOrder{}).Where("id = ?", submitted.ID).Updates(map[string]interface{}{
"order_status": model.OrderStatusShipFailed,
"provider_order_no": "PROV-UPSTREAM-001",
"failure_reason": "上游角色校验失败",
}).Error; err != nil {
t.Fatalf("seed submitted failed order: %v", err)
}
if _, err := svc.CancelOrder(merchantID, 12, submitted.OrderNo, "想取消"); err == nil {
t.Fatalf("cancel must be rejected when order already submitted upstream")
}
var walletAfterReject model.WalletAccount
_ = db.Where("merchant_id = ?", merchantID).First(&walletAfterReject).Error
if walletAfterReject.AvailableBalance != 700 {
t.Fatalf("wallet must not be refunded after rejected cancel, got %d", walletAfterReject.AvailableBalance)
}
// 未提交上游的失败订单(提交中断)可以正常取消退款。
interrupted := create("client-cancel-interrupted")
if err := db.Model(&model.FulfillmentOrder{}).Where("id = ?", interrupted.ID).Updates(map[string]interface{}{
"order_status": model.OrderStatusShipFailed,
"failure_reason": "发货提交中断",
}).Error; err != nil {
t.Fatalf("seed interrupted failed order: %v", err)
}
cancelled, err := svc.CancelOrder(merchantID, 12, interrupted.OrderNo, "确认中断,取消退款")
if err != nil {
t.Fatalf("cancel interrupted failed order: %v", err)
}
if cancelled.OrderStatus != model.OrderStatusCancelled {
t.Fatalf("expected cancelled, got %s", cancelled.OrderStatus)
}
var walletFinal model.WalletAccount
_ = db.Where("merchant_id = ?", merchantID).First(&walletFinal).Error
if walletFinal.AvailableBalance != 700 {
t.Fatalf("wallet should refund the interrupted order (1000-300-300+300), got %d", walletFinal.AvailableBalance)
}
}
func TestOrderStatusTransitions(t *testing.T) {
db := newServiceTestDB(t)
merchantID, product := seedFulfillmentMerchant(t, db, "merchant-c", 1000, -1, 100)