同步EXCEL 第一版

This commit is contained in:
807875765@qq.com
2022-03-14 09:05:28 +08:00
parent cc437c32c2
commit cde7d11563
6 changed files with 278 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
package cms package cms
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -144,6 +145,18 @@ type tGetStoresSkusInfo struct {
AuditUnitPrice int 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 { type SheetParam struct {
OutSkuIDCol int OutSkuIDCol int
SkuNameIDCol int SkuNameIDCol int
@@ -5817,6 +5830,225 @@ func GetVendorStoreSkus(ctx *jxcontext.Context, storeID, vendorID int) (err erro
return err 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) { func CopyMtToJd(ctx *jxcontext.Context, mtStoreID, mtOrgCode, jdStoreID, jdOrgCode string) (err error) {
type funcType func(mtID, parentID string, catInfo []*mtwmapi.RetailCategoryInfo) type funcType func(mtID, parentID string, catInfo []*mtwmapi.RetailCategoryInfo)
var ( var (

View File

@@ -53,6 +53,9 @@ type LoopStoreMapInfo struct {
type VendorSync struct { type VendorSync struct {
} }
type ComparePlatform struct {
}
type SyncError struct { type SyncError struct {
Original error `json:"original"` Original error `json:"original"`
Message string `json:"message"` Message string `json:"message"`
@@ -73,7 +76,8 @@ type SingleStoreHandlerWrapper struct {
} }
var ( var (
CurVendorSync VendorSync CurVendorSync VendorSync
CurComparePlatform ComparePlatform
) )
var ( 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) 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) { 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") globals.SugarLogger.Debug("FullSyncStoresSkus")
hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("初始化门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs, hint, err = v.LoopStoresMap(ctx, db, fmt.Sprintf("初始化门店商品信息:%v", storeIDs), isAsync, true, vendorIDs, storeIDs,

View File

@@ -312,7 +312,7 @@ type SkuExinfoMap struct {
EndAt time.Time `orm:"type(datetime);index" json:"endAt"` //设置结束时间 EndAt time.Time `orm:"type(datetime);index" json:"endAt"` //设置结束时间
ExPrefix string `orm:"size(255)" json:"exPrefix"` //额外前缀 ExPrefix string `orm:"size(255)" json:"exPrefix"` //额外前缀
ImgWatermark string `orm:"size(512)" json:"imgWatermark"` //图片水印 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 BrandID int `orm:"column(brand_id);default(0)" json:"brandID"` //品牌ID
} }

View File

@@ -587,6 +587,7 @@ func vendorSku2Jx(appFood *mtwmapi.AppFood) (skuName *partner.SkuNameInfo) {
Prefix: prefix, Prefix: prefix,
Name: name, Name: name,
Unit: unit, Unit: unit,
Status: mtwmSkuStatus2Jx(appFood.IsSoldOut), //此处为之前一个bug 如果吧状态放到切片内层 对于内层函数中映射无法关联 永远获取到的初始值为0
SkuList: []*partner.SkuInfo{ SkuList: []*partner.SkuInfo{
&partner.SkuInfo{ &partner.SkuInfo{
StoreSkuInfo: partner.StoreSkuInfo{ StoreSkuInfo: partner.StoreSkuInfo{

View File

@@ -2,14 +2,14 @@ package controllers
import ( import (
"fmt" "fmt"
"io"
"git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/jxstore/cms" "git.rosy.net.cn/jx-callback/business/jxstore/cms"
"git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils"
"git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"github.com/astaxie/beego/server/web" "github.com/astaxie/beego/server/web"
"io"
) )
type SyncController struct { 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 删除门店平台商品信息(包括分类) // @Title 删除门店平台商品信息(包括分类)
// @Description 删除门店平台商品信息(包括分类)(!!!此操作会先清除平台上已有的商品及分类信息),此操作只适用于单门店模式平台 // @Description 删除门店平台商品信息(包括分类)(!!!此操作会先清除平台上已有的商品及分类信息),此操作只适用于单门店模式平台
// @Param token header string true "认证token" // @Param token header string true "认证token"

View File

@@ -3059,6 +3059,15 @@ func init() {
MethodParams: param.Make(), MethodParams: param.Make(),
Filters: nil, Filters: nil,
Params: 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.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"] = append(web.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:SyncController"],
web.ControllerComments{ web.ControllerComments{