diff --git a/business/jxstore/cms/sku.go b/business/jxstore/cms/sku.go index c9eb8e766..cdedfa20e 100644 --- a/business/jxstore/cms/sku.go +++ b/business/jxstore/cms/sku.go @@ -639,17 +639,28 @@ func AddSkuName(ctx *jxcontext.Context, skuNameExt *model.SkuNameExt, userName s skuNameExt.SpecUnit = skuNameExt.Skus[0].SpecUnit } + picType := true for _, imgName := range []string{skuNameExt.Img, skuNameExt.Img2} { if imgName != "" { - _, err2 := datares.TryRegisterDataResource(ctx, skuNameExt.Name, imgName, model.ImgTypeMain, false) + dataRes, err2 := datares.TryRegisterDataResource(ctx, skuNameExt.Name, imgName, model.ImgTypeMain, false) + if dataRes.ResourceType == model.MimeTypeJpeg || dataRes.ResourceType == model.MimeTypePng { + picType = false + } if err = err2; err != nil { return nil, err } } } + if picType { + return nil, fmt.Errorf("商品图片应至少包含一张非gif格式的图片!") + } + if skuNameExt.DescImg != "" { - _, err2 := datares.TryRegisterDataResource(ctx, skuNameExt.Name+"desc", skuNameExt.DescImg, model.ImgTypeDesc, false) + dataRes, err2 := datares.TryRegisterDataResource(ctx, skuNameExt.Name+"desc", skuNameExt.DescImg, model.ImgTypeDesc, false) + if dataRes.ResourceType == model.MimeTypeGif { + return nil, fmt.Errorf("商品详情图片不能上传gif格式的图片!") + } if err = err2; err != nil { return nil, err } diff --git a/business/jxstore/cms/store_sku.go b/business/jxstore/cms/store_sku.go index 081813b41..8549e52f3 100644 --- a/business/jxstore/cms/store_sku.go +++ b/business/jxstore/cms/store_sku.go @@ -3,7 +3,9 @@ package cms import ( "errors" "fmt" + "io" "math" + "mime/multipart" "sort" "strconv" "sync" @@ -23,6 +25,7 @@ import ( "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/360EntSecGroup-Skylar/excelize" "github.com/astaxie/beego/orm" ) @@ -34,80 +37,6 @@ const ( CopyStoreSkuModeUpdatePrice = "updatePrice" // 增量复制价格 ) -type StoreSkuExt struct { - NameID int `orm:"column(name_id)" json:"nameID"` - SkuID int `orm:"column(sku_id)" json:"id"` - Comment string `orm:"size(255)" json:"comment"` - SkuCategoryID int `orm:"column(sku_category_id)" json:"categoryID"` - SkuSpecQuality float32 `json:"specQuality"` - SkuSpecUnit string `orm:"size(8)" json:"specUnit"` // 质量或容量 - Weight int `json:"weight"` // 重量/质量,单位为克,当相应的SkuName的SpecUnit为g或kg时,必须等于SpecQuality - JdID string `orm:"column(sku_jd_id);null;index" json:"jdID"` - SkuStatus int `json:"status"` - - BindCreatedAt time.Time `orm:"auto_now_add;type(datetime)" json:"createdAt"` - BindUpdatedAt time.Time `orm:"auto_now;type(datetime)" json:"updatedAt"` - BindLastOperator string `orm:"size(32)" json:"lastOperator"` // 最后操作员 - BindDeletedAt time.Time `orm:"type(datetime)" json:"deletedAt"` - SubStoreID int `orm:"column(sub_store_id)" json:"subStoreID"` - BindPrice int `json:"price"` // 单位为分,不用int64的原因是这里不需要累加 - UnitPrice int `json:"unitPrice"` // 这个是一斤的门店商品价,放在这里的原因是避免额外增加一张store sku_name表,逻辑上要保证同一SKU NAME中的所有SKU这个字段的数据一致 - StoreSkuStatus int `json:"storeSkuStatus"` - - EbaiID string `orm:"column(ebai_id);index" json:"ebaiID"` - MtwmID string `orm:"column(mtwm_id)" json:"mtwmID"` // 这个也不是必须的,只是为了DAO取数据语句一致 - // WscID string `orm:"column(wsc_id);index" json:"wscID"` // 表示微盟skuId - // WscID2 string `orm:"column(wsc_id2);index" json:"wscID2"` // 表示微盟goodsId - - JdSyncStatus int8 `orm:"default(2)" json:"jdSyncStatus"` - EbaiSyncStatus int8 `orm:"default(2)" json:"ebaiSyncStatus"` - MtwmSyncStatus int8 `orm:"default(2)" json:"mtwmSyncStatus"` - // WscSyncStatus int8 `orm:"default(2)" json:"wscSyncStatus"` - - JdPrice int `json:"jdPrice"` - EbaiPrice int `json:"ebaiPrice"` - MtwmPrice int `json:"mtwmPrice"` - JxPrice int `json:"jxPrice"` - - AutoSaleAt time.Time `orm:"type(datetime);null" json:"autoSaleAt"` - - ActPrice int `json:"actPrice"` - ActID int `orm:"column(act_id)" json:"actID"` - ActType int `orm:"column(act_type)" json:"actType"` - - EarningPrice int `json:"earningPrice"` - EarningActID int `orm:"column(earning_act_id)" json:"earningActID"` - - RealEarningPrice int `json:"realEarningPrice"` - - StatusSaleBegin int16 `json:"statusSaleBegin"` //商品可售时间范围 - StatusSaleEnd int16 `json:"statusSaleEnd"` - - Count int `json:"count"` - Times int `json:"times"` -} - -// GetStoreSkus用 -type StoreSkuNameExt struct { - StoreID int `orm:"column(store_id)" json:"storeID"` - StoreName string `json:"storeName"` - - model.SkuName - PayPercentage int `json:"-"` - UnitPrice int `json:"unitPrice"` - Skus []*StoreSkuExt `orm:"-" json:"skus,omitempty"` - SkusStr string `json:"-"` - - PendingOpType int8 `json:"pendingOpType"` // 取值同 StoreOpRequest.Type - PendingUnitPrice int `json:"pendingUnitPrice"` // 这个是待审核的价格申请 -} - -// GetStoreSkus用 -type StoreSkuNamesInfo struct { - TotalCount int `json:"totalCount"` - SkuNames []*StoreSkuNameExt `json:"skuNames"` -} - // UpdateStoreSku用,API调用时 type StoreSkuBindSkuInfo struct { SkuID int `json:"skuID"` @@ -171,7 +100,36 @@ type tGetStoresSkusInfo struct { model.SkuName PayPercentage int `json:"-"` - StoreSkuExt + dao.StoreSkuExt +} + +type SheetParam struct { + OutSkuIDCol int + SkuNameIDCol int + SkuPriceCol int + SkuNameCol int + SkuUnitCol int + SkuRow int +} + +type DataSuccess struct { + NameID string `json:"商品编码"` + Name string `json:"商品名称"` + OrgPrice float64 `json:"原价"` + NowPrice float64 `json:"现价"` + MixPrice float64 `json:"涨跌"` +} + +type DataFailed struct { + NameID string `json:"商品ID"` + Name string `json:"商品名称"` + Comment string `json:"备注"` +} + +type DataLock struct { + dataSuccessList []DataSuccess + dataFailedList []DataFailed + locker sync.RWMutex } const ( @@ -187,9 +145,10 @@ var ( "18180948107": 1, // 徐 // "13684045763": 1, // 周 } + dataLock DataLock ) -func GetStoreSkus(ctx *jxcontext.Context, storeID int, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) { +func GetStoreSkus(ctx *jxcontext.Context, storeID int, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *dao.StoreSkuNamesInfo, err error) { return GetStoresSkus(ctx, []int{storeID}, skuIDs, isFocus, keyword, isBySku, isAct, params, offset, pageSize) } @@ -373,11 +332,11 @@ func getGetStoresSkusBaseSQL(db *dao.DaoDB, storeIDs, skuIDs []int, isFocus bool return sql, sqlParams, err } -func GetStoresSkus(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) { +func GetStoresSkus(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *dao.StoreSkuNamesInfo, err error) { return GetStoresSkusNew(ctx, storeIDs, skuIDs, isFocus, keyword, isBySku, isAct, params, offset, pageSize) } -func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *StoreSkuNamesInfo, err error) { +func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bool, keyword string, isBySku, isAct bool, params map[string]interface{}, offset, pageSize int) (skuNamesInfo *dao.StoreSkuNamesInfo, err error) { if !isFocus && !isBySku && (len(storeIDs) > 1 || len(storeIDs) == 0) { return nil, fmt.Errorf("未关注按SkuName只能查询单店") } @@ -416,7 +375,7 @@ func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bo catOrderBy = "t2.seq, " } - skuNamesInfo = &StoreSkuNamesInfo{} + skuNamesInfo = &dao.StoreSkuNamesInfo{} if !isBySku && sqlPageSize != model.UnlimitedPageSize { sql2 := ` SELECT SQL_CALC_FOUND_ROWS @@ -476,12 +435,12 @@ func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bo } dao.Commit(db) globals.SugarLogger.Debugf("GetStoresSkusNew get result2:%v", time.Now().Sub(beginTime)) - storeNameMap := make(map[int64]*StoreSkuNameExt) + storeNameMap := make(map[int64]*dao.StoreSkuNameExt) for _, v := range tmpList { - var storeName *StoreSkuNameExt + var storeName *dao.StoreSkuNameExt index := jxutils.Combine2Int(v.StoreID, v.ID) if isBySku || storeNameMap[index] == nil { - storeName = &StoreSkuNameExt{ + storeName = &dao.StoreSkuNameExt{ StoreID: v.StoreID, StoreName: v.StoreName, SkuName: v.SkuName, @@ -508,7 +467,7 @@ func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bo storeIDs, skuIDs = GetStoreAndSkuIDsFromInfo(skuNamesInfo) } beginTime := time.Now() - err = updateActPrice4StoreSkuNameNew(db, storeIDs, skuIDs, skuNamesInfo, actVendorID) + err = dao.UpdateActPrice4StoreSkuNameNew(db, storeIDs, skuIDs, skuNamesInfo, actVendorID) globals.SugarLogger.Debugf("GetStoresSkusNew updateActPrice4StoreSkuName:%v", time.Now().Sub(beginTime)) if !isFocus { err = updateUnitPrice4StoreSkuNameNew(db, skuNamesInfo) @@ -519,7 +478,7 @@ func GetStoresSkusNew(ctx *jxcontext.Context, storeIDs, skuIDs []int, isFocus bo return skuNamesInfo, err } -func GetStoreAndSkuIDsFromInfo(skuNamesInfo *StoreSkuNamesInfo) (storeIDs, skuIDs []int) { +func GetStoreAndSkuIDsFromInfo(skuNamesInfo *dao.StoreSkuNamesInfo) (storeIDs, skuIDs []int) { storeIDMap := make(map[int]int) skuIDMap := make(map[int]int) for _, skuName := range skuNamesInfo.SkuNames { @@ -532,7 +491,7 @@ func GetStoreAndSkuIDsFromInfo(skuNamesInfo *StoreSkuNamesInfo) (storeIDs, skuID } // 根据已经部分关注的商品,得到已经存在的门店商品单价 -func updateUnitPrice4StoreSkuNameNew(db *dao.DaoDB, skuNamesInfo *StoreSkuNamesInfo) (err error) { +func updateUnitPrice4StoreSkuNameNew(db *dao.DaoDB, skuNamesInfo *dao.StoreSkuNamesInfo) (err error) { storeIDMap := make(map[int]int) skuNameIDMap := make(map[int]int) for _, skuName := range skuNamesInfo.SkuNames { @@ -554,49 +513,7 @@ func updateUnitPrice4StoreSkuNameNew(db *dao.DaoDB, skuNamesInfo *StoreSkuNamesI return err } -// skuIDs为空,会导致性能极低,所以要skuIDs必须有值 -func updateActPrice4StoreSkuNameNew(db *dao.DaoDB, storeIDs, skuIDs []int, skuNamesInfo *StoreSkuNamesInfo, actVendorID int) (err error) { - if len(skuIDs) == 0 { - return nil - } - var vendorIDs []int - if actVendorID >= 0 { - vendorIDs = []int{actVendorID} - } - actStoreSkuList, err := dao.GetEffectiveActStoreSkuInfo(db, 0, vendorIDs, storeIDs, skuIDs, time.Now(), time.Now()) - if err != nil { - globals.SugarLogger.Errorf("GetEffectiveActStoreSkuInfo can not get sku promotion info for error:%v", err) - return err - } - actStoreSkuMap4Act := jxutils.NewActStoreSkuMap(actStoreSkuList, true) - actStoreSkuMap4EarningPrice := jxutils.NewActStoreSkuMap(actStoreSkuList, false) - - for _, skuName := range skuNamesInfo.SkuNames { - if len(skuName.Skus) > 0 { - for _, v := range skuName.Skus { - if actStoreSku := actStoreSkuMap4Act.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil { - v.ActPrice = int(actStoreSku.ActualActPrice) - v.ActID = actStoreSku.ActID - v.ActType = actStoreSku.Type - } - if actStoreSku := actStoreSkuMap4EarningPrice.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil { - v.EarningPrice = int(actStoreSku.EarningPrice) - v.EarningActID = actStoreSku.ActID - } - - v.RealEarningPrice = v.EarningPrice - if v.RealEarningPrice == 0 { - v.RealEarningPrice = int(jxutils.CaculateSkuEarningPrice(int64(v.BindPrice), int64(v.BindPrice), skuName.PayPercentage)) - } - } - } else { - skuName.UnitPrice = skuName.Price - } - } - return err -} - -func updateSaleInfo4StoreSkuName(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs, skuIDs []int, params map[string]interface{}, skuNamesInfo *StoreSkuNamesInfo, offset, pageSize int) (err error) { +func updateSaleInfo4StoreSkuName(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs, skuIDs []int, params map[string]interface{}, skuNamesInfo *dao.StoreSkuNamesInfo, offset, pageSize int) (err error) { var ( saleInfoList []*SkuSaleInfo timeList []time.Time @@ -625,9 +542,9 @@ func updateSaleInfo4StoreSkuName(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs for _, saleInfo := range saleInfoList { saleInfoMap[jxutils.Combine2Int(saleInfo.StoreID, saleInfo.SkuID)] = saleInfo } - var newSkuNames []*StoreSkuNameExt + var newSkuNames []*dao.StoreSkuNameExt for _, skuName := range skuNamesInfo.SkuNames { - var newSkus []*StoreSkuExt + var newSkus []*dao.StoreSkuExt for _, sku := range skuName.Skus { saleInfo := saleInfoMap[jxutils.Combine2Int(skuName.StoreID, sku.SkuID)] if saleInfo == nil && fromCount == 0 { @@ -2161,16 +2078,16 @@ func ReCalculateJxPrice(ctx *jxcontext.Context, storeIDs []int) (err error) { return err } -func GetTopSkusByStoreIDs(ctx *jxcontext.Context, storeIDs []int) (skuAndNameExt []*dao.SkuAndNameExt, err error) { +func GetTopSkusByStoreIDs(ctx *jxcontext.Context, storeIDs []int) (storeSkuNameExt []*dao.StoreSkuNameExt, err error) { if len(storeIDs) == 0 { - return skuAndNameExt, err + return storeSkuNameExt, err } db := dao.GetDB() - skuAndNameExt, err = dao.GetTopSkusByStoreIDs(db, storeIDs) + storeSkuNameExt, err = dao.GetTopSkusByStoreIDs(db, storeIDs) if err != nil { return nil, err } - return skuAndNameExt, err + return storeSkuNameExt, err } func GetTopCategorysByStoreIDs(ctx *jxcontext.Context, storeIDs []int) (skuCategory []*model.SkuCategory, err error) { @@ -2190,3 +2107,77 @@ func RefershStoreSkusMidPrice(ctx *jxcontext.Context, storeIDs []int) (err error _, err = dao.RefershStoreSkusMidPrice(db, storeIDs) return err } + +func RefreshJxPriceByExcel(ctx *jxcontext.Context, storeIDs []int, files []*multipart.FileHeader, isAsync, isContinueWhenError bool) (hint string, err error) { + if len(files) == 0 { + return "", errors.New("没有文件上传!") + } + if len(storeIDs) == 0 { + return "", errors.New("请选择至少一个门店!") + } + fileHeader := files[0] + file, err := fileHeader.Open() + hint, err = RefreshJxPriceByExcelBin(ctx, storeIDs, file, true, true) + file.Close() + return hint, err +} + +func RefreshJxPriceByExcelBin(ctx *jxcontext.Context, storeIDs []int, reader io.Reader, isAsync, isContinueWhenError bool) (hint string, err error) { + var storeSkuNamePriceList []*model.StoreSkuNamePrice + dataLock.dataFailedList = dataLock.dataFailedList[0:0] + dataLock.dataSuccessList = dataLock.dataSuccessList[0:0] + sheetParam := &SheetParam{ + SkuNameIDCol: 5, + SkuPriceCol: 3, + SkuNameCol: 1, + SkuRow: 1, + SkuUnitCol: 2, + OutSkuIDCol: 0, + } + xlsx, err := excelize.OpenReader(reader) + if err != nil { + return "", err + } + rows, _ := xlsx.GetRows(xlsx.GetSheetName(1)) + for rowNum, row := range rows { + if rowNum < sheetParam.SkuRow { + continue + } + storeSkuNamePrice := &model.StoreSkuNamePrice{} + GetCellIntoStruct(rowNum, row, sheetParam, storeSkuNamePrice) + storeSkuNamePriceList = append(storeSkuNamePriceList, storeSkuNamePrice) + } + return hint, err +} + +func GetCellIntoStruct(rowNum int, row []string, sheetParam *SheetParam, storeSkuNamePrice *model.StoreSkuNamePrice) { + for k, cell := range row { + if k == sheetParam.OutSkuIDCol { + storeSkuNamePrice.OutSkuID = cell + } + if k == sheetParam.SkuNameCol { + storeSkuNamePrice.Name = cell + } + if k == sheetParam.SkuNameIDCol { + storeSkuNamePrice.NameIDGroup = cell + } + if k == sheetParam.SkuPriceCol { + storeSkuNamePrice.Price = int(utils.Float64TwoInt64(utils.Str2Float64(cell) * 100)) + } + if k == sheetParam.SkuUnitCol { + storeSkuNamePrice.Unit = cell + } + } +} + +func (d *DataLock) AppendDataSuccess(dataSuccess DataSuccess) { + d.locker.Lock() + defer d.locker.Unlock() + d.dataSuccessList = append(d.dataSuccessList, dataSuccess) +} + +func (d *DataLock) AppendDataFailed(dataFailed DataFailed) { + d.locker.Lock() + defer d.locker.Unlock() + d.dataFailedList = append(d.dataFailedList, dataFailed) +} diff --git a/business/jxstore/cms/store_sku_check.go b/business/jxstore/cms/store_sku_check.go index bc66aef59..e876eb343 100644 --- a/business/jxstore/cms/store_sku_check.go +++ b/business/jxstore/cms/store_sku_check.go @@ -224,8 +224,8 @@ func GetMultiStoreAllSkuInfo(ctx *jxcontext.Context, vendorMap map[int]bool) { } } -func GetFilterJxSkuInfoMap(jxSkuInfoList []*StoreSkuNameExt) map[int]*StoreSkuNameExt { - filterJxSkuInfoMap := make(map[int]*StoreSkuNameExt) +func GetFilterJxSkuInfoMap(jxSkuInfoList []*dao.StoreSkuNameExt) map[int]*dao.StoreSkuNameExt { + filterJxSkuInfoMap := make(map[int]*dao.StoreSkuNameExt) for _, value := range jxSkuInfoList { for _, skuInfo := range value.Skus { filterJxSkuInfoMap[skuInfo.SkuID] = value @@ -322,7 +322,7 @@ func IsSkuCanSale(saleStatus int) bool { //京西平台和其他平台商品的对比 //storeIDStr 京西商家id ,vendorStoreID 平台商家id -func CompareJxAndVendor(vendorID int, storeIDStr, vendorStoreID, storeName string, filterJxSkuInfoMap map[int]*StoreSkuNameExt, filterVendorSkuInfoMap map[int]*partner.SkuNameInfo) { +func CompareJxAndVendor(vendorID int, storeIDStr, vendorStoreID, storeName string, filterJxSkuInfoMap map[int]*dao.StoreSkuNameExt, filterVendorSkuInfoMap map[int]*partner.SkuNameInfo) { for skuID, jxSkuInfo := range filterJxSkuInfoMap { skuIDStr := utils.Int2Str(skuID) var jxSkuDetailName string @@ -542,11 +542,11 @@ func CheckSkuDiffBetweenJxAndVendor(ctx *jxcontext.Context, vendorIDList []int, storeID := jxStoreInfoListValue.ID storeIDStr := utils.Int2Str(storeID) storeName := jxStoreInfoListValue.Name - jxSkuInfoDataSingle := &StoreSkuNamesInfo{} - jxSkuInfoDataMulti := &StoreSkuNamesInfo{} + jxSkuInfoDataSingle := &dao.StoreSkuNamesInfo{} + jxSkuInfoDataMulti := &dao.StoreSkuNamesInfo{} if jxStoreInfoListValue.StoreMaps != nil { - var filterJxSkuInfoMapSingle map[int]*StoreSkuNameExt - var filterJxSkuInfoMapMulti map[int]*StoreSkuNameExt + var filterJxSkuInfoMapSingle map[int]*dao.StoreSkuNameExt + var filterJxSkuInfoMapMulti map[int]*dao.StoreSkuNameExt for _, vendorListValue := range jxStoreInfoListValue.StoreMaps { vendorID := int(utils.MustInterface2Int64(vendorListValue["vendorID"])) var flag = false diff --git a/business/jxstore/misc/store_sku_sales.go b/business/jxstore/misc/store_sku_sales.go index e4de9b4ac..ca0dfc7fe 100644 --- a/business/jxstore/misc/store_sku_sales.go +++ b/business/jxstore/misc/store_sku_sales.go @@ -74,7 +74,7 @@ func GetStoreSkuSalesInfo(ctx *jxcontext.Context, storeID int) (outStoreSkuSales } //得到当前门店商品数据 - storeSkuMapData := make(map[int]*cms.StoreSkuNameExt) + storeSkuMapData := make(map[int]*dao.StoreSkuNameExt) storeSkuData, err := cms.GetStoreSkus(ctx, storeID, citySkuIDs, true, "", true, false, map[string]interface{}{}, 0, -1) if err == nil { for _, value := range storeSkuData.SkuNames { diff --git a/business/model/dao/store_sku.go b/business/model/dao/store_sku.go index 0f5867518..5e4699a9d 100644 --- a/business/model/dao/store_sku.go +++ b/business/model/dao/store_sku.go @@ -6,8 +6,10 @@ import ( "strings" "time" - "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" + + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/globals" ) @@ -109,10 +111,78 @@ type StoreSkuNameInfo struct { UnitPrice int64 } -type SkuAndNameExt struct { - SkuID int `orm:"column(sku_id)" json:"skuID"` +// GetStoreSkus用 +type StoreSkuNameExt struct { + StoreID int `orm:"column(store_id)" json:"storeID"` + StoreName string `json:"storeName"` + SkuID int `orm:"column(sku_id)" json:"skuID"` model.SkuName - Skus []*model.Sku `orm:"-" json:"skus,omitempty"` + PayPercentage int `json:"-"` + UnitPrice int `json:"unitPrice"` + Skus []*StoreSkuExt `orm:"-" json:"skus,omitempty"` + SkusStr string `json:"-"` + + PendingOpType int8 `json:"pendingOpType"` // 取值同 StoreOpRequest.Type + PendingUnitPrice int `json:"pendingUnitPrice"` // 这个是待审核的价格申请 +} + +// GetStoreSkus用 +type StoreSkuNamesInfo struct { + TotalCount int `json:"totalCount"` + SkuNames []*StoreSkuNameExt `json:"skuNames"` +} + +type StoreSkuExt struct { + NameID int `orm:"column(name_id)" json:"nameID"` + SkuID int `orm:"column(sku_id)" json:"id"` + Comment string `orm:"size(255)" json:"comment"` + SkuCategoryID int `orm:"column(sku_category_id)" json:"categoryID"` + SkuSpecQuality float32 `json:"specQuality"` + SkuSpecUnit string `orm:"size(8)" json:"specUnit"` // 质量或容量 + Weight int `json:"weight"` // 重量/质量,单位为克,当相应的SkuName的SpecUnit为g或kg时,必须等于SpecQuality + JdID string `orm:"column(sku_jd_id);null;index" json:"jdID"` + SkuStatus int `json:"status"` + + BindCreatedAt time.Time `orm:"auto_now_add;type(datetime)" json:"createdAt"` + BindUpdatedAt time.Time `orm:"auto_now;type(datetime)" json:"updatedAt"` + BindLastOperator string `orm:"size(32)" json:"lastOperator"` // 最后操作员 + BindDeletedAt time.Time `orm:"type(datetime)" json:"deletedAt"` + SubStoreID int `orm:"column(sub_store_id)" json:"subStoreID"` + BindPrice int `json:"price"` // 单位为分,不用int64的原因是这里不需要累加 + UnitPrice int `json:"unitPrice"` // 这个是一斤的门店商品价,放在这里的原因是避免额外增加一张store sku_name表,逻辑上要保证同一SKU NAME中的所有SKU这个字段的数据一致 + StoreSkuStatus int `json:"storeSkuStatus"` + + EbaiID string `orm:"column(ebai_id);index" json:"ebaiID"` + MtwmID string `orm:"column(mtwm_id)" json:"mtwmID"` // 这个也不是必须的,只是为了DAO取数据语句一致 + // WscID string `orm:"column(wsc_id);index" json:"wscID"` // 表示微盟skuId + // WscID2 string `orm:"column(wsc_id2);index" json:"wscID2"` // 表示微盟goodsId + + JdSyncStatus int8 `orm:"default(2)" json:"jdSyncStatus"` + EbaiSyncStatus int8 `orm:"default(2)" json:"ebaiSyncStatus"` + MtwmSyncStatus int8 `orm:"default(2)" json:"mtwmSyncStatus"` + // WscSyncStatus int8 `orm:"default(2)" json:"wscSyncStatus"` + + JdPrice int `json:"jdPrice"` + EbaiPrice int `json:"ebaiPrice"` + MtwmPrice int `json:"mtwmPrice"` + JxPrice int `json:"jxPrice"` + + AutoSaleAt time.Time `orm:"type(datetime);null" json:"autoSaleAt"` + + ActPrice int `json:"actPrice"` + ActID int `orm:"column(act_id)" json:"actID"` + ActType int `orm:"column(act_type)" json:"actType"` + + EarningPrice int `json:"earningPrice"` + EarningActID int `orm:"column(earning_act_id)" json:"earningActID"` + + RealEarningPrice int `json:"realEarningPrice"` + + StatusSaleBegin int16 `json:"statusSaleBegin"` //商品可售时间范围 + StatusSaleEnd int16 `json:"statusSaleEnd"` + + Count int `json:"count"` + Times int `json:"times"` } // todo 应该通过需要同步的skuid来驱动同步分类,而不是当前这种分开的逻辑 @@ -843,18 +913,24 @@ func GetStoreSkusByNameIDs(db *DaoDB, storeIDs []int, nameID int) (skuList []*St return skuList, err } -func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNameExt, err error) { +func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (storeSkuNameExt []*StoreSkuNameExt, err error) { sql := ` - SELECT t2.id sku_id,t3.* + SELECT t2.id sku_id,t3.*,t1.store_id,t1.store_name FROM( - SELECT SUM(b.count) count,c.id + SELECT SUM(b.count) count,c.id,a.store_id,d.name store_name FROM goods_order a - JOIN order_sku b ON a.vendor_order_id = b.vendor_order_id + JOIN order_sku b ON a.vendor_order_id = b.vendor_order_id AND a.vendor_id = b.vendor_id JOIN sku c ON b.sku_id = c.id AND c.deleted_at = ? + JOIN sku_name t1 ON t1.id = c.name_id AND t1.deleted_at = ? + STRAIGHT_JOIN store_sku_bind t4 ON t4.store_id = IF(a.store_id = 0,a.jx_store_id,a.store_id) AND t4.sku_id = b.sku_id AND t4.status = ? AND t4.deleted_at = ? + JOIN store d ON d.id = a.store_id WHERE 1=1 AND a.order_created_at BETWEEN ? and NOW() ` sqlParams := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, + model.SkuStatusNormal, utils.DefaultTimeValue, time.Now().AddDate(0, -1, 0), } @@ -864,30 +940,46 @@ func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNam } sql += ` AND b.sale_price > ? - GROUP BY c.id)t1 + GROUP BY 2,3,4)t1 JOIN sku t2 ON t2.id = t1.id JOIN sku_name t3 ON t3.id = t2.name_id ORDER BY t1.count DESC LIMIT ? ` sqlParams = append(sqlParams, 100, 30) - err = GetRows(db, &skuAndNameExt, sql, sqlParams...) - for _, v := range skuAndNameExt { - var skus []*model.Sku + err = GetRows(db, &storeSkuNameExt, sql, sqlParams...) + var skuNamesInfo = &StoreSkuNamesInfo{ + SkuNames: storeSkuNameExt, + } + for _, v := range storeSkuNameExt { + var skus []*StoreSkuExt sql2 := ` - SELECT * - FROM sku - WHERE id = ? - AND deleted_at = ? + SELECT a.id sku_id,a.*,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.ebai_sync_status, t4.mtwm_sync_status, + t4.jd_price, t4.ebai_price, t4.mtwm_price, t4.jx_price + FROM sku a + JOIN sku_name t1 ON a.name_id = t1.id AND t1.deleted_at = ? + JOIN store_sku_bind t4 ON t4.sku_id = a.id AND t4.deleted_at = ? + WHERE a.id = ? + AND a.deleted_at = ? ` sqlParams2 := []interface{}{ + utils.DefaultTimeValue, + utils.DefaultTimeValue, v.SkuID, utils.DefaultTimeValue, } + if len(storeIDs) > 0 { + sql2 += " AND t4.store_id IN(" + GenQuestionMarks(len(storeIDs)) + ")" + sqlParams2 = append(sqlParams2, storeIDs) + } err = GetRows(db, &skus, sql2, sqlParams2...) v.Skus = skus + err = UpdateActPrice4StoreSkuNameNew(db, storeIDs, []int{v.SkuID}, skuNamesInfo, -1) } - return skuAndNameExt, err + return storeSkuNameExt, err } func GetTopCategorysByStoreIDs(db *DaoDB, storeIDs []int) (skuCategory []*model.SkuCategory, err error) { @@ -1016,3 +1108,45 @@ func SetStoreCatMapSyncStatus(storeCatMap *model.StoreSkuCategoryMap, vendorID i storeCatMap.EbaiSyncStatus = syncStatus } } + +// skuIDs为空,会导致性能极低,所以要skuIDs必须有值 +func UpdateActPrice4StoreSkuNameNew(db *DaoDB, storeIDs, skuIDs []int, skuNamesInfo *StoreSkuNamesInfo, actVendorID int) (err error) { + if len(skuIDs) == 0 { + return nil + } + var vendorIDs []int + if actVendorID >= 0 { + vendorIDs = []int{actVendorID} + } + actStoreSkuList, err := GetEffectiveActStoreSkuInfo(db, 0, vendorIDs, storeIDs, skuIDs, time.Now(), time.Now()) + if err != nil { + globals.SugarLogger.Errorf("updateActPrice4StoreSkuNameNew can not get sku promotion info for error:%v", err) + return err + } + actStoreSkuMap4Act := jxutils.NewActStoreSkuMap(actStoreSkuList, true) + actStoreSkuMap4EarningPrice := jxutils.NewActStoreSkuMap(actStoreSkuList, false) + + for _, skuName := range skuNamesInfo.SkuNames { + if len(skuName.Skus) > 0 { + for _, v := range skuName.Skus { + if actStoreSku := actStoreSkuMap4Act.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil { + v.ActPrice = int(actStoreSku.ActualActPrice) + v.ActID = actStoreSku.ActID + v.ActType = actStoreSku.Type + } + if actStoreSku := actStoreSkuMap4EarningPrice.GetActStoreSku(skuName.StoreID, v.SkuID, -1); actStoreSku != nil { + v.EarningPrice = int(actStoreSku.EarningPrice) + v.EarningActID = actStoreSku.ActID + } + + v.RealEarningPrice = v.EarningPrice + if v.RealEarningPrice == 0 { + v.RealEarningPrice = int(jxutils.CaculateSkuEarningPrice(int64(v.BindPrice), int64(v.BindPrice), skuName.PayPercentage)) + } + } + } else { + skuName.UnitPrice = skuName.Price + } + } + return err +} diff --git a/business/model/store.go b/business/model/store.go index 31a912722..732266daf 100644 --- a/business/model/store.go +++ b/business/model/store.go @@ -484,6 +484,27 @@ func (*StorePriceScoreSnapshot) TableIndex() [][]string { } } +type StoreSkuNamePrice struct { + ModelIDCULD + OutSkuID string `orm:"column(out_sku_id)" json:"outSkuID"` + Name string `json:"name"` + Price int `json:"price"` + Unit string `json:"unit"` + NameIDGroup string `orm:"column(name_id_group)" json:"nameIDGroup"` +} + +func (*StoreSkuNamePrice) TableUnique() [][]string { + return [][]string{ + []string{"Name", "NameIDGroup"}, + } +} + +func (*StoreSkuNamePrice) TableIndex() [][]string { + return [][]string{ + []string{"Name"}, + } +} + type VendorStoreSnapshot struct { ModelIDCULD diff --git a/business/partner/purchase/jx/localjx/order.go b/business/partner/purchase/jx/localjx/order.go index 8514b3511..640ec7d39 100644 --- a/business/partner/purchase/jx/localjx/order.go +++ b/business/partner/purchase/jx/localjx/order.go @@ -360,7 +360,7 @@ func generateOrder(ctx *jxcontext.Context, jxOrder *JxOrderInfo, addressID int64 if err != nil { return nil, nil, err } - storeSkuMap := make(map[int]*cms.StoreSkuExt) + storeSkuMap := make(map[int]*dao.StoreSkuExt) for _, v1 := range storeSkuInfo.SkuNames { for _, v2 := range v1.Skus { storeSkuMap[v2.SkuID] = v2 diff --git a/controllers/cms_store_sku.go b/controllers/cms_store_sku.go index 12b1d39db..8378b7e49 100644 --- a/controllers/cms_store_sku.go +++ b/controllers/cms_store_sku.go @@ -498,3 +498,24 @@ func (c *StoreSkuController) RefershStoreSkusMidPrice() { return retVal, "", err }) } + +// @Title 根据Excel刷新京西门店商品价 +// @Description 根据Excel刷新京西门店商品价 +// @Param token header string true "认证token" +// @Param storeIDs query string true "门店列表" +// @Param isAsync query bool true "是否异步,缺省是同步" +// @Param isContinueWhenError query bool true "单个同步失败是否继续,缺省false" +// @Success 200 {object} controllers.CallResult +// @Failure 200 {object} controllers.CallResult +// @router /RefreshJxPriceByExcel [post] +func (c *StoreSkuController) RefreshJxPriceByExcel() { + var storeIDList []int + c.callRefreshJxPriceByExcel(func(params *tStoreSkuRefreshJxPriceByExcelParams) (retVal interface{}, errCode string, err error) { + if jxutils.Strings2Objs(params.StoreIDs, &storeIDList); err == nil { + r := c.Ctx.Request + files := r.MultipartForm.File["userfiles"] + retVal, err = cms.RefreshJxPriceByExcel(params.Ctx, storeIDList, files, params.IsAsync, params.IsContinueWhenError) + } + return retVal, "", err + }) +} diff --git a/globals/beegodb/beegodb.go b/globals/beegodb/beegodb.go index cbb0b076a..6ac7d068d 100644 --- a/globals/beegodb/beegodb.go +++ b/globals/beegodb/beegodb.go @@ -40,6 +40,7 @@ func Init() { orm.RegisterModel(&model.VendorStoreSnapshot{}) orm.RegisterModel(&model.PriceReferSnapshot{}) orm.RegisterModel(&model.StorePriceScoreSnapshot{}) + orm.RegisterModel(&model.StoreSkuNamePrice{}) // orm.RegisterModel(&model.ActivityForSku{}) // orm.RegisterModel(&legacymodel.JxBadComments2{}) diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index 6998e7369..2aaab868c 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -1656,6 +1656,15 @@ func init() { Filters: nil, Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], + beego.ControllerComments{ + Method: "RefreshJxPriceByExcel", + Router: `/RefreshJxPriceByExcel`, + AllowHTTPMethods: []string{"post"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:StoreSkuController"], beego.ControllerComments{ Method: "RefreshStoresSkuByVendor",