From cde7d11563af762f2a80133f5fe3512ec434af51 Mon Sep 17 00:00:00 2001 From: "807875765@qq.com" <807875765@qq.com> Date: Mon, 14 Mar 2022 09:05:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5EXCEL=20=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- business/jxstore/cms/store_sku.go | 232 +++++++++++++++++++ business/jxstore/cms/sync.go | 12 +- business/model/sku.go | 2 +- business/partner/purchase/mtwm/store_sku2.go | 1 + controllers/cms_sync.go | 26 ++- routers/commentsRouter_controllers.go | 9 + 6 files changed, 278 insertions(+), 4 deletions(-) diff --git a/business/jxstore/cms/store_sku.go b/business/jxstore/cms/store_sku.go index 681ea9859..b4b8d4f7a 100644 --- a/business/jxstore/cms/store_sku.go +++ b/business/jxstore/cms/store_sku.go @@ -1,6 +1,7 @@ package cms import ( + "bytes" "errors" "fmt" "io" @@ -144,6 +145,18 @@ type tGetStoresSkusInfo struct { AuditUnitPrice int } +type tGetStoresSkusInfoContainCategoryName struct { + StoreID int `orm:"column(store_id)"` + StoreName string + model.SkuName + PayPercentage int `json:"-"` + dao.StoreSkuExt + RealMidUnitPrice int `json:"realMidUnitPrice"` //真实的该商品的全国中位价 + YbSkuName string + AuditUnitPrice int + CategoryName string `orm:"column(categoryName)"` +} + type SheetParam struct { OutSkuIDCol int SkuNameIDCol int @@ -5817,6 +5830,225 @@ func GetVendorStoreSkus(ctx *jxcontext.Context, storeID, vendorID int) (err erro return err } +type VendorStoreSkus struct { + SkuID int `json:"商品ID"` // 两平台共同包含的ID 桥梁 + SkuName string `json:"商品名"` + Status int `json:"上下架状态"` + CategoryList []string `json:"商品类别列表"` + Price int64 `json:"售价"` + Reason string `json:"原因"` +} + +//@creat by 2020-03-09 +//@Description :用于获取第三方平台一个门店下所有的商品数据 +func GetVendorStoreSkusToStruct(ctx *jxcontext.Context, storeID, vendorID int) (err error) { + + var ( + db = dao.GetDB() + vendorStoreSkus []*VendorStoreSkus + excelTitle = []string{ + "商品ID", + "商品名", + "上下架状态", + "商品类别列表", + "售价", + "原因", + } + sheetList []*excel.Obj2ExcelSheetConfig + downloadURL, fileName string + ) + storeDetail, err := dao.GetStoreDetail(db, storeID, vendorID, "") + if err != nil || storeDetail == nil { + return err + } + if partner.IsMultiStore(vendorID) { + // handler := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.IPurchasePlatformStoreSkuHandler) + // skuBareInfoList, _ := handler.GetStoreSkusBareInfo(ctx, storeDetail.VendorOrgCode, nil, storeID, storeDetail.VendorStoreID, nil) + return fmt.Errorf("暂不支持京东!") + } else { + handler := partner.GetPurchasePlatformFromVendorID(vendorID).(partner.ISingleStoreStoreSkuHandler) + // 使用处理器获取后台所有的商品 存入一个Slice中 + skuList, _ := handler.GetStoreSkusFullInfo(ctx, nil, storeID, storeDetail.VendorStoreID, nil) + // 设置一个默认原因 + var defaultReason = "不存在该商品待同步" + for _, sku := range skuList { + skus := &VendorStoreSkus{ + SkuID: sku.NameID, + SkuName: sku.Name, + Status: sku.Status, + CategoryList: sku.VendorCatIDList, + Price: sku.SkuList[0].VendorPrice, + Reason: defaultReason, + } + vendorStoreSkus = append(vendorStoreSkus, skus) + } + } + + tmpList, err := GetLocalStoreSkusByStoreID(storeID) + if err != nil { + globals.SugarLogger.Debug(err) + } else { + globals.SugarLogger.Debug(len(tmpList)) + if len(tmpList) == 0 { //数据库中无匹配项 直接将第三方数据进行导出 + goto creatExec + } else { //进行比较 + if result := ComPareLocalWithVendors(tmpList, vendorStoreSkus); result != nil { + // 将返回的切片进行覆盖 + vendorStoreSkus = result + goto creatExec + } + } + } +creatExec: + excelConf := &excel.Obj2ExcelSheetConfig{ + Title: "sheet1", + Data: vendorStoreSkus, + CaptionList: excelTitle, + } + sheetList = append(sheetList, excelConf) + if excelConf != nil { + downloadURL, fileName, err = jxutils.UploadExeclAndPushMsg(sheetList, "比较差异") + } else { + baseapi.SugarLogger.Debug("WriteToExcel: dataSuccess is nil!") + } + if err != nil { + baseapi.SugarLogger.Errorf("WriteToExcel:upload %s , %s failed error:%v", fileName, err) + } else { + noticeMsg := fmt.Sprintf("[详情点我]%s/billshow/?normal=true&path=%s \n", globals.BackstageHost, downloadURL) + ddmsg.SendUserMessage(dingdingapi.MsgTyeText, ctx.GetUserID(), "异步任务完成", noticeMsg) + baseapi.SugarLogger.Debug("WriteToExcel: dataSuccess downloadURL: [%v]", downloadURL) + } + return err +} + +// 通过门店ID 查询数据库对应门店商品管理按SKU查询门店下的所有商品 +func GetLocalStoreSkusByStoreID(storeID int) (tmp []*tGetStoresSkusInfoContainCategoryName, err error) { + sql := ` + SELECT SQL_CALC_FOUND_ROWS + t3.id store_id, t3.name store_name, t3.pay_percentage, + t1.*, + t2.name_id, t2.id sku_id, t2.spec_quality sku_spec_quality, t2.spec_unit sku_spec_unit, t2.weight, t2m.vendor_thing_id sku_jd_id, + t2.comment, t2.category_id sku_category_id, t2.status sku_status, t2.eclp_id, + t4.created_at bind_created_at, t4.updated_at bind_updated_at, t4.last_operator bind_last_operator, t4.deleted_at bind_deleted_at, + t4.sub_store_id, t4.price bind_price, IF(t4.unit_price IS NOT NULL, t4.unit_price, t1.price) unit_price, t4.status store_sku_status, t4.auto_sale_at, + t4.ebai_id, t4.mtwm_id, t4.yb_id, CONCAT(smm.yb_store_prefix,t1.yb_name_suffix) yb_sku_name, t4.jds_id, t4.jds_ware_id, + t4.jd_sync_status, t4.ebai_sync_status, t4.mtwm_sync_status, t4.yb_sync_status, t4.jds_sync_status, + t4.jd_price, t4.ebai_price, t4.mtwm_price, t4.jx_price, t4.yb_price, t4.jds_price, + t4.jd_lock_time, t4.ebai_lock_time, t4.mtwm_lock_time, t4.jx_lock_time, t4.yb_lock_time, t4.jds_lock_time, + t4.status_sale_begin, t4.status_sale_end, t4.stock, t4.mt_ladder_box_price, + t6.mid_unit_price real_mid_unit_price, + t7.unit_price audit_unit_price, + t8.name categoryName + FROM sku_name t1 + JOIN sku t2 FORCE INDEX(PRIMARY) ON t1.id = t2.name_id AND t2.deleted_at = '1970-01-01 00:00:00 +0800' + JOIN store t3 ON t3.deleted_at = '1970-01-01 00:00:00 +0800' + LEFT JOIN store_map sm ON sm.store_id = t3.id AND sm.vendor_id = 0 AND sm.deleted_at = '1970-01-01 00:00:00 +0800' + LEFT JOIN store_map smm ON smm.store_id = t3.id AND smm.deleted_at = '1970-01-01 00:00:00 +0800' AND smm.vendor_id = 4 + LEFT JOIN thing_map t2m ON t2m.thing_type = 3 AND t2m.thing_id = t2.id AND t2m.vendor_id = sm.vendor_id AND t2m.vendor_org_code = sm.vendor_org_code AND t2m.deleted_at = '1970-01-01 00:00:00 +0800' + + JOIN store_sku_bind t4 ON t4.store_id = t3.id AND t4.sku_id = t2.id AND t4.deleted_at = '1970-01-01 00:00:00 +0800' + LEFT JOIN sku_name_place_bind t5 ON t1.id = t5.name_id AND t3.city_code = t5.place_code + LEFT JOIN price_refer_snapshot t6 ON t6.city_code = 0 AND t6.sku_id = t2.id AND t6.snapshot_at = '2099-03-08 00:00:00 +0800' + LEFT JOIN store_sku_audit t7 ON t7.store_id = t3.id AND t7.name_id = t1.id AND t7.status = 0 AND t7.deleted_at = '1970-01-01 00:00:00 +0800' + LEFT JOIN sku_category t8 ON t1.category_id= t8.id + WHERE t1.deleted_at = '1970-01-01 00:00:00 +0800' AND (t1.is_global = 1 OR t5.id IS NOT NULL OR 1 = 1) + AND ((t2.status = 1 AND t1.status = 1) OR t4.status = 1) AND t3.id IN (?) AND IF(INSTR(t3.name,'饿鲜达') > 0, t2.exd_sku_id <> '', t2.exd_sku_id = '') AND t4.status >= 0 AND t4.status <= 1 AND t4.unit_price <= 99900 AND t4.unit_price >= 0 + ORDER BY t3.id, t2.name_id, t2.id` + var tmpList []*tGetStoresSkusInfoContainCategoryName + txDB, _ := dao.Begin(dao.GetDB()) + if err = dao.GetRowsTx(txDB, &tmpList, sql, storeID); err != nil { + dao.Rollback(dao.GetDB(), txDB) + + } + return tmpList, err +} + +// 比较本地和第三方平台的差异 返回一个Excel 可接收实体 +func ComPareLocalWithVendors(localSku []*tGetStoresSkusInfoContainCategoryName, vendorSkus []*VendorStoreSkus) (result []*VendorStoreSkus) { + // 进行取参比较 + var localStoreSkus []*VendorStoreSkus + var updateStoreSkus []*VendorStoreSkus + var defaultReason = "平台不存在该商品待同步" + for _, sku := range localSku { + skus := &VendorStoreSkus{ + SkuID: sku.StoreSkuExt.SkuID, + SkuName: sku.SkuName.Name, + Status: sku.Status, + CategoryList: jxStatusTransForm(sku.CategoryName), //此处需要使用函数进行转换 进行查询根据类别ID去sku_category查询返回一个子类别放入List中 + Price: vendorPriceTransForm(&sku.StoreSkuExt, 1), //此处需要使用函数转换传入一个VendorID进行比较根据ID的不同返回对应不同的字段 + Reason: defaultReason, + } + localStoreSkus = append(localStoreSkus, skus) + } + globals.SugarLogger.Debug(localStoreSkus, vendorSkus) + // 由于go语言中无法子定义struct比较规则 因此需要把数据提取出来进行处理 + // 进行比较处理 对两个slice 进行遍历 放到map中 以 skuID slice的形式进行存储 如果不存在则直接进行put操作 如果存在则进行比较类别,价格,以及上下架状态 + compareMap := make(map[int]*VendorStoreSkus) + for _, local := range localStoreSkus { + compareMap[local.SkuID] = local + } + for _, vendorSku := range vendorSkus { + if value, ok := compareMap[vendorSku.SkuID]; ok { //进行逻辑处理 + // 进行比较 如果同时满足价格状态以及末级类别 则进行remove操作 否则什么也不做 + if value.Price == vendorSku.Price && value.Status == vendorSku.Status && value.CategoryList[0] == vendorSku.CategoryList[len(vendorSku.CategoryList)-1] { + delete(compareMap, vendorSku.SkuID) + } else { //对原因进行赋值并展示出来 + //需要考虑是否存在多种情况并存的情况可以更直观的看出差异所在 + var buffer bytes.Buffer + var count = 1 + if value.Price != vendorSku.Price { + buffer.WriteString(strconv.Itoa(count)) + buffer.WriteString(".") + buffer.WriteString("价格不同步,平台价格:" + strconv.FormatInt(vendorSku.Price, 10)) + count++ + } + if value.Status != vendorSku.Status { + buffer.WriteString(strconv.Itoa(count)) + buffer.WriteString(".") + buffer.WriteString("商品上下架状态不一致") + count++ + } + if value.CategoryList[0] != vendorSku.CategoryList[len(vendorSku.CategoryList)-1] { + buffer.WriteString(strconv.Itoa(count)) + buffer.WriteString(".") + buffer.WriteString("类别不一致:平台类别为" + vendorSku.CategoryList[len(vendorSku.CategoryList)-1]) + count++ + } + value.Reason = buffer.String() + } + } + } + for _, v := range compareMap { + updateStoreSkus = append(updateStoreSkus, v) + } + globals.SugarLogger.Debug(updateStoreSkus) + return updateStoreSkus +} + +func jxStatusTransForm(categoryName string) []string { + var result []string + result = append(result, categoryName) + return result +} + +// 根据不同平台判断返回不同的价格类型 +func vendorPriceTransForm(storeSkuExt *dao.StoreSkuExt, vendorID int) int64 { + switch vendorID { + case 1: + return int64(storeSkuExt.MtwmPrice) + case 0: + return int64(storeSkuExt.JdPrice) + case 3: + return int64(storeSkuExt.EbaiPrice) + case 4: + return int64(storeSkuExt.YbPrice) + case 5: + return int64(storeSkuExt.JdsPrice) + default: + return 0 + } +} + func CopyMtToJd(ctx *jxcontext.Context, mtStoreID, mtOrgCode, jdStoreID, jdOrgCode string) (err error) { type funcType func(mtID, parentID string, catInfo []*mtwmapi.RetailCategoryInfo) var ( diff --git a/business/jxstore/cms/sync.go b/business/jxstore/cms/sync.go index 929de37aa..6a4329dd0 100644 --- a/business/jxstore/cms/sync.go +++ b/business/jxstore/cms/sync.go @@ -53,6 +53,9 @@ type LoopStoreMapInfo struct { type VendorSync struct { } +type ComparePlatform struct { +} + type SyncError struct { Original error `json:"original"` Message string `json:"message"` @@ -73,7 +76,8 @@ type SingleStoreHandlerWrapper struct { } var ( - CurVendorSync VendorSync + CurVendorSync VendorSync + CurComparePlatform ComparePlatform ) var ( @@ -549,6 +553,12 @@ func (v *VendorSync) SyncStoresSkus(ctx *jxcontext.Context, parentTask tasksch.I return v.SyncStoresSkus2(ctx, parentTask, causeFlag, db, vendorIDs, storeIDs, true, skuIDs, nil, setSyncStatus, isAsync, isContinueWhenError) } +//执行比较店铺与运营商的sku差异 +func (c *ComparePlatform) CompareStoreWithOperator(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, syncDisabled bool, excludeSkuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { + globals.SugarLogger.Debug("开始执行CompareStoreWithOperator") + return hint, makeSyncError(err) +} + func (v *VendorSync) FullSyncStoresSkus(ctx *jxcontext.Context, db *dao.DaoDB, vendorIDs []int, storeIDs []int, syncDisabled bool, excludeSkuIDs []int, isAsync, isContinueWhenError bool) (hint string, err error) { globals.SugarLogger.Debug("FullSyncStoresSkus") hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("初始化门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, diff --git a/business/model/sku.go b/business/model/sku.go index 6d717f6bf..09242cb71 100644 --- a/business/model/sku.go +++ b/business/model/sku.go @@ -312,7 +312,7 @@ type SkuExinfoMap struct { EndAt time.Time `orm:"type(datetime);index" json:"endAt"` //设置结束时间 ExPrefix string `orm:"size(255)" json:"exPrefix"` //额外前缀 ImgWatermark string `orm:"size(512)" json:"imgWatermark"` //图片水印 - ImgWatermarkMix string `orm:"size(512)" json:"imgWatermark"` //合成水印图 + ImgWatermarkMix string `orm:"size(512)" json:"ImgWatermarkMix"` //合成水印图 BrandID int `orm:"column(brand_id);default(0)" json:"brandID"` //品牌ID } diff --git a/business/partner/purchase/mtwm/store_sku2.go b/business/partner/purchase/mtwm/store_sku2.go index 52776b059..b382258f6 100644 --- a/business/partner/purchase/mtwm/store_sku2.go +++ b/business/partner/purchase/mtwm/store_sku2.go @@ -587,6 +587,7 @@ func vendorSku2Jx(appFood *mtwmapi.AppFood) (skuName *partner.SkuNameInfo) { Prefix: prefix, Name: name, Unit: unit, + Status: mtwmSkuStatus2Jx(appFood.IsSoldOut), //此处为之前一个bug 如果吧状态放到切片内层 对于内层函数中映射无法关联 永远获取到的初始值为0 SkuList: []*partner.SkuInfo{ &partner.SkuInfo{ StoreSkuInfo: partner.StoreSkuInfo{ diff --git a/controllers/cms_sync.go b/controllers/cms_sync.go index 4cb237e7f..969f9167f 100644 --- a/controllers/cms_sync.go +++ b/controllers/cms_sync.go @@ -2,14 +2,14 @@ package controllers import ( "fmt" - "io" - "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxstore/cms" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" + "git.rosy.net.cn/jx-callback/globals" "github.com/astaxie/beego/server/web" + "io" ) type SyncController struct { @@ -124,6 +124,28 @@ func (c *SyncController) FullSyncStoresSkus() { }) } +// @Title 比较门店中后台商品和运营商后台商品的差异 导出一个Excel 表格 +// @Description 由于目前同步会因为类别不同会导致商品每晚无法成功同步 因此在同步功能前新增一个导出查看差异功能 比较商品类别 +// @Param token header string true "认证token" +// @Param storeIDs formData string true "门店ID列表" +// @Param vendorIDs formData string true "厂商ID列表" +// @Param isAsync formData bool true "是否异步操作" +// @Param isContinueWhenError formData bool false "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /CompareStoreWithOperator [put] +func (c *SyncController) CompareStoreWithOperator() { + c.callCompareStoreWithOperator(func(params *tSyncCompareStoreWithOperatorParams) (retVal interface{}, errCode string, err error) { + var vendorIDs, storeIDs []int + if err = jxutils.Strings2Objs(params.StoreIDs, &storeIDs, params.VendorIDs, &vendorIDs); err != nil { + return retVal, "", err + } + globals.SugarLogger.Debug(params.StoreIDs, params.VendorIDs) + err = cms.GetVendorStoreSkusToStruct(params.Ctx, storeIDs[0], vendorIDs[0]) + return retVal, "", err + }) +} + // @Title 删除门店平台商品信息(包括分类) // @Description 删除门店平台商品信息(包括分类)(!!!此操作会先清除平台上已有的商品及分类信息),此操作只适用于单门店模式平台 // @Param token header string true "认证token" diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index 8d2914fb2..b2e07b5fd 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -3059,6 +3059,15 @@ func init() { MethodParams: param.Make(), Filters: nil, Params: nil}) + // update by 2022 /3/14 hang 比较店铺及第三方商品差异 同步前置操作 + web.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"] = append(web.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"], + web.ControllerComments{ + Method: "CompareStoreWithOperator", + Router: `/CompareStoreWithOperator`, + AllowHTTPMethods: []string{"put"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) web.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"] = append(web.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"], web.ControllerComments{