package ebai import ( "regexp" "time" "git.rosy.net.cn/baseapi/platformapi/ebaiapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/business/partner/putils" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) const ( defVendorCatID = 201222934 // 其他蔬菜 ) var ( sensitiveWordRegexp = regexp.MustCompile(`商品名称中含有敏感词(\[.*\])`) ) func (p *PurchaseHandler) GetStoreSkusBatchSize(funcID int) (batchSize int) { switch funcID { case partner.FuncUpdateStoreSkusStock, partner.FuncUpdateStoreSkusStatus, partner.FuncUpdateStoreSkusPrice, partner.FuncDeleteStoreSkus: batchSize = ebaiapi.MaxStoreSkuBatchSize case partner.FuncCreateStoreSkus, partner.FuncUpdateStoreSkus: batchSize = 1 case partner.FuncGetStoreSkusFullInfo: batchSize = 1 } return batchSize } // 门店分类 func (p *PurchaseHandler) GetStoreAllCategories(ctx *jxcontext.Context, storeID int, vendorStoreID string) (cats []*partner.BareCategoryInfo, err error) { remoteCats, err := api.EbaiAPI.ShopCategoryGet(utils.Int2Str(storeID)) if err == nil { cats = convertVendorCatList(remoteCats) } return cats, err } func convertVendorCatList(remoteCats []*ebaiapi.CategoryInfo) (cats []*partner.BareCategoryInfo) { for _, rCat := range remoteCats { cat := &partner.BareCategoryInfo{ VendorCatID: utils.Int64ToStr(rCat.CategoryID), Name: rCat.Name, Level: rCat.Level, Seq: jxCatSeq2Ebai(rCat.Rank), Children: convertVendorCatList(rCat.Children), } cats = append(cats, cat) } return cats } func (p *PurchaseHandler) IsErrCategoryExist(err error) (isExist bool) { return ebaiapi.IsErrCategoryExist(err) } func (p *PurchaseHandler) IsErrCategoryNotExist(err error) (isNotExist bool) { return ebaiapi.IsErrCategoryNotExist(err) } func (p *PurchaseHandler) CreateStoreCategory(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { var vendorCatID int64 if globals.EnableEbaiStoreWrite { vendorCatID, err = api.EbaiAPI.ShopCategoryCreate(utils.Int2Str(storeID), utils.Str2Int64WithDefault(storeCat.ParentVendorCatID, 0), formatCatName(storeCat.Name), jxCatSeq2Ebai(storeCat.Seq)) } else { vendorCatID = jxutils.GenFakeID() } storeCat.VendorCatID = utils.Int64ToStr(vendorCatID) if err != nil { failedList = putils.GetErrMsg2FailedSingleList(nil, err, storeID, model.VendorIDEBAI, "创建分类") } return failedList, err } func (p *PurchaseHandler) UpdateStoreCategory(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeCat *dao.SkuStoreCatInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopCategoryUpdate(utils.Int2Str(storeID), utils.Str2Int64WithDefault(storeCat.VendorCatID, 0), formatCatName(storeCat.Name), jxCatSeq2Ebai(storeCat.Seq)) // todo, 饿百将一个分类重复改名,也会报分类名重复错,特殊处理一下,不过因为GetStoreCategory其实会拉取所有的门店分类,是比较耗时的操作 if utils.IsErrMatch(err, "1", []string{"分类名称已经存在"}) { if cat, err2 := p.GetStoreCategory(ctx, storeID, vendorStoreID, storeCat.Name); err2 == nil { if cat.VendorCatID == storeCat.VendorCatID { err = nil } } } if err != nil { failedList = putils.GetErrMsg2FailedSingleList(nil, err, storeID, model.VendorIDEBAI, "修改分类") } } return failedList, err } func (p *PurchaseHandler) DeleteStoreCategory(ctx *jxcontext.Context, storeID int, vendorStoreID, vendorCatID string, level int) (failedList []*partner.StoreSkuInfoWithErr, err error) { if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.ShopCategoryDelete(utils.Int2Str(storeID), utils.Str2Int64WithDefault(vendorCatID, 0)) if err != nil { failedList = putils.GetErrMsg2FailedSingleList(nil, err, storeID, model.VendorIDEBAI, "删除分类") } } return failedList, err } // 门店商品 // 多门店平台不需要实现这个接口 func (p *PurchaseHandler) IsErrSkuExist(err error) (isExist bool) { return ebaiapi.IsErrSkuExist(err) } func (p *PurchaseHandler) IsErrSkuNotExist(err error) (isNotExist bool) { return ebaiapi.IsErrSkuNotExist(err) } func (p *PurchaseHandler) updateStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo, isNeedMapCat bool) (failedList []*partner.StoreSkuInfoWithErr, err error) { storeSku := storeSkuList[0] strStoreID := utils.Int2Str(storeID) params := genSkuParamsFromStoreSkuInfo2(storeSku, false) if globals.EnableEbaiStoreWrite { _, err = api.EbaiAPI.SkuUpdate(ctx.GetTrackInfo(), strStoreID, utils.Str2Int64(storeSku.VendorSkuID), params) if err != nil { failedList = putils.GetErrMsg2FailedSingleList(storeSkuList, err, storeID, model.VendorIDEBAI, "更新商品基础信息") } if isNeedMapCat { utils.CallFuncAsync(func() { api.EbaiAPI.SkuShopCategoryMap(strStoreID, utils.Str2Int64(storeSku.VendorSkuID), "", utils.Str2Int64(storeSku.VendorCatID), genSkuCatRank(storeSku)) }) } } return failedList, err } func (p *PurchaseHandler) UpdateStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { return p.updateStoreSkus(ctx, storeID, vendorStoreID, storeSkuList, true) } // 对于多门店平台来说,storeSkuList中只有SkuID与VendorSkuID有意义 func (p *PurchaseHandler) CreateStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*dao.StoreSkuSyncInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { storeSku := storeSkuList[0] var vendorSkuID int64 params := genSkuParamsFromStoreSkuInfo2(storeSku, true) if globals.EnableEbaiStoreWrite { strStoreID := utils.Int2Str(storeID) if vendorSkuID, err = api.EbaiAPI.SkuCreate(ctx.GetTrackInfo(), strStoreID, storeSku.SkuID, params); err == nil { utils.AfterFuncWithRecover(5*time.Second, func() { api.EbaiAPI.SkuShopCategoryMap(strStoreID, vendorSkuID, "", utils.Str2Int64(storeSku.VendorCatID), genSkuCatRank(storeSku)) // 饿百平台有BUG,会导致新建一个之前删除的商品时,信息不会及时更新,强制刷新一下 // 比如门店:100887, skuID:33805,订单:1577176719141226065 p.updateStoreSkus(ctx, storeID, vendorStoreID, storeSkuList, false) }) } else { failedList = putils.GetErrMsg2FailedSingleList(storeSkuList, err, storeID, model.VendorIDEBAI, "创建商品") } } else { vendorSkuID = jxutils.GenFakeID() } storeSku.VendorSkuID = utils.Int64ToStr(vendorSkuID) return failedList, err } func getFailedVendorSkuIDsFromOpResult(opResult *ebaiapi.BatchOpResult) (skuIDs []string) { if opResult != nil { for _, v := range opResult.FailedList { skuIDs = append(skuIDs, utils.Int64ToStr(v.SkuID)) } } return skuIDs } func (p *PurchaseHandler) DeleteStoreSkus(ctx *jxcontext.Context, storeID int, vendorStoreID string, storeSkuList []*partner.StoreSkuInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { if globals.EnableEbaiStoreWrite { opResult, err2 := api.EbaiAPI.SkuDelete(ctx.GetTrackInfo(), utils.Int2Str(storeID), partner.BareStoreSkuInfoList(storeSkuList).GetVendorSkuIDIntList(), nil) if err = err2; err2 != nil && opResult != nil { // if len(storeSkuList) > len(opResult.FailedList) { failedList = putils.SelectStoreSkuListByOpResult(storeSkuList, opResult, storeID, model.VendorIDEBAI, "删除商品") // successList = putils.UnselectStoreSkuListByVendorSkuIDs(storeSkuList, getFailedVendorSkuIDsFromOpResult(opResult)) // } } } return failedList, err } func (p *PurchaseHandler) UpdateStoreSkusStatus(ctx *jxcontext.Context, vendorOrgCode string, storeID int, vendorStoreID string, storeSkuList []*partner.StoreSkuInfo, status int) (failedList []*partner.StoreSkuInfoWithErr, err error) { vendorSkuIDs := partner.BareStoreSkuInfoList(storeSkuList).GetVendorSkuIDIntList() if globals.EnableEbaiStoreWrite { var opResult *ebaiapi.BatchOpResult if status == model.SkuStatusNormal { if len(vendorSkuIDs) > 1 { opResult, err = api.EbaiAPI.SkuOnline(ctx.GetTrackInfo(), utils.Int2Str(storeID), vendorSkuIDs, nil, nil) } else if len(vendorSkuIDs) == 1 { err = api.EbaiAPI.SkuOnlineOne(ctx.GetTrackInfo(), utils.Int2Str(storeID), vendorSkuIDs[0], "", "") failedList = putils.GetErrMsg2FailedSingleList(storeSkuList, err, storeID, model.VendorIDEBAI, "更新商品状态") return failedList, err } } else { if len(vendorSkuIDs) > 1 { opResult, err = api.EbaiAPI.SkuOffline(ctx.GetTrackInfo(), utils.Int2Str(storeID), vendorSkuIDs, nil, nil) } else if len(vendorSkuIDs) == 1 { err = api.EbaiAPI.SkuOfflineOne(ctx.GetTrackInfo(), utils.Int2Str(storeID), vendorSkuIDs[0], "", "") failedList = putils.GetErrMsg2FailedSingleList(storeSkuList, err, storeID, model.VendorIDEBAI, "更新商品状态") return failedList, err } } if err != nil && opResult != nil { failedList = putils.SelectStoreSkuListByOpResult(storeSkuList, opResult, storeID, model.VendorIDEBAI, "更新商品状态") // failedList = putils.UnselectStoreSkuListByVendorSkuIDs(storeSkuList, getFailedVendorSkuIDsFromOpResult(opResult)) } } return failedList, err } func StoreSkuInfoList2Ebai(storeSkuList []*partner.StoreSkuInfo) (outList ebaiapi.ShopSkuInfoList) { outList = make(ebaiapi.ShopSkuInfoList, len(storeSkuList)) for k, v := range storeSkuList { outList[k] = &ebaiapi.ShopSkuInfo{ SkuID: utils.Str2Int64WithDefault(v.VendorSkuID, 0), // CustomSkuID: utils.Int2Str(v.SkuID), SalePrice: v.VendorPrice, Stock: v.Stock, } } return outList } func (p *PurchaseHandler) UpdateStoreSkusPrice(ctx *jxcontext.Context, vendorOrgCode string, storeID int, vendorStoreID string, storeSkuList []*partner.StoreSkuInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { if globals.EnableEbaiStoreWrite { if len(storeSkuList) > 1 { opResult, err2 := api.EbaiAPI.SkuPriceUpdateBatch(ctx.GetTrackInfo(), utils.Int2Str(storeID), StoreSkuInfoList2Ebai(storeSkuList), ebaiapi.SkuIDTypeSkuID) if err = err2; err != nil && opResult != nil { failedList = putils.SelectStoreSkuListByOpResult(storeSkuList, opResult, storeID, model.VendorIDEBAI, "更新商品价格") } } else if len(storeSkuList) == 1 { opResult2, err := api.EbaiAPI.SkuPriceUpdateOne(ctx.GetTrackInfo(), utils.Int2Str(storeID), StoreSkuInfoList2Ebai(storeSkuList)[0]) if err != nil && opResult2 != nil { failedList = putils.SelectStoreSkuListByOpResult(storeSkuList, opResult2, storeID, model.VendorIDEBAI, "更新商品价格") } } } return failedList, err } func (p *PurchaseHandler) UpdateStoreSkusStock(ctx *jxcontext.Context, vendorOrgCode string, storeID int, vendorStoreID string, storeSkuList []*partner.StoreSkuInfo) (failedList []*partner.StoreSkuInfoWithErr, err error) { if globals.EnableEbaiStoreWrite { if len(storeSkuList) > 1 { opResult, err2 := api.EbaiAPI.SkuStockUpdateBatch(ctx.GetTrackInfo(), utils.Int2Str(storeID), StoreSkuInfoList2Ebai(storeSkuList), ebaiapi.SkuIDTypeSkuID) if err = err2; err != nil && opResult != nil { failedList = putils.SelectStoreSkuListByOpResult(storeSkuList, opResult, storeID, model.VendorIDEBAI, "更新商品库存") // successList = putils.UnselectStoreSkuListByVendorSkuIDs(storeSkuList, getFailedVendorSkuIDsFromOpResult(opResult)) } } else if len(storeSkuList) == 1 { err = api.EbaiAPI.SkuStockUpdateOne(ctx.GetTrackInfo(), utils.Int2Str(storeID), StoreSkuInfoList2Ebai(storeSkuList)[0]) failedList = putils.GetErrMsg2FailedSingleList(storeSkuList, err, storeID, model.VendorIDEBAI, "更新商品库存") } } return failedList, err } func genSkuParamsFromStoreSkuInfo2(storeSku *dao.StoreSkuSyncInfo, isCreate bool) (params map[string]interface{}) { photos := []map[string]interface{}{ map[string]interface{}{ "is_master": true, "url": storeSku.Img, }, } if storeSku.Img2 != "" { photos = append(photos, map[string]interface{}{ "is_master": false, "url": storeSku.Img2, }) } params = map[string]interface{}{ "name": utils.LimitMixedStringLen(storeSku.SkuName, ebaiapi.MaxSkuNameByteCount), "left_num": model.MaxStoreSkuStockQty, "category_id": utils.Str2Int64(storeSku.VendorCatID), "predict_cat": 0, // 不使用推荐类目 "cat3_id": getEbaiCat(storeSku.VendorVendorCatID), "weight": storeSku.Weight, "photos": photos, } if storeSku.DescImg != "" { params["rtf"] = storeSku.DescImg } if isCreate /*storeSku.SkuSyncStatus&(model.SyncFlagPriceMask| model.SyncFlagNewMask) != 0 */ { params["sale_price"] = storeSku.VendorPrice } if storeSku.SkuSyncStatus&(model.SyncFlagSaleMask|model.SyncFlagNewMask) != 0 { params["status"] = jxSkuStatus2Ebai(storeSku.MergedStatus) } // todo 饿百如果给的UPC是空要报错,但如果我要删除UPC怎么弄? // if storeSku.Upc != "" { // params["upc"] = storeSku.Upc // } return params } func ebaiSkuStatus2Jx(ebaiSkuStatus int) (jxSkuStatus int) { if ebaiSkuStatus == ebaiapi.SkuStatusOnline { jxSkuStatus = model.SkuStatusNormal } else if ebaiSkuStatus == ebaiapi.SkuStatusOffline { jxSkuStatus = model.SkuStatusDontSale } else if ebaiSkuStatus == ebaiapi.SkuStatusOnline { jxSkuStatus = model.SkuStatusDeleted } return jxSkuStatus } func jxSkuStatus2Ebai(status int) int { if status <= 0 { return ebaiapi.SkuStatusOffline } return ebaiapi.SkuStatusOnline } func getEbaiCat(catID int64) int64 { if catID == 0 { return defVendorCatID } return catID } // 饿百的排序是从大到小 func genSkuCatRank(storeSku *dao.StoreSkuSyncInfo) int { return int(ebaiapi.MaxSkuCatRank - storeSku.GetSeq()) } // 饿百的排序是从大到小 func jxCatSeq2Ebai(seq int) int { return ebaiapi.MaxCatCatRank - seq } func formatCatName(name string) string { return utils.LimitUTF8StringLen(name, ebaiapi.MaxCategoryNameLen) } func (p *PurchaseHandler) GetStoreSkusFullInfo(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, vendorStoreID string, storeSkuList []*partner.StoreSkuInfo) (skuNameList []*partner.SkuNameInfo, err error) { params := &ebaiapi.SkuListParams{ PageSize: ebaiapi.MaxSkuListPageSize, } if len(storeSkuList) == 1 { if storeSkuList[0].SkuID > 0 { params.CustomSkuID = utils.Int2Str(storeSkuList[0].SkuID) } if storeSkuList[0].VendorSkuID != "" { params.SkuID = utils.Str2Int64WithDefault(storeSkuList[0].VendorSkuID, 0) } } page1, err := api.EbaiAPI.SkuList(utils.Int2Str(storeID), params) if err == nil { skuNameList = append(skuNameList, vendorSkuList2Jx(page1.List)...) if page1.Pages > 1 { pages := make([]int, page1.Pages-1) for i := 2; i <= page1.Pages; i++ { pages[i-2] = i } task := tasksch.NewParallelTask("ebai GetStoreSkusFullInfo", nil, ctx, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { callParams := &ebaiapi.SkuListParams{ PageSize: ebaiapi.MaxSkuListPageSize, Page: batchItemList[0].(int), } pageSku, err2 := api.EbaiAPI.SkuList(utils.Int2Str(storeID), callParams) if err2 == nil { return pageSku.List, err2 } return nil, err2 }, pages) tasksch.HandleTask(task, parentTask, true).Run() result, err2 := task.GetResult(0) if err = err2; err == nil { for _, v := range result { skuNameList = append(skuNameList, vendorSku2Jx(v.(*ebaiapi.SkuInfo))) } } } } return skuNameList, err } func vendorSku2Jx(vendorSku *ebaiapi.SkuInfo) (skuName *partner.SkuNameInfo) { prefix, name, comment, specUnit, unit, specQuality := jxutils.SplitSkuName(vendorSku.Name) weight := vendorSku.Weight if weight <= 0 { weight = jxutils.FormatSkuWeight(specQuality, specUnit) } skuID := int(utils.Str2Int64WithDefault(vendorSku.CustomSkuID, 0)) vendorSkuID := utils.Int64ToStr(vendorSku.SkuID) skuName = &partner.SkuNameInfo{ NameID: skuID, VendorNameID: vendorSkuID, Prefix: prefix, Name: name, Unit: unit, SkuList: []*partner.SkuInfo{ &partner.SkuInfo{ StoreSkuInfo: partner.StoreSkuInfo{ VendorSkuID: vendorSkuID, SkuID: skuID, Stock: vendorSku.LeftNum, VendorPrice: vendorSku.SalePrice, Status: ebaiSkuStatus2Jx(vendorSku.Status), }, SkuName: vendorSku.Name, Comment: comment, SpecQuality: float64(specQuality), SpecUnit: specUnit, Weight: weight, }, }, } for _, v := range vendorSku.Photos { skuName.PictureList = append(skuName.PictureList, v.URL) } // todo, 看起来饿百只返回了最低一级的商家分类信息 for _, v := range vendorSku.CustomCatList { skuName.VendorCatIDList = append(skuName.VendorCatIDList, v.CustomCatID) } return skuName } func vendorSkuList2Jx(vendorSkuList []*ebaiapi.SkuInfo) (skuNameList []*partner.SkuNameInfo) { for _, vendorSku := range vendorSkuList { skuNameList = append(skuNameList, vendorSku2Jx(vendorSku)) } return skuNameList } func (p *PurchaseHandler) GetSensitiveWordRegexp() *regexp.Regexp { return sensitiveWordRegexp }