Files
jx-callback/business/partner/purchase/ebai/store_sku.go
2018-11-13 10:11:41 +08:00

487 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ebai
import (
"fmt"
"strings"
"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/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)"`
PricePercentage int
}
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(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, skuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) {
userName := ctx.GetUserName()
globals.SugarLogger.Debugf("SyncStoreSkus storeID:%d, skuIDs:%v, isContinueWhenError:%t, userName:%s", storeID, skuIDs, isContinueWhenError, userName)
db := dao.GetDB()
if err = p.SyncLocalStoreCategory(db, storeID, userName); err != nil {
return "", err
}
sql := `
SELECT t8.price_percentage, t1.*, t2.spec_quality, t2.spec_unit, t2.weight, t2.status sku_status,
t3.prefix, t3.name, t2.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 AND t5.deleted_at = ?
LEFT JOIN store_sku_category_map t5p ON t5p.store_id = t1.store_id AND t5p.category_id = t4p.id AND t5p.deleted_at = ?
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 = ?
JOIN store_map t8 ON t8.store_id = t1.store_id AND t8.vendor_id = ? AND t8.deleted_at = ?
WHERE t1.store_id = ? AND (t1.ebai_sync_status <> 0)
`
sqlParams := []interface{}{
utils.DefaultTimeValue,
utils.DefaultTimeValue,
model.VendorIDEBAI,
model.VendorIDEBAI,
model.VendorIDEBAI,
utils.DefaultTimeValue,
storeID,
}
if 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.SyncStoreCategory(ctx, parentTask, storeID, false); err != nil {
return "", err
}
if err = dao.GetRows(db, &storeSkuInfoList, sql, sqlParams...); err == nil {
task := tasksch.NewParallelTask("SyncStoreSkus skus", tasksch.NewParallelConfig().SetIsContinueWhenError(isContinueWhenError), userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
storeSku := batchItemList[0].(*tStoreSkuFullInfo)
updateFields := []string{model.FieldEbaiSyncStatus}
isCreate := false
if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite {
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 {
updateFields = append(updateFields, model.FieldEbaiID)
isCreate = true
// 创建SKU后马上绑定分类会失败所以延迟绑定
time.AfterFunc(3*time.Second, func() {
if err := api.EbaiAPI.SkuShopCategoryMap(strStoreID, storeSku.EbaiID, utils.Int64ToStr(storeSku.CatEbaiID)); err == nil {
storeSku.EbaiSyncStatus = 0
dao.UpdateEntity(nil, &storeSku.StoreSkuBind, updateFields...)
}
})
}
} else if storeSku.EbaiSyncStatus&model.SyncFlagModifiedMask != 0 {
if _, err = api.EbaiAPI.SkuUpdate(strStoreID, storeSku.EbaiID, genSkuParamsFromStoreSkuInfo(storeSku)); err == nil {
err = api.EbaiAPI.SkuShopCategoryMap(strStoreID, storeSku.EbaiID, utils.Int64ToStr(storeSku.CatEbaiID))
}
}
}
if err == nil {
if isCreate {
storeSku.EbaiSyncStatus = model.SyncFlagModifiedMask
} else {
storeSku.EbaiSyncStatus = 0
}
_, err = dao.UpdateEntity(nil, &storeSku.StoreSkuBind, updateFields...)
}
return nil, err
}, storeSkuInfoList)
tasksch.AddChild(parentTask, task).Run()
_, err = task.GetResult(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))
if err == nil {
skus = append(skus, 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("GetAllRemoteSkus", nil, "", func(t *tasksch.ParallelTask, 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)
task.Run()
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.NewParallelTask("DeleteRemoteSkus", tasksch.NewParallelConfig().SetBatchSize(100), "", func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
strList := make([]string, len(batchItemList))
for k, v := range batchItemList {
strList[k] = v.(string)
}
if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite {
err = api.EbaiAPI.SkuDelete(utils.Int2Str(storeID), strings.Join(strList, ","))
}
return nil, err
}, vendorSkuIDs)
task.Run()
_, 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.NewParallelTask("DeleteRemoteCategories", nil, "", func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite {
err = api.EbaiAPI.ShopCategoryDelete(strStoreID, batchItemList[0].(int64))
}
return nil, err
}, vendorCatIDs)
task.Run()
_, err = task.GetResult(0)
return err
}
func (p *PurchaseHandler) RefreshStoresAllSkusID(ctx *jxcontext.Context, parentTask tasksch.ITask, isAsync bool, storeIDs []int) (hint string, err error) {
return hint, err
}
///////////
func genSkuParamsFromStoreSkuInfo(storeSku *tStoreSkuFullInfo) map[string]interface{} {
price := jxutils.CaculateSkuVendorPrice(storeSku.Price, storeSku.PricePercentage)
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": model.MaxStoreSkuStockQty,
"sale_price": price,
"market_price": 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) SyncLocalStoreCategory(db *dao.DaoDB, storeID int, userName string) (err error) {
globals.SugarLogger.Debugf("SyncLocalStoreCategory storeID:%d, userName:%s", storeID, userName)
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 = formatName(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
}
v.EbaiSyncStatus |= model.SyncFlagNewMask
}
result, err2 := api.EbaiAPI.ShopCategoryGet(utils.Int2Str(storeID))
if err = err2; err == nil {
dao.Begin(db)
defer func() {
dao.Rollback(db)
}()
// globals.SugarLogger.Debug(utils.Format4Output(cat1Map, false))
if err = p.processLocalCatByRemote(db, storeID, cat1Map, result, userName); err == nil {
err = p.updateLocalCatAsNew(db, cat1Map, userName)
}
if err == nil {
dao.Commit(db)
}
}
}
return err
}
// 从本地同步分类信息到饿百
// 测试过程中出现过,父分类创建成功后马上创建子分类会报没有父分类错
func (p *PurchaseHandler) SyncStoreCategory(ctx *jxcontext.Context, parentTask tasksch.ITask, storeID int, isAsync bool) (hint string, err error) {
userName := ctx.GetUserName()
globals.SugarLogger.Debugf("SyncOneStoreCategories storeID:%d, userName:%s", storeID, userName)
db := dao.GetDB()
rootTask := tasksch.NewSeqTask("ebai SyncStoreCategory", userName, func(rootTask *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) {
level := step + 1
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 = ? AND t2p.deleted_at = ?
WHERE t1.level = ?
`
var catList []*tStoreCatInfo
sqlParams := []interface{}{
storeID,
storeID,
utils.DefaultTimeValue,
level,
}
if err = dao.GetRows(db, &catList, sql, sqlParams...); err == nil {
strStoreID := utils.Int2Str(storeID)
task := tasksch.NewParallelTask("SyncStoreCategory", nil, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) {
updateFields := []string{model.FieldEbaiSyncStatus}
catInfo := batchItemList[0].(*tStoreCatInfo)
// globals.SugarLogger.Debug(utils.Format4Output(catInfo, false))
if globals.EnableStoreWrite && globals.EnableEbaiStoreWrite {
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, formatName(catInfo.Name), jxCatSeq2Ebai(catInfo.Seq), utils.Int2Str(catInfo.CategoryID))
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, formatName(catInfo.Name), jxCatSeq2Ebai(catInfo.Seq), utils.Int2Str(catInfo.CategoryID))
}
}
if err == nil {
db2 := dao.GetDB()
catInfo.EbaiSyncStatus = 0
_, err = dao.UpdateEntity(db2, &catInfo.StoreSkuCategoryMap, updateFields...)
}
return nil, err
}, catList)
rootTask.AddChild(task).Run()
_, err = task.GetResult(0)
}
return nil, err
}, 2)
tasksch.AddChild(parentTask, rootTask).Run()
if !isAsync {
_, err = rootTask.GetResult(0)
}
return rootTask.ID, err
}
func (p *PurchaseHandler) processLocalCatByRemote(db *dao.DaoDB, storeID int, localCatMap map[string]*tStoreCatInfo, remoteCatList []*ebaiapi.CategoryInfo, userName string) (err error) {
if localCatMap == nil || remoteCatList == nil {
return nil
}
for _, v := range remoteCatList {
jxCat := localCatMap[v.Name]
if jxCat == nil { // 远程有,本地没有,非法类别
// globals.SugarLogger.Debug(v.Name)
// globals.SugarLogger.Debug(utils.Format4Output(localCatMap, false))
} 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.UpdateEntityLogicallyAndUpdateSyncStatus(db, &jxCat.StoreSkuCategoryMap, map[string]interface{}{
model.FieldEbaiID: v.CategoryID,
}, userName, nil, model.FieldEbaiSyncStatus)
}
} else { // 两边都有,且信息一致
}
jxCat.EbaiSyncStatus = 0
if err = p.processLocalCatByRemote(db, storeID, jxCat.Children, v.Children, userName); err != nil {
return err
}
}
if err != nil {
return err
}
}
return nil
}
func (p *PurchaseHandler) updateLocalCatAsNew(db *dao.DaoDB, localCatMap map[string]*tStoreCatInfo, userName string) (err error) {
if localCatMap == nil {
return nil
}
for _, v := range localCatMap {
if v.EbaiSyncStatus&model.SyncFlagNewMask != 0 {
dao.WrapUpdateULEntity(&v.StoreSkuCategoryMap, userName)
if _, err = dao.UpdateEntity(db, &v.StoreSkuCategoryMap); err != nil {
return err
}
}
if err = p.updateLocalCatAsNew(db, v.Children, userName); err != nil {
return err
}
}
return nil
}
func formatName(name string) string {
return utils.TrimBlankChar(utils.FilterMb4(name))
}
// 饿百的排序是从大到小
func jxCatSeq2Ebai(seq int) int {
return 10000 - seq
}