导入Excel更新京西价,推荐商品

This commit is contained in:
苏尹岚
2019-12-10 11:47:52 +08:00
parent 3229e3db25
commit 62569dfe55
7 changed files with 244 additions and 69 deletions

View File

@@ -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,59 +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"`
@@ -95,7 +45,7 @@ type StoreSkuNameExt struct {
model.SkuName
PayPercentage int `json:"-"`
UnitPrice int `json:"unitPrice"`
Skus []*StoreSkuExt `orm:"-" json:"skus,omitempty"`
Skus []*dao.StoreSkuExt `orm:"-" json:"skus,omitempty"`
SkusStr string `json:"-"`
PendingOpType int8 `json:"pendingOpType"` // 取值同 StoreOpRequest.Type
@@ -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)
}

View File

@@ -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来驱动同步分类而不是当前这种分开的逻辑
@@ -672,12 +725,17 @@ func GetTopSkusByStoreIDs(db *DaoDB, storeIDs []int) (skuAndNameExt []*SkuAndNam
FROM(
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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
})
}

View File

@@ -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{})

View File

@@ -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",