diff --git a/business/jxstore/cms/system_store_sku.go b/business/jxstore/cms/system_store_sku.go index d751386fe..eca1a237d 100644 --- a/business/jxstore/cms/system_store_sku.go +++ b/business/jxstore/cms/system_store_sku.go @@ -4,14 +4,19 @@ import ( "encoding/json" "fmt" "git.rosy.net.cn/baseapi/platformapi/ebaiapi" + product_addV2_request "git.rosy.net.cn/baseapi/platformapi/tiktok_shop/sdk-golang/api/product_addV2/request" + "git.rosy.net.cn/baseapi/platformapi/tiktok_shop/tiktok_api" + "git.rosy.net.cn/jx-callback/business/jxutils" "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/purchase/mtwm" + "git.rosy.net.cn/jx-callback/business/partner/purchase/tiktok_store" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" beego "github.com/astaxie/beego/server/web" + "strings" "time" "git.rosy.net.cn/baseapi/platformapi/mtwmapi" @@ -50,6 +55,14 @@ func CopyOnStoreSkuToOther(ctx *jxcontext.Context, fromStoreId, toStoreId string _, copySkuErr, err = CopyMtToMT(ctx, fromStore, toStore, isAsync, offSet) case model.VendorIDEBAI: _, copySkuErr, err = CopyEBaiToEBai(ctx, fromStore, toStore, isAsync, offSet) + case 114: // 美团到抖音 + _, copySkuErr, err = CopyMtToTiktok(ctx, fromStore, toStore, isAsync, offSet) + case 13: // 美团到饿了么 + _, copySkuErr, err = CopyMtToEBai(ctx, fromStore, toStore, isAsync, offSet) + case 31: // 饿了么到美团 + _, copySkuErr, err = CopyEBaiToMt(ctx, fromStore, toStore, isAsync, offSet) + case 10: // 美团到京东 + default: return nil, fmt.Errorf("暂时还不支持") } @@ -64,7 +77,7 @@ func CopyOnStoreSkuToOther(ctx *jxcontext.Context, fromStoreId, toStoreId string return copySkuErr, err } -//#region 同步商品 +//#region 饿百同步商品 // CopyEBaiToEBai 饿了么商品复制到饿了么 func CopyEBaiToEBai(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isAsync bool, offset int) (hint string, errList []string, err error) { @@ -180,10 +193,14 @@ func BatchInitSkuEBai2EBai(ctx *jxcontext.Context, fromSku []*ebaiapi.SkuInfo, t return errList } +//#endregion + +//#region 美团同步商品 + // CopyMtToMT 美团商品复制到美团 func CopyMtToMT(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isAsync bool, offSet int) (hint string, data []string, err error) { - var fromApi *mtwmapi.API //= mtwm.GetAPI(fromStore.VendorOrgCode, fromStore.ID, fromStore.VendorStoreID) - var toApi *mtwmapi.API // = mtwm.GetAPI(toStore.VendorOrgCode, toStore.ID, toStore.VendorStoreID) + var fromApi *mtwmapi.API + var toApi *mtwmapi.API var errList = make([]*mtwmapi.AppFoodResult, 0, 0) var errData = make([]string, 0, 0) @@ -207,35 +224,35 @@ func CopyMtToMT(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isA switch step { case 1: // 同步分类 - //fromCategoryList, err := fromApi.RetailCatList(fromStore.VendorStoreID) - //if len(fromCategoryList) == model.NO { + fromCategoryList, err := fromApi.RetailCatList(fromStore.VendorStoreID) + if len(fromCategoryList) == model.NO { + return nil, err + } + //toCategoryList, err := toApi.RetailCatList(toStore.VendorStoreID) + //if err != nil { // return nil, err //} - ////toCategoryList, err := toApi.RetailCatList(toStore.VendorStoreID) - ////if err != nil { - //// return nil, err - ////} - //for _, v := range fromCategoryList { - // categoryErr := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ - // CategoryCode: v.Code, - // Sequence: v.Sequence, - // }) - // if categoryErr != nil { - // globals.SugarLogger.Debugf("err := RetailCatUpdate : %v", categoryErr) - // } - // if v.Children != nil && len(v.Children) != 0 { - // for _, c := range v.Children { - // if err3 := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ - // CategoryNameOrigin: v.Name, - // SecondaryCategoryCode: c.Code, - // SecondaryCategoryName: c.Name, - // Sequence: c.Sequence, - // }); err3 != nil { - // globals.SugarLogger.Debugf("err := RetailCatUpdate Children : %v", err3) - // } - // } - // } - //} + for _, v := range fromCategoryList { + categoryErr := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ + CategoryCode: v.Code, + Sequence: v.Sequence, + }) + if categoryErr != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate : %v", categoryErr) + } + if v.Children != nil && len(v.Children) != 0 { + for _, c := range v.Children { + if err3 := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ + CategoryNameOrigin: v.Name, + SecondaryCategoryCode: c.Code, + SecondaryCategoryName: c.Name, + Sequence: c.Sequence, + }); err3 != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate Children : %v", err3) + } + } + } + } case 2: i := offSet @@ -462,6 +479,743 @@ func createCommonAttrValue(apiObj *mtwmapi.API, tempCatID int64, name string) (s //#endregion +//#region 美团同步到抖音商品 + +// CopyMtToTiktok 复制美团商品到抖音 +func CopyMtToTiktok(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isAsync bool, offSet int) (hint string, data []string, err error) { + var fromApi *mtwmapi.API //= mtwm.GetAPI(fromStore.VendorOrgCode, fromStore.ID, fromStore.VendorStoreID) + var toApi = partner.CurAPIManager.GetAPI(model.VendorIDDD, toStore.VendorOrgCode).(*tiktok_api.API) + var errList = make([]*mtwmapi.AppFoodResult, 0, 0) + var errData = make([]string, 0, 0) + + if fromStore.VendorOrgCode == globals.Mtwm2Code { + fromApi = mtwmapi.New(beego.AppConfig.DefaultString("mtwmAppID2", ""), beego.AppConfig.DefaultString("mtwmSecret2", ""), beego.AppConfig.DefaultString("mtwmCallbackURL2", ""), "") + fromApi.SetToken(fromStore.MtwmToken) + } else { + fromApi = partner.CurAPIManager.GetAPI(model.VendorIDMTWM, fromStore.VendorOrgCode).(*mtwmapi.API) + } + + taskName := fmt.Sprintf("将美团平台门店[%s],分类和商品复制到抖音[%s]", fromStore.VendorStoreID, toStore.VendorStoreID) + config := tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(false) + work := func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + step := batchItemList[0].(int) + switch step { + case 1: + + case 2: + i := offSet + for { + // 同步商品 + fromFoodList, err1 := fromApi.RetailListAll(fromStore.VendorStoreID, i) + if len(fromFoodList) == 0 || fromFoodList == nil { + return nil, fmt.Errorf("fromFoodList 为空 %s ,i:= %d", utils.Format4Output(err1, false), i) + } + + errDataList, err := BatchInitSkuMT2TT(ctx, fromFoodList, fromStore.VendorStoreID, toApi, toStore, i) + if err != nil { + globals.SugarLogger.Debugf("BatchInitData : %v", err) + } + if len(errDataList) > model.NO { + globals.SugarLogger.Debugf("errListData %d:= %s", i, utils.Format4Output(errDataList, false)) + errList = append(errList, errDataList...) + } + globals.SugarLogger.Debugf("==========页数[%d],数据长度[%d]", i, len(fromFoodList)) + if len(fromFoodList) < 100 { + break + } + i++ + } + } + return + } + task := tasksch.NewParallelTask(taskName, config, ctx, work, []int{1, 2}) + tasksch.HandleTask(task, nil, true).Run() + if !isAsync { + _, err = task.GetResult(0) + hint = "1" + } else { + hint = task.ID + } + + globals.SugarLogger.Debugf("======errrList := %s", utils.Format4Output(errList, false)) + for _, v := range errList { + errData = append(errData, fmt.Sprintf("food_id:%s,错误:%s", v.AppFoodCode, v.ErrorMsg)) + } + + return hint, errData, err +} + +var StoreTemp = make(map[string]string, 0) + +// BatchInitSkuMT2TT 批量创建商品美团->抖音 +func BatchInitSkuMT2TT(ctx *jxcontext.Context, fromSku []*mtwmapi.AppFood, fromStoreId string, toApi *tiktok_api.API, toStoreDetail *dao.StoreDetail, offset int) ([]*mtwmapi.AppFoodResult, error) { + errList := make([]*mtwmapi.AppFoodResult, 0, 0) + db := dao.GetDB() + + copyMap, _ := dao.GetCopyInfo(fromStoreId, model.VendorIDMTWM, toStoreDetail.VendorStoreID, model.VendorIDDD) + for k, storeSku := range fromSku { + if storeSku.AppFoodCode == "" { + storeSku.AppFoodCode = fmt.Sprintf("%d_%d", storeSku.Ctime, k) + } + // 没查询到主品创建记录,创建主品 + if copyMap[storeSku.AppFoodCode] == nil || copyMap[storeSku.AppFoodCode].MainSkuId == "" { + param := &product_addV2_request.ProductAddV2Param{ + Name: utils.LimitUTF8StringLen(storeSku.Name, 90), + PayType: tiktok_api.TiktokPayType1, + ReduceType: tiktok_api.SkuReduceTypePayMakeOrder, + DeliveryDelayDay: tiktok_api.DeliveryDelayDayToDay, + PresellType: tiktok_api.SendGoodsTypeNow, + Supply7dayReturn: 0, // 是否支持7天无理由,0不支持,1支持,2支持(拆封后不支持) + Mobile: toStoreDetail.Tel1, + Commit: true, + //Specs: "重量|" + utils.Float64ToStr(float64(storeSku.SpecQuality)) + storeSku.SpecUnit, + NeedRechargeMode: false, + SellChannel: []int64{0}, + StartSaleType: 0, + PickupMethod: "0", + OuterProductId: storeSku.AppFoodCode, // 本地skuId为外部商品id + } + specs := "重量|" + specsList := make([]string, 0, 0) + upc := "" + for _, sl := range storeSku.SkuList { + specsList = append(specsList, strings.Split(sl.Spec, "*")[0]) + param.Weight = utils.Str2Float64(sl.Weight) + upc = sl.Upc + } + param.Specs = specs + strings.Join(specsList, ",") + // 获取上传图,商品轮播图 + imgs := make([]tiktok_api.Imgs, 0, 0) + for spk, spl := range storeSku.PictureList { + switch spk { + case 0: + imgs = append(imgs, tiktok_api.Imgs{ + Name: "white_" + utils.Int2Str(toStoreDetail.ID) + "_" + spl[21:54], + Url: spl, + }) + case len(storeSku.PictureList) - 1: + imgs = append(imgs, tiktok_api.Imgs{ + Name: "detail_" + utils.Int2Str(toStoreDetail.ID) + "_" + spl[21:54], + Url: spl, + }) + default: + imgs = append(imgs, tiktok_api.Imgs{ + Name: utils.Int2Str(toStoreDetail.ID) + "_" + spl[21:54], + Url: spl, + }) + } + } + tiktokImgList, err := toApi.BatchUploadImages(imgs) + if err != nil { + errList = append(errList, &mtwmapi.AppFoodResult{ + AppFoodCode: storeSku.AppFoodCode, + ErrorMsg: fmt.Sprintf("%s:%s,图片上传错误", storeSku.Name, err.Error()), + }) + continue + } + + picList := make([]string, 0, 0) + for til, v := range tiktokImgList { + if strings.Contains(til, "detail_") { + param.Description = v.ByteUrl + continue + } + if strings.Contains(til, "white_") { + param.WhiteBackGroundPicUrl = v.ByteUrl + } + picList = append(picList, v.ByteUrl) + } + param.Pic = strings.Join(picList, "|") + + // 自动推导分类id + if len(param.Pic) != 0 { + var vendorCategoryId int64 = 0 + // 根据图片推导分类 + picList = append(picList, param.WhiteBackGroundPicUrl, param.Description) + vendorCategoryId, _ = toApi.GetRecommendCategory(picList) + // 根据名字推导分类 + if vendorCategoryId == 0 { + vendorCategoryId, _ = toApi.GetRecommendCategoryByName(param.Name) + } + if vendorCategoryId == 0 || err != nil { + errList = append(errList, &mtwmapi.AppFoodResult{ + AppFoodCode: storeSku.AppFoodCode, + ErrorMsg: fmt.Sprintf("%s:%s,自动推导分类错误", storeSku.Name, err.Error()), + }) + continue + } + param.CategoryLeafId = vendorCategoryId + } + + // 是否支持七天无理由 + if toApi.GetProductUpdateRule(param.CategoryLeafId) { + param.Supply7dayReturn = 1 + } else { + param.Supply7dayReturn = 0 + } + + // weight_unit 目前抖音只支持g和kg两种 + param.WeightUnit = tiktok_api.WeightUint_G + + // spec_prices + //param.SpecPrices = GetSpecPrices(param.Specs, vendorStoreID, 0, storeSku) + skuSize := make([]*tiktok_api.SpecDetailList, 0, 0) + name1 := strings.Split(strings.Split(param.Specs, "|")[1], ",") + for _, sl := range storeSku.SkuList { + for i := 0; i < len(name1); i++ { + if name1[i] == strings.Split(sl.Spec, "*")[0] { + sku := &tiktok_api.SpecDetailList{ + SpecDetailName1: name1[i], + Price: utils.Float64TwoInt(utils.Str2Float64(sl.Price)), + Code: storeSku.AppFoodCode + fmt.Sprintf("_%d", i), + StepStockNum: 0, + SupplierID: "", + OuterSkuID: storeSku.AppFoodCode + fmt.Sprintf("_%d", i), + } + + sku.DeliveryInfos = []*tiktok_api.DeliveryInfos{ + {InfoType: "weight", InfoUnit: name1[i][len(name1[i])-1:], InfoValue: name1[i]}, + } + sku.StockNum = utils.Str2Int(sl.Stock) + sku.SkuType = 1 + sku.StockNumMap = map[string]int64{toStoreDetail.VendorStoreID: int64(sku.StockNum)} + skuSize = append(skuSize, sku) + } + } + } + data, _ := json.Marshal(skuSize) + param.SpecPrices = string(data) + + // 获取商品的属性 + //param.ProductFormatNew, param.StandardBrandId, err = MakeProductFormatNew(api, int64(storeSku.NameID), param.CategoryLeafId, storeSku.Upc, storeSku.UpcBrandName, storeSku.UpcTiktokBrandId) + if upc == "" { + param.StandardBrandId = 596120136 + } else { + brandName, err := tiktok_store.GetBrandByBrandName(upc) + if err != nil { + param.StandardBrandId = 596120136 + } else { + if strings.Contains(brandName, "/") || strings.Contains(brandName, "(") || strings.Contains(brandName, "(") { + brandName = strings.Split(brandName, "/")[0] + } + if strings.Contains(brandName, "(") { + brandName = strings.Split(brandName, "(")[0] + } + if strings.Contains(brandName, "(") { + brandName = strings.Split(brandName, "(")[0] + } + standardBrandId, err := toApi.GetSkuBrand(param.CategoryLeafId, brandName) + if err != nil { + param.StandardBrandId = 596120136 + } else { + param.StandardBrandId = standardBrandId + } + } + } + if param.StandardBrandId == 0 { + param.StandardBrandId = 596120136 + } + categoryList, err := toApi.GetCatePropertyV2(param.CategoryLeafId) + if err != nil { + errList = append(errList, &mtwmapi.AppFoodResult{ + AppFoodCode: storeSku.AppFoodCode, + ErrorMsg: fmt.Sprintf("%s:%s,根据商品分类推导属性错误", storeSku.Name, err.Error()), + }) + continue + } + categoryMap := make(map[string][]map[string]interface{}) + for _, v := range categoryList.Data.Data { + if v.Required != model.YES { + continue + } + options := make([]map[string]interface{}, 0) + if v.PropertyName == "品牌" { + options = append(options, map[string]interface{}{"name": v.PropertyName, "value": param.StandardBrandId, "diy_type": v.DiyType}) + categoryMap[utils.Int64ToStr(v.PropertyId)] = options + } else if v.PropertyName == "产地" { + options = append(options, map[string]interface{}{"name": v.PropertyName, "value": 13850, "diy_type": v.DiyType}) + categoryMap[utils.Int64ToStr(v.PropertyId)] = options + } else if len(options) == 0 { + options = append(options, map[string]interface{}{"name": v.PropertyName, "value": 0, "diy_type": v.DiyType}) + categoryMap[utils.Int64ToStr(v.PropertyId)] = options + } else { + options = append(options, map[string]interface{}{"name": v.PropertyName, "value": v.Options[0].Value, "diy_type": v.DiyType}) + categoryMap[utils.Int64ToStr(v.PropertyId)] = options + } + } + + param.ProductFormatNew = utils.Format4Output(categoryMap, false) + //param.ProductFormatNew, err = MakeProductFormatNew(api, int64(storeSku.NameID), param.CategoryLeafId) + if StoreTemp[toStoreDetail.VendorStoreID] != "" { + idList := strings.Split(StoreTemp[toStoreDetail.VendorStoreID], "_") + param.FreightId, param.SaleLimitId = utils.Str2Int64(idList[0]), utils.Str2Int64(idList[1]) + } else { + // 运费模板 + param.FreightId, err = tiktok_store.GetDeliveryTemp(toApi, toStoreDetail.VendorStoreID, toStoreDetail) + if err != nil { + errList = append(errList, &mtwmapi.AppFoodResult{ + AppFoodCode: storeSku.AppFoodCode, + ErrorMsg: fmt.Sprintf("%s:%s,运费模版获取错误", storeSku.Name, err.Error()), + }) + return errList, nil + } + // 获取门店限售模板 + param.SaleLimitId, err = tiktok_store.CreateSaleTemp(utils.Str2Int64(toStoreDetail.VendorStoreID), toApi) + if err != nil { + errList = append(errList, &mtwmapi.AppFoodResult{ + AppFoodCode: storeSku.AppFoodCode, + ErrorMsg: fmt.Sprintf("%s:%s,限售模版获取错误", storeSku.Name, err.Error()), + }) + return errList, nil + } + StoreTemp[toStoreDetail.VendorStoreID] = fmt.Sprintf("%d_%d", param.FreightId, param.SaleLimitId) + } + + tiktokResult, errCreate := toApi.CreateStoreCommodity(param) // 创建主商品,同步主商品 + copyData := &model.CopyVendorSku{ + FromSkuID: storeSku.AppPoiCode, + FromSkuName: storeSku.Name, + FromStoreId: fromStoreId, + FromVendorId: model.VendorIDMTWM, + ToStoreId: toStoreDetail.VendorStoreID, + ToVendorId: model.VendorIDDD, + MainSkuId: "", + ChildrenSkuId: "", + ErrMsg: "", + } + if errCreate != nil { + copyData.ErrMsg = errCreate.Error() + } + if tiktokResult.ProductId != 0 { + copyData.MainSkuId = utils.Int64ToStr(tiktokResult.ProductId) + } + dao.CreateEntity(db, copyData) + } else { + // 主商品存在,直接同步子商品 + childrenProductId, err := toApi.CreateSubProduct(utils.Str2Int64(copyMap[storeSku.AppPoiCode].MainSkuId), utils.Str2Int64(toStoreDetail.VendorStoreID)) + // 2010004:主商品非在线审核通过状态,不允许绑定子商品 + if err != nil && strings.Contains(err.Error(), "2010004") { + // 线上本地都存在,但是线上审核不成功,就去更新主商品 + copyMap[storeSku.AppPoiCode].ErrMsg = err.Error() + dao.UpdateEntity(db, copyMap[storeSku.AppPoiCode], "ErrMsg") + continue + } + + if err != nil && strings.Contains(err.Error(), "2010001") { // 2010001:重复创建渠道商品 + storeSkuDetail, err := toApi.GetSkuDetailLocalID(toStoreDetail.VendorStoreID, storeSku.AppFoodCode) + if err != nil { + copyMap[storeSku.AppPoiCode].ErrMsg = err.Error() + dao.UpdateEntity(db, copyMap[storeSku.AppPoiCode], "ErrMsg") + continue + } + childrenProductId = storeSkuDetail.ProductId + copyMap[storeSku.AppPoiCode].ChildrenSkuId = utils.Int64ToStr(childrenProductId) + dao.UpdateEntity(db, copyMap[storeSku.AppPoiCode], "ChildrenSkuId") + } + + if childrenProductId != model.NO { + // 同步价格,库存,上架 + //childrenDetail, err := toApi.GetSkuDetail(utils.Int64ToStr(childrenProductId), "") + //if err != nil { + // copyMap[storeSku.AppPoiCode].ErrMsg = err.Error() + // dao.UpdateEntity(db, copyMap[storeSku.AppPoiCode], "ErrMsg") + // continue + //} + //for _, v := range childrenDetail.SpecPrices { + // childrenSkuId := v.SkuId + // storeSku.VendorSonSkuID = utils.Int64ToStr(childrenSkuId) + // return childrenSkuId, nil + //} + //failedList2 := upDateChildrenPriceStockLaunch(api, storeSku, childrenProductId, vendorStoreID, syncType) + if err := toApi.LaunchProduct(childrenProductId); err != nil { + copyMap[storeSku.AppPoiCode].ErrMsg = "上架失败" + err.Error() + dao.UpdateEntity(db, copyMap[storeSku.AppPoiCode], "ChildrenSkuId") + } + } + } + } + return errList, nil +} + +//#endregion + +//#region 美团同步到饿百 + +// CopyMtToEBai 复制美团商品到饿百 +func CopyMtToEBai(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isAsync bool, offSet int) (hint string, data []string, err error) { + VendorCategoryIDMap := map[string]int64{} + var fromApi *mtwmapi.API //= mtwm.GetAPI(fromStore.VendorOrgCode, fromStore.ID, fromStore.VendorStoreID) + var ebaiApi = api.EbaiAPI + var errListData = make([]string, 0, 0) + if fromStore.VendorOrgCode == globals.Mtwm2Code { + fromApi = mtwmapi.New(beego.AppConfig.DefaultString("mtwmAppID2", ""), beego.AppConfig.DefaultString("mtwmSecret2", ""), beego.AppConfig.DefaultString("mtwmCallbackURL2", ""), "") + fromApi.SetToken(fromStore.MtwmToken) + } else { + fromApi = partner.CurAPIManager.GetAPI(model.VendorIDMTWM, fromStore.VendorOrgCode).(*mtwmapi.API) + } + + taskName := fmt.Sprintf("将美团平台门店[%s],分类和商品复制到饿百[%s]", fromStore.VendorStoreID, toStore.VendorStoreID) + config := tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(false) + work := func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + step := batchItemList[0].(int) + switch step { + case 1: + // 1.加载门店商品,删除商品.当分类下没有商品时.删除分类 + //errs := LoadingStoreSkuList(ctx, toApi, toStore.VendorStoreID) + // errs := LoadingStoreSkuList(ctx, toApi, toStore.VendorStoreID) + //if errs != nil && len(errs) > 0 { + // return nil, errs[0] + //} + case 2: + //同步分类 + fromCategoryList, err := fromApi.RetailCatList(fromStore.VendorStoreID) + if len(fromCategoryList) == model.NO { + return nil, err + } + for k, v := range fromCategoryList { + parentID, categoryErr := ebaiApi.ShopCategoryCreate(utils.Int2Str(toStore.ID), 0, v.Name, len(fromCategoryList)-k) + if categoryErr != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate : %v", categoryErr) + continue + } + if v.Code == "" { + VendorCategoryIDMap[v.Name] = parentID + } else { + VendorCategoryIDMap[v.Code] = parentID + } + if v.Children != nil && len(v.Children) != 0 { + for _, c := range v.Children { + childrenCateId, err := ebaiApi.ShopCategoryCreate(utils.Int2Str(toStore.ID), parentID, c.Name, len(v.Children)-k) + if err != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate Children : %v", err) + continue + } + if c.Code == "" { + VendorCategoryIDMap[c.Name] = childrenCateId + } else { + VendorCategoryIDMap[c.Code] = childrenCateId + } + } + } + } + case 3: + i := offSet + for { + // 同步商品 + fromFoodList, err1 := fromApi.RetailListAll(fromStore.VendorStoreID, i) + if len(fromFoodList) == 0 || fromFoodList == nil { + return nil, fmt.Errorf("fromFoodList 为空 %s ,i:= %d", utils.Format4Output(err1, false), i) + } + errList := BatchInitSkuMtwm2EBai(ctx, fromFoodList, ebaiApi, utils.Int2Str(toStore.ID), VendorCategoryIDMap) + if errList != nil { + for _, err2 := range errList { + errListData = append(errListData, err2.Error()) + } + globals.SugarLogger.Debugf("BatchInitData : %s", utils.Format4Output(errList, false)) + } + i = i + 1 + } + + } + return + } + task := tasksch.NewParallelTask(taskName, config, ctx, work, []int{1, 2, 3}) + tasksch.HandleTask(task, nil, true).Run() + if !isAsync { + _, err = task.GetResult(0) + hint = "1" + } else { + hint = task.ID + } + return hint, errListData, err +} + +// BatchInitSkuMtwm2EBai 批量创建商品美团到饿百 +func BatchInitSkuMtwm2EBai(ctx *jxcontext.Context, fromSku []*mtwmapi.AppFood, toApi *ebaiapi.API, storeID string, VendorCategoryIDMap map[string]int64) []error { + var errList = make([]error, 0, 0) + for k, storeSku := range fromSku { + params := map[string]interface{}{} + + photos := []map[string]interface{}{} + for _, img := range storeSku.PictureList { + imgEbai, _ := api.EbaiAPI.PictureUpload(img, nil) + if k == 0 { + photos = append(photos, map[string]interface{}{ + "is_master": 1, + "url": imgEbai, + }) + } else { + photos = append(photos, map[string]interface{}{ + "is_master": 0, + "url": imgEbai, + }) + } + } + params["photos"] = photos + params["weight"] = storeSku.SkuList[0].Weight + params["weight"] = storeSku.SkuList[0].Weight + params["upc"] = storeSku.SkuList[0].Upc + params["name"] = storeSku.Name + //params["cat3_id"] = storeSku.CateId + if storeSku.CategoryCode != "" { + params["category_id"] = VendorCategoryIDMap[storeSku.CategoryCode] + } else { + params["category_id"] = VendorCategoryIDMap[storeSku.Name] + } + if storeSku.PictureContents != "" { + params["desc"] = storeSku.PictureContents + } + params["left_num"] = storeSku.SkuList[0].Stock + params["process_type"] = 0 // 是否支持加工服务0-不支持/1-支持 + //params["process_detail"] = storeSku.ProcessDetail + params["sale_price"] = jxutils.StandardPrice2Int(utils.Str2Float64(storeSku.SkuList[0].Price)) + if storeSku.IsSoldOut == 1 { + params["status"] = 0 + } else { + params["status"] = 1 + } + //params["minimum"] = storeSku.Minimum + //params["seven_days_no_reason"] = storeSku.SevenDaysNoReason + //if len(storeSku.SkuProperty) != model.NO { + // params["sku_property"] = storeSku.SkuProperty + //} + customSkuID := int64(storeSku.Ctime + k) + _, err := toApi.SkuCreate(ctx.GetTrackInfo(), storeID, customSkuID, params) + if err != nil { + errList = append(errList, err) + } + } + + return errList +} + +//#endregion + +//#region 饿百同步到美团 + +func CopyEBaiToMt(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail, isAsync bool, offSet int) (hint string, errList []string, err error) { + var api = api.EbaiAPI + var toApi *mtwmapi.API + if toStore.VendorOrgCode == globals.Mtwm2Code { + toApi = mtwmapi.New(beego.AppConfig.DefaultString("mtwmAppID2", ""), beego.AppConfig.DefaultString("mtwmSecret2", ""), beego.AppConfig.DefaultString("mtwmCallbackURL2", ""), "") + toApi.SetToken(toStore.MtwmToken) + } else { + toApi = partner.CurAPIManager.GetAPI(model.VendorIDMTWM, toStore.VendorOrgCode).(*mtwmapi.API) + } + + taskName := fmt.Sprintf("将美团平台门店[%s],分类和商品复制到[%s]", fromStore.VendorStoreID, toStore.VendorStoreID) + config := tasksch.NewParallelConfig().SetParallelCount(1).SetIsContinueWhenError(false) + work := func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + step := batchItemList[0].(int) + switch step { + case 1: + //同步分类 + fromCategoryList, err := api.ShopCategoryGet(utils.Int2Str(fromStore.ID)) + if err != nil { + return nil, err + } + for _, v := range fromCategoryList { + categoryErr := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ + CategoryCode: utils.Int64ToStr(v.CategoryID), + Sequence: v.Rank, + }) + if categoryErr != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate : %v", categoryErr) + } + if v.Children != nil && len(v.Children) != 0 { + for _, c := range v.Children { + if err3 := toApi.RetailCatUpdate(toStore.VendorStoreID, v.Name, &mtwmapi.Param4UpdateCat{ + CategoryNameOrigin: v.Name, + SecondaryCategoryCode: utils.Int64ToStr(c.CategoryID), + SecondaryCategoryName: c.Name, + Sequence: c.Rank, + }); err3 != nil { + globals.SugarLogger.Debugf("err := RetailCatUpdate Children : %v", err3) + } + } + } + } + + case 2: + i := offSet + for { + // 同步商品 + fromFoodList, err1 := api.SkuList(utils.Int2Str(fromStore.ID), &ebaiapi.SkuListParams{ + Page: i, + }) + if len(fromFoodList.List) == 0 || fromFoodList == nil { + return nil, fmt.Errorf("fromFoodList 为空 %s ,i:= %d", utils.Format4Output(err1, false), i) + } + + errDataList, err := BatchInitSkuEBai2Mt(ctx, fromFoodList.List, toApi, toStore.VendorStoreID, i) + if err != nil { + globals.SugarLogger.Debugf("BatchInitData : %v", err) + } + if len(errDataList) > model.NO { + globals.SugarLogger.Debugf("errListData %d:= %s", i, utils.Format4Output(errDataList, false)) + for _, v := range errDataList { + errList = append(errList, fmt.Sprintf("%s,%s", v.AppFoodCode, v.ErrorMsg)) + } + } + globals.SugarLogger.Debugf("==========页数[%d],数据长度[%d]", i, len(fromFoodList.List)) + if len(fromFoodList.List) < 100 { + break + } + i++ + } + } + return + } + task := tasksch.NewParallelTask(taskName, config, ctx, work, []int{1, 2}) + tasksch.HandleTask(task, nil, true).Run() + if !isAsync { + _, err = task.GetResult(0) + hint = "1" + } else { + hint = task.ID + } + + globals.SugarLogger.Debugf("======errrList := %s", utils.Format4Output(errList, false)) + return hint, errList, err + +} + +func BatchInitSkuEBai2Mt(ctx *jxcontext.Context, fromSku []*ebaiapi.SkuInfo, toApi *mtwmapi.API, vendorStoreID string, offset int) ([]*mtwmapi.AppFoodResult, error) { + errList := make([]*mtwmapi.AppFoodResult, 0, 0) + foodDataList := make([]map[string]interface{}, len(fromSku)) + isNeedUpdatePrice := true + for i, storeSku := range fromSku { + foodData := make(map[string]interface{}) + foodDataList[i] = foodData + if storeSku.CustomSkuId != "" { + foodData[mtwmapi.KeyAppFoodCode] = storeSku.CustomSkuId + } else { + foodData[mtwmapi.KeyAppFoodCode] = storeSku.SkuId + } + skus := []map[string]interface{}{ + map[string]interface{}{ + "sku_id": foodData[mtwmapi.KeyAppFoodCode], + }, + } + foodData["skus"] = skus + foodData["name"] = utils.LimitUTF8StringLen(storeSku.Name, mtwmapi.MaxSkuNameCharCount) + //foodData["description"] = storeSku.Comment + if isNeedUpdatePrice { + foodData["price"] = jxutils.IntPrice2Standard(int64(storeSku.SalePrice)) + } + if storeSku.Minimum != 0 { + foodData["min_order_count"] = storeSku.Minimum + } else { + foodData["min_order_count"] = 1 + } + foodData["unit"] = "份" + //todo 增加商品必填属性 + attr := mtwm.SwitchAttr(toApi, vendorStoreID, 0, 0, storeSku.Name) + if attr != "" { + foodData["common_attr_value"] = attr + } + //if storeSku.SellPoint != "" {// 卖点 + // foodData["sell_point"] = storeSku.SellPoint + //} + //if storeSku.SellPointTimes != "" {// 卖点展示期 + // foodData["sell_point_available_times"] = storeSku.SellPointTimes + //} + if storeSku.CateId2 != 0 { + foodData["category_code"] = storeSku.CateId2 + } else { + foodData["category_name"] = storeSku.CateName2 + } + if storeSku.Status == "" { + foodData["is_sold_out"] = 0 + } else { + foodData["is_sold_out"] = 1 + } + photos := make([]string, 0, len(storeSku.Photos)) + for _, v := range storeSku.Photos { + photos = append(photos, v.Url) + } + foodData["picture"] = strings.Join(photos, ",") + if storeSku.Rtf != "" { + foodData["picture_contents"] = storeSku.Rtf + } + + //if storeSku.QuaPictures != "" { + // foodData["qua_pictures"] = storeSku.QuaPictures + // foodData["qua_effective_date"] = storeSku.QuaEffectiveDate + // foodData["qua_approval_date"] = storeSku.QuaApprovalDate + //} + + // 周期性可售时间段 + //if storeSku.StatusSaleBegin != model.NO && storeSku.StatusSaleEnd != model.NO { + // saleStart := utils.Int2Str(int(storeSku.StatusSaleBegin)) + // saleEnd := utils.Int2Str(int(storeSku.StatusSaleEnd)) + // for { + // if len(saleStart) != 4 { + // saleStart = "0" + saleStart + // } + // if len(saleEnd) != 4 { + // saleEnd += "0" + saleEnd + // } + // if len(saleEnd) == 4 && len(saleStart) == 4 { + // break + // } + // } + // saleStart = fmt.Sprintf("%s:%s", saleStart[:2], saleStart[2:]) + // saleEnd = fmt.Sprintf("%s:%s", saleEnd[:2], saleEnd[2:]) + // availableTimes := fmt.Sprintf("%s-%s", saleStart, saleEnd) + // available, _ := json.Marshal(map[string]string{"monday": availableTimes, "tuesday": availableTimes, "wednesday": availableTimes, "thursday": availableTimes, "friday": availableTimes, "saturday": availableTimes, "sunday": availableTimes}) + // foodData["available_times"] = string(available) + //} + //foodData["sequence"] = storeSku.GetSeq() + if tempCateId, err := toApi.RetailRecommendTag(storeSku.Name, vendorStoreID, 0, mtwmapi.TypeCategory); err == nil { + foodData["tag_id"] = int64(tempCateId.TagID) + } + + //skus[0]["spec"] = jxutils.ComposeSkuSpec(storeSku.SpecQuality, storeSku.SpecUnit) + skus[0]["price"] = utils.Float64ToStr(jxutils.IntPrice2Standard(int64(storeSku.SalePrice))) + skus[0]["stock"] = utils.Int2Str(storeSku.LeftNum) + skus[0]["upc"] = storeSku.Upc + if storeSku.ShelfNumber != "" { + skus[0]["location_code"] = storeSku.ShelfNumber + } + //skus[0]["ladder_box_num"] = "0" + //skus[0]["ladder_box_price"] = "0" + // 下面这个两个和上面有点重复,但是上面两个在更新的时候美团不识别,不知道创建的时候会不会覆盖一下吧(更新只能用下面这个) + skus[0]["box_num"] = "0" + skus[0]["box_price"] = "0" + if foodData["tag_id"] != nil { + skus[0]["weight"] = storeSku.Weight // weight字段仅限服饰鞋帽、美妆、日用品、母婴、生鲜果蔬、生活超市下的便利店/超市门店品类的商家使用 + } + + } + + count := len(foodDataList) / 10 + if len(foodDataList)%10 != 0 { + count += 1 + } + for i := 0; i < count; i++ { + if i == count-1 { + failedFoodList, _ := toApi.RetailBatchInitData(ctx.GetTrackInfo(), vendorStoreID, foodDataList[i*10:]) + if len(failedFoodList) != 0 { + globals.SugarLogger.Debugf("failedFoodList := %s", utils.Format4Output(failedFoodList, false)) + errList = append(errList, failedFoodList...) + } + } else { + failedFoodList, _ := toApi.RetailBatchInitData(ctx.GetTrackInfo(), vendorStoreID, foodDataList[i*10:(i+1)*10]) + if len(failedFoodList) != 0 { + globals.SugarLogger.Debugf("failedFoodList := %s", utils.Format4Output(failedFoodList, false)) + errList = append(errList, failedFoodList...) + } + } + } + + return errList, nil +} + +//#endregion + +//#endregion + ////#region 同步活动 // //func CopyMtActToMt(ctx *jxcontext.Context, fromStore, toStore *dao.StoreDetail) { diff --git a/business/jxstore/cms/user2.go b/business/jxstore/cms/user2.go index 2e05af556..3f233421b 100644 --- a/business/jxstore/cms/user2.go +++ b/business/jxstore/cms/user2.go @@ -672,6 +672,7 @@ func AddUserDeliveryAddress(ctx *jxcontext.Context, address *model.UserDeliveryA if address.UserID == "" { return nil, fmt.Errorf("操作用户配送地址时必须指定UserID") } + db := dao.GetDB() lng := address.Lng lat := address.Lat diff --git a/business/jxstore/financial/financial.go b/business/jxstore/financial/financial.go index 276f5ceee..02ee2541c 100644 --- a/business/jxstore/financial/financial.go +++ b/business/jxstore/financial/financial.go @@ -41,9 +41,10 @@ func SendFilesToStores(ctx *jxcontext.Context, files []*multipart.FileHeader, ti return "", fmt.Errorf("文件名:%s不规范,没有包含三个必要的部分", fileHeader.Filename) } fileList[k].StoreID = int(utils.Str2Int64WithDefault(fileNameParts[0], 0)) - if fileList[k].StoreID < 100000 || fileList[k].StoreID > 1000000 { - return "", fmt.Errorf("文件名:%s不规范,不以合法的京西门店ID开始", fileHeader.Filename) - } + // 果园和菜市账号冲突,果园门店ID调整变大 + //if fileList[k].StoreID < 100000 || fileList[k].StoreID > 1000000 { + // return "", fmt.Errorf("文件名:%s不规范,不以合法的京西门店ID开始", fileHeader.Filename) + //} } putPolicy := storage.PutPolicy{ Scope: globals.QiniuBucket, diff --git a/business/model/dao/tao_sku_img.go b/business/model/dao/tao_sku_img.go index 6d206a49e..872cef65f 100644 --- a/business/model/dao/tao_sku_img.go +++ b/business/model/dao/tao_sku_img.go @@ -14,3 +14,19 @@ func GetVendorImg(skuId, vendorId int) (*model.TaoSkuImg, error) { return result, nil } + +func GetCopyInfo(fromVendorStoreId string, fromVendorId int, toVendorStoreID string, toVendorId int) (map[string]*model.CopyVendorSku, error) { + result := make([]*model.CopyVendorSku, 0, 0) + sql := ` SELECT * FROM copy_vendor_sku WHERE from_store_id = ? AND from_vendor_id = ? AND to_store_id = ? AND to_vendor_id = ?` + + if err := GetRows(GetDB(), &result, sql, []interface{}{fromVendorStoreId, fromVendorId, toVendorStoreID, toVendorId}); err != nil { + return nil, err + } + + data := make(map[string]*model.CopyVendorSku, len(result)) + for _, v := range result { + data[v.FromSkuID] = v + } + + return data, nil +} diff --git a/business/model/tao_sku_img.go b/business/model/tao_sku_img.go index c2148bd3d..d49ff92e0 100644 --- a/business/model/tao_sku_img.go +++ b/business/model/tao_sku_img.go @@ -1,5 +1,6 @@ package model +// 记录图片的 type TaoSkuImg struct { ID int64 `orm:"column(id)" json:"id"` SkuID int `orm:"column(sku_id)"` @@ -17,3 +18,23 @@ func (*TaoSkuImg) TableUnique() [][]string { []string{"SkuID", "VendorID"}, } } + +// CopyVendorSku 美团商品复制到抖音 +type CopyVendorSku struct { + ID int64 `orm:"column(id)" json:"id"` + FromSkuID string `orm:"column(from_sku_id);size(256)" json:"from_sku_id"` // 来源商品Id + FromSkuName string `orm:"column(from_sku_name);size(512)" json:"from_sku_name"` // 来源商品名称 + FromStoreId string `orm:"column(from_store_id);size(64)" json:"from_store_id"` // 来源门店ID + FromVendorId int `orm:"column(from_vendor_id);size(2)" json:"from_vendor_id"` // 来源平台ID + ToStoreId string `orm:"column(to_store_id);size(64)" json:"to_store_id"` // 目标门店ID + ToVendorId int `orm:"column(to_vendor_id);size(2)" json:"to_vendor_id"` // 目标平台 + MainSkuId string `orm:"column(main_sku_id);size(128)" json:"main_sku_id"` // 目标平台商品主ID + ChildrenSkuId string `orm:"column(children_sku_id);size(128)" json:"children_sku_id"` // 目标平台商品ID + ErrMsg string `orm:"column(err_msg);size(512)" json:"err_msg"` // 复制错误信息 +} + +func (*CopyVendorSku) TableUnique() [][]string { + return [][]string{ + []string{"FromStoreId", "FromSkuID"}, + } +} diff --git a/business/partner/purchase/tiktok_store/store_sku2_utils.go b/business/partner/purchase/tiktok_store/store_sku2_utils.go index ed9ab871c..f3e6bcb7b 100644 --- a/business/partner/purchase/tiktok_store/store_sku2_utils.go +++ b/business/partner/purchase/tiktok_store/store_sku2_utils.go @@ -561,7 +561,7 @@ func getTiktokBrandId(api *tiktokShop.API, db *dao.DaoDB, upc, upcBrandName, upc dao.UpdateSkuNameTiktokBrandName(db, upc, upcBrandName, utils.Int64ToStr(standardBrandId)) return standardBrandId, nil } else if upc != "" && upcBrandName == "" && upcTiktokBrandId == "" { - brandName, err := getBrandByBrandName(upc) + brandName, err := GetBrandByBrandName(upc) if err != nil { return 0, err } @@ -584,7 +584,7 @@ func getTiktokBrandId(api *tiktokShop.API, db *dao.DaoDB, upc, upcBrandName, upc return 0, errors.New("getTiktokBrandId 获取抖音品牌id异常") } -func getBrandByBrandName(upcCode string) (string, error) { +func GetBrandByBrandName(upcCode string) (string, error) { vendorOrgCode := "" if beego.BConfig.RunMode == model.ServerTypeFruits { vendorOrgCode = jdapi.JdVendorOrgCodeFruit @@ -1010,6 +1010,7 @@ func GetTiktokImgList(api *tiktokShop.API, storeId string, skuId int, detailImg, if strings.Contains(k, "white_") { whiteTiktok = v.ByteUrl localTiktokObj.Img = v.ByteUrl + continue } tiktokImg = append(tiktokImg, v.ByteUrl) } diff --git a/controllers/cms_store_sku.go b/controllers/cms_store_sku.go index 699641b22..93367224f 100644 --- a/controllers/cms_store_sku.go +++ b/controllers/cms_store_sku.go @@ -1051,9 +1051,9 @@ func (c *StoreSkuController) CopyMtToJd() { // @Title 美团门店复制到美团(相同平台门店相互复制) // @Description 美团门店复制到美团(相同平台门店相互复制) // @Param token header string true "认证token" -// @Param fromStoreID formData string true "被复制门店id" -// @Param toStoreID formData string true "复制到门店id" -// @Param vendorID formData int true "平台id" +// @Param fromStoreID formData string true "被复制门店id(平台ID)" +// @Param toStoreID formData string true "复制到门店id(平台ID)" +// @Param vendorID formData int true "平台id[(0京东-京东)(3饿百-饿百)(1美团-美团)(14抖音-抖音)(16淘宝-淘宝)(114美团-抖音)(31饿百-美团)(13美团-饿百)]" // @Param offSet formData int false "跳过页码" // @Param syncType formData int true "同步类型[1-商品/2-活动/3-前两者一起同步]" // @Success 200 {object} controllers.CallResult