Files
baseapi/platformapi/ebaiapi/shop_sku.go
邹宗楠 ffb7bb81da 1
2024-01-26 14:51:34 +08:00

631 lines
20 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
MaxCategoryNameLen = 12 // 商家分类名最大长度
)
var (
shopSkuBatchFailedSkuReg = regexp.MustCompile(`请核查以下sku(\{.*\})`)
skuExistMsgList = []string{"商品已存在", "shop sku exist", "已被使用,请更换"}
skuNotExistMsgList = []string{"sku_id与shop_id不匹配", "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 {
CateId int `json:"cate_id"`
CateId1 int `json:"cate_id1"`
CateId2 int `json:"cate_id2"`
CateName string `json:"cate_name"`
CateName1 string `json:"cate_name1"`
CateName2 string `json:"cate_name2"`
CustomCatIds string `json:"custom_cat_ids"`
CustomSkuId string `json:"custom_sku_id"`
DurationSaleFlag bool `json:"duration_sale_flag"`
IsInActivity int `json:"is_in_activity"`
ItemId int64 `json:"item_id"`
LeftNum int `json:"left_num"`
MarketPrice int `json:"market_price"`
Minimum int `json:"minimum"`
Name string `json:"name"`
NeedIce string `json:"need_ice"`
PackageFlag bool `json:"package_flag"`
Photos []struct {
IsMaster int `json:"is_master"`
Url string `json:"url"`
} `json:"photos"`
PreminusWeight int `json:"preminus_weight"`
PreparationTime int `json:"preparation_time"`
PrescriptionType string `json:"prescription_type"`
ProcessDetail []interface{} `json:"process_detail"`
ProcessType int `json:"process_type"`
ProductionAddr1 string `json:"production_addr1"`
ProductionAddr2 string `json:"production_addr2"`
ProductionAddr3 string `json:"production_addr3"`
Rtf string `json:"rtf"`
SalePrice int `json:"sale_price"`
SaleStep int `json:"sale_step"`
SaleUnit string `json:"sale_unit"`
SevenDaysNoReason bool `json:"seven_days_no_reason"`
SkuId int64 `json:"sku_id"`
SkuProperty []interface{} `json:"sku_property"`
Status string `json:"status"`
Summary string `json:"summary"`
Upc string `json:"upc"`
UpcType int `json:"upc_type"`
Weight string `json:"weight"`
WeightFlag int `json:"weight_flag"`
CustomCatIDs string `json:"custom_cat_ids"`
CustomCatList []*SkuCustomCatInfo `json:"custom_cat_list"`
CustomSkuID string `json:"custom_sku_id"`
ShelfNumber string `json:"shelf_number"`
UpdateTime string `json:"update_time"`
}
type PageDataInfo struct {
Total int `json:"Total"`
Page int `json:"Page"`
Pages int `json:"Pages"`
SkuIdOffset int `json:"sku_id_offset"`
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 {
if result.Data != nil {
return utils.Interface2Int64WithDefault((result.Data.(map[string]interface{})["category_id"]), 0), nil
}
}
if err != nil && strings.Contains(err.Error(), "新增的店内分类已存在") {
errStr := err.Error()
startIndex := strings.Index(errStr, "")
endIndex := strings.Index(errStr, "")
return utils.Str2Int64(errStr[startIndex+3 : endIndex]), 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 {
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,
})
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) {
params.PageSize = 100
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 int64, params map[string]interface{}) (skuID int64, err error) {
defParams := map[string]interface{}{
KeyShopID: shopID,
KeyCustomSkuID: customSkuID,
}
if params["upc"] == nil {
defParams["upc"] = "upc-" + utils.Int2Str(int(customSkuID))
}
/*if params["brand_id"] == nil {
defParams["brand_id"] = 0
}
if params["brand_name"] == nil {
defParams["brand_name"] = "无" // 很狗血的是,你还必须填个无才行。。。
}*/
//
params = utils.MergeMaps(params, defParams)
result, err := a.AccessAPI2("sku.create", params, trackInfo)
if err == nil && result.Data != nil {
return utils.Interface2Int64WithDefault(result.Data.(map[string]interface{})[KeySkuID], 0), nil
} else if err != nil && strings.Contains(err.Error(), "商品已存在") {
skuIDStr := err.Error()
start := strings.LastIndex(skuIDStr, "sku_id")
end := strings.Index(skuIDStr, "level")
return utils.Str2Int64(skuIDStr[start+7 : end-1]), 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.Interface2Int64WithDefault(result.Data.(map[string]interface{})[KeySkuID], 0), 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.FailedList)))
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
// 饿百删除商品后还要等待5分钟后才会归档商品才会真正删除生效
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) (opResult *BatchOpResult, 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 opResult, 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", skuExistMsgList)
}
func IsErrSkuNotExist(err error) (isExist bool) {
return utils.IsErrMatch(err, "1", skuNotExistMsgList)
}
func IsMsgSkuExist(msg string) bool {
return utils.IsErrMsgMatch(msg, skuExistMsgList)
}
func IsMsgSkuNotExist(msg string) bool {
return utils.IsErrMsgMatch(msg, skuNotExistMsgList)
}
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
}