Files
baseapi/platformapi/mtwmapi/retail.go
邹宗楠 34e0934ddd 1
2025-05-14 14:00:47 +08:00

652 lines
28 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 mtwmapi
import (
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/globals"
"regexp"
"strings"
)
const (
MaxSkuNameCharCount = 30 // SkuName的最大字符数
MaxStoreSkuBatchSize = 200 // retail/sku/stock, retail/sku/sellStatus和retail/sku/price这些批量操作的最大值
MaxRetailListPageSize = 200
MaxBatchDeleteSize = 100 // retailCat/batchdelete/catandretail这个接口的批量最大值
)
const (
SellStatusOnline = 0 // 上架
SellStatusOffline = 1 // 下架
SpecialAttrBrand = "1200000088" //属性id取值范围仅支持两个1200000088-品牌, 1200000094-产地
SpecialAttrProducer = "1200000094"
SpecialAttrOrigin = "1200000132" // 产地
TypeCategory = 1 //1-类目2-类目属性 不传默认1
TypeCategoryAttr = 2
NeedYes = "1" //1-必传,2-非必传
)
const ( // {"attrId":1200000275,"attrName":"是否有机","valueList":[{"valueId":1300005199,"value":"非有机"}]},
MtwmSkuAttr200002727 = `[{"attrId":1200000088,"attrName":"品牌","valueList":[{"valueId":1000001,"value":"其他品牌"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000210,"attrName":"商品类别","valueList":[{"valueId":1300025224,"value":"水果混合果切/果拼"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]},{"attrId":1200004449,"attrName":"拼数","valueList":[{"valueId":1300015654,"value":"单拼"}]}]`
MtwmSkuAttr200001555 = `[{"attrId":1200000088,"attrName":"品牌","valueList":[{"valueId":1002998,"value":"伊利"}]},{"attrId":1200000210,"attrName":"商品类别","valueList":[{"valueId":1300001219,"value":"冰激凌"}]}]`
MtwmSkuAttr200002728 = `[{"attrId":1200000088,"attrName":"品牌","valueList":[{"valueId":1000001,"value":"其他品牌"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]},{"attrId":1200000210,"attrName":"商品类别","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
//200001519
MtwmSkuAttr200000592 = `[{"attrId":1200000088,"attrName":"品牌","valueList":[{"valueId":1000001,"value":"其他品牌"}]}]`
//200002704
MtwmSkuAttr200002731 = `[{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
MtwmSkuAttr200002716 = `[{"attrId":1200000287,"attrName":"特产品种","valueList":[{"valueId":1300016304,"value":"其他"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
//200002667200002713
MtwmSkuAttr200002670 = `[{"attrId":1200000202,"attrName":"品种","valueList":[{"valueId":1300000249,"value":"其他"}]},{"attrId":1200000287,"attrName":"特产品种","valueList":[{"valueId":1300016304,"value":"其他"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
//MtwmSkuAttr200002680 = `[{"attrId":1200000289,"attrName":"品规","valueList":[{"valueId":1300004255,"value":"未区分品规"}]},{"attrId":1200000202,"attrName":"品种","valueList":[{"valueId":1300000249,"value":"其他"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
MtwmSkuAttr200002680 = `[{"attrId":1200004607,"attrName":"水果形态","valueList":[{"valueId":1300017364,"value":"新鲜整果"}]},{"attrId":1200004608,"attrName":"单果重量","valueList":[{"valueId":1300017449,"value":"约4.5kg~5kg"}]},{"attrId":1200000202,"attrName":"品种","valueList":[{"valueId":1300000249,"value":"其他"}]},{"attrId":1200000094,"attrName":"产地","valueList":[{"valueId":100000050,"value":"中国"}]},{"attrId":1200000132,"attrName":"国产/进口","valueList":[{"valueId":1300000003,"value":"国产"}]},{"attrId":1200000286,"attrName":"果品品牌","valueList":[{"valueId":1300000249,"value":"其他"}]}]`
)
var (
retailBatchFailedSkuReg = regexp.MustCompile(`((?:\d+;)+)`)
)
type RetailCategoryInfo struct {
Name string `json:"name"`
Code string `json:"code"`
Sequence int `json:"sequence"`
Level int `json:"level"`
Children []*RetailCategoryInfo `json:"children"`
}
type RetailTag struct {
ID int64 `json:"id"`
Name string `json:"name"`
Level int `json:"level"`
NamePath string `json:"namePath"`
}
type BareStoreSkuInfo struct {
SkuID string `json:"sku_id"`
Price string `json:"price,omitempty"`
Stock string `json:"stock,omitempty"`
}
type BareStoreFoodInfo struct {
AppFoodCode string `json:"app_food_code"`
Skus []*BareStoreSkuInfo `json:"skus,omitempty"`
}
type AvailableTimesInfo struct {
Friday string `json:"friday"`
Monday string `json:"monday"`
Saturday string `json:"saturday"`
Sunday string `json:"sunday"`
Thursday string `json:"thursday"`
Tuesday string `json:"tuesday"`
Wednesday string `json:"wednesday"`
}
type SkuInfo struct {
AvailableTimes *AvailableTimesInfo `json:"available_times"`
BoxNum string `json:"box_num"`
BoxPrice string `json:"box_price"`
IsSellFlag int `json:"isSellFlag"`
LadderBoxNum string `json:"ladder_box_num"`
LadderBoxPrice string `json:"ladder_box_price"`
LimitOpenSyncStockNow bool `json:"limit_open_sync_stock_now"`
LocationCode string `json:"location_code"`
MinOrderCount string `json:"min_order_count"`
Price string `json:"price"`
SkuId string `json:"sku_id"`
Spec string `json:"spec"`
Stock string `json:"stock"`
Unit string `json:"unit"`
Upc string `json:"upc"`
Weight string `json:"weight"`
//WeightForUnit string `json:"weight_for_unit"` // weight weight_for_unit 至多填写一个
//WeightUnit string `json:"weight_unit"`
}
type AppFood struct {
AppFoodCode string `json:"app_food_code"`
AppPoiCode string `json:"app_poi_code"`
BoxNum float64 `json:"box_num"`
BoxPrice float64 `json:"box_price"`
CategoryCode string `json:"category_code"`
CategoryName string `json:"category_name"`
Ctime int `json:"ctime"`
Description string `json:"description"`
IsSp int `json:"isSp"`
IsSoldOut int `json:"is_sold_out"`
IsSpecialty int `json:"is_specialty"`
MinOrderCount int `json:"min_order_count"`
Name string `json:"name"`
Picture string `json:"picture"`
PictureList []string `json:"pictureList"`
PictureContents string `json:"picture_contents"`
Price float64 `json:"price"`
SecondaryCategoryCode string `json:"secondary_category_code"`
SecondaryCategoryName string `json:"secondary_category_name"`
Sequence int `json:"sequence"`
UpcCode string `json:"upc_code"`
Skus string `json:"skus"`
SkuList []*SkuInfo `json:"skuList"`
TagID int `json:"tag_id"`
Unit string `json:"unit"`
Utime int `json:"utime"`
ZhName string `json:"zh_name"`
CommonAttrValue string `json:"common_attr_value"`
}
type AppFoodResult struct {
AppFoodCode string `json:"app_food_code"`
ErrorMsg string `json:"error_msg"`
}
type AppFoodResult4SellStatus struct {
AppFoodCode string `json:"app_food_code"` // 印象里一开始是appFoodCode还专门问过美团https://developer.waimai.meituan.com/home/myquestionDetail/6566但现在这个又变成app_food_code了。。。
Msg string `json:"msg"`
}
type Param4UpdateCat struct {
CategoryCodeOrigin string `json:"category_code_origin,omitempty"`
CategoryNameOrigin string `json:"category_name_origin,omitempty"`
CategoryCode string `json:"category_code,omitempty"`
SecondaryCategoryCode string `json:"secondary_category_code,omitempty"`
SecondaryCategoryName string `json:"secondary_category_name,omitempty"`
Sequence int `json:"sequence,omitempty"`
TargetLevel string `json:"target_level,omitempty"`
TargetParentName string `json:"target_parent_name,omitempty"`
TopFlag string `json:"top_flag,omitempty"`
WeeksTime string `json:"weeks_time,omitempty"`
Period string `json:"period,omitempty"`
}
type CategoryAttrListResult struct {
AttrID string `json:"attr_id"`
AttrName string `json:"attr_name"`
AttrValueType string `json:"attr_value_type"`
Need string `json:"need"`
CharacterType string `json:"character_type"`
TextMaxLength string `json:"text_max_length"`
Sequence int `json:"sequence"`
SupportExtend string `json:"support_extend"`
ValueList []struct {
ValueID string `json:"value_id"`
Value string `json:"value"`
} `json:"value_list"`
}
// 美团分类没有ID就以名字为唯一标识不论级别都必须不能重名
// name和originName的长度不能超过10个字符字符不是字节
// 创建一级分类originName为空name为新分类名secondaryName为空
// 修改一级分类originName为分类名name为分类新名secondaryName为空
// 创建二级分类secondaryName为二级分类名
// 如果originName为空同时创建一级分类所以如果只是创建二级分类originName与name要填一样的此时sequence指的二级分类的sequence一级分类的sequence为缺省值
// 修改二级分类originName为二级分类名name为二级分类新名secondaryName为空
// https://developer.waimai.meituan.com/home/docDetail/71
// https://developer.waimai.meituan.com/home/questionDetail/4669
// 此接口并发访问,可能导致创建相同的分类,见如下问题回复
// https://developer.waimai.meituan.com/home/myquestionDetail/6931
func (a *API) RetailCatUpdate(poiCode, catName string, updateParams *Param4UpdateCat) (err error) {
if updateParams != nil {
if updateParams.CategoryCodeOrigin != "" {
updateParams.CategoryNameOrigin = ""
}
}
params := utils.Struct2MapByJson(updateParams)
params[KeyAppPoiCode] = poiCode
params["category_name"] = catName
_, err = a.AccessAPI("retailCat/update", false, params)
return err
}
// 删除商品分类
// 当分类下存在子级分类或商品时,不允许直接删除此分类。
func (a *API) RetailCatDelete(poiCode, code, name string, moveProductToUncate int) (err error) {
params := map[string]interface{}{
KeyAppPoiCode: poiCode,
}
if code != "" {
params["category_code"] = code
} else {
params["category_name"] = name
}
if moveProductToUncate != 0 {
params["move_product_to_uncate"] = 1 // 分类存在商品时将商品转移到未分类当中
}
_, err = a.AccessAPI("retailCat/delete", false, params)
return err
}
func (a *API) RetailCatList(poiCode string) (retailCatList []*RetailCategoryInfo, err error) {
result, err := a.AccessAPI("retailCat/list", true, map[string]interface{}{
KeyAppPoiCode: poiCode,
})
if err == nil {
return interface2CatList(result, 1, nil), nil
}
return nil, err
}
// offset 从0开始limit最大不能超过200
// 返回的app_poi_code始终是空手动建的商品app_food_code也为空导致无法通过API删除
func (a *API) RetailList(poiCode string, offset, limit int) (foodList []*AppFood, err error) {
result, err := a.AccessAPI("retail/list", true, map[string]interface{}{
KeyAppPoiCode: poiCode,
"offset": offset,
"limit": limit,
})
if err == nil {
if err = utils.Map2StructByJson(result, &foodList, true); err == nil {
for _, food := range foodList {
utils.UnmarshalUseNumber([]byte(food.Skus), &food.SkuList)
food.PictureList = strings.Split(food.Picture, ",")
}
}
}
return foodList, err
}
func (a *API) RetailListAll(poiCode string, offset int) (foodList []*AppFood, err error) {
data := make([]*AppFood, 0, 0)
offset = offset * GeneralMaxLimit
batchList, err2 := a.RetailList(poiCode, offset, GeneralMaxLimit) // GeneralMaxLimit
if err = err2; err == nil {
data = append(data, batchList...)
}
return data, err
}
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
}
// 商品名最长30个字符非字节
// 此函数可能创建相同foodCode的商品如下问题的回复中有提到
// https://developer.waimai.meituan.com/home/myquestionDetail/6716
// 另外这个接口即使不指定operate_type为1也可能报错”商品spu名称在该店内分类中已存在“原因就是已经存在两个相同的SKU了
func (a *API) RetailInitData(trackInfo, poiCode, foodCode string, params map[string]interface{}) (err error) {
result, err := a.AccessAPI2("retail/initdata", false, utils.MergeMaps(map[string]interface{}{
KeyAppPoiCode: poiCode,
KeyAppFoodCode: foodCode,
}, params), resultKeyData, trackInfo)
if foodCode == "29351" {
globals.SugarLogger.Debugf("--------result := %s", utils.Format4Output(result, false))
globals.SugarLogger.Debugf("--------result err:= %v", err)
}
return err
}
// 这个创建加更新
func (a *API) RetailBatchInitData(trackInfo, 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)),
}, resultKeyMsg, trackInfo)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
globals.SugarLogger.Debugf("--------result := %s", utils.Format4Output(result, false))
globals.SugarLogger.Debugf("--------result err:= %v", err)
return failedFoodList, err
}
// RetailBatchInitData2 这个只更新打包费
func (a *API) RetailBatchInitData2(trackInfo, 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)),
"operate_type": 2,
}, resultKeyMsg, trackInfo)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
func (a *API) RetailSkuPrice(trackInfo, 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)),
}, resultKeyMsg, trackInfo)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
func (a *API) RetailSkuStock(trackInfo, 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)),
}, resultKeyMsg, trackInfo)
if err == nil {
failedFoodList, err = handleRetailBatchResult(result)
}
return failedFoodList, err
}
// retail/sku/sellStatus在部分失败时会返回错误其它相应的批处理函数则会返回成功
// 此接口已准备废弃
// 2019年9月17日开放平台已上线新接口【retail/sellStatus】用于零售类商家批量更新商品售卖状态。请已接入老接口retail/sku/sellStatus的开发者在2019年10月31日前完成接口迁移使用新接口的请求地址https://waimaiopen.meituan.com/api/v1/retail/sellStatus。
// 开放平台将于2019年11月1日开始全面下线老接口retail/sku/sellStatus如开发者逾期未完成接口迁移调用老接口失败所造成的相关问题或损失由商家自行承担。
//func (a *API) RetailSkuSellStatus(trackInfo, 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, trackInfo)
// if err != nil {
// if errExt, ok := err.(*utils.ErrorWithCode); ok {
// failedFoodList, _ = handleRetailBatchResultByRegexp(errExt.ErrMsg())
// }
// }
// return failedFoodList, err
//}
// 此接口部分失败也返回成功但错误消息格式errorMsg, appFoodCode与其它两个不一样
func (a *API) RetailSellStatus(trackInfo, poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (failedFoodList []*AppFoodResult, err error) {
result, err := a.AccessAPI2("retail/sellStatus", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": string(utils.MustMarshal(foodData)),
"sell_status": sellStatus,
}, resultKeyMsg, trackInfo)
if err == nil {
var tmpFailedFoodList []*AppFoodResult4SellStatus
if msg, ok := result.(string); ok && msg != "" {
if err = utils.UnmarshalUseNumber([]byte(msg), &tmpFailedFoodList); err == nil {
for _, v := range tmpFailedFoodList {
failedFoodList = append(failedFoodList, &AppFoodResult{
AppFoodCode: v.AppFoodCode,
ErrorMsg: v.Msg,
})
}
}
}
}
return failedFoodList, err
}
// 此接口部分失败也返回成功但错误消息格式errorMsg, appFoodCode与其它两个不一样
func (a *API) RetailSellStatus2(trackInfo, poiCode string, foodData []map[string]string, sellStatus int) (failedFoodList []*AppFoodResult, err error) {
result, err := a.AccessAPI2("retail/sellStatus", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
"food_data": foodData,
"sell_status": sellStatus,
}, resultKeyMsg, trackInfo)
if err == nil {
var tmpFailedFoodList []*AppFoodResult4SellStatus
if msg, ok := result.(string); ok && msg != "" {
if err = utils.UnmarshalUseNumber([]byte(msg), &tmpFailedFoodList); err == nil {
for _, v := range tmpFailedFoodList {
failedFoodList = append(failedFoodList, &AppFoodResult{
AppFoodCode: v.AppFoodCode,
ErrorMsg: v.Msg,
})
}
}
}
}
return failedFoodList, err
}
func (a *API) RetailGet(poiCode, foodCode string) (food *AppFood, err error) {
result, err := a.AccessAPI("retail/get", true, map[string]interface{}{
KeyAppPoiCode: poiCode,
KeyAppFoodCode: foodCode,
})
if err == nil {
if err = utils.Map2StructByJson(result, &food, true); err == nil && food.Skus != "" {
utils.UnmarshalUseNumber([]byte(food.Skus), &food.SkuList)
food.PictureList = strings.Split(food.Picture, ",")
}
}
return food, err
}
func (a *API) RetailSkuSave(poiCode, foodCode string, standardSkus, unstandardSkus []map[string]interface{}) (err error) {
_, err = a.AccessAPI("retail/sku/save", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
KeyAppFoodCode: foodCode,
"standard_skus": string(utils.MustMarshal(standardSkus)),
"unstandard_skus": string(utils.MustMarshal(unstandardSkus)),
})
return err
}
// RetailDelete 根据APP方门店id和商品id删除商品。
func (a *API) RetailDelete(trackInfo, poiCode, foodCode string) (err error) {
_, err = a.AccessAPI2("retail/delete", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
KeyAppFoodCode: foodCode,
"is_delete_retail_cat": 1,
}, resultKeyData, trackInfo)
return err
}
func (a *API) RetailSkuDelete(trackInfo, poiCode, foodCode, skuID string) (err error) {
_, err = a.AccessAPI2("retail/sku/delete", false, map[string]interface{}{
KeyAppPoiCode: poiCode,
KeyAppFoodCode: foodCode,
"sku_id": skuID,
}, resultKeyData, trackInfo)
return err
}
//category/attr/list 根据末级类目id获取类目属性列表
//https://open-shangou.meituan.com/home/docDetail/386
func (a *API) CategoryAttrList(tag_id int64) (categoryAttrListResult []*CategoryAttrListResult, err error) {
result, err := a.AccessAPI("gw/category/attr/list", true, map[string]interface{}{
"tag_id": tag_id,
})
if err == nil {
utils.Map2StructByJson(result.(map[string]interface{})["general_attrs"], &categoryAttrListResult, false)
}
return categoryAttrListResult, err
}
type CategoryAttrValueListResult struct {
ValueID string `json:"value_id"`
Value string `json:"value"`
}
//category/attr/value/list 查询特殊属性的属性值列表
//https://open-shangou.meituan.com/home/docDetail/387
func (a *API) CategoryAttrValueList(attr_id int64, keyword string) (categoryAttrValueListResult []*CategoryAttrValueListResult, err error) {
result, err := a.AccessAPI("gw/category/attr/value/list", true, map[string]interface{}{
"attr_id": attr_id,
"keyword": keyword,
"page_num": 1,
"page_size": 20,
"category_type": 1,
})
if err == nil {
utils.Map2StructByJson(result, &categoryAttrValueListResult, false)
}
return categoryAttrValueListResult, err
}
type RetailRecommendTagResp struct {
UpcCode string `json:"upc_code"` //条形码编号 如类目匹配失败返回null
Name string `json:"name"` //商品名称 如类目匹配失败返回null
TagID int `json:"tag_id"` //美团内部类目id
WeightForUnit float64 `json:"weight_for_unit"` //重量值
WeightUnit string `json:"weight_unit"` // 表示sku的重量数值单位枚举值如下 1."克(g)" 2."千克(kg)" 3."毫升(ml)" 4."升(L)" 5."磅" 6."斤" 7."两"。
GeneralAttrs []GeneralAttrs `json:"general_attrs"`
}
type GeneralAttrs struct {
AttrID string `json:"attr_id"` //属性id
AttrName string `json:"attr_name"` //属性名称
ValueList []ValueList `json:"value_list"`
}
type ValueList struct {
ValueID string `json:"value_id"` //属性值id
Value string `json:"value"` //属性值
}
// RetailRecommendTag 根据商品UPC或商品名称或类目ID查询平台推荐类目及类目属性信息
func (a *API) RetailRecommendTag(name, appPoiCode string, tagID, tagType int) (retVal *RetailRecommendTagResp, err error) {
result, err := a.AccessAPI("retail/recommend/tag", true, map[string]interface{}{
"name": name,
"app_poi_code": appPoiCode,
"tag_id": tagID,
"type": tagType,
})
if err == nil {
if err = utils.Map2StructByJson(result, &retVal, false); err == nil {
return retVal, nil
}
}
return nil, err
}
// 就是厂商商品类别
func (a *API) RetailGetSpTagIds() (tagIds []*RetailTag, err error) {
result, err := a.AccessAPI("retail/getSpTagIds", true, nil)
if err == nil {
if err = utils.Map2StructByJson(result, &tagIds, false); err == nil {
return tagIds, nil
}
}
return nil, err
}
// 此接口将申请授权后方可接入
func (a *API) RetailCatSkuBatchDelete(trackInfo, poiCode string, catNames []string, secondaryCatNames []string, foodCodes []string) (err error) {
params := map[string]interface{}{
KeyAppPoiCode: poiCode,
}
if len(catNames) > 0 {
params["category_names"] = strings.Join(catNames, ",")
}
if len(secondaryCatNames) > 0 {
params["secondary_category_names"] = strings.Join(secondaryCatNames, ",")
}
if len(foodCodes) > 0 {
params["app_food_codes"] = strings.Join(foodCodes, ",")
}
_, err = a.AccessAPI2("retailCat/batchdelete/catandretail", false, params, resultKeyData, trackInfo)
return err
}
// 批量删除商品分类及商品
// https://developer.waimai.meituan.com/home/docDetail/286
func (a *API) RetailCatSkuBatchDelete2(trackInfo, poiCode string, catCodes, catNames, secondaryCatCodes, secondaryCatNames, foodCodes []string) (err error) {
params := map[string]interface{}{
KeyAppPoiCode: poiCode,
}
if len(catCodes) > 0 {
params["category_codes"] = strings.Join(catCodes, ",")
} else if len(catNames) > 0 {
params["category_names"] = strings.Join(catNames, ",")
}
if len(secondaryCatCodes) > 0 {
params["secondary_category_codes"] = strings.Join(secondaryCatCodes, ",")
} else if len(secondaryCatNames) > 0 {
params["secondary_category_names"] = strings.Join(secondaryCatNames, ",")
}
if len(foodCodes) > 0 {
params["app_food_codes"] = strings.Join(foodCodes, ",")
}
_, err = a.AccessAPI2("retailCat/batchdelete/catandretail", false, params, resultKeyData, trackInfo)
return err
}
type SpuData struct {
AppSpuCode string `json:"appSpuCode"` //商家中台系统里商品的编码
SkuID string `json:"skuID"` //SKU码(商家的规格编码)
PurchasePrice string `json:"purchasePrice"` //商品进货价
}
// https://opendj.meituan.com/home/docDetail/821
// 【代运营】批量更新商品进货价 /retail/purchase/price/update
func (a *API) BatchSetRestockingPrice(trackInfo, appPoiCode string, spuData []*SpuData) error {
if len(spuData) > 0 {
for _, v := range spuData {
globals.SugarLogger.Debugf("BatchSetRestockingPrice params======%s %s %s", v.PurchasePrice, v.AppSpuCode, v.SkuID)
}
}
_, err := a.AccessAPI2("/retail/purchase/price/update", false, map[string]interface{}{
KeyAppPoiCode: appPoiCode,
"spu_data": spuData,
}, resultKeyMsg, trackInfo)
return err
}
// 私有辅助函数
func interface2Cat(data interface{}, level int) (cat *RetailCategoryInfo) {
mapData, ok := data.(map[string]interface{})
if ok {
cat = &RetailCategoryInfo{
Name: utils.Interface2String(mapData["name"]),
Code: utils.Interface2String(mapData["code"]),
Sequence: int(utils.Interface2Int64WithDefault(mapData["sequence"], 0)),
Level: level,
}
children := mapData["children"]
if children != nil {
cat.Children = interface2CatList(children, level+1, nil)
}
}
return cat
}
func interface2CatList(data interface{}, level int, interface2CatHandler func(data interface{}, level int) (cat *RetailCategoryInfo)) (cats []*RetailCategoryInfo) {
if interface2CatHandler == nil {
interface2CatHandler = interface2Cat
}
maps, ok := data.([]interface{})
if ok {
cats = make([]*RetailCategoryInfo, len(maps))
for index, v := range maps {
cats[index] = interface2CatHandler(v, level)
}
}
return cats
}
func IsErrCategoryExist(err error) (isExist bool) {
return utils.IsErrMatch(err, utils.Int2Str(ErrCodeSkuCategoryExist), nil)
}
func IsErrCategoryNotExist(err error) (isNotExist bool) {
return utils.IsErrMatch(err, utils.Int2Str(ErrCodeSkuCategoryNotExist), nil) ||
utils.IsErrMatch(err, utils.Int2Str(ErrCodeParameterFormatWrong), []string{
"门店内不存在该分类",
})
}
func IsErrSkuNotExist(err error) (isExist bool) {
return utils.IsErrMatch(err, utils.Int2Str(ErrCodeNoAppFood), nil)
}