package ebai import ( "fmt" "strings" "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/tasksch" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) const ( MaxPageSize = 100 ) type tStoreSkuFullInfo struct { model.StoreSkuBind SpecQuality float32 `json:"specQuality"` SpecUnit string `orm:"size(8)" json:"specUnit"` // 质量或容量 Weight int `json:"weight"` // 重量/质量,单位为克,当相应的SkuName的SpecUnit为g或kg时,必须等于SpecQuality SkuStatus int Prefix string `orm:"size(255)" json:"prefix"` Name string `orm:"size(255);index" json:"name"` Comment string `orm:"size(255)" json:"comment"` IsGlobal int8 `orm:"default(1)" json:"isGlobal"` // 是否是全部(全国)可见,如果否的话,可见性由SkuPlace决定 Unit string `orm:"size(8)" json:"unit"` Img string `orm:"size(255)" json:"img"` PlaceStr string CatName string `orm:"size(255)"` CatID int `orm:"column(cat_id)"` CatEbaiID int64 `orm:"column(cat_ebai_id)"` CatEbaiSyncStatus int CatLevel int ParentCatID int `orm:"column(parent_cat_id)"` ParentCatEbaiID int64 `orm:"column(parent_cat_ebai_id)"` EbaiCat1ID int64 `orm:"column(ebai_cat1_id)"` EbaiCat2ID int64 `orm:"column(ebai_cat2_id)"` EbaiCat3ID int64 `orm:"column(ebai_cat3_id)"` } type tStoreCatInfo struct { model.StoreSkuCategoryMap CatID int `orm:"column(cat_id)"` Name string ParentID int `orm:"column(parent_id)"` Level int Type int Seq int ParentEbaiID int64 `orm:"column(parent_ebai_id)"` Children map[string]*tStoreCatInfo `orm:"-"` } var ( defCatMap = map[int]int64{ 1: 151301831158987, 2: 15347484581335, 3: 15347484581339, } ) func (p *PurchaseHandler) SyncStoreSkus(db *dao.DaoDB, storeIDs []int, skuIDs []int, isForce bool, userName string) (err error) { for _, storeID := range storeIDs { err = p.syncOneStoreSkus(db, storeID, skuIDs, isForce, userName) if err != nil { break } } return err } func (p *PurchaseHandler) syncOneStoreSkus(db *dao.DaoDB, storeID int, skuIDs []int, isForce bool, userName string) (err error) { if err = p.syncOneStoreCategoriesFromRemote2Local(db, storeID, userName); err != nil { return err } sql := ` SELECT t1.*, t2.spec_quality, t2.spec_unit, t2.weight, t2.status sku_status, t3.prefix, t3.name, t3.comment, t3.is_global, t3.unit, t3.img, t4.name cat_name, t4.id cat_id, t4.level cat_level, t5.ebai_id cat_ebai_id, t4p.id parent_cat_id, t5p.ebai_id parent_cat_ebai_id, t5p.ebai_sync_status parent_cat_ebai_sync_status, cat1.vendor_category_id ebai_cat3_id, cat2.vendor_category_id ebai_cat2_id, cat2.parent_id ebai_cat1_id FROM store_sku_bind t1 JOIN sku t2 ON t1.sku_id = t2.id JOIN sku_name t3 ON t2.name_id = t3.id JOIN sku_category t4 ON t3.category_id = t4.id JOIN sku_category t4p ON t4p.id = t4.parent_id LEFT JOIN store_sku_category_map t5 ON t5.store_id = t1.store_id AND t5.category_id = t4.id LEFT JOIN store_sku_category_map t5p ON t5p.store_id = t1.store_id AND t5p.category_id = t4p.id LEFT JOIN sku_vendor_category cat1 ON t4.ebai_category_id = cat1.vendor_category_id AND cat1.vendor_id = ? LEFT JOIN sku_vendor_category cat2 ON cat1.parent_id = cat2.vendor_category_id AND cat1.vendor_id = ? WHERE t1.store_id = ? AND (t1.ebai_sync_status <> 0) ` sqlParams := []interface{}{ model.VendorIDEBAI, model.VendorIDEBAI, storeID, } if skuIDs != nil && len(skuIDs) > 0 { sql += " AND t1.sku_id IN (" + dao.GenQuestionMarks(len(skuIDs)) + ")" sqlParams = append(sqlParams, skuIDs) } strStoreID := utils.Int2Str(storeID) var storeSkuInfoList []*tStoreSkuFullInfo if err = dao.GetRows(db, &storeSkuInfoList, sql, sqlParams...); err == nil { // globals.SugarLogger.Debug(utils.Format4Output(storeSkuInfoList, false)) catList2Add := make(map[int]int) for _, storeSku := range storeSkuInfoList { if storeSku.EbaiSyncStatus&model.SyncFlagNewMask != 0 { if storeSku.ParentCatEbaiID == 0 { catList2Add[storeSku.ParentCatID] = 1 } if storeSku.CatEbaiID == 0 { catList2Add[storeSku.CatID] = 1 } } } for k := range catList2Add { if err = dao.AddStoreCategoryMap(db, storeID, k, model.VendorIDEBAI, "", model.SyncFlagNewMask, userName); err != nil { return err } } if err = p.syncOneStoreCategoriesFromLocal2Remote(db, storeID, userName); err != nil { return err } if err = dao.GetRows(db, &storeSkuInfoList, sql, sqlParams...); err == nil { task := tasksch.RunTask("syncOneStoreSkus skus", false, nil, 0, 1, userName, func(batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { storeSku := batchItemList[0].(*tStoreSkuFullInfo) updateFields := []string{model.FieldEbaiSyncStatus} if storeSku.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { err = api.EbaiAPI.SkuDelete(strStoreID, utils.Int64ToStr(storeSku.EbaiID)) } else if storeSku.EbaiSyncStatus&model.SyncFlagNewMask != 0 { // globals.SugarLogger.Debug(utils.Format4Output(genSkuParamsFromStoreSkuInfo(storeSku), false)) if storeSku.EbaiID, err = api.EbaiAPI.SkuCreate(strStoreID, storeSku.SkuID, genSkuParamsFromStoreSkuInfo(storeSku)); err == nil { // todo 创建SKU后马上绑定分类,会失败,待解决 err = api.EbaiAPI.SkuShopCategoryMap(strStoreID, storeSku.EbaiID, utils.Int64ToStr(storeSku.CatEbaiID)) updateFields = append(updateFields, model.FieldEbaiID) } } else if storeSku.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 { _, err = api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)) } if err == nil { storeSku.EbaiSyncStatus = 0 _, err = dao.UpdateEntity(nil, &storeSku.StoreSkuBind, updateFields...) } return nil, err }, storeSkuInfoList) _, err = task.GetResult(0) } } return err } func (p *PurchaseHandler) SyncStoreCategories(db *dao.DaoDB, storeIDs []int, catIDs []int) (err error) { // sql := ` // SELECT t1.*, t2.spec_quality, t2.spec_unit, t2.weight, // t3.prefix, t3.name, t3.comment, t3.is_global, t3.unit, t3.img, // t4.ebai_category_id, t4.name cat_name, t5.ebai_id cat_id, t5.ebai_category_id // FROM store_sku_bind t1 // JOIN sku t2 ON t1.sku_id = t2.skuIDs // JOIN sku_name t3 ON t2.name_id = t3.id // JOIN sku_category t4 ON t3.category_id = t4.id // LEFT JOIN store_sku_category_map t5 ON t5.store_id = t1.store_id AND t5.category_id = t4.id // WHERE t1.store_id = ? AND (t1.ebai_sync_status <> 0 // ` return err } func (p *PurchaseHandler) ReadStoreCategories(storeID int) (cats []*model.SkuCategory, err error) { return nil, err } func (p *PurchaseHandler) ReadStoreSku(storeID, skuID int) (skuNameExt *model.SkuNameExt, err error) { return skuNameExt, err } func (p *PurchaseHandler) GetAllRemoteSkus(storeID int) (skus []map[string]interface{}, err error) { page1, err := api.EbaiAPI.SkuList(utils.Int2Str(storeID), utils.Params2Map("pagesize", MaxPageSize)) skus = append(skus, page1.List...) if err == nil { if page1.Pages > 1 { pages := make([]int, page1.Pages-1) for i := 2; i <= page1.Pages; i++ { pages[i-2] = i } task := tasksch.RunTask("GetAllRemoteSkus", false, nil, 0, 1, "", func(batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { callParams := map[string]interface{}{ "pagesize": MaxPageSize, "page": batchItemList[0], } pageSku, err2 := api.EbaiAPI.SkuList(utils.Int2Str(storeID), callParams) if err2 == nil { return pageSku.List, err2 } globals.SugarLogger.Debug(utils.Format4Output(callParams, false)) return nil, err2 }, pages) result, err2 := task.GetResult(0) if err = err2; err == nil { for _, v := range result { skus = append(skus, v.(map[string]interface{})) } } } } return skus, err } func (p *PurchaseHandler) DeleteRemoteSkus(storeID int, vendorSkuIDs []string) (err error) { if vendorSkuIDs == nil { result, err2 := p.GetAllRemoteSkus(storeID) if err = err2; err == nil { vendorSkuIDs = make([]string, len(result)) for k, v := range result { vendorSkuIDs[k] = utils.Interface2String(v["sku_id"]) } } } task := tasksch.RunTask("DeleteRemoteSkus", false, nil, 0, 100, "", func(batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { strList := make([]string, len(batchItemList)) for k, v := range batchItemList { strList[k] = v.(string) } return nil, api.EbaiAPI.SkuDelete(utils.Int2Str(storeID), strings.Join(strList, ",")) }, vendorSkuIDs) _, err = task.GetResult(0) return err } func (p *PurchaseHandler) DeleteRemoteCategories(storeID int, vendorCatIDs []int64) (err error) { strStoreID := utils.Int2Str(storeID) if vendorCatIDs == nil { result, err2 := api.EbaiAPI.ShopCategoryGet(strStoreID) if err = err2; err == nil { vendorCatIDs = make([]int64, len(result)) for k, v := range result { vendorCatIDs[k] = v.CategoryID } } } task := tasksch.RunTask("DeleteRemoteCategories", false, nil, 0, 1, "", func(batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { return nil, api.EbaiAPI.ShopCategoryDelete(strStoreID, batchItemList[0].(int64)) }, vendorCatIDs) _, err = task.GetResult(0) return err } /////////// func genSkuParamsFromStoreSkuInfo(storeSku *tStoreSkuFullInfo) map[string]interface{} { return map[string]interface{}{ "name": jxutils.ComposeSkuName(storeSku.Prefix, storeSku.Name, storeSku.Comment, storeSku.Unit, storeSku.SpecQuality, storeSku.SpecUnit, 0), "status": jxSkuStatus2Ebai(jxutils.MergeSkuStatus(storeSku.SkuStatus, storeSku.Status)), "left_num": ebaiapi.MaxLeftNum, "sale_price": storeSku.Price, "market_price": storeSku.Price, "cat1_id": getEbaiCat(storeSku.EbaiCat1ID, 1), "cat2_id": getEbaiCat(storeSku.EbaiCat2ID, 2), "cat3_id": getEbaiCat(storeSku.EbaiCat3ID, 3), "weight": storeSku.Weight, "photos": []map[string]interface{}{ map[string]interface{}{ "is_master": true, "url": storeSku.Img, }, }, } } func jxSkuStatus2Ebai(status int) int { if status <= 0 { return ebaiapi.SkuStatusOffline } return ebaiapi.SkuStatusOnline } func getEbaiCat(catID int64, level int) int64 { if catID == 0 { return defCatMap[level] } return catID } // 从饿百同步分类信息到本地 func (p *PurchaseHandler) syncOneStoreCategoriesFromRemote2Local(db *dao.DaoDB, storeID int, userName string) (err error) { sql := ` SELECT t2.*, t1.id cat_id, t1.name, t1.parent_id, t1.level, t1.type, t1.seq FROM sku_category t1 LEFT JOIN store_sku_category_map t2 ON t1.id = t2.category_id AND t2.store_id = ? AND (t2.deleted_at = ?) WHERE t1.deleted_at = ? ORDER BY t1.level ` var catList []*tStoreCatInfo if err = dao.GetRows(db, &catList, sql, storeID, utils.DefaultTimeValue, utils.DefaultTimeValue); err == nil { cat1Map := map[string]*tStoreCatInfo{} for _, v := range catList { v.Name = utils.FilterMb4(v.Name) if v.Level == 1 { cat1 := cat1Map[v.Name] if cat1 == nil { cat1Map[v.Name] = v cat1Map[utils.Int2Str(v.CatID)] = v v.Children = make(map[string]*tStoreCatInfo) } } else { cat1 := cat1Map[utils.Int2Str(v.ParentID)] if cat1 == nil { panic(fmt.Sprintf("can not find category, id:%d", v.ParentID)) } cat1.Children[v.Name] = v } } if result, err := api.EbaiAPI.ShopCategoryGet(utils.Int2Str(storeID)); err == nil { for _, v := range result { jxCat := cat1Map[v.Name] if jxCat == nil { // 远程有,本地没有,非法类别 } else { if jxCat.EbaiID != v.CategoryID || utils.Int2Str(jxCat.CatID) != v.ShopCustomID { if jxCat.ID == 0 { // 远程有,本门店没有 globals.SugarLogger.Debug(jxCat.CatID) err = dao.AddStoreCategoryMap(db, storeID, jxCat.CatID, model.VendorIDEBAI, utils.Int64ToStr(v.CategoryID), model.SyncFlagModifiedMask, userName) } else { // 远程有,本门店有,但ID信息不一致 err = dao.AddStoreCategoryMap(db, storeID, jxCat.CatID, model.VendorIDEBAI, utils.Int64ToStr(v.CategoryID), model.SyncFlagModifiedMask, userName) } } else { // 两边都有,且信息一致 } } if err != nil { return err } } } } return err } // 从本地同步分类信息到饿百 // 测试过程中出现过,父分类创建成功后马上创建子分类会报没有父分类错 func (p *PurchaseHandler) syncOneStoreCategoriesFromLocal2Remote(db *dao.DaoDB, storeID int, userName string) (err error) { for level := 1; level <= 2; level++ { sql := ` SELECT t2.*, t1.name, t1.parent_id, t1.level, t1.type, t1.seq, t2p.ebai_id parent_ebai_id FROM sku_category t1 LEFT JOIN sku_category t1p ON t1.parent_id = t1p.id JOIN store_sku_category_map t2 ON t1.id = t2.category_id AND t2.store_id = ? AND t2.ebai_sync_status <> 0 LEFT JOIN store_sku_category_map t2p ON t1p.id = t2p.category_id AND t2p.store_id = ? WHERE t1.level = ? ` var catList []*tStoreCatInfo sqlParams := []interface{}{ storeID, storeID, level, } if err = dao.GetRows(db, &catList, sql, sqlParams...); err == nil { strStoreID := utils.Int2Str(storeID) task := tasksch.RunTask("syncOneStoreCategoriesFromLocal2Remote", false, nil, 0, 1, userName, func(batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { updateFields := []string{model.FieldEbaiSyncStatus} catInfo := batchItemList[0].(*tStoreCatInfo) // globals.SugarLogger.Debug(utils.Format4Output(catInfo, false)) db2 := dao.GetDB() if catInfo.EbaiSyncStatus&model.SyncFlagDeletedMask != 0 { // 删除 err = api.EbaiAPI.ShopCategoryDelete(strStoreID, catInfo.EbaiID) } else if catInfo.EbaiSyncStatus&model.SyncFlagNewMask != 0 { // 新增 ebaiID, err2 := api.EbaiAPI.ShopCategoryCreate(strStoreID, catInfo.ParentEbaiID, utils.FilterMb4(catInfo.Name), int(catInfo.Seq+1), utils.Int2Str(catInfo.ID)) if err = err2; err == nil { catInfo.EbaiID = ebaiID updateFields = append(updateFields, model.FieldEbaiID) } } else if catInfo.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 { // 修改 err = api.EbaiAPI.ShopCategoryUpdate(strStoreID, catInfo.EbaiID, utils.FilterMb4(catInfo.Name), int(catInfo.Seq+1), utils.Int2Str(catInfo.ID)) } if err == nil { catInfo.EbaiSyncStatus = 0 _, err = dao.UpdateEntity(db2, &catInfo.StoreSkuCategoryMap, updateFields...) } return nil, err }, catList) _, err = task.GetResult(0) } } return err }