- no platform dependency in sync.go
This commit is contained in:
@@ -25,7 +25,8 @@ func (c *BaseScheduler) Init() {
|
||||
c.DeliveryPlatformHandlers = make(map[int]*scheduler.DeliveryPlatformHandlerInfo)
|
||||
}
|
||||
|
||||
func (c *BaseScheduler) RegisterPurchasePlatform(vendorID int, handler partner.IPurchasePlatformHandler) {
|
||||
func (c *BaseScheduler) RegisterPurchasePlatform(handler partner.IPurchasePlatformHandler) {
|
||||
vendorID := handler.GetVendorID()
|
||||
if !(vendorID >= model.VendorIDPurchaseBegin && vendorID <= model.VendorIDPurchaseEnd) {
|
||||
panic(fmt.Sprintf("purchase vendor:%d is illegal", vendorID))
|
||||
}
|
||||
@@ -35,7 +36,8 @@ func (c *BaseScheduler) RegisterPurchasePlatform(vendorID int, handler partner.I
|
||||
c.PurchasePlatformHandlers[vendorID] = handler
|
||||
}
|
||||
|
||||
func (c *BaseScheduler) RegisterDeliveryPlatform(vendorID int, handler partner.IDeliveryPlatformHandler, isUse4CreateWaybill bool) {
|
||||
func (c *BaseScheduler) RegisterDeliveryPlatform(handler partner.IDeliveryPlatformHandler, isUse4CreateWaybill bool) {
|
||||
vendorID := handler.GetVendorID()
|
||||
if !(vendorID >= model.VendorIDDeliveryBegin && vendorID <= model.VendorIDDeliveryEnd) {
|
||||
panic(fmt.Sprintf("delivery vendor:%d is illegal", vendorID))
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ type DeliveryPlatformHandlerInfo struct {
|
||||
}
|
||||
|
||||
type IScheduler interface {
|
||||
RegisterPurchasePlatform(vendorID int, handler partner.IPurchasePlatformHandler)
|
||||
RegisterDeliveryPlatform(vendorID int, handler partner.IDeliveryPlatformHandler, isUse4CreateWaybill bool)
|
||||
RegisterPurchasePlatform(handler partner.IPurchasePlatformHandler)
|
||||
RegisterDeliveryPlatform(handler partner.IDeliveryPlatformHandler, isUse4CreateWaybill bool)
|
||||
|
||||
// 以下是订单
|
||||
OnOrderNew(order *model.GoodsOrder, isPending bool) (err error)
|
||||
|
||||
@@ -3,9 +3,7 @@ package cms
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.rosy.net.cn/baseapi/utils"
|
||||
"git.rosy.net.cn/jx-callback/business/jxcallback/scheduler/basesch"
|
||||
@@ -129,12 +127,3 @@ func UpdatePlace(placeCode int, payload map[string]interface{}, userName string)
|
||||
func genPicFileName(suffix string) string {
|
||||
return fmt.Sprintf("%x%s", md5.Sum([]byte(utils.GetUUID()+suffix)), suffix)
|
||||
}
|
||||
|
||||
// 生成一个不重复的临时ID
|
||||
func genTmpID() int64 {
|
||||
return time.Now().UnixNano() / 1000000
|
||||
}
|
||||
|
||||
func isFakeVendorThingID(id int64) bool {
|
||||
return id == 0 || (int(math.Log10(float64(id))) == int(math.Log10(float64(genTmpID()))))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.rosy.net.cn/baseapi/utils"
|
||||
"git.rosy.net.cn/jx-callback/business/jxutils"
|
||||
"git.rosy.net.cn/jx-callback/business/model"
|
||||
"git.rosy.net.cn/jx-callback/business/model/dao"
|
||||
"git.rosy.net.cn/jx-callback/business/partner"
|
||||
@@ -46,7 +47,7 @@ func GetCategories(parentID int) (cats []*model.SkuCategory, err error) {
|
||||
func AddCategory(cat *model.SkuCategory, userName string) (outCat *model.SkuCategory, err error) {
|
||||
dao.WrapAddIDCULDEntity(cat, userName)
|
||||
cat.JdSyncStatus = model.SyncFlagNewMask
|
||||
cat.JdID = genTmpID()
|
||||
cat.JdID = jxutils.GenFakeID()
|
||||
db := dao.GetDB()
|
||||
if cat.Seq <= 0 {
|
||||
var maxSeq struct {
|
||||
@@ -331,7 +332,7 @@ func AddSkuName(skuNameExt *model.SkuNameExt, userName string) (outSkuNameExt *m
|
||||
dao.WrapAddIDCULDEntity(sku, userName)
|
||||
sku.NameID = skuNameExt.ID
|
||||
sku.JdSyncStatus = model.SyncFlagNewMask
|
||||
sku.JdID = genTmpID()
|
||||
sku.JdID = jxutils.GenFakeID()
|
||||
if err = dao.CreateEntity(db, sku); err != nil {
|
||||
dao.Rollback(db)
|
||||
return nil, err
|
||||
@@ -449,7 +450,7 @@ func AddSku(nameID int, sku *model.Sku, userName string) (outSkuNameExt *model.S
|
||||
dao.WrapAddIDCULDEntity(sku, userName)
|
||||
sku.JdSyncStatus = model.SyncFlagNewMask
|
||||
sku.NameID = nameID
|
||||
sku.JdID = genTmpID()
|
||||
sku.JdID = jxutils.GenFakeID()
|
||||
if err = dao.CreateEntity(db, sku); err == nil {
|
||||
result, err2 := GetSkuNames("", utils.Params2Map("skuID", sku.ID), 0, 0)
|
||||
if err = err2; err == nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"git.rosy.net.cn/baseapi/utils"
|
||||
"git.rosy.net.cn/jx-callback/business/jxcallback/scheduler/basesch"
|
||||
"git.rosy.net.cn/jx-callback/business/jxutils"
|
||||
"git.rosy.net.cn/jx-callback/business/jxutils/tasksch"
|
||||
"git.rosy.net.cn/jx-callback/business/model"
|
||||
"git.rosy.net.cn/jx-callback/business/model/dao"
|
||||
@@ -46,6 +47,7 @@ func Init() {
|
||||
func (v *VendorSync) SyncCategory(db *dao.DaoDB, categoryID int, isForce bool, userName string) (err error) {
|
||||
err = v.LoopMultiStoresVendors(db, "SyncCategory", userName, func(batchItemList []interface{}, params ...interface{}) (interface{}, error) {
|
||||
multiStoresHandler := batchItemList[0].(partner.IMultipleStoresHandler)
|
||||
syncStatusFieldName := multiStoresHandler.GetFieldSyncStatusName()
|
||||
var cats []*model.SkuCategory
|
||||
cond := make(map[string]interface{})
|
||||
if categoryID > 0 {
|
||||
@@ -56,17 +58,18 @@ func (v *VendorSync) SyncCategory(db *dao.DaoDB, categoryID int, isForce bool, u
|
||||
task := tasksch.RunTask("", false, nil, len(cats), 1, userName, func(batchItemList []interface{}, params ...interface{}) (interface{}, error) {
|
||||
for _, v := range batchItemList {
|
||||
cat := v.(*model.SkuCategory)
|
||||
updateFields := []string{multiStoresHandler.GetFieldSyncStatusName()}
|
||||
if (cat.JdSyncStatus & model.SyncFlagDeletedMask) != 0 { //删除
|
||||
updateFields := []string{syncStatusFieldName}
|
||||
syncStatus := jxutils.GetObjFieldByName(cat, syncStatusFieldName).(int8)
|
||||
if (syncStatus & model.SyncFlagDeletedMask) != 0 { //删除
|
||||
err = multiStoresHandler.DeleteCategory(db, cat, userName)
|
||||
} else if (cat.JdSyncStatus&model.SyncFlagNewMask) != 0 || isForce { // 新增
|
||||
} else if (syncStatus&model.SyncFlagNewMask) != 0 || isForce { // 新增
|
||||
err = multiStoresHandler.CreateCategory(db, cat, userName)
|
||||
updateFields = append(updateFields, multiStoresHandler.GetFieldIDName())
|
||||
} else if (cat.JdSyncStatus & model.SyncFlagModifiedMask) != 0 { // 修改
|
||||
} else if (syncStatus & model.SyncFlagModifiedMask) != 0 { // 修改
|
||||
err = multiStoresHandler.UpdateCategory(db, cat, userName)
|
||||
}
|
||||
if err == nil {
|
||||
cat.JdSyncStatus = 0
|
||||
jxutils.SetObjFieldByName(cat, syncStatusFieldName, int8(0))
|
||||
_, err = dao.UpdateEntity(db, cat, updateFields...)
|
||||
} else {
|
||||
break
|
||||
@@ -116,6 +119,7 @@ func (v *VendorSync) SyncSku(db *dao.DaoDB, nameID, skuID int, isForce bool, use
|
||||
globals.SugarLogger.Debugf("SyncSku, nameID:%d, skuID:%d, isForce:%t, userName:%s", nameID, skuID, isForce, userName)
|
||||
err = v.LoopMultiStoresVendors(db, "SyncSku", userName, func(batchItemList []interface{}, params ...interface{}) (interface{}, error) {
|
||||
multiStoresHandler := batchItemList[0].(partner.IMultipleStoresHandler)
|
||||
syncStatusFieldName := multiStoresHandler.GetFieldSyncStatusName()
|
||||
var skuList []*model.Sku
|
||||
cond := make(map[string]interface{})
|
||||
if nameID != -1 {
|
||||
@@ -130,18 +134,19 @@ func (v *VendorSync) SyncSku(db *dao.DaoDB, nameID, skuID int, isForce bool, use
|
||||
task := tasksch.RunTask("SyncSku", false, nil, len(skuList), 1, userName, func(batchItemList []interface{}, params ...interface{}) (interface{}, error) {
|
||||
for _, v := range batchItemList {
|
||||
sku := v.(*model.Sku)
|
||||
if (skuID == -1 || skuID == sku.ID) && (isForce || sku.JdSyncStatus != 0) {
|
||||
updateFields := []string{multiStoresHandler.GetFieldSyncStatusName()}
|
||||
if sku.JdSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除
|
||||
syncStatus := jxutils.GetObjFieldByName(sku, syncStatusFieldName).(int8)
|
||||
if (skuID == -1 || skuID == sku.ID) && (isForce || syncStatus != 0) {
|
||||
updateFields := []string{syncStatusFieldName}
|
||||
if syncStatus&model.SyncFlagDeletedMask != 0 { // 删除
|
||||
err = multiStoresHandler.DeleteSku(db, sku, userName)
|
||||
} else if sku.JdSyncStatus&model.SyncFlagNewMask != 0 { // 新增
|
||||
} else if syncStatus&model.SyncFlagNewMask != 0 { // 新增
|
||||
err = multiStoresHandler.CreateSku(db, sku, userName)
|
||||
updateFields = append(updateFields, multiStoresHandler.GetFieldIDName())
|
||||
} else if sku.JdSyncStatus&model.SyncFlagModifiedMask != 0 { // 修改
|
||||
} else if syncStatus&model.SyncFlagModifiedMask != 0 { // 修改
|
||||
err = multiStoresHandler.UpdateSku(db, sku, userName)
|
||||
}
|
||||
if err == nil {
|
||||
sku.JdSyncStatus = 0
|
||||
jxutils.SetObjFieldByName(sku, syncStatusFieldName, int8(0))
|
||||
dao.UpdateEntity(db, sku, updateFields...)
|
||||
} else {
|
||||
break
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -333,10 +332,6 @@ func FilterMapByFieldList(mapData map[string]interface{}, fields []string) (vali
|
||||
return valid, invalid
|
||||
}
|
||||
|
||||
func GetObjFieldByName(obj interface{}, fieldName string) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(obj)).FieldByName(fieldName).Interface()
|
||||
}
|
||||
|
||||
func MakeValidationMapFromSlice(validValues []string, flag int) map[string]int {
|
||||
retVal := make(map[string]int)
|
||||
for _, v := range validValues {
|
||||
|
||||
@@ -131,3 +131,20 @@ func GetSliceLen(list interface{}) int {
|
||||
func CaculateSkuVendorPrice(price int, percentage int) int {
|
||||
return price * percentage / 100
|
||||
}
|
||||
|
||||
// 生成一个不重复的临时ID
|
||||
func genFakeID1() int64 {
|
||||
return time.Now().UnixNano() / 1000000
|
||||
}
|
||||
|
||||
func GenFakeID() int64 {
|
||||
return genFakeID1() * 2
|
||||
}
|
||||
|
||||
func IsFakeID(id int64) bool {
|
||||
if id == 0 {
|
||||
return true
|
||||
}
|
||||
multiple := id / genFakeID1()
|
||||
return multiple == 2 || multiple == 3
|
||||
}
|
||||
|
||||
@@ -31,3 +31,18 @@ func TestGetPolygonFromCircle(t *testing.T) {
|
||||
}
|
||||
t.Log(points)
|
||||
}
|
||||
|
||||
func TestFakeID(t *testing.T) {
|
||||
id := GenFakeID()
|
||||
if !IsFakeID(id) {
|
||||
t.Fatalf("wrong result for id:%d", id)
|
||||
}
|
||||
id = 0
|
||||
if !IsFakeID(id) {
|
||||
t.Fatalf("wrong result for id:%d", id)
|
||||
}
|
||||
id = 23424234
|
||||
if IsFakeID(id) {
|
||||
t.Fatalf("wrong result for id:%d", id)
|
||||
}
|
||||
}
|
||||
|
||||
22
business/jxutils/jxutils_reflect.go
Normal file
22
business/jxutils/jxutils_reflect.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package jxutils
|
||||
|
||||
import "reflect"
|
||||
|
||||
func CheckAndGetStructValue(item interface{}) *reflect.Value {
|
||||
value := reflect.ValueOf(item)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
} else {
|
||||
panic("item ust be ptr type")
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func GetObjFieldByName(obj interface{}, fieldName string) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(obj)).FieldByName(fieldName).Interface()
|
||||
}
|
||||
|
||||
func SetObjFieldByName(obj interface{}, fieldName string, value interface{}) {
|
||||
refValue := CheckAndGetStructValue(obj)
|
||||
refValue.FieldByName(fieldName).Set(reflect.ValueOf(value))
|
||||
}
|
||||
41
business/jxutils/jxutils_reflect_test.go
Normal file
41
business/jxutils/jxutils_reflect_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package jxutils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.rosy.net.cn/baseapi/utils"
|
||||
)
|
||||
|
||||
func TestObjFieldByName(t *testing.T) {
|
||||
kk := struct {
|
||||
ID int8
|
||||
Name string
|
||||
}{
|
||||
ID: 1,
|
||||
Name: "hello",
|
||||
}
|
||||
if GetObjFieldByName(kk, "ID").(int8) != 1 {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
if GetObjFieldByName(kk, "Name").(string) != "hello" {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
|
||||
pKK := &kk
|
||||
if GetObjFieldByName(pKK, "ID").(int8) != 1 {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
if GetObjFieldByName(pKK, "Name").(string) != "hello" {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
|
||||
SetObjFieldByName(pKK, "ID", int8(100))
|
||||
SetObjFieldByName(pKK, "Name", "world")
|
||||
if GetObjFieldByName(pKK, "ID").(int8) != 100 {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
if GetObjFieldByName(pKK, "Name").(string) != "world" {
|
||||
t.Fatal("value is not ok")
|
||||
}
|
||||
t.Log(utils.Format4Output(pKK, false))
|
||||
}
|
||||
@@ -30,16 +30,6 @@ func NormalMakeMapByFieldList(mapData map[string]interface{}, fields []string, u
|
||||
return retVal
|
||||
}
|
||||
|
||||
func checkAndGetStructValue(item interface{}) *reflect.Value {
|
||||
value := reflect.ValueOf(item)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
} else {
|
||||
panic("item ust be ptr type")
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func WrapAddIDCULEntity(item interface{}, lastOperator string) interface{} {
|
||||
now := time.Now()
|
||||
if mapData, ok := item.(map[string]interface{}); ok {
|
||||
@@ -48,7 +38,7 @@ func WrapAddIDCULEntity(item interface{}, lastOperator string) interface{} {
|
||||
mapData[model.FieldUpdatedAt] = now
|
||||
mapData[model.FieldLastOperator] = lastOperator
|
||||
} else {
|
||||
value := checkAndGetStructValue(item)
|
||||
value := jxutils.CheckAndGetStructValue(item)
|
||||
nowValue := reflect.ValueOf(now)
|
||||
value.FieldByName(model.FieldID).SetInt(0)
|
||||
value.FieldByName(model.FieldCreatedAt).Set(nowValue)
|
||||
@@ -62,7 +52,7 @@ func WrapAddIDCULDEntity(item interface{}, lastOperator string) interface{} {
|
||||
if mapData, ok := item.(map[string]interface{}); ok {
|
||||
mapData[model.FieldDeletedAt] = utils.DefaultTimeValue
|
||||
} else {
|
||||
value := checkAndGetStructValue(item)
|
||||
value := jxutils.CheckAndGetStructValue(item)
|
||||
value.FieldByName(model.FieldDeletedAt).Set(reflect.ValueOf(utils.DefaultTimeValue))
|
||||
}
|
||||
return WrapAddIDCULEntity(item, lastOperator)
|
||||
@@ -74,7 +64,7 @@ func WrapUpdateULEntity(item interface{}, lastOperator string) interface{} {
|
||||
mapData[model.FieldUpdatedAt] = now
|
||||
mapData[model.FieldLastOperator] = lastOperator
|
||||
} else {
|
||||
value := checkAndGetStructValue(item)
|
||||
value := jxutils.CheckAndGetStructValue(item)
|
||||
nowValue := reflect.ValueOf(now)
|
||||
value.FieldByName(model.FieldUpdatedAt).Set(nowValue)
|
||||
value.FieldByName(model.FieldLastOperator).SetString(lastOperator)
|
||||
|
||||
@@ -32,13 +32,17 @@ type DeliveryHandler struct {
|
||||
|
||||
func init() {
|
||||
curDeliveryHandler = new(DeliveryHandler)
|
||||
scheduler.CurrentScheduler.RegisterDeliveryPlatform(model.VendorIDDada, curDeliveryHandler, true)
|
||||
scheduler.CurrentScheduler.RegisterDeliveryPlatform(curDeliveryHandler, true)
|
||||
}
|
||||
|
||||
func OnWaybillMsg(msg *dadaapi.CallbackMsg) (retVal *dadaapi.CallbackResponse) {
|
||||
return curDeliveryHandler.OnWaybillMsg(msg)
|
||||
}
|
||||
|
||||
func (c *DeliveryHandler) GetVendorID() int {
|
||||
return model.VendorIDDada
|
||||
}
|
||||
|
||||
func (c *DeliveryHandler) OnWaybillMsg(msg *dadaapi.CallbackMsg) (retVal *dadaapi.CallbackResponse) {
|
||||
jxutils.CallMsgHandler(func() {
|
||||
retVal = c.onWaybillMsg(msg)
|
||||
|
||||
@@ -34,7 +34,11 @@ type DeliveryHandler struct {
|
||||
|
||||
func init() {
|
||||
curDeliveryHandler = new(DeliveryHandler)
|
||||
scheduler.CurrentScheduler.RegisterDeliveryPlatform(model.VendorIDMTPS, curDeliveryHandler, true)
|
||||
scheduler.CurrentScheduler.RegisterDeliveryPlatform(curDeliveryHandler, true)
|
||||
}
|
||||
|
||||
func (c *DeliveryHandler) GetVendorID() int {
|
||||
return model.VendorIDMTPS
|
||||
}
|
||||
|
||||
func OnWaybillMsg(msg *mtpsapi.CallbackOrderMsg) (retVal *mtpsapi.CallbackResponse) {
|
||||
|
||||
@@ -77,6 +77,7 @@ type IPurchasePlatformHandler interface {
|
||||
|
||||
SyncStoresSkus(db *dao.DaoDB, storeIDs []int, skuIDs []int, isSync bool, userName string) (err error)
|
||||
|
||||
GetVendorID() int
|
||||
GetFieldIDName() string
|
||||
GetFieldSyncStatusName() string
|
||||
}
|
||||
@@ -111,6 +112,8 @@ type ISingleStoreHandler interface {
|
||||
type IDeliveryPlatformHandler interface {
|
||||
CreateWaybill(order *model.GoodsOrder, policy func(deliveryFee, addFee int64) error) (bill *model.Waybill, err error)
|
||||
CancelWaybill(bill *model.Waybill, cancelReasonID int, cancelReason string) (err error)
|
||||
|
||||
GetVendorID() int
|
||||
}
|
||||
|
||||
func Init(curOrderManager IOrderManager) {
|
||||
|
||||
@@ -16,7 +16,7 @@ type PurchaseHandler struct {
|
||||
|
||||
func init() {
|
||||
curPurchaseHandler = new(PurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(model.VendorIDEBAI, curPurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(curPurchaseHandler)
|
||||
}
|
||||
|
||||
func EbaiBusStatus2JxStatus(ebaiStatus int) int {
|
||||
@@ -25,3 +25,7 @@ func EbaiBusStatus2JxStatus(ebaiStatus int) int {
|
||||
}
|
||||
return model.StoreStatusOpened
|
||||
}
|
||||
|
||||
func (p *PurchaseHandler) GetVendorID() int {
|
||||
return model.VendorIDEBAI
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ type PurchaseHandler struct {
|
||||
|
||||
func init() {
|
||||
curPurchaseHandler = new(PurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(model.VendorIDELM, curPurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(curPurchaseHandler)
|
||||
}
|
||||
|
||||
func (c *PurchaseHandler) GetVendorID() int {
|
||||
return model.VendorIDELM
|
||||
}
|
||||
|
||||
func OnCallbackMsg(msg *elmapi.CallbackMsg) (retVal *elmapi.CallbackResponse) {
|
||||
|
||||
@@ -17,7 +17,11 @@ type PurchaseHandler struct {
|
||||
|
||||
func init() {
|
||||
curPurchaseHandler = new(PurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(model.VendorIDJD, curPurchaseHandler)
|
||||
scheduler.CurrentScheduler.RegisterPurchasePlatform(curPurchaseHandler)
|
||||
}
|
||||
|
||||
func (c *PurchaseHandler) GetVendorID() int {
|
||||
return model.VendorIDJD
|
||||
}
|
||||
|
||||
func OnOrderMsg(msg *jdapi.CallbackOrderMsg) (retVal *jdapi.CallbackResponse) {
|
||||
|
||||
Reference in New Issue
Block a user