增加备注
This commit is contained in:
parent
4bd22c61d6
commit
ff5df240dd
File diff suppressed because it is too large
Load Diff
@ -228,6 +228,7 @@ message AdminCreditAssetRequest {
|
|||||||
string reason = 6;
|
string reason = 6;
|
||||||
string evidence_ref = 7;
|
string evidence_ref = 7;
|
||||||
string app_code = 8;
|
string app_code = 8;
|
||||||
|
string remark = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AdminCreditAssetResponse {
|
message AdminCreditAssetResponse {
|
||||||
|
|||||||
@ -72,6 +72,7 @@ type coinAdjustmentDTO struct {
|
|||||||
AvailableDelta int64 `json:"availableDelta"`
|
AvailableDelta int64 `json:"availableDelta"`
|
||||||
AvailableAfter int64 `json:"availableAfter"`
|
AvailableAfter int64 `json:"availableAfter"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
OperatorUserID string `json:"operatorUserId"`
|
OperatorUserID string `json:"operatorUserId"`
|
||||||
Operator coinAdjustmentOperatorDTO `json:"operator"`
|
Operator coinAdjustmentOperatorDTO `json:"operator"`
|
||||||
CreatedAtMS int64 `json:"createdAtMs"`
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
@ -84,4 +85,5 @@ type coinAdjustmentCreateDTO struct {
|
|||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
AvailableDelta int64 `json:"availableDelta"`
|
AvailableDelta int64 `json:"availableDelta"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,7 +125,7 @@ func (h *Handler) CreateCoinAdjustment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
shared.OperationLogWithResourceID(c, h.audit, "coin-adjustment-create", "wallet_entries", result.TransactionID, "success",
|
shared.OperationLogWithResourceID(c, h.audit, "coin-adjustment-create", "wallet_entries", result.TransactionID, "success",
|
||||||
fmt.Sprintf("target_user_id=%v amount=%d transaction_id=%s reason=%q", req.TargetUserID, req.Amount, result.TransactionID, strings.TrimSpace(req.Reason)))
|
fmt.Sprintf("target_user_id=%v amount=%d transaction_id=%s reason=%q remark=%q", req.TargetUserID, req.Amount, result.TransactionID, strings.TrimSpace(req.Reason), strings.TrimSpace(req.Remark)))
|
||||||
response.Created(c, result)
|
response.Created(c, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,4 +28,5 @@ type coinAdjustmentRequest struct {
|
|||||||
TargetUserID any `json:"targetUserId"`
|
TargetUserID any `json:"targetUserId"`
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"hyapp-admin-server/internal/integration/walletclient"
|
"hyapp-admin-server/internal/integration/walletclient"
|
||||||
"hyapp-admin-server/internal/modules/shared"
|
"hyapp-admin-server/internal/modules/shared"
|
||||||
@ -419,6 +420,7 @@ func (s *Service) ListCoinAdjustments(ctx context.Context, appCode string, query
|
|||||||
item.Direction = directionForDelta(item.AvailableDelta)
|
item.Direction = directionForDelta(item.AvailableDelta)
|
||||||
item.Amount = item.AvailableDelta
|
item.Amount = item.AvailableDelta
|
||||||
item.Reason = metadataString(metadata, "reason")
|
item.Reason = metadataString(metadata, "reason")
|
||||||
|
item.Remark = metadataString(metadata, "remark")
|
||||||
item.OperatorUserID = formatOptionalID(operatorID)
|
item.OperatorUserID = formatOptionalID(operatorID)
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
itemUserIDs = append(itemUserIDs, userID)
|
itemUserIDs = append(itemUserIDs, userID)
|
||||||
@ -509,9 +511,16 @@ func (s *Service) CreateCoinAdjustment(ctx context.Context, appCode string, acto
|
|||||||
if reason == "" {
|
if reason == "" {
|
||||||
return coinAdjustmentCreateDTO{}, fmt.Errorf("reason is required")
|
return coinAdjustmentCreateDTO{}, fmt.Errorf("reason is required")
|
||||||
}
|
}
|
||||||
if len(reason) > 512 {
|
if utf8.RuneCountInString(reason) > 512 {
|
||||||
return coinAdjustmentCreateDTO{}, fmt.Errorf("reason is too long")
|
return coinAdjustmentCreateDTO{}, fmt.Errorf("reason is too long")
|
||||||
}
|
}
|
||||||
|
remark := strings.TrimSpace(req.Remark)
|
||||||
|
if remark == "" {
|
||||||
|
return coinAdjustmentCreateDTO{}, fmt.Errorf("remark is required")
|
||||||
|
}
|
||||||
|
if utf8.RuneCountInString(remark) > 512 {
|
||||||
|
return coinAdjustmentCreateDTO{}, fmt.Errorf("remark is too long")
|
||||||
|
}
|
||||||
user, err := s.LookupCoinAdjustmentTarget(ctx, appCode, strconv.FormatInt(targetUserID, 10))
|
user, err := s.LookupCoinAdjustmentTarget(ctx, appCode, strconv.FormatInt(targetUserID, 10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return coinAdjustmentCreateDTO{}, err
|
return coinAdjustmentCreateDTO{}, err
|
||||||
@ -533,6 +542,7 @@ func (s *Service) CreateCoinAdjustment(ctx context.Context, appCode string, acto
|
|||||||
Reason: reason,
|
Reason: reason,
|
||||||
EvidenceRef: evidenceRef,
|
EvidenceRef: evidenceRef,
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
|
Remark: remark,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return coinAdjustmentCreateDTO{}, err
|
return coinAdjustmentCreateDTO{}, err
|
||||||
@ -548,6 +558,7 @@ func (s *Service) CreateCoinAdjustment(ctx context.Context, appCode string, acto
|
|||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
AvailableDelta: req.Amount,
|
AvailableDelta: req.Amount,
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
|
Remark: remark,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,118 @@
|
|||||||
package coinledger
|
package coinledger
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"context"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/integration/walletclient"
|
||||||
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeCoinAdjustmentWallet struct {
|
||||||
|
walletclient.Client
|
||||||
|
req *walletv1.AdminCreditAssetRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCoinAdjustmentWallet) AdminCreditAsset(_ context.Context, req *walletv1.AdminCreditAssetRequest) (*walletv1.AdminCreditAssetResponse, error) {
|
||||||
|
f.req = req
|
||||||
|
return &walletv1.AdminCreditAssetResponse{
|
||||||
|
TransactionId: "tx-admin-coin-adjustment",
|
||||||
|
Balance: &walletv1.AssetBalance{AvailableAmount: 880},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCoinAdjustmentRequiresRemark(t *testing.T) {
|
||||||
|
service := NewService(nil, nil, nil, &fakeCoinAdjustmentWallet{})
|
||||||
|
_, err := service.CreateCoinAdjustment(context.Background(), "lalu", 90001, "req-coin-remark", coinAdjustmentRequest{
|
||||||
|
TargetUserID: "20001",
|
||||||
|
Amount: 100,
|
||||||
|
Reason: "manual adjustment",
|
||||||
|
Remark: " ",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "remark is required") {
|
||||||
|
t.Fatalf("expected required remark error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCoinAdjustmentRejectsLongRemark(t *testing.T) {
|
||||||
|
service := NewService(nil, nil, nil, &fakeCoinAdjustmentWallet{})
|
||||||
|
_, err := service.CreateCoinAdjustment(context.Background(), "lalu", 90001, "req-coin-remark", coinAdjustmentRequest{
|
||||||
|
TargetUserID: "20001",
|
||||||
|
Amount: 100,
|
||||||
|
Reason: "manual adjustment",
|
||||||
|
Remark: strings.Repeat("r", 513),
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "remark is too long") {
|
||||||
|
t.Fatalf("expected long remark error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCoinAdjustmentRemarkLengthUsesCharacters(t *testing.T) {
|
||||||
|
service := NewService(nil, nil, nil, &fakeCoinAdjustmentWallet{})
|
||||||
|
_, validErr := service.CreateCoinAdjustment(context.Background(), "lalu", 90001, "req-coin-remark", coinAdjustmentRequest{
|
||||||
|
TargetUserID: "20001",
|
||||||
|
Amount: 100,
|
||||||
|
Reason: "manual adjustment",
|
||||||
|
Remark: strings.Repeat("补", 512),
|
||||||
|
})
|
||||||
|
if validErr == nil || strings.Contains(validErr.Error(), "remark is too long") {
|
||||||
|
t.Fatalf("512 multibyte characters should pass length validation, got %v", validErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, longErr := service.CreateCoinAdjustment(context.Background(), "lalu", 90001, "req-coin-remark", coinAdjustmentRequest{
|
||||||
|
TargetUserID: "20001",
|
||||||
|
Amount: 100,
|
||||||
|
Reason: "manual adjustment",
|
||||||
|
Remark: strings.Repeat("补", 513),
|
||||||
|
})
|
||||||
|
if longErr == nil || !strings.Contains(longErr.Error(), "remark is too long") {
|
||||||
|
t.Fatalf("expected long multibyte remark error, got %v", longErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCoinAdjustmentTrimsAndSendsRemark(t *testing.T) {
|
||||||
|
userDB, mock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("new sqlmock failed: %v", err)
|
||||||
|
}
|
||||||
|
defer userDB.Close()
|
||||||
|
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta("SELECT u.user_id, u.current_display_user_id, COALESCE(u.username, ''), COALESCE(u.avatar, '')")).
|
||||||
|
WithArgs("lalu", "20001", "20001", "20001", sqlmock.AnyArg(), "20001", "%20001%", "20001", "20001", "20001", sqlmock.AnyArg(), "20001").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id", "current_display_user_id", "username", "avatar"}).AddRow(int64(20001), "6688", "target", "avatar.png"))
|
||||||
|
|
||||||
|
wallet := &fakeCoinAdjustmentWallet{}
|
||||||
|
service := NewService(userDB, nil, nil, wallet)
|
||||||
|
result, err := service.CreateCoinAdjustment(context.Background(), "lalu", 90001, "req-coin-remark", coinAdjustmentRequest{
|
||||||
|
CommandID: " cmd-admin-coin ",
|
||||||
|
TargetUserID: "20001",
|
||||||
|
Amount: 120,
|
||||||
|
Reason: " manual adjustment ",
|
||||||
|
Remark: " ticket approved by finance ",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateCoinAdjustment failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Fatalf("sql expectations mismatch: %v", err)
|
||||||
|
}
|
||||||
|
if wallet.req == nil {
|
||||||
|
t.Fatalf("wallet request was not sent")
|
||||||
|
}
|
||||||
|
if wallet.req.GetRemark() != "ticket approved by finance" || wallet.req.GetReason() != "manual adjustment" {
|
||||||
|
t.Fatalf("wallet audit fields mismatch: reason=%q remark=%q", wallet.req.GetReason(), wallet.req.GetRemark())
|
||||||
|
}
|
||||||
|
if wallet.req.GetCommandId() != "cmd-admin-coin" || wallet.req.GetTargetUserId() != 20001 || wallet.req.GetAmount() != 120 || wallet.req.GetOperatorUserId() != 90001 {
|
||||||
|
t.Fatalf("wallet request mismatch: %+v", wallet.req)
|
||||||
|
}
|
||||||
|
if result.Remark != "ticket approved by finance" || result.Reason != "manual adjustment" || result.BalanceAfter != 880 {
|
||||||
|
t.Fatalf("create dto mismatch: %+v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeListQueryCapsPageSize(t *testing.T) {
|
func TestNormalizeListQueryCapsPageSize(t *testing.T) {
|
||||||
query := normalizeListQuery(listQuery{Page: -1, PageSize: 1000, UserKeyword: " 10001 "})
|
query := normalizeListQuery(listQuery{Page: -1, PageSize: 1000, UserKeyword: " 10001 "})
|
||||||
|
|||||||
@ -245,6 +245,7 @@ type AdminCreditAssetCommand struct {
|
|||||||
Amount int64
|
Amount int64
|
||||||
OperatorUserID int64
|
OperatorUserID int64
|
||||||
Reason string
|
Reason string
|
||||||
|
Remark string
|
||||||
EvidenceRef string
|
EvidenceRef string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1534,6 +1534,7 @@ func TestAdminCreditAssetIsIdempotent(t *testing.T) {
|
|||||||
Amount: 500,
|
Amount: 500,
|
||||||
OperatorUserID: 90001,
|
OperatorUserID: 90001,
|
||||||
Reason: "manual recharge",
|
Reason: "manual recharge",
|
||||||
|
Remark: "finance ticket approved",
|
||||||
EvidenceRef: "ticket-1",
|
EvidenceRef: "ticket-1",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1556,6 +1557,54 @@ func TestAdminCreditAssetIsIdempotent(t *testing.T) {
|
|||||||
if balanceAmount(balances, ledger.AssetCoin) != 500 || balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
|
if balanceAmount(balances, ledger.AssetCoin) != 500 || balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
|
||||||
t.Fatalf("balances mismatch: %+v", balances)
|
t.Fatalf("balances mismatch: %+v", balances)
|
||||||
}
|
}
|
||||||
|
if got := repository.CountRows("wallet_transactions", "transaction_id = ? AND JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.remark')) = ?", txID, "finance ticket approved"); got != 1 {
|
||||||
|
t.Fatalf("admin credit metadata should carry remark, got %d", got)
|
||||||
|
}
|
||||||
|
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND JSON_UNQUOTE(JSON_EXTRACT(payload, '$.metadata.remark')) = ?", txID, "WalletBalanceChanged", "finance ticket approved"); got != 1 {
|
||||||
|
t.Fatalf("admin credit outbox payload should carry remark, got %d", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
conflict := command
|
||||||
|
conflict.Remark = "different finance ticket"
|
||||||
|
if _, _, err := svc.AdminCreditAsset(context.Background(), conflict); !xerr.IsCode(err, xerr.LedgerConflict) {
|
||||||
|
t.Fatalf("same command with different remark should conflict, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdminCreditAssetKeepsLegacyIdempotencyWithoutRemark 验证未传 remark 的旧调用仍沿用原幂等哈希。
|
||||||
|
func TestAdminCreditAssetKeepsLegacyIdempotencyWithoutRemark(t *testing.T) {
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
svc := walletservice.New(repository)
|
||||||
|
command := ledger.AdminCreditAssetCommand{
|
||||||
|
CommandID: "cmd-admin-credit-legacy",
|
||||||
|
TargetUserID: 20003,
|
||||||
|
AssetType: ledger.AssetCoin,
|
||||||
|
Amount: 300,
|
||||||
|
OperatorUserID: 90001,
|
||||||
|
Reason: "legacy manual recharge",
|
||||||
|
EvidenceRef: "ticket-legacy",
|
||||||
|
}
|
||||||
|
|
||||||
|
first, txID, err := svc.AdminCreditAsset(context.Background(), command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("legacy AdminCreditAsset failed: %v", err)
|
||||||
|
}
|
||||||
|
second, secondTxID, err := svc.AdminCreditAsset(context.Background(), command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("legacy AdminCreditAsset retry failed: %v", err)
|
||||||
|
}
|
||||||
|
if txID == "" || txID != secondTxID || first.AvailableAmount != 300 || second.AvailableAmount != 300 {
|
||||||
|
t.Fatalf("legacy idempotency mismatch: first=%+v second=%+v tx=%s/%s", first, second, txID, secondTxID)
|
||||||
|
}
|
||||||
|
if got := repository.CountRows("wallet_transactions", "transaction_id = ? AND JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.remark')) = ?", txID, ""); got != 1 {
|
||||||
|
t.Fatalf("legacy admin credit metadata should keep empty remark, got %d", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
withRemark := command
|
||||||
|
withRemark.Remark = "added after legacy request"
|
||||||
|
if _, _, err := svc.AdminCreditAsset(context.Background(), withRemark); !xerr.IsCode(err, xerr.LedgerConflict) {
|
||||||
|
t.Fatalf("legacy command retried with remark should conflict, got %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAdminCreditAssetAllowsSignedAdjustment 验证后台调账同一 RPC 可表达加金币和扣金币,且扣账不能穿透余额约束。
|
// TestAdminCreditAssetAllowsSignedAdjustment 验证后台调账同一 RPC 可表达加金币和扣金币,且扣账不能穿透余额约束。
|
||||||
|
|||||||
@ -51,6 +51,7 @@ func (r *Repository) AdminCreditAsset(ctx context.Context, command ledger.AdminC
|
|||||||
Amount: command.Amount,
|
Amount: command.Amount,
|
||||||
OperatorUserID: command.OperatorUserID,
|
OperatorUserID: command.OperatorUserID,
|
||||||
Reason: command.Reason,
|
Reason: command.Reason,
|
||||||
|
Remark: command.Remark,
|
||||||
EvidenceRef: command.EvidenceRef,
|
EvidenceRef: command.EvidenceRef,
|
||||||
}
|
}
|
||||||
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeManualCredit, requestHash, command.EvidenceRef, metadata, nowMs); err != nil {
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeManualCredit, requestHash, command.EvidenceRef, metadata, nowMs); err != nil {
|
||||||
@ -93,13 +94,25 @@ func (r *Repository) AdminCreditAsset(ctx context.Context, command ledger.AdminC
|
|||||||
}
|
}
|
||||||
|
|
||||||
func adminCreditRequestHash(command ledger.AdminCreditAssetCommand) string {
|
func adminCreditRequestHash(command ledger.AdminCreditAssetCommand) string {
|
||||||
return stableHash(fmt.Sprintf("admin_credit|%s|%d|%s|%d|%d|%s|%s",
|
if command.Remark == "" {
|
||||||
|
return stableHash(fmt.Sprintf("admin_credit|%s|%d|%s|%d|%d|%s|%s",
|
||||||
|
appcode.Normalize(command.AppCode),
|
||||||
|
command.TargetUserID,
|
||||||
|
command.AssetType,
|
||||||
|
command.Amount,
|
||||||
|
command.OperatorUserID,
|
||||||
|
command.Reason,
|
||||||
|
command.EvidenceRef,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return stableHash(fmt.Sprintf("admin_credit_v2|%s|%d|%s|%d|%d|%s|%s|%s",
|
||||||
appcode.Normalize(command.AppCode),
|
appcode.Normalize(command.AppCode),
|
||||||
command.TargetUserID,
|
command.TargetUserID,
|
||||||
command.AssetType,
|
command.AssetType,
|
||||||
command.Amount,
|
command.Amount,
|
||||||
command.OperatorUserID,
|
command.OperatorUserID,
|
||||||
command.Reason,
|
command.Reason,
|
||||||
|
command.Remark,
|
||||||
command.EvidenceRef,
|
command.EvidenceRef,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,7 @@ type adminCreditMetadata struct {
|
|||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
OperatorUserID int64 `json:"operator_user_id"`
|
OperatorUserID int64 `json:"operator_user_id"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
EvidenceRef string `json:"evidence_ref"`
|
EvidenceRef string `json:"evidence_ref"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ func (s *Server) AdminCreditAsset(ctx context.Context, req *walletv1.AdminCredit
|
|||||||
Amount: req.GetAmount(),
|
Amount: req.GetAmount(),
|
||||||
OperatorUserID: req.GetOperatorUserId(),
|
OperatorUserID: req.GetOperatorUserId(),
|
||||||
Reason: req.GetReason(),
|
Reason: req.GetReason(),
|
||||||
|
Remark: req.GetRemark(),
|
||||||
EvidenceRef: req.GetEvidenceRef(),
|
EvidenceRef: req.GetEvidenceRef(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user