646 lines
28 KiB
Go
646 lines
28 KiB
Go
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":"其他"}]}]`
|
||
//200002667,200002713
|
||
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) {
|
||
_, err = a.AccessAPI2("retail/initdata", false, utils.MergeMaps(map[string]interface{}{
|
||
KeyAppPoiCode: poiCode,
|
||
KeyAppFoodCode: foodCode,
|
||
}, params), resultKeyData, trackInfo)
|
||
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)
|
||
}
|
||
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)
|
||
}
|