Files
baseapi/platformapi/ebaiapi/shop_sku.go

588 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ebaiapi
import (
"fmt"
"regexp"
"strings"
"git.rosy.net.cn/baseapi/utils"
)
const (
SkuStatusOnline = 1 // 为上架
SkuStatusOffline = 0 // 为下架
SkuStatusDeleted = 2 // 为删除
)
const (
UPCTypeStandard = 1
UPCTypePrivate = 0
)
const (
MaxLeftNum = 99999
MaxSkuNameByteCount = 100 // skuname的最大字节数注意不是字符数超长饿百会报错{"data":"","errno":20200,"error":"invalid param:[name]...
MaxStoreSkuBatchSize = 100 // sku.offline, sku.online, sku.price.update.batch和sku.stock.update.batch这些批量操作的最大值
MaxSkuCatRank = 100000 // 商品排序优先级 最小值为1最大值为100000rank值较大的优先展示
MaxCatCatRank = 100000 // 自定义分类排序优先级 最小值为1最大值为100000rank值较大的优先展示
MaxSkuListPageSize = 100 //获取数量,1-100,默认20
)
var (
shopSkuBatchFailedSkuReg = regexp.MustCompile(`请核查以下sku(\{.*\})`)
)
type CategoryInfo struct {
CategoryID int64 `json:"category_id"`
Name string `json:"name"`
Rank int `json:"rank"` // 店内分类独有
Children []*CategoryInfo `json:"children"`
Level int `json:"level"`
}
type SkuListParams struct {
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
Upc string `json:"upc,omitempty"`
SkuID int64 `json:"sku_id,omitempty"`
CustomSkuID string `json:"custom_sku_id,omitempty"`
UpcType int `json:"upc_type,omitempty"`
GetUncate int `json:"get_uncate,omitempty"`
Delete int `json:"delete,omitempty"`
Enabled int `json:"enabled,omitempty"`
StartTime int `json:"start_time,omitempty"`
EndTime int `json:"end_time,omitempty"`
}
type SkuPhotoInfo struct {
IsMaster int `json:"is_master"`
URL string `json:"url"`
}
type SkuCustomCatInfo struct {
CustomCatID string `json:"custom_cat_id"`
CustomCatName string `json:"custom_cat_name"`
}
type SkuInfo struct {
CustomCatIDs string `json:"custom_cat_ids"`
CustomCatList []*SkuCustomCatInfo `json:"custom_cat_list"`
CustomSkuID string `json:"custom_sku_id"`
IsInActivity int `json:"is_in_activity"`
LeftNum int `json:"left_num"`
MarketPrice int `json:"market_price"`
Minimum int `json:"minimum"`
Name string `json:"name"`
NeedIce int `json:"need_ice"`
Photos []*SkuPhotoInfo `json:"photos"`
PreminusWeight int `json:"preminus_weight"`
PreparationTime string `json:"preparation_time"`
ProductionAddr1 string `json:"production_addr1"`
ProductionAddr2 string `json:"production_addr2"`
ProductionAddr3 string `json:"production_addr3"`
Rtf string `json:"rtf"`
SalePrice int64 `json:"sale_price"`
SaleStep string `json:"sale_step"`
SaleUnit string `json:"sale_unit"`
ShelfNumber string `json:"shelf_number"`
SkuID int64 `json:"sku_id"`
// SkuProperty []interface{} `json:"sku_property"`
Status int `json:"status"`
Summary string `json:"summary"`
Upc string `json:"upc"`
UpcType string `json:"upc_type"`
UpdateTime string `json:"update_time"`
Weight int `json:"weight"`
WeightFlag int `json:"weight_flag"`
}
type PageDataInfo struct {
Total int `json:"Total"`
Page int `json:"Page"`
Pages int `json:"Pages"`
List []*SkuInfo `json:"List"`
}
type BatchOpSkuResult struct {
SkuID int64 `json:"sku_id"`
Price int `json:"price,omitempty"`
ErrorNo int `json:"error_no,omitempty"`
ErrorMsg string `json:"error_msg,omitempty"`
}
type BatchOpResult struct {
FailedList []*BatchOpSkuResult `json:"failed_list"`
SuccessList []*BatchOpSkuResult `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 skuIDstr != "" {
params[KeySkuID] = skuIDstr
} else if customSkuID != "" {
params[KeyCustomSkuID] = customSkuID
} else if upc != "" {
params[KeyUPC] = upc
} else {
panic("skuID, customSkuID and upc are all no value!")
}
return params
}
// category相关的函数shop_custom_id可重
func (a *API) ShopCategoryCreate(shopID string, parentID int64, name string, rank int) (catID int64, err error) {
result, err := a.AccessAPI("sku.shop.category.create", map[string]interface{}{
KeyShopID: shopID,
"parent_category_id": parentID,
"name": name,
"rank": rank,
})
if err == nil {
return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})["category_id"])), nil
}
return 0, err
}
func (a *API) ShopCategoryGet(shopID string) (cats []*CategoryInfo, err error) {
result, err := a.AccessAPI("sku.shop.category.get", utils.Params2Map(KeyShopID, shopID))
if err == nil {
if inMap, ok := result.Data.(map[string]interface{}); ok { // fuck it
cats := interface2CatList(inMap["categorys"], 1)
return cats, nil
}
}
return nil, err
}
func (a *API) ShopCategoryUpdate(shopID string, categoryID int64, name string, rank int) (err error) {
_, err = a.AccessAPI("sku.shop.category.update", map[string]interface{}{
KeyShopID: shopID,
"category_id": categoryID,
"name": name,
"rank": rank,
})
if errWithCode, ok := err.(*utils.ErrorWithCode); ok {
if errWithCode.Level() == 0 && errWithCode.IntCode() == 1 { //忽略同名错误
err = nil
}
}
return err
}
func (a *API) ShopCategoryDelete(shopID string, categoryID int64) (err error) {
_, err = a.AccessAPI("sku.shop.category.delete", map[string]interface{}{
KeyShopID: shopID,
"category_id": categoryID,
})
return err
}
func (a *API) SkuGetItemsByCategoryId(shopID string, categoryID int64) (skus []map[string]interface{}, err error) {
result, err := a.AccessAPI("sku.getItemsByCategoryId", map[string]interface{}{
KeyShopID: shopID,
"category_id": categoryID,
})
if err == nil {
return utils.Slice2MapSlice(result.Data.([]interface{})), nil
}
return nil, err
}
// 此函数查不到结果是不会报错的
func (a *API) SkuList(shopID string, params *SkuListParams) (skuInfo *PageDataInfo, err error) {
paramMap := utils.Struct2FlatMap(params)
paramMap[KeyShopID] = shopID
result, err := a.AccessAPI("sku.list", paramMap)
if err == nil {
err = utils.Map2StructByJson(result.Data, &skuInfo, true)
}
return skuInfo, err
}
// 饿百商品名可以相同,不会报错
func (a *API) SkuCreate(trackInfo, shopID string, customSkuID int, params map[string]interface{}) (skuID int64, err error) {
defParams := map[string]interface{}{
KeyShopID: shopID,
KeyCustomSkuID: customSkuID,
}
if params["upc"] == nil {
defParams["upc_type"] = UPCTypePrivate
defParams["upc"] = "upc-" + utils.Int2Str(customSkuID)
}
if params["brand_id"] == nil {
defParams["brand_id"] = 0
}
if params["brand_name"] == nil {
defParams["brand_name"] = "无" // 很狗血的是,你还必须填个无才行。。。
}
params = utils.MergeMaps(params, defParams)
// baseapi.SugarLogger.Debugf(utils.Format4Output(params, false))
result, err := a.AccessAPI2("sku.create", params, trackInfo)
if err == nil {
return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})[KeySkuID])), nil
}
return 0, err
}
func (a *API) SkuUpdate(trackInfo, shopID string, ebaiSkuID int64, params map[string]interface{}) (skuID int64, err error) {
defParams := map[string]interface{}{
KeyShopID: shopID,
KeySkuID: ebaiSkuID,
}
result, err := a.AccessAPI2("sku.update", utils.MergeMaps(params, defParams), trackInfo)
if err == nil {
return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})[KeySkuID])), nil
}
return 0, err
}
func intIDs2Str(intIDs []int64) (str string) {
return strings.Join(utils.Int64Slice2String(intIDs), ",")
}
func strIDs2Str(strIDs []string) (str string) {
return strings.Join(strIDs, ",")
}
// 之前的处理方法
func handleShopSkuBatchResult(result *ResponseResult) (opResult *BatchOpResult, 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(dataStr), &failedMap); err == nil && len(failedMap) > 0 {
opResult = &BatchOpResult{}
for _, v := range failedMap {
var skuID int64
if vStr, ok := v.(string); ok {
skuID = utils.Str2Int64WithDefault(vStr, 0)
} else {
skuID = utils.Interface2Int64WithDefault(v, 0)
}
opResult.FailedList = append(opResult.FailedList, &BatchOpSkuResult{
SkuID: skuID,
})
}
}
}
}
return opResult, err
}
func handleShopSkuBatchErr(err error) (opResult *BatchOpResult, outErr error) {
outErr = err
if errExt, ok := err.(*utils.ErrorWithCode); ok {
if errExt.ErrMsg() != "" {
var data interface{}
if err2 := utils.UnmarshalUseNumber([]byte(errExt.ErrMsg()), &data); err2 == nil {
if err2 = utils.Map2StructByJson(data, &opResult, true); err2 == nil {
// 将以\u表示的字符串标准化
errExt.SetErrMsg(string(utils.MustMarshal(opResult)))
outErr = errExt
}
}
}
}
return opResult, outErr
}
// 门店商品批量修改可售价格库存的API,在2019/07/26之前除了SkuPriceUpdateBatch外其它API在部分失败时会返回成功
// 失败信息在data中但2019/07/26之后行为与SkuPriceUpdateBatch一致了即完全成功时返回成功没有失败成功明细
// 部分失败时返回失败详情在error中
// 另外需要注意的是SkuPriceUpdateBatch的失败明细中的skuID是int64但其它几个是string...
// 文档上说支持custom_sku_id但实际好像只支持skuid
func (a *API) SkuDelete(trackInfo, shopID string, skuIDs []int64, customSkuDs []string) (opResult *BatchOpResult, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), "")
params[KeyShopID] = shopID
result, err := a.AccessAPI2("sku.delete", params, trackInfo)
if err == nil {
opResult, err = handleShopSkuBatchResult(result)
} else {
opResult, err = handleShopSkuBatchErr(err)
}
return opResult, err
}
func (a *API) SkuOnline(trackInfo, shopID string, skuIDs []int64, customSkuDs, upcs []string) (opResult *BatchOpResult, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs))
params[KeyShopID] = shopID
result, err := a.AccessAPI2("sku.online", params, trackInfo)
if err == nil {
opResult, err = handleShopSkuBatchResult(result)
} else {
opResult, err = handleShopSkuBatchErr(err)
}
return opResult, err
}
func (a *API) SkuOnlineOne(trackInfo, shopID string, skuID int64, customSkuID, upc string) (err error) {
params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc)
params[KeyShopID] = shopID
_, err = a.AccessAPI2("sku.online.one", params, trackInfo)
return err
}
func (a *API) SkuOffline(trackInfo, shopID string, skuIDs []int64, customSkuDs, upcs []string) (opResult *BatchOpResult, err error) {
params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs))
params[KeyShopID] = shopID
result, err := a.AccessAPI2("sku.offline", params, trackInfo)
if err == nil {
opResult, err = handleShopSkuBatchResult(result)
} else {
opResult, err = handleShopSkuBatchErr(err)
}
return opResult, err
}
func (a *API) SkuOfflineOne(trackInfo, shopID string, skuID int64, customSkuID, upc string) (err error) {
params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc)
params[KeyShopID] = shopID
_, err = a.AccessAPI2("sku.offline.one", params, trackInfo)
return err
}
func (a *API) SkuPriceUpdateBatch(trackInfo, shopID string, priceList ShopSkuInfoList, skuIDType int) (opResult *BatchOpResult, err error) {
params := map[string]interface{}{
KeyShopID: shopID,
priceUpdateKeyIDMap[skuIDType]: priceList.PriceString(skuIDType),
}
result, err := a.AccessAPI2("sku.price.update.batch", params, trackInfo)
if err == nil {
opResult, err = handleShopSkuBatchResult(result)
} else {
opResult, err = handleShopSkuBatchErr(err)
}
return opResult, err
}
func (a *API) SkuPriceUpdateOne(trackInfo, shopID string, priceInfo *ShopSkuInfo) (err error) {
skuIDType := priceInfo.GuessIDType()
params := map[string]interface{}{
KeyShopID: shopID,
priceUpdateKeyIDMap[skuIDType]: priceInfo.PriceString(skuIDType),
}
_, err = a.AccessAPI2("sku.price.update.one", params, trackInfo)
return err
}
func (a *API) SkuStockUpdateBatch(trackInfo, shopID string, stockList ShopSkuInfoList, skuIDType int) (opResult *BatchOpResult, err error) {
params := map[string]interface{}{
KeyShopID: shopID,
stockUpdateKeyIDMap[skuIDType]: stockList.StockString(skuIDType),
}
result, err := a.AccessAPI2("sku.stock.update.batch", params, trackInfo)
if err == nil {
opResult, err = handleShopSkuBatchResult(result)
} else {
opResult, err = handleShopSkuBatchErr(err)
}
return opResult, err
}
func (a *API) SkuStockUpdateOne(trackInfo, shopID string, stockInfo *ShopSkuInfo) (err error) {
skuIDType := stockInfo.GuessIDType()
params := map[string]interface{}{
KeyShopID: shopID,
stockUpdateKeyIDMap[skuIDType]: stockInfo.StockString(skuIDType),
}
_, err = a.AccessAPI2("sku.stock.update.one", params, trackInfo)
return err
}
func (a *API) SkuShopCategoryMap(shopID string, skuID int64, customSkuID string, categoryID int64, rank int) (err error) {
params := map[string]interface{}{
KeyShopID: shopID,
KeyCategoryID: categoryID,
}
if skuID > 0 {
params[KeySkuID] = skuID
} else {
params[KeyCustomSkuID] = customSkuID
}
if rank > 0 {
params["rank"] = rank
}
_, err = a.AccessAPI("sku.shop.category.map", params)
return err
}
// 饿百的这个API有点怪虽然有shopID参数但返回的链接可以跨店使用
func (a *API) SkuUploadRTF(shopID, rtfDetail string) (rtfURL string, err error) {
params := map[string]interface{}{
KeyShopID: shopID,
"rtf_detail": rtfDetail,
}
result, err := a.AccessAPI("sku.uploadrtf", params)
if err == nil {
rtfURL = utils.Interface2String(result.Data.(map[string]interface{})["url"])
}
return rtfURL, err
}
func BuildRFTFromImgs(imgList ...string) string {
imgList2 := make([]string, len(imgList))
for index, img := range imgList {
imgList2[index] = fmt.Sprintf(`<img align="absmiddle" src="%s" alt="%s" />`, img, img)
}
return strings.Join(imgList2, "\n")
}
//
func interface2CatList(data interface{}, level int) (cats []*CategoryInfo) {
maps, ok := data.([]interface{})
if ok {
cats = make([]*CategoryInfo, len(maps))
for index, v := range maps {
cats[index] = interface2Cat(v, level)
}
}
return cats
}
func interface2Cat(data interface{}, level int) (cat *CategoryInfo) {
catMap := data.(map[string]interface{})
cat = &CategoryInfo{
CategoryID: utils.MustInterface2Int64(catMap["category_id"]),
Name: utils.Interface2String(catMap["name"]),
Rank: int(utils.MustInterface2Int64(catMap["rank"])),
Children: interface2CatList(catMap["children"], level+1),
Level: level,
}
return cat
}
func IsErrCategoryExist(err error) (isExist bool) {
return utils.IsErrMatch(err, "1", []string{"商户已存在该自定义分类"})
}
func IsErrCategoryNotExist(err error) (isNotExist bool) {
return utils.IsErrMatch(err, "", []string{"不存在该自定义分类ID", "商户不存在该自定义分类"})
}
func IsErrSkuExist(err error) (isExist bool) {
return utils.IsErrMatch(err, "1", []string{"商品已存在", "shop sku exist", "已被使用,请更换"})
}
func IsErrSkuNotExist(err error) (isExist bool) {
return utils.IsErrMatch(err, "1", []string{"sku_id与shop_id不匹配", "SKU不存在或者已经被删除", "商品不存在"})
}
func (a *API) GetEbaiSkuIDFromCustomID(shopID, customSkuID string) (ebaiSkuID int64) {
if skuInfo, err2 := a.SkuList(shopID, &SkuListParams{
CustomSkuID: customSkuID,
}); err2 == nil && skuInfo != nil && len(skuInfo.List) > 0 {
ebaiSkuID = skuInfo.List[0].SkuID
}
return ebaiSkuID
}
func (a *API) GetEbaiCatIDFromName(shopID, catName string) (ebaiCatID int64) {
if catList, err2 := a.ShopCategoryGet(shopID); err2 == nil {
for _, v := range catList {
if v.Name == catName {
ebaiCatID = v.CategoryID
break
}
for _, v2 := range v.Children {
if v2.Name == catName {
ebaiCatID = v2.CategoryID
break
}
}
}
}
return ebaiCatID
}