fix: 上游拒绝取消已付款采购单时给出明确提示,并补齐采购单错误文案三语
This commit is contained in:
1 parent
290ea27ba3
commit
95bff49a92
7 files changed
+111
-76
No files matched your search
+87
-72
@@ -350,30 +350,35 @@ var messages = map[string]map[string]string{
|
||||
"validation.rule.alphanum": "只能包含字母和数字",
|
||||
|
||||
// 商品映射
|
||||
"error.mapping_fetch_failed": "获取商品映射失败",
|
||||
"error.mapping_not_found": "商品映射不存在",
|
||||
"error.mapping_already_exists": "该上游商品已存在映射",
|
||||
"error.mapping_import_failed": "导入上游商品失败",
|
||||
"error.mapping_batch_import_in_progress": "该对接站点正在批量导入,请稍后刷新查看结果",
|
||||
"error.mapping_sync_failed": "同步商品映射失败",
|
||||
"error.mapping_update_failed": "更新商品映射失败",
|
||||
"error.mapping_delete_failed": "删除商品映射失败",
|
||||
"error.connection_not_found": "站点连接不存在",
|
||||
"error.upstream_product_not_found": "上游商品不存在",
|
||||
"error.upstream_products_fetch_failed": "获取上游商品列表失败",
|
||||
"error.upstream_categories_fetch_failed": "获取上游分类列表失败",
|
||||
"error.category_import_failed": "按分类导入商品失败",
|
||||
"error.totp_already_enabled": "已启用两步验证,无需重复绑定",
|
||||
"error.totp_not_enabled": "尚未启用两步验证",
|
||||
"error.totp_pending_expired": "绑定流程已超时,请重新发起",
|
||||
"error.totp_code_invalid": "动态验证码错误",
|
||||
"error.totp_code_required": "请提供动态验证码或恢复码",
|
||||
"error.recovery_code_invalid": "恢复码错误或已使用",
|
||||
"error.totp_too_many_attempts": "失败次数过多,请稍后重试",
|
||||
"error.totp_challenge_invalid": "登录会话已失效,请重新输入密码",
|
||||
"error.totp_cannot_reset_self": "无法通过此入口重置自己的 2FA,请使用 admin-tool CLI",
|
||||
"error.invalid_upstream_status": "无效的上游状态参数",
|
||||
"error.invalid_product_status": "无效的商品状态参数",
|
||||
"error.mapping_fetch_failed": "获取商品映射失败",
|
||||
"error.mapping_not_found": "商品映射不存在",
|
||||
"error.mapping_already_exists": "该上游商品已存在映射",
|
||||
"error.mapping_import_failed": "导入上游商品失败",
|
||||
"error.mapping_batch_import_in_progress": "该对接站点正在批量导入,请稍后刷新查看结果",
|
||||
"error.mapping_sync_failed": "同步商品映射失败",
|
||||
"error.procurement_fetch_failed": "获取采购单失败",
|
||||
"error.procurement_not_found": "采购单不存在",
|
||||
"error.procurement_retry_failed": "重试采购单失败",
|
||||
"error.procurement_cancel_failed": "取消采购单失败",
|
||||
"error.procurement_upstream_cancel_refused": "上游订单已付款,无法取消,请到上游站点申请退款",
|
||||
"error.mapping_update_failed": "更新商品映射失败",
|
||||
"error.mapping_delete_failed": "删除商品映射失败",
|
||||
"error.connection_not_found": "站点连接不存在",
|
||||
"error.upstream_product_not_found": "上游商品不存在",
|
||||
"error.upstream_products_fetch_failed": "获取上游商品列表失败",
|
||||
"error.upstream_categories_fetch_failed": "获取上游分类列表失败",
|
||||
"error.category_import_failed": "按分类导入商品失败",
|
||||
"error.totp_already_enabled": "已启用两步验证,无需重复绑定",
|
||||
"error.totp_not_enabled": "尚未启用两步验证",
|
||||
"error.totp_pending_expired": "绑定流程已超时,请重新发起",
|
||||
"error.totp_code_invalid": "动态验证码错误",
|
||||
"error.totp_code_required": "请提供动态验证码或恢复码",
|
||||
"error.recovery_code_invalid": "恢复码错误或已使用",
|
||||
"error.totp_too_many_attempts": "失败次数过多,请稍后重试",
|
||||
"error.totp_challenge_invalid": "登录会话已失效,请重新输入密码",
|
||||
"error.totp_cannot_reset_self": "无法通过此入口重置自己的 2FA,请使用 admin-tool CLI",
|
||||
"error.invalid_upstream_status": "无效的上游状态参数",
|
||||
"error.invalid_product_status": "无效的商品状态参数",
|
||||
},
|
||||
LocaleTW: {
|
||||
"error.jwt_secret_missing": "JWT secret 未配置",
|
||||
@@ -710,30 +715,35 @@ var messages = map[string]map[string]string{
|
||||
"validation.rule.alphanum": "只能包含字母和數字",
|
||||
|
||||
// 商品映射
|
||||
"error.mapping_fetch_failed": "獲取商品映射失敗",
|
||||
"error.mapping_not_found": "商品映射不存在",
|
||||
"error.mapping_already_exists": "該上游商品已存在映射",
|
||||
"error.mapping_import_failed": "導入上游商品失敗",
|
||||
"error.mapping_batch_import_in_progress": "該對接站點正在批量導入,請稍後重新整理查看結果",
|
||||
"error.mapping_sync_failed": "同步商品映射失敗",
|
||||
"error.mapping_update_failed": "更新商品映射失敗",
|
||||
"error.mapping_delete_failed": "刪除商品映射失敗",
|
||||
"error.connection_not_found": "站點連接不存在",
|
||||
"error.upstream_product_not_found": "上游商品不存在",
|
||||
"error.upstream_products_fetch_failed": "獲取上游商品列表失敗",
|
||||
"error.upstream_categories_fetch_failed": "獲取上游分類列表失敗",
|
||||
"error.category_import_failed": "按分類匯入商品失敗",
|
||||
"error.totp_already_enabled": "已啟用兩步驗證,無需重複綁定",
|
||||
"error.totp_not_enabled": "尚未啟用兩步驗證",
|
||||
"error.totp_pending_expired": "綁定流程已逾時,請重新發起",
|
||||
"error.totp_code_invalid": "動態驗證碼錯誤",
|
||||
"error.totp_code_required": "請提供動態驗證碼或恢復碼",
|
||||
"error.recovery_code_invalid": "恢復碼錯誤或已使用",
|
||||
"error.totp_too_many_attempts": "失敗次數過多,請稍後重試",
|
||||
"error.totp_challenge_invalid": "登入工作階段已失效,請重新輸入密碼",
|
||||
"error.totp_cannot_reset_self": "無法透過此入口重設自己的 2FA,請使用 admin-tool CLI",
|
||||
"error.invalid_upstream_status": "無效的上游狀態參數",
|
||||
"error.invalid_product_status": "無效的商品狀態參數",
|
||||
"error.mapping_fetch_failed": "獲取商品映射失敗",
|
||||
"error.mapping_not_found": "商品映射不存在",
|
||||
"error.mapping_already_exists": "該上游商品已存在映射",
|
||||
"error.mapping_import_failed": "導入上游商品失敗",
|
||||
"error.mapping_batch_import_in_progress": "該對接站點正在批量導入,請稍後重新整理查看結果",
|
||||
"error.mapping_sync_failed": "同步商品映射失敗",
|
||||
"error.procurement_fetch_failed": "獲取採購單失敗",
|
||||
"error.procurement_not_found": "採購單不存在",
|
||||
"error.procurement_retry_failed": "重試採購單失敗",
|
||||
"error.procurement_cancel_failed": "取消採購單失敗",
|
||||
"error.procurement_upstream_cancel_refused": "上游訂單已付款,無法取消,請到上游站點申請退款",
|
||||
"error.mapping_update_failed": "更新商品映射失敗",
|
||||
"error.mapping_delete_failed": "刪除商品映射失敗",
|
||||
"error.connection_not_found": "站點連接不存在",
|
||||
"error.upstream_product_not_found": "上游商品不存在",
|
||||
"error.upstream_products_fetch_failed": "獲取上游商品列表失敗",
|
||||
"error.upstream_categories_fetch_failed": "獲取上游分類列表失敗",
|
||||
"error.category_import_failed": "按分類匯入商品失敗",
|
||||
"error.totp_already_enabled": "已啟用兩步驗證,無需重複綁定",
|
||||
"error.totp_not_enabled": "尚未啟用兩步驗證",
|
||||
"error.totp_pending_expired": "綁定流程已逾時,請重新發起",
|
||||
"error.totp_code_invalid": "動態驗證碼錯誤",
|
||||
"error.totp_code_required": "請提供動態驗證碼或恢復碼",
|
||||
"error.recovery_code_invalid": "恢復碼錯誤或已使用",
|
||||
"error.totp_too_many_attempts": "失敗次數過多,請稍後重試",
|
||||
"error.totp_challenge_invalid": "登入工作階段已失效,請重新輸入密碼",
|
||||
"error.totp_cannot_reset_self": "無法透過此入口重設自己的 2FA,請使用 admin-tool CLI",
|
||||
"error.invalid_upstream_status": "無效的上游狀態參數",
|
||||
"error.invalid_product_status": "無效的商品狀態參數",
|
||||
},
|
||||
LocaleEN: {
|
||||
"error.jwt_secret_missing": "JWT secret is not configured",
|
||||
@@ -1070,30 +1080,35 @@ var messages = map[string]map[string]string{
|
||||
"validation.rule.alphanum": "must be alphanumeric",
|
||||
|
||||
// Product mappings
|
||||
"error.mapping_fetch_failed": "Failed to fetch product mapping",
|
||||
"error.mapping_not_found": "Product mapping not found",
|
||||
"error.mapping_already_exists": "Mapping already exists for this upstream product",
|
||||
"error.mapping_import_failed": "Failed to import upstream product",
|
||||
"error.mapping_batch_import_in_progress": "A batch import is already running for this connection. Please refresh later to see the results.",
|
||||
"error.mapping_sync_failed": "Failed to sync product mapping",
|
||||
"error.mapping_update_failed": "Failed to update product mapping",
|
||||
"error.mapping_delete_failed": "Failed to delete product mapping",
|
||||
"error.connection_not_found": "Site connection not found",
|
||||
"error.upstream_product_not_found": "Upstream product not found",
|
||||
"error.upstream_products_fetch_failed": "Failed to fetch upstream products",
|
||||
"error.upstream_categories_fetch_failed": "Failed to fetch upstream categories",
|
||||
"error.category_import_failed": "Failed to import products by category",
|
||||
"error.totp_already_enabled": "Two-factor authentication is already enabled",
|
||||
"error.totp_not_enabled": "Two-factor authentication is not enabled",
|
||||
"error.totp_pending_expired": "2FA setup expired, please start over",
|
||||
"error.totp_code_invalid": "Invalid one-time code",
|
||||
"error.totp_code_required": "A one-time code or recovery code is required",
|
||||
"error.recovery_code_invalid": "Invalid or already-used recovery code",
|
||||
"error.totp_too_many_attempts": "Too many failed attempts, please try again later",
|
||||
"error.totp_challenge_invalid": "Login session expired, please re-enter your password",
|
||||
"error.totp_cannot_reset_self": "You cannot reset your own 2FA from this endpoint; use admin-tool CLI",
|
||||
"error.invalid_upstream_status": "Invalid upstream status parameter",
|
||||
"error.invalid_product_status": "Invalid product status parameter",
|
||||
"error.mapping_fetch_failed": "Failed to fetch product mapping",
|
||||
"error.mapping_not_found": "Product mapping not found",
|
||||
"error.mapping_already_exists": "Mapping already exists for this upstream product",
|
||||
"error.mapping_import_failed": "Failed to import upstream product",
|
||||
"error.mapping_batch_import_in_progress": "A batch import is already running for this connection. Please refresh later to see the results.",
|
||||
"error.mapping_sync_failed": "Failed to sync product mapping",
|
||||
"error.procurement_fetch_failed": "Failed to fetch procurement orders",
|
||||
"error.procurement_not_found": "Procurement order not found",
|
||||
"error.procurement_retry_failed": "Failed to retry procurement order",
|
||||
"error.procurement_cancel_failed": "Failed to cancel procurement order",
|
||||
"error.procurement_upstream_cancel_refused": "The upstream order is already paid and cannot be canceled. Please request a refund on the upstream site.",
|
||||
"error.mapping_update_failed": "Failed to update product mapping",
|
||||
"error.mapping_delete_failed": "Failed to delete product mapping",
|
||||
"error.connection_not_found": "Site connection not found",
|
||||
"error.upstream_product_not_found": "Upstream product not found",
|
||||
"error.upstream_products_fetch_failed": "Failed to fetch upstream products",
|
||||
"error.upstream_categories_fetch_failed": "Failed to fetch upstream categories",
|
||||
"error.category_import_failed": "Failed to import products by category",
|
||||
"error.totp_already_enabled": "Two-factor authentication is already enabled",
|
||||
"error.totp_not_enabled": "Two-factor authentication is not enabled",
|
||||
"error.totp_pending_expired": "2FA setup expired, please start over",
|
||||
"error.totp_code_invalid": "Invalid one-time code",
|
||||
"error.totp_code_required": "A one-time code or recovery code is required",
|
||||
"error.recovery_code_invalid": "Invalid or already-used recovery code",
|
||||
"error.totp_too_many_attempts": "Too many failed attempts, please try again later",
|
||||
"error.totp_challenge_invalid": "Login session expired, please re-enter your password",
|
||||
"error.totp_cannot_reset_self": "You cannot reset your own 2FA from this endpoint; use admin-tool CLI",
|
||||
"error.invalid_upstream_status": "Invalid upstream status parameter",
|
||||
"error.invalid_product_status": "Invalid product status parameter",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -8,4 +8,6 @@ var (
|
||||
ErrStatusInvalid = errors.New("procurement order status invalid")
|
||||
ErrOrderNotFound = errors.New("order not found")
|
||||
ErrConnectionNotFound = errors.New("site connection not found")
|
||||
// ErrUpstreamCancelRefused 上游拒绝取消(订单已在上游付款),需到上游申请退款
|
||||
ErrUpstreamCancelRefused = errors.New("upstream refused to cancel order")
|
||||
)
|
||||
@@ -2,6 +2,8 @@ package upstreamgateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
procurementcontract "github.com/dujiao-next/internal/modules/procurement/contract"
|
||||
siteconnectiondomain "github.com/dujiao-next/internal/modules/siteconnection/domain"
|
||||
@@ -81,7 +83,11 @@ func (s *session) GetOrder(ctx context.Context, orderID uint) (*procurementcontr
|
||||
}
|
||||
|
||||
func (s *session) CancelOrder(ctx context.Context, orderID uint) error {
|
||||
return s.adapter.CancelOrder(ctx, orderID)
|
||||
err := s.adapter.CancelOrder(ctx, orderID)
|
||||
if errors.Is(err, upstream.ErrUpstreamCancelNotAllowed) {
|
||||
return fmt.Errorf("%w: %v", procurementcontract.ErrUpstreamCancelRefused, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func fromUpstreamFulfillment(value *upstream.UpstreamFulfillment) *procurementcontract.Fulfillment {
|
||||
|
||||
@@ -2,6 +2,7 @@ package procurement_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
mappingdomain "github.com/dujiao-next/internal/modules/catalog/mapping/domain"
|
||||
|
||||
"github.com/dujiao-next/internal/constants"
|
||||
procurementcontract "github.com/dujiao-next/internal/modules/procurement/contract"
|
||||
siteconnectionapp "github.com/dujiao-next/internal/modules/siteconnection/application"
|
||||
)
|
||||
|
||||
@@ -243,7 +245,7 @@ func TestCancelManual_AcceptedKeepsStatusWhenUpstreamRefuses(t *testing.T) {
|
||||
// 上游已支付的订单不允许取消
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
json.NewEncoder(w).Encode(map[string]any{"ok": false, "error_code": "cancel_not_allowed"})
|
||||
}))
|
||||
defer server.Close()
|
||||
@@ -260,8 +262,8 @@ func TestCancelManual_AcceptedKeepsStatusWhenUpstreamRefuses(t *testing.T) {
|
||||
db.Model(proc).Update("upstream_order_id", 999)
|
||||
|
||||
svc := newTestProcurementService(db, connSvc)
|
||||
if err := svc.CancelManual(proc.ID); err == nil {
|
||||
t.Fatal("expected cancel to fail when upstream refuses")
|
||||
if err := svc.CancelManual(proc.ID); !errors.Is(err, procurementcontract.ErrUpstreamCancelRefused) {
|
||||
t.Fatalf("expected ErrUpstreamCancelRefused, got %v", err)
|
||||
}
|
||||
var reloaded ProcurementOrder
|
||||
db.First(&reloaded, proc.ID)
|
||||
|
||||
@@ -214,6 +214,10 @@ func (h *AdminHandler) CancelProcurementOrder(c *gin.Context) {
|
||||
ginutil.RespondErrorWithMsg(c, response.CodeBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, procurementcontract.ErrUpstreamCancelRefused) {
|
||||
ginutil.RespondError(c, response.CodeBadRequest, "error.procurement_upstream_cancel_refused", nil)
|
||||
return
|
||||
}
|
||||
ginutil.RespondError(c, response.CodeInternal, "error.procurement_cancel_failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ var (
|
||||
// 旧版上游对下架商品返回 product_unavailable,新版改为 200 + is_active=false;
|
||||
// 此错误仅作为旧版兼容兜底使用。
|
||||
ErrUpstreamProductUnavailable = errors.New("upstream product unavailable")
|
||||
// ErrUpstreamCancelNotAllowed 上游拒绝取消订单(如订单已付款),重试也不会成功
|
||||
ErrUpstreamCancelNotAllowed = errors.New("upstream order cannot be canceled")
|
||||
)
|
||||
|
||||
// PingResult 连接测试结果
|
||||
|
||||
@@ -171,6 +171,10 @@ func (a *DujiaoNextAdapter) CancelOrder(ctx context.Context, orderID uint) error
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
if err := a.doRequest(ctx, http.MethodPost, path, nil, &result); err != nil {
|
||||
var ue *upstreamHTTPError
|
||||
if errors.As(err, &ue) && ue.Code == "cancel_not_allowed" {
|
||||
return fmt.Errorf("%w: %s", ErrUpstreamCancelNotAllowed, ue.Message)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !result.OK {
|
||||
|
||||
Reference in new issue
Block a user