- 清理三个平台的门店商品批处理操作,部分失败返回失败条目

This commit is contained in:
gazebo
2019-07-21 16:08:37 +08:00
parent 0298f7de71
commit 414c359200
12 changed files with 597 additions and 179 deletions

View File

@@ -2,8 +2,11 @@ package ebaiapi
import (
"fmt"
"regexp"
"strings"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
)
@@ -24,6 +27,10 @@ const (
MaxStoreSkuBatchSize = 100 // sku.offline, sku.online, sku.price.update.batch和sku.stock.update.batch这些批量操作的最大值
)
var (
shopSkuBatchFailedSkuReg = regexp.MustCompile(`请核查以下sku(\{.*\})`)
)
type CategoryInfo struct {
CategoryID int64 `json:"category_id"`
Name string `json:"name"`
@@ -95,10 +102,112 @@ type PageDataInfo struct {
List []*SkuInfo `json:"List"`
}
func genSkuIDParams(skuID int64, customSkuID, upc string) map[string]interface{} {
type ShopSkuPriceUpdateResponseItem struct {
SkuID int64 `json:"sku_id"`
Price int `json:"price"`
ErrorNo int `json:"error_no,omitempty"`
ErrorMsg string `json:"error_msg,omitempty"`
}
type ShopSkuPriceUpdateResponse struct {
FailedList []*ShopSkuPriceUpdateResponseItem `json:"failed_list"`
SuccessList []*ShopSkuPriceUpdateResponseItem `json:"success_list"`
}
const (
SkuIDTypeUnknown = 0
SkuIDTypeSkuID = 1
SkuIDTypeCustomSkuID = 2
SkuIDTypeUpc = 3
)
var (
priceUpdateKeyIDMap = map[int]string{
SkuIDTypeSkuID: "skuid_price",
SkuIDTypeCustomSkuID: "custom_sku_id",
SkuIDTypeUpc: "upc_price",
}
stockUpdateKeyIDMap = map[int]string{
SkuIDTypeSkuID: "skuid_stocks",
SkuIDTypeCustomSkuID: "custom_sku_id",
SkuIDTypeUpc: "upc_stocks",
}
)
type ShopSkuInfo struct {
SkuID int64
CustomSkuID string
Upc string
SalePrice int64
MarketPrice int64
Stock int
}
type ShopSkuInfoList []*ShopSkuInfo
func (v *ShopSkuInfo) GuessIDType() int {
if v.SkuID > 0 {
return SkuIDTypeSkuID
} else if v.CustomSkuID != "" {
return SkuIDTypeCustomSkuID
}
return SkuIDTypeUpc
}
func (v *ShopSkuInfo) ToSkuID(skuIDType int) (str string) {
if skuIDType == SkuIDTypeUnknown {
skuIDType = v.GuessIDType()
}
if skuIDType == SkuIDTypeSkuID {
str = utils.Int64ToStr(v.SkuID)
} else if skuIDType == SkuIDTypeCustomSkuID {
str = v.CustomSkuID
} else {
str = v.Upc
}
return str
}
func (v *ShopSkuInfo) PriceString(skuIDType int) (str string) {
str = v.ToSkuID(skuIDType) + ":" + utils.Int64ToStr(v.SalePrice)
if v.MarketPrice > 0 {
str += "," + utils.Int64ToStr(v.MarketPrice)
}
return str
}
func (v *ShopSkuInfo) StockString(skuIDType int) (str string) {
str = v.ToSkuID(skuIDType) + ":" + utils.Int2Str(v.Stock)
return str
}
func (l ShopSkuInfoList) PriceString(skuIDType int) (str string) {
if len(l) > 0 {
strList := make([]string, len(l))
for k, v := range l {
strList[k] = v.PriceString(skuIDType)
}
str = strings.Join(strList, ";")
}
return str
}
func (l ShopSkuInfoList) StockString(skuIDType int) (str string) {
if len(l) > 0 {
strList := make([]string, len(l))
for k, v := range l {
strList[k] = v.StockString(skuIDType)
}
str = strings.Join(strList, ";")
}
return str
}
func genSkuIDParams(skuIDstr string, customSkuID, upc string) map[string]interface{} {
params := map[string]interface{}{}
if skuID != 0 {
params[KeySkuID] = skuID
if skuIDstr != "" {
params[KeySkuID] = skuIDstr
} else if customSkuID != "" {
params[KeyCustomSkuID] = customSkuID
} else if upc != "" {
@@ -216,100 +325,123 @@ func (a *API) SkuUpdate(shopID string, ebaiSkuID int64, params map[string]interf
return 0, err
}
func (a *API) SkuDelete(shopID, skuIDsStr string) (err error) {
params := map[string]interface{}{
KeyShopID: shopID,
KeySkuID: skuIDsStr,
}
_, err = a.AccessAPI("sku.delete", params)
return err
func intIDs2Str(intIDs []int64) (str string) {
return strings.Join(utils.Int64Slice2String(intIDs), ",")
}
func (a *API) SkuDeleteByCustomIDs(shopID, customSkuIDsStr string) (err error) {
params := map[string]interface{}{
KeyShopID: shopID,
KeyCustomSkuID: customSkuIDsStr,
}
_, err = a.AccessAPI("sku.delete", params)
return err
func strIDs2Str(strIDs []string) (str string) {
return strings.Join(strIDs, ",")
}
func (a *API) SkuOnline(shopID, skuIDsStr string) (err error) {
params := map[string]interface{}{
KeyShopID: shopID,
KeySkuID: skuIDsStr,
func handleShopSkuBatchResult(result *ResponseResult) (failedSkuIDs []int64, err error) {
if dataStr, ok := result.Data.(string); ok && dataStr != "" {
matchList := shopSkuBatchFailedSkuReg.FindStringSubmatch(dataStr)
if len(matchList) == 2 {
var failedMap map[string]interface{}
if err = utils.UnmarshalUseNumber([]byte(matchList[1]), &failedMap); err == nil && len(failedMap) > 0 {
for _, v := range failedMap {
if vStr, ok := v.(string); ok {
failedSkuIDs = append(failedSkuIDs, utils.Str2Int64WithDefault(vStr, 0))
} else {
failedSkuIDs = append(failedSkuIDs, utils.Interface2Int64WithDefault(v, 0))
}
}
}
}
}
_, err = a.AccessAPI("sku.online", params)
return err
return failedSkuIDs, err
}
// 文档上说支持custom_sku_id但实际好像只支持skuid
func (a *API) SkuDelete(shopID string, skuIDs []int64, customSkuDs []string) (failedSkuIDs []int64, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), "")
params[KeyShopID] = shopID
result, err := a.AccessAPI("sku.delete", params)
if err == nil {
failedSkuIDs, err = handleShopSkuBatchResult(result)
}
return failedSkuIDs, err
}
func (a *API) SkuOnline(shopID string, skuIDs []int64, customSkuDs, upcs []string) (failedSkuIDs []int64, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs))
params[KeyShopID] = shopID
result, err := a.AccessAPI("sku.online", params)
if err == nil {
failedSkuIDs, err = handleShopSkuBatchResult(result)
}
return failedSkuIDs, err
}
func (a *API) SkuOnlineOne(shopID string, skuID int64, customSkuID, upc string) (err error) {
params := genSkuIDParams(skuID, customSkuID, upc)
params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc)
params[KeyShopID] = shopID
_, err = a.AccessAPI("sku.online.one", params)
return err
}
func (a *API) SkuOffline(shopID, skuIDsStr string) (err error) {
params := map[string]interface{}{
KeyShopID: shopID,
KeySkuID: skuIDsStr,
func (a *API) SkuOffline(shopID string, skuIDs []int64, customSkuDs, upcs []string) (failedSkuIDs []int64, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs))
params[KeyShopID] = shopID
result, err := a.AccessAPI("sku.offline", params)
if err == nil {
failedSkuIDs, err = handleShopSkuBatchResult(result)
}
_, err = a.AccessAPI("sku.offline", params)
return err
return failedSkuIDs, err
}
func (a *API) SkuOfflineOne(shopID string, skuID int64, customSkuID, upc string) (err error) {
params := genSkuIDParams(skuID, customSkuID, upc)
params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc)
params[KeyShopID] = shopID
_, err = a.AccessAPI("sku.offline.one", params)
return err
}
func genSkuPriceParams(isPrice bool, skuPriceStr, customSkuPriceStr, upcPriceStr string) map[string]interface{} {
params := map[string]interface{}{}
skuKey := "skuid_price"
upcKey := "upc_price"
if !isPrice {
skuKey = "skuid_stocks"
upcKey = "upc_stocks"
// 此函数在部分失败时会返回错误全部成功时不会返回明细其它ShopSku批处理操作的的则在部分失败时会返回成功
func (a *API) SkuPriceUpdateBatch(shopID string, priceList ShopSkuInfoList, skuIDType int) (updateResponse *ShopSkuPriceUpdateResponse, err error) {
params := map[string]interface{}{
KeyShopID: shopID,
priceUpdateKeyIDMap[skuIDType]: priceList.PriceString(skuIDType),
}
if skuPriceStr != "" {
params[skuKey] = skuPriceStr
} else if skuPriceStr != "" {
params["custom_sku_id"] = customSkuPriceStr
} else if upcPriceStr != "" {
params[upcKey] = upcPriceStr
} else {
panic("skuPriceStr, customSkuPriceStr and upcPriceStr are all empty!")
}
return params
}
func (a *API) SkuPriceUpdateBatch(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) {
params := genSkuPriceParams(true, skuPriceStr, customSkuPriceStr, upcPriceStr)
params[KeyShopID] = shopID
_, err = a.AccessAPI("sku.price.update.batch", params)
return err
if err != nil {
// 文档上说的详情在data中但其实是在error中...
if errExt, ok := err.(*utils.ErrorWithCode); ok {
baseapi.SugarLogger.Debug(errExt.ErrMsg())
utils.UnmarshalUseNumber([]byte(errExt.ErrMsg()), &updateResponse)
}
}
return updateResponse, err
}
func (a *API) SkuPriceUpdateOne(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) {
params := genSkuPriceParams(true, skuPriceStr, customSkuPriceStr, upcPriceStr)
params[KeyShopID] = shopID
func (a *API) SkuPriceUpdateOne(shopID string, priceInfo *ShopSkuInfo) (err error) {
skuIDType := priceInfo.GuessIDType()
params := map[string]interface{}{
KeyShopID: shopID,
priceUpdateKeyIDMap[skuIDType]: priceInfo.PriceString(skuIDType),
}
_, err = a.AccessAPI("sku.price.update.one", params)
return err
}
func (a *API) SkuStockUpdateBatch(shopID, skuStockStr, customSkuStockStr, upcStockStr string) (err error) {
params := genSkuPriceParams(false, skuStockStr, customSkuStockStr, upcStockStr)
params[KeyShopID] = shopID
_, err = a.AccessAPI("sku.stock.update.batch", params)
return err
func (a *API) SkuStockUpdateBatch(shopID string, stockList ShopSkuInfoList, skuIDType int) (failedSkuIDs []int64, err error) {
params := map[string]interface{}{
KeyShopID: shopID,
stockUpdateKeyIDMap[skuIDType]: stockList.PriceString(skuIDType),
}
result, err := a.AccessAPI("sku.stock.update.batch", params)
if err == nil {
failedSkuIDs, err = handleShopSkuBatchResult(result)
}
return failedSkuIDs, err
}
func (a *API) SkuStockUpdateOne(shopID, skuStockStr, customSkuStockStr, upcStockStr string) (err error) {
params := genSkuPriceParams(false, skuStockStr, customSkuStockStr, upcStockStr)
params[KeyShopID] = shopID
func (a *API) SkuStockUpdateOne(shopID string, stockInfo *ShopSkuInfo) (err error) {
skuIDType := stockInfo.GuessIDType()
params := map[string]interface{}{
KeyShopID: shopID,
stockUpdateKeyIDMap[skuIDType]: stockInfo.PriceString(skuIDType),
}
_, err = a.AccessAPI("sku.stock.update.one", params)
return err
}

View File

@@ -16,7 +16,7 @@ func TestShopCategoryCreate(t *testing.T) {
}
func TestShopCategoryGet(t *testing.T) {
result, err := api.ShopCategoryGet(testShopID)
result, err := api.ShopCategoryGet("102493")
if err != nil {
t.Fatal(err)
} else {
@@ -48,8 +48,8 @@ func TestSkuGetItemsByCategoryId(t *testing.T) {
}
func TestSkuList(t *testing.T) {
result, err := api.SkuList(testShopID, &SkuListParams{
SkuID: 15579787500720732,
result, err := api.SkuList("102023", &SkuListParams{
// SkuID: 15579787500720732,
})
if err != nil {
t.Fatal(err)
@@ -107,23 +107,82 @@ func TestSkuUpdate(t *testing.T) {
}
}
func TestSkuDelete(t *testing.T) {
err := api.SkuDelete(testShopID, "153922044227304")
if err != nil {
t.Fatal(err)
}
}
func TestSkuDeleteByCustomIDs(t *testing.T) {
err := api.SkuDeleteByCustomIDs(testShopID, "17")
if err != nil {
t.Fatal(err)
}
}
func TestSkuShopCategoryMap(t *testing.T) {
err := api.SkuShopCategoryMap(testShopID, 15378849314129969, "153760472317166")
if err != nil {
t.Fatal(err)
}
}
func TestSkuDelete(t *testing.T) {
failedSkuIDs, err := api.SkuDelete(testShopID, []int64{12345678, 12423432}, nil)
if err != nil {
// t.Fatal(err)
}
t.Log(utils.Format4Output(failedSkuIDs, false))
}
func TestSkuOnline(t *testing.T) {
failedSkuIDs, err := api.SkuOnline(testShopID, []int64{156369111807787, 12345678, 12423432}, nil, nil)
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(failedSkuIDs, false))
}
func TestSkuOnlineOne(t *testing.T) {
err := api.SkuOnlineOne(testShopID, 13211, "", "")
if err == nil {
t.Fatal("应该要报错才对")
}
}
func TestSkuOffline(t *testing.T) {
failedSkuIDs, err := api.SkuOffline(testShopID, []int64{156291398007698, 12345678, 12423432}, nil, nil)
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(failedSkuIDs, false))
}
func TestSkuPriceUpdateBatch(t *testing.T) {
failedSkuIDs, err := api.SkuPriceUpdateBatch(testShopID, ShopSkuInfoList{
&ShopSkuInfo{
SkuID: 156369111807787,
SalePrice: 100,
},
&ShopSkuInfo{
SkuID: 156369111707770,
SalePrice: 100,
},
&ShopSkuInfo{
SkuID: 12321,
SalePrice: 100,
},
}, SkuIDTypeSkuID)
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(failedSkuIDs, false))
}
func TestSkuStockUpdateBatch(t *testing.T) {
failedSkuIDs, err := api.SkuStockUpdateBatch(testShopID, ShopSkuInfoList{
&ShopSkuInfo{
SkuID: 156291398007698,
Stock: 2,
},
&ShopSkuInfo{
SkuID: 13121231,
Stock: 2,
},
&ShopSkuInfo{
SkuID: 12321,
Stock: 2,
},
}, SkuIDTypeSkuID)
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(failedSkuIDs, false))
}

View File

@@ -207,28 +207,23 @@ func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal
}
func genNoPageResultParser(codeKey, msgKey, resultKey, okCode string) func(data map[string]interface{}) (interface{}, error) {
return func(data map[string]interface{}) (interface{}, error) {
return func(data map[string]interface{}) (innerData interface{}, err error) {
rawInnerCode, ok := data[codeKey]
if !ok {
panic(fmt.Sprintf("genNoPageResultParser codeKey %s can not be found in result:%v", codeKey, data))
}
innerCode := forceInnerCode2Str(rawInnerCode)
errMsg := formatErrorMsg(data[msgKey])
if innerCode == okCode && errMsg != "调用订单中心修改承运商接口失败!" { // todo 京东的order/modifySellerDelivery在失败时也返回成功code临时处理一下
if resultKey != "" {
if innerData, ok := data[resultKey]; ok {
return innerData, nil
}
baseapi.SugarLogger.Warnf("genNoPageResultParser resultKey %s can not be found in result:%v", resultKey, utils.Format4Output(data, false))
return nil, nil // 容错
// panic(fmt.Sprintf("genNoPageResultParser resultKey %s can not be found in result:%v", resultKey, data))
if resultKey != "" {
innerData, _ = data[resultKey]
}
if innerCode != okCode {
errMsg := formatErrorMsg(data[msgKey])
if innerCode == ResponseInnerCodePartialFailed {
errMsg += ", " + utils.Format4Output(innerData, true)
}
return nil, nil
err = utils.NewErrorCode(errMsg, innerCode, 1)
}
if innerCode == ResponseInnerCodePartialFailed {
errMsg += ", " + utils.Format4Output(data[resultKey], true)
}
return nil, utils.NewErrorCode(errMsg, innerCode, 1)
return innerData, err
}
}

View File

@@ -187,3 +187,12 @@ func TestUpdateSpu(t *testing.T) {
t.Fatal(err)
}
}
func TestUpdateSku(t *testing.T) {
_, err := api.UpdateSku("27379", map[string]interface{}{
"upc": "ttld20190712",
})
if err != nil {
t.Fatal(err)
}
}

View File

@@ -2,7 +2,6 @@ package jdapi
import (
"errors"
"fmt"
"git.rosy.net.cn/baseapi/utils"
)
@@ -63,22 +62,23 @@ type QueryStockResponse struct {
}
type UpdateVendibilityResponse struct {
Code int `json:"code"`
CurrentQty int `json:"currentQty"`
LockQty int `json:"lockQty"`
Msg string `json:"msg"`
OrderQty int `json:"orderQty"`
OutSkuID string `json:"outSkuId"`
SkuID int64 `json:"skuId"`
UsableQty int `json:"usableQty"`
Vendibility int `json:"vendibility"`
}
type StoreSkuBatchUpdateResponse struct {
OutSkuID string `json:"outSkuId"`
Code int `json:"code"`
Msg string `json:"msg"`
SkuID int64 `json:"skuId"`
CurrentQty int `json:"currentQty"`
LockQty int `json:"lockQty"`
OrderQty int `json:"orderQty"`
UsableQty int `json:"usableQty"`
Vendibility int `json:"vendibility"`
}
type StoreSkuBatchUpdateResponse struct {
OutSkuID string `json:"outSkuId" json2:"outSkuId"`
Code int `json:"code" json2:"errorCode"`
Msg string `json:"msg" json2:"errorMessage"`
// UpdateVendibility会返回以下字段
SkuID int64 `json:"skuId"`
StationNo string `json:"stationNo"`
@@ -97,10 +97,7 @@ type StoreSkuBatchUpdateResponse struct {
// 根据商家商品编码和商家门店编码批量修改门店价格接口
// https://opendj.jd.com/staticnew/widgets/resources.html?groupid=205&apiid=fcbf346648a54d03b92dec8fa62ea643
func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceInfoList []*SkuPriceInfo) ([]map[string]interface{}, error) {
if (outStationNo == "" && stationNo == "") || (outStationNo != "" && stationNo != "") {
return nil, errors.New("outStationNo and stationNo can not all be empty or have value")
}
func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceInfoList []*SkuPriceInfo) (responseList []*StoreSkuBatchUpdateResponse, err error) {
jdParams := map[string]interface{}{
"skuPriceInfoList": skuPriceInfoList,
}
@@ -109,11 +106,14 @@ func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceI
} else {
jdParams["stationNo"] = stationNo
}
result, err := a.AccessAPINoPage("venderprice/updateStationPrice", jdParams, nil, nil, nil)
if err == nil && result != nil {
return utils.Slice2MapSlice(result.([]interface{})), nil
result, err := a.AccessAPINoPage("venderprice/updateStationPrice", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "0"))
if result != nil {
var err2 error
if responseList, err2 = a.handleBatchOpResult(len(skuPriceInfoList), result, "json2"); err2 != nil && err == nil {
err = err2
}
}
return nil, err
return responseList, err
}
// 根据到家商品编码和到家门店编码修改门店价格接口
@@ -146,16 +146,16 @@ func (a *API) GetStationInfoList(stationNo string, skuIds []int64) (priceInfo []
return priceInfo, err
}
func (a *API) handleBatchOpResult(batchCount int, result interface{}) (responseList []*StoreSkuBatchUpdateResponse, err error) {
if err = utils.Map2StructByJson(result, &responseList, true); err == nil {
func (a *API) handleBatchOpResult(batchCount int, result interface{}, tagName string) (responseList []*StoreSkuBatchUpdateResponse, err error) {
if err = utils.Map2Struct(result, &responseList, true, tagName); err == nil {
var failedList []*StoreSkuBatchUpdateResponse
for _, v := range responseList {
if v.Code != 0 {
failedList = append(failedList, v)
}
}
if batchCount == len(failedList) {
err = fmt.Errorf(string(utils.MustMarshal(failedList)))
if len(failedList) >= batchCount {
err = utils.NewErrorCode(string(utils.MustMarshal(failedList)), ResponseCodeAccessFailed, 1) // 此错误基本用不到
} else if len(failedList) > 0 { // 部分失败
err = utils.NewErrorCode(string(utils.MustMarshal(failedList)), ResponseInnerCodePartialFailed, 1)
}
@@ -179,8 +179,11 @@ func (a *API) BatchUpdateCurrentQtys(outStationNo, stationNo string, skuStockLis
jdParams["stationNo"] = stationNo
}
result, err := a.AccessAPINoPage("stock/batchUpdateCurrentQtys", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"))
if err == nil && result != nil {
responseList, err = a.handleBatchOpResult(len(skuStockList), result)
if result != nil {
var err2 error
if responseList, err2 = a.handleBatchOpResult(len(skuStockList), result, ""); err2 != nil && err == nil {
err = err2
}
}
return responseList, err
}
@@ -220,16 +223,19 @@ func (a *API) UpdateCurrentQty(stationNo string, skuID int64, currentQty int) er
// 根据到家商品编码和到家门店编码批量修改门店商品可售状态接口
// https://opendj.jd.com/staticnew/widgets/resources.html?groupid=200&apiid=b783a508e2cf4aca94681e4eed9af5bc
// 尽量不用这个接口,用下面那个
// 尽量不用这个接口,用下面那个原因是这个不支持设置操作人BatchUpdateVendibility可以
func (a *API) UpdateVendibility(listBaseStockCenterRequest []*QueryStockRequest) (responseList []*StoreSkuBatchUpdateResponse, err error) {
jdParams := map[string]interface{}{
"listBaseStockCenterRequest": listBaseStockCenterRequest,
}
result, err := a.AccessAPINoPage("stock/updateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"))
if err == nil && result != nil {
responseList, err = a.handleBatchOpResult(len(listBaseStockCenterRequest), result)
if result != nil {
var err2 error
if responseList, err2 = a.handleBatchOpResult(len(listBaseStockCenterRequest), result, ""); err2 != nil && err == nil {
err = err2
}
}
return nil, err
return responseList, err
}
// 根据商家商品编码和门店编码批量修改门店商品可售状态接口
@@ -248,8 +254,11 @@ func (a *API) BatchUpdateVendibility(outStationNo, stationNo string, stockVendib
jdParams["stationNo"] = stationNo
}
result, err := a.AccessAPINoPage("stock/batchUpdateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"))
if err == nil && result != nil {
responseList, err = a.handleBatchOpResult(len(stockVendibilityList), result)
if result != nil {
var err2 error
if responseList, err2 = a.handleBatchOpResult(len(stockVendibilityList), result, ""); err2 != nil && err == nil {
err = err2
}
}
return responseList, err
}

View File

@@ -186,7 +186,7 @@ func (a *API) FullDiscountBatchSave(poiCode string, actInfo *FullDiscountActInfo
if actInfo.ActType == ActTypeSkuFullDiscount {
params["app_foods"] = string(utils.MustMarshal(actSkuList))
}
result, err := a.AccessAPI2("act/full/discount/batchsave", false, params, resultKeyMsg)
result, err := a.AccessAPI2("act/full/discount/batchsave", false, params, resultKeySuccessMsg)
if err == nil {
err = utils.UnmarshalUseNumber([]byte(result.(string)), &actIDList)
}
@@ -322,7 +322,7 @@ func (a *API) RetailDiscountBatchSave(poiCode string, actData []*RetailDiscountA
result, err := a.AccessAPI2("act/retail/discount/batchsave", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"act_data": string(utils.MustMarshal(actData)),
}, resultKeyMsg)
}, resultKeySuccessMsg)
if err == nil {
err = utils.UnmarshalUseNumber([]byte(result.(string)), &actResult)
}
@@ -394,7 +394,7 @@ func (a *API) InStoreCouponBatchSave(poiCode string, limitTime *LimitTime, coupo
KeyAppPoiCode: poiCode,
"limit_time": string(utils.MustMarshal(limitTime)),
"act_data": string(utils.MustMarshal(couponInfoList)),
}, resultKeyMsg)
}, resultKeySuccessMsg)
if err == nil {
err = utils.UnmarshalUseNumber([]byte(result.(string)), &couponResultList)
}

View File

@@ -33,8 +33,9 @@ const (
)
const (
resultKeyData = "data"
resultKeyMsg = "success_msg"
resultKeyData = "data"
resultKeyMsg = "msg"
resultKeySuccessMsg = "success_msg"
)
const (
GeneralMaxLimit = 200 // 大多数的API的批处理最大条数

View File

@@ -1,6 +1,7 @@
package mtwmapi
import (
"regexp"
"strings"
"git.rosy.net.cn/baseapi/utils"
@@ -14,6 +15,15 @@ const (
MaxBatchDeleteSize = 100 // retailCat/batchdelete/catandretail这个接口的批量最大值
)
const (
SellStatusOnline = 0 // 上架
SellStatusOffline = 1 // 下架
)
var (
retailBatchFailedSkuReg = regexp.MustCompile(`((?:\d+;)+)`)
)
type RetailCategoryInfo struct {
Name string `json:"name"`
Sequence int `json:"sequence"`
@@ -91,6 +101,11 @@ type AppFood struct {
ZhName string `json:"zh_name"`
}
type AppFoodResult struct {
AppFoodCode string `json:"app_food_code"`
ErrorMsg string `json:"error_msg"`
}
// 美团分类没有ID就以名字为唯一标识不论级别都必须不能重名
// name和originName的长度不能超过10个字符字符不是字节
// 创建一级分类originName为空name为新分类名secondaryName为空
@@ -168,28 +183,78 @@ func (a *API) RetailList(poiCode string, offset, limit int) (foodList []*AppFood
return foodList, err
}
func (a *API) RetailBatchInitData(poiCode string, foodDataList []map[string]interface{}) (err error) {
_, err = a.AccessAPI("retail/batchinitdata", false, map[string]interface{}{
func handleRetailBatchResult(result interface{}) (failedFoodList []*AppFoodResult, err error) {
if msg, ok := result.(string); ok && msg != "" {
err = utils.UnmarshalUseNumber([]byte(msg), &failedFoodList)
}
return failedFoodList, err
}
func handleRetailBatchResultByRegexp(result interface{}) (failedFoodList []*AppFoodResult, err error) {
if msg, ok := result.(string); ok && msg != "" {
findList := retailBatchFailedSkuReg.FindStringSubmatch(msg)
if len(findList) == 2 {
ids := strings.Split(strings.Trim(findList[1], ";"), ";")
if len(ids) > 0 {
failedFoodList = make([]*AppFoodResult, len(ids))
for k, v := range ids {
failedFoodList[k] = &AppFoodResult{
AppFoodCode: v,
ErrorMsg: "",
}
}
}
}
}
return failedFoodList, err
}
func (a *API) RetailBatchInitData(poiCode string, foodDataList []map[string]interface{}) (failedFoodList []*AppFoodResult, err error) {
result, err := a.AccessAPI2("retail/batchinitdata", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodDataList)),
})
return err
}, resultKeyMsg)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
func (a *API) RetailSkuPrice(poiCode string, foodData []*BareStoreFoodInfo) (err error) {
_, err = a.AccessAPI("retail/sku/price", false, map[string]interface{}{
func (a *API) RetailSkuPrice(poiCode string, foodData []*BareStoreFoodInfo) (failedFoodList []*AppFoodResult, err error) {
result, err := a.AccessAPI2("retail/sku/price", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
})
return err
}, resultKeyMsg)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
func (a *API) RetailSkuStock(poiCode string, foodData []*BareStoreFoodInfo) (err error) {
_, err = a.AccessAPI("retail/sku/stock", false, map[string]interface{}{
func (a *API) RetailSkuStock(poiCode string, foodData []*BareStoreFoodInfo) (failedFoodList []*AppFoodResult, err error) {
result, err := a.AccessAPI2("retail/sku/stock", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
})
return err
}, resultKeyMsg)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
// retail/sku/sellStatus在部分失败时会返回错误其它相应的批处理函数则会返回成功
func (a *API) RetailSkuSellStatus(poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (failedFoodList []*AppFoodResult, err error) {
_, err = a.AccessAPI2("retail/sku/sellStatus", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
"sell_status": sellStatus,
}, resultKeyMsg)
if err != nil {
if errExt, ok := err.(*utils.ErrorWithCode); ok {
failedFoodList, _ = handleRetailBatchResultByRegexp(errExt.ErrMsg())
}
}
return failedFoodList, err
}
func (a *API) RetailGet(poiCode, foodCode string) (food *AppFood, err error) {
@@ -216,24 +281,6 @@ func (a *API) RetailSkuSave(poiCode, foodCode string, standardSkus, unstandardSk
return err
}
func (a *API) RetailSkuSellStatus(poiCode string, foodData []map[string]interface{}, sellStatus int) (err error) {
_, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
"sell_status": sellStatus,
})
return err
}
func (a *API) RetailSkuSellStatus2(poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (err error) {
_, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
"sell_status": sellStatus,
})
return err
}
func (a *API) RetailDelete(poiCode, foodCode string) (err error) {
_, err = a.AccessAPI("retail/delete", false, map[string]interface{}{
KeyAppPoiCode: poiCode,

View File

@@ -82,13 +82,14 @@ func TestRetailDelete(t *testing.T) {
t.Fatal(err)
}
}
func TestRetailBatchInitData(t *testing.T) {
err := api.RetailBatchInitData("2464052", []map[string]interface{}{
failedFoods, err := api.RetailBatchInitData(testPoiCode, []map[string]interface{}{
map[string]interface{}{
"app_food_code": "23841",
"box_num": 0,
"box_price": 0,
"category_name": "豆制良品🌾🌾",
"category_name": "南北干货",
"description": "",
"is_sold_out": 0,
"min_order_count": 1,
@@ -104,6 +105,29 @@ func TestRetailBatchInitData(t *testing.T) {
"upc": "",
},
},
// "tag_id": "200000380",
"unit": "份",
},
map[string]interface{}{
"app_food_code": "23840",
"box_num": 0,
"box_price": 0,
"category_name": "南北干货",
"description": "",
"is_sold_out": 0,
"min_order_count": 1,
"name": "干腐竹约150g/份",
"picture": "http://image.jxc4.com/5f7fba025fc9348796039423c48ac3f5.jpg",
"price": 1000,
"skus": []map[string]interface{}{
map[string]interface{}{
"price": 1000,
"sku_id": "23840",
"spec": "150g",
"stock": "*",
"upc": "",
},
},
"tag_id": "200000380",
"unit": "份",
},
@@ -111,4 +135,97 @@ func TestRetailBatchInitData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(failedFoods, false))
}
func TestRetailSkuPrice(t *testing.T) {
result, err := api.RetailSkuPrice(testPoiCode, []*BareStoreFoodInfo{
&BareStoreFoodInfo{
AppFoodCode: "23841",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23841",
Price: "1.2",
},
},
},
&BareStoreFoodInfo{
AppFoodCode: "23840",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23840",
Price: "1.1",
},
},
},
})
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(result, false))
}
func TestRetailSkuStock(t *testing.T) {
result, err := api.RetailSkuStock(testPoiCode, []*BareStoreFoodInfo{
&BareStoreFoodInfo{
AppFoodCode: "23841",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23841",
Price: "1.2",
Stock: "123",
},
},
},
&BareStoreFoodInfo{
AppFoodCode: "23840",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23840",
Stock: "123",
},
},
},
})
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(result, false))
}
func TestRetailSkuSellStatus(t *testing.T) {
result, err := api.RetailSkuSellStatus(testPoiCode, []*BareStoreFoodInfo{
&BareStoreFoodInfo{
AppFoodCode: "23841",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23841",
Price: "1.2",
Stock: "123",
},
},
},
&BareStoreFoodInfo{
AppFoodCode: "23840",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "23840",
Stock: "123",
},
},
},
&BareStoreFoodInfo{
AppFoodCode: "2384999",
Skus: []*BareStoreSkuInfo{
&BareStoreSkuInfo{
SkuID: "2384999",
Stock: "123",
},
},
},
}, SellStatusOffline)
if err != nil {
t.Fatal(err)
}
t.Log(utils.Format4Output(result, false))
}

View File

@@ -183,15 +183,11 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.
func RebuildError(inErr error, bzParams map[string]interface{}, watchKeys []string) (outErr error) {
if inErr != nil {
if codeErr, ok := inErr.(*utils.ErrorWithCode); ok {
appendErrList := []string{}
for _, key := range watchKeys {
if bzParams[key] != nil {
appendErrList = append(appendErrList, fmt.Sprintf("[%s:%v]", key, bzParams[key]))
codeErr.AddPrefixMsg(fmt.Sprintf("[%s:%v]", key, bzParams[key]))
}
}
if len(appendErrList) > 0 {
inErr = utils.NewErrorCode(strings.Join(appendErrList, ",")+", "+codeErr.ErrMsg(), codeErr.Code())
}
}
}
return inErr

View File

@@ -7,10 +7,11 @@ import (
)
type ErrorWithCode struct {
level int
errMsg string
code string
intCode int
level int
prefixList []string
errMsg string
code string
intCode int
}
func NewErrorCode(errMsg, code string, level ...int) *ErrorWithCode {
@@ -31,7 +32,11 @@ func NewErrorIntCode(errMsg string, code int, level ...int) *ErrorWithCode {
}
func (e *ErrorWithCode) Error() string {
return fmt.Sprintf("%s level:%d, code:%s", e.errMsg, e.level, e.code)
fullErrMsg := e.ErrMsg()
if len(e.prefixList) > 0 {
fullErrMsg = strings.Join(e.prefixList, ",") + ", " + fullErrMsg
}
return fmt.Sprintf("%s level:%d, code:%s", fullErrMsg, e.Level(), e.Code())
}
func (e *ErrorWithCode) String() string {
@@ -54,6 +59,10 @@ func (e *ErrorWithCode) ErrMsg() string {
return e.errMsg
}
func (e *ErrorWithCode) AddPrefixMsg(prefix string) {
e.prefixList = append(e.prefixList, prefix)
}
func IsErrMatch(err error, strCode string, strList []string) (isMatch bool) {
if err != nil {
if codeErr, ok := err.(*ErrorWithCode); ok {

View File

@@ -247,6 +247,13 @@ func Int64ToStr(value int64) string {
return strconv.FormatInt(value, 10)
}
func Int64ToStrNoZero(value int64) string {
if value == 0 {
return ""
}
return strconv.FormatInt(value, 10)
}
func Int2Str(value int) string {
return strconv.Itoa(value)
}
@@ -469,11 +476,48 @@ func Struct2FlatMap(obj interface{}) map[string]interface{} {
}
// !!! 此函数好像不支持struct是内嵌结构的
func Map2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) {
func Map2Struct(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool, tagName string) (err error) {
if tagName == "" {
tagName = "json"
}
decoder, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
TagName: tagName,
Result: outObjAddr,
WeaklyTypedInput: weaklyTypedInput,
})
return decoder.Decode(inObj)
}
func Map2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) {
return Map2Struct(inObj, outObjAddr, weaklyTypedInput, "")
}
func Int64Slice2String(intList []int64) (outList []string) {
if len(intList) > 0 {
outList = make([]string, len(intList))
for k, v := range intList {
outList[k] = Int64ToStr(v)
}
}
return outList
}
func StringSlice2Int64(intList []string) (outList []int64) {
if len(intList) > 0 {
outList = make([]int64, len(intList))
for k, v := range intList {
outList[k] = Str2Int64WithDefault(v, 0)
}
}
return outList
}
func IntSlice2Int64(intList []int) (outList []int64) {
if len(intList) > 0 {
outList = make([]int64, len(intList))
for k, v := range intList {
outList[k] = int64(v)
}
}
return outList
}