diff --git a/business/jxstore/cms/store_sku.go b/business/jxstore/cms/store_sku.go index 85152f771..d701e2d4f 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,69 +37,16 @@ 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:"-"` + PayPercentage int `json:"-"` + UnitPrice int `json:"unitPrice"` + Skus []*dao.StoreSkuExt `orm:"-" json:"skus,omitempty"` + SkusStr string `json:"-"` PendingOpType int8 `json:"pendingOpType"` // 取值同 StoreOpRequest.Type PendingUnitPrice int `json:"pendingUnitPrice"` // 这个是待审核的价格申请 @@ -171,7 +121,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,6 +166,7 @@ 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) { @@ -627,7 +607,7 @@ func updateSaleInfo4StoreSkuName(ctx *jxcontext.Context, db *dao.DaoDB, storeIDs } var newSkuNames []*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 { @@ -2190,3 +2170,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/model/dao/store_sku.go b/business/model/dao/store_sku.go index 1bd6c6127..1d4e1e6d1 100644 --- a/business/model/dao/store_sku.go +++ b/business/model/dao/store_sku.go @@ -112,7 +112,60 @@ type StoreSkuNameInfo struct { type SkuAndNameExt struct { SkuID int `orm:"column(sku_id)" json:"skuID"` model.SkuName - Skus []*model.Sku `orm:"-" json:"skus,omitempty"` + Skus []*StoreSkuExt `orm:"-" json:"skus,omitempty"` +} + +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来驱动同步分类,而不是当前这种分开的逻辑 @@ -670,14 +723,19 @@ func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNam sql := ` SELECT t2.id sku_id,t3.* FROM( - SELECT SUM(b.count) count,c.id + SELECT SUM(b.count) count,c.id 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 = ? 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), } @@ -687,7 +745,7 @@ func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNam } sql += ` AND b.sale_price > ? - GROUP BY c.id)t1 + GROUP BY 2)t1 JOIN sku t2 ON t2.id = t1.id JOIN sku_name t3 ON t3.id = t2.name_id ORDER BY t1.count DESC @@ -696,17 +754,28 @@ func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNam sqlParams = append(sqlParams, 100, 30) err = GetRows(db, &skuAndNameExt, sql, sqlParams...) for _, v := range skuAndNameExt { - var skus []*model.Sku + var skus []*StoreSkuExt sql2 := ` - SELECT * - FROM sku - WHERE id = ? - AND deleted_at = ? + SELECT 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 b ON a.name_id = b.id AND b.delete_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, 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 } 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 fc30aa3fe..1a7b18742 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -1647,6 +1647,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",