diff --git a/business/jxstore/misc/misc2_test.go b/business/jxstore/misc/misc2_test.go index 38d1a9be5..f4eee87f1 100644 --- a/business/jxstore/misc/misc2_test.go +++ b/business/jxstore/misc/misc2_test.go @@ -2,8 +2,9 @@ package misc import ( "testing" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" ) func TestStartOrEndOpStore(t *testing.T) { - StartOrEndOpStore(1, true, 0, 0, false, true) + StartOrEndOpStore(jxcontext.AdminCtx, true, []int{}, []int{}, 0, 0, false, true) } diff --git a/business/jxstore/misc/store_score.go b/business/jxstore/misc/store_score.go index 3f614ad8f..bc34910b4 100644 --- a/business/jxstore/misc/store_score.go +++ b/business/jxstore/misc/store_score.go @@ -17,19 +17,28 @@ const ( ItemScore = 10 TotalScore = 100 - StoreOpenTimeNormalScore = 12.0 - SaleSkuNormalCount = 1000 - SaleSkuScorePerUnit = float64(ItemScore) / SaleSkuNormalCount - PromotionSkuNormalCount = 20 + StoreOpenTimeNormalTime = 12.0 //小时 + SaleSkuNormalCount = 1000 + SaleSkuScorePerUnit = float64(ItemScore) / SaleSkuNormalCount + PromotionSkuNormalCount = 20 + AveragePickupTimeNormalTime = 10.0 //分钟 + BadCommentOrderNormalRatio = 0.2 + UnfinishOrderNormalRatio = 1.0 + StoreRangeGoodRadius = 2000 //米 + StoreRangeBadRadius = 1000 //米 ) var ( + scoreStoreTimeList = []string{ + "23:30:00", + } storeScoreDataWrapper StoreScoreDataWrapper fullVendorList = map[int]bool{ model.VendorIDJD: true, model.VendorIDMTWM: true, model.VendorIDEBAI: true, } + allStoreSkus map[int]map[int]*cms.StoreSkuNameExt ) type StoreScoreDataWrapper struct { @@ -53,19 +62,6 @@ func (s *StoreScoreDataWrapper) InitData() { s.storeScoreData = make(map[int]*model.StoreScore) } -/* 分日评分和周评分两种,周评分为最近一周的平均值 -1 营业时间12小时及以上满分,总分10分,每少一个小时扣1分 -2 可售商品数量大于1000,总分10分,按比例扣 -3 平均捡货时间10分钟内为满分10分,每超1分钟扣1分 -4 差评订单和完成订单比例小于0.2%,得满分10分,每增加0.1,%减1分 -5 未完成订单和完成订单比小于1%,得满分10分,比例每增加5%,分数减1 -6 缺货订单和完成订单比小于1%得10分,比例每增加0.1%减1分 -7 促销品数量20个以上为满分10分,每少2个扣1分 -8 经营全平台满分10分,每少一个平台扣2分(???一个都没有,是否为0) -9 经营范围面积大于半径2km的圆得满分10分,低于1km得分0 -10 可售商品价格在附近5km内门店比较,价格低于等于平均值的商品数占90%以上满分10分,比例每降低10%减1分,100%超标得0分 -*/ - func GetOpenTime(opTimeList []int16) (opTime float64) { opTime = 0 for index, _ := range opTimeList { @@ -77,6 +73,7 @@ func GetOpenTime(opTimeList []int16) (opTime float64) { return opTime } +//营业时间12小时及以上满分,总分10分,每少一个小时扣1分 func ScoreStoreOpenTime(storeList []*cms.StoreExt) { for _, storeInfo := range storeList { storeID := storeInfo.ID @@ -94,10 +91,10 @@ func ScoreStoreOpenTime(storeList []*cms.StoreExt) { opTimeList := storeInfo.GetOpTimeList() if len(opTimeList) > 0 && isStoreOpen && isVendorStoreOpen { opTime := GetOpenTime(opTimeList) - if opTime >= StoreOpenTimeNormalScore { + if opTime >= StoreOpenTimeNormalTime { storeScoreData.StoreOpenTime = ItemScore } else { - decScore := int(math.Round(StoreOpenTimeNormalScore - opTime)) + decScore := int(math.Round(StoreOpenTimeNormalTime - opTime)) storeScoreData.StoreOpenTime = ItemScore - decScore if storeScoreData.StoreOpenTime < 0 { storeScoreData.StoreOpenTime = 0 @@ -108,6 +105,7 @@ func ScoreStoreOpenTime(storeList []*cms.StoreExt) { } } +//可售商品数量大于1000,总分10分,按比例扣 func ScoreSaleSkuCount(ctx *jxcontext.Context, storeList []*cms.StoreExt) { for _, storeInfo := range storeList { storeID := storeInfo.ID @@ -128,25 +126,103 @@ func ScoreSaleSkuCount(ctx *jxcontext.Context, storeList []*cms.StoreExt) { } } storeScoreData.SaleSkuCount = int(math.Round(float64(saleSkuCount) * SaleSkuScorePerUnit)) + if storeScoreData.SaleSkuCount > ItemScore { + storeScoreData.SaleSkuCount = ItemScore + } } } -func ScoreAveragePickupTime() { - -} - -func ScoreBadReviewOrder() { - -} - -func ScoreUnfinishOrder() { - -} - -func ScoreLackStockOrder() { +//平均捡货时间小于等于拣货截止时间为10分满分,每超出1分钟,减1分 +func ScoreAveragePickupTime(storeList []*cms.StoreExt) { + for _, storeInfo := range storeList { + storeID := storeInfo.ID + storeScoreData := storeScoreDataWrapper.GetData(storeID) + if storeScoreData == nil { + storeScoreData = &model.StoreScore{} + storeScoreData.StoreID = storeID + storeScoreDataWrapper.AppendData(storeScoreData) + } + db := dao.GetDB() + orderList, err := dao.GetDailyFinishOrderList(db, storeID) + if err == nil { + totalScore := 0 + for _, value := range orderList { + statusTime := value.StatusTime.Unix() + pickDeadline := value.PickDeadline.Unix() + if statusTime <= pickDeadline { + totalScore += ItemScore + } else { + decScore := int(math.Round(float64(statusTime-pickDeadline) / 60)) + tempScore := ItemScore - decScore + if tempScore < 0 { + tempScore = 0 + } + totalScore += tempScore + } + } + storeScoreData.AveragePickupTime = totalScore / len(orderList) + } + } +} + +//差评订单和完成订单比例小于0.2%,得满分10分,每增加0.1%减1分 +func ScoreBadCommentOrder(storeList []*cms.StoreExt) { + for _, storeInfo := range storeList { + storeID := storeInfo.ID + storeScoreData := storeScoreDataWrapper.GetData(storeID) + if storeScoreData == nil { + storeScoreData = &model.StoreScore{} + storeScoreData.StoreID = storeID + storeScoreDataWrapper.AppendData(storeScoreData) + } + db := dao.GetDB() + badCommentOrderCount, _ := dao.GetDailyBadCommentOrderCount(db, storeID) + finishOrderCount, _ := dao.GetDailyFinishOrderCount(db, storeID) + badCommentOrderRatio := float64(badCommentOrderCount) * 100 / float64(finishOrderCount) + if badCommentOrderRatio <= BadCommentOrderNormalRatio { + storeScoreData.BadCommentOrder = ItemScore + } else { + decScore := int(math.Round((badCommentOrderRatio - BadCommentOrderNormalRatio) / 0.1)) + storeScoreData.BadCommentOrder = ItemScore - decScore + if storeScoreData.BadCommentOrder < 0 { + storeScoreData.BadCommentOrder = 0 + } + } + } +} + +//未完成订单和完成订单比小于1%,得满分10分,比例每增加5%,分数减1 +func ScoreUnfinishOrder(storeList []*cms.StoreExt) { + for _, storeInfo := range storeList { + storeID := storeInfo.ID + storeScoreData := storeScoreDataWrapper.GetData(storeID) + if storeScoreData == nil { + storeScoreData = &model.StoreScore{} + storeScoreData.StoreID = storeID + storeScoreDataWrapper.AppendData(storeScoreData) + } + db := dao.GetDB() + unFinishOrderCount, _ := dao.GetDailyUnFinishOrderCount(db, storeID) + finishOrderCount, _ := dao.GetDailyFinishOrderCount(db, storeID) + unfinishOrderRatio := float64(unFinishOrderCount) * 100 / float64(finishOrderCount) + if unfinishOrderRatio <= UnfinishOrderNormalRatio { + storeScoreData.UnfinishOrder = ItemScore + } else { + decScore := int(math.Round((unfinishOrderRatio - UnfinishOrderNormalRatio) / 5)) + storeScoreData.UnfinishOrder = ItemScore - decScore + if storeScoreData.UnfinishOrder < 0 { + storeScoreData.UnfinishOrder = 0 + } + } + } +} + +//缺货订单和完成订单比小于1%得10分,比例每增加0.1%减1分 +func ScoreAbsentGoodsOrder(storeList []*cms.StoreExt) { } +//促销品数量20个以上为满分10分,每少2个扣1分 func ScorePromotionSku(storeList []*cms.StoreExt) { for _, storeInfo := range storeList { storeID := storeInfo.ID @@ -163,7 +239,9 @@ func ScorePromotionSku(storeList []*cms.StoreExt) { if err == nil { actStoreSkuMap := make(map[int]int) for _, value := range actStoreSkuList { - actStoreSkuMap[value.SkuID] = 1 + if value.Type != model.ActSkuFake { + actStoreSkuMap[value.SkuID] = 1 + } } promotionSkuCount := len(actStoreSkuMap) if promotionSkuCount >= PromotionSkuNormalCount { @@ -179,6 +257,7 @@ func ScorePromotionSku(storeList []*cms.StoreExt) { } } +//经营全平台满分10分,每少一个平台扣2分(???一个都没有,是否为0) func ScoreFullVendor(storeList []*cms.StoreExt) { fullVendorCount := len(fullVendorList) for _, storeInfo := range storeList { @@ -209,12 +288,108 @@ func ScoreFullVendor(storeList []*cms.StoreExt) { } } -func ScoreStoreRange() { +func CalcPolygonArea(points [][2]float64) (area float64) { + count := len(points) + for i := 0; i < count-1; i++ { + area += (points[i][0] - points[i+1][0]) * (points[i][1] + points[i+1][1]) + } + area += (points[count-1][0] - points[0][0]) * (points[count-1][1] + points[0][1]) + area = math.Abs(area) / 2 + return area } -func ScoreSaleSkuPrice() { +//经营范围面积大于半径2km的圆得满分10分,低于1km得分0 +func ScoreStoreRange(storeList []*cms.StoreExt) { + for _, storeInfo := range storeList { + storeID := storeInfo.ID + storeScoreData := storeScoreDataWrapper.GetData(storeID) + if storeScoreData == nil { + storeScoreData = &model.StoreScore{} + storeScoreData.StoreID = storeID + storeScoreDataWrapper.AppendData(storeScoreData) + } + if storeInfo.DeliveryRangeType == model.DeliveryRangeTypePolygon { + points := jxutils.CoordinateStr2Points(storeInfo.DeliveryRange) + area := CalcPolygonArea(points) + goodArea := math.Pi * StoreRangeGoodRadius * StoreRangeGoodRadius + badArea := math.Pi * StoreRangeBadRadius * StoreRangeBadRadius + if area >= goodArea { + storeScoreData.StoreRange = ItemScore + } else if area <= badArea { + storeScoreData.StoreRange = 0 + } else { + diff := float64(goodArea - area) + ratio := float64(ItemScore) / (goodArea - badArea) + storeScoreData.StoreRange = ItemScore - int(math.Round(diff*ratio)) + } + } else if storeInfo.DeliveryRangeType == model.DeliveryRangeTypeRadius { + deliveryRadius := utils.Str2Int64WithDefault(storeInfo.DeliveryRange, 0) + if deliveryRadius >= StoreRangeGoodRadius { + storeScoreData.StoreRange = ItemScore + } else if deliveryRadius <= StoreRangeBadRadius { + storeScoreData.StoreRange = 0 + } else { + diff := float64(StoreRangeGoodRadius - deliveryRadius) + ratio := float64(ItemScore) / (StoreRangeGoodRadius - StoreRangeBadRadius) + storeScoreData.StoreRange = ItemScore - int(math.Round(diff*ratio)) + } + } + } +} +func GetRangeStoreList(storeID int, lng, lat, checkDist float64, storeList []*cms.StoreExt) (outStoreList []*cms.StoreExt) { + for _, storeInfo := range storeList { + if storeInfo.ID == storeID { + outStoreList = append(outStoreList, storeInfo) + } else { + distance := jxutils.EarthDistance(lng, lat, storeInfo.FloatLng, storeInfo.FloatLat) + if distance <= checkDist { + outStoreList = append(outStoreList, storeInfo) + } + } + } + + return outStoreList +} + +func GetAllStoreSkus(ctx *jxcontext.Context, storeList []*cms.StoreExt) { + allStoreSkus = make(map[int]map[int]*cms.StoreSkuNameExt) + for _, storeInfo := range storeList { + storeID := storeInfo.ID + jxSkuInfoData, _ := cms.GetStoreSkus(ctx, storeID, []int{}, true, "", true, map[string]interface{}{}, 0, -1) + jxSkuMapData := make(map[int]*cms.StoreSkuNameExt) + for _, value := range jxSkuInfoData.SkuNames { + for _, skuInfo := range value.Skus2 { + saleStatus := jxutils.MergeSkuStatus(skuInfo.SkuStatus, skuInfo.StoreSkuStatus) + if saleStatus == model.SkuStatusNormal { + jxSkuMapData[skuInfo.SkuID] = value + } + } + } + allStoreSkus[storeID] = jxSkuMapData + } +} + +func ClearAllStoreSkus() { + allStoreSkus = nil +} + +//得到所有门店的商品,map[int]*cms.StoreSkuNamesInfo +//得到距离某个门店5KM内的所有门店信息 +//可售商品价格在附近5km内门店比较,价格低于等于平均值的商品数占90%以上满分10分,比例每降低10%减1分,100%超标得0分 +func ScoreSaleSkuPrice(ctx *jxcontext.Context, storeList []*cms.StoreExt) { + GetAllStoreSkus(ctx, storeList) + for _, storeInfo := range storeList { + storeID := storeInfo.ID + storeScoreData := storeScoreDataWrapper.GetData(storeID) + if storeScoreData == nil { + storeScoreData = &model.StoreScore{} + storeScoreData.StoreID = storeID + storeScoreDataWrapper.AppendData(storeScoreData) + } + //rangeStoreList := GetRangeStoreList(storeID, storeInfo.FloatLng, storeInfo.FloatLat, 5, storeList) + } } func GetFilterStoreListEx(storeList []*cms.StoreExt, storeIDMap map[int]int) (outStoreList []*cms.StoreExt) { @@ -245,6 +420,14 @@ func ScoreStore(ctx *jxcontext.Context, storeIDList []int) (retVal interface{}, ScoreStoreOpenTime(storeList) ScoreSaleSkuCount(ctx, storeList) + ScoreAveragePickupTime(storeList) + ScoreBadCommentOrder(storeList) + ScoreUnfinishOrder(storeList) + ScoreAbsentGoodsOrder(storeList) + ScorePromotionSku(storeList) + ScoreFullVendor(storeList) + ScoreStoreRange(storeList) + ScoreSaleSkuPrice(ctx, storeList) InsertStoreScore() return retVal, err @@ -252,17 +435,12 @@ func ScoreStore(ctx *jxcontext.Context, storeIDList []int) (retVal interface{}, func InsertStoreScore() { for _, value := range storeScoreDataWrapper.storeScoreData { - scores := make(map[string]int) - scores["storeOpenTime"] = value.StoreOpenTime - scores["saleSkuCount"] = value.SaleSkuCount - scores["averagePickupTime"] = value.AveragePickupTime - scores["badReviewOrder"] = value.BadReviewOrder - scores["unfinishOrder"] = value.UnfinishOrder - scores["lackStockOrder"] = value.LackStockOrder - scores["promotionSku"] = value.PromotionSku - scores["fullVendor"] = value.FullVendor - scores["storeRange"] = value.StoreRange - scores["saleSkuPrice"] = value.SaleSkuPrice - dao.InsertStoreScore(value.StoreID, scores) + dao.InsertStoreScore(value) } } + +func ScheduleScoreStore() { + ScheduleTimerFunc("ScheduleScoreStore", func() { + ScoreStore(jxcontext.AdminCtx, []int{}) + }, scoreStoreTimeList) +} diff --git a/business/jxstore/misc/store_score_test.go b/business/jxstore/misc/store_score_test.go new file mode 100644 index 000000000..7d4798157 --- /dev/null +++ b/business/jxstore/misc/store_score_test.go @@ -0,0 +1,17 @@ +package misc + +import ( + "testing" + "git.rosy.net.cn/jx-callback/globals/api2" + "git.rosy.net.cn/jx-callback/globals/testinit" + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" +) + +func init() { + testinit.Init() + api2.Init() +} + +func TestScoreStore(t *testing.T) { + ScoreStore(jxcontext.AdminCtx, []int{}) +} diff --git a/business/model/api.go b/business/model/api.go index 1a3396774..79dddef04 100644 --- a/business/model/api.go +++ b/business/model/api.go @@ -73,3 +73,8 @@ type OrderFinancialSkuExt struct { OrderSkuFinancial Image string `json:"image"` } + +type OrderPickupTime struct { + StatusTime time.Time `orm:"type(datetime)" json:"statusTime"` + PickDeadline time.Time `orm:"type(datetime)" json:"pickDeadline"` +} diff --git a/business/model/dao/dao_order.go b/business/model/dao/dao_order.go index 1438e7c7d..2d7f7220b 100644 --- a/business/model/dao/dao_order.go +++ b/business/model/dao/dao_order.go @@ -290,3 +290,46 @@ func GetStoreAfsOrderSkuList(db *DaoDB, storeIDs []int, finishedAtBegin, finishe err = GetRows(db, &afsSkuList, sql, sqlParams...) return afsSkuList, err } + +func GetDailyFinishOrderList(db *DaoDB, storeID int) (orderList []*model.OrderPickupTime, err error) { + sql := ` + select t2.status_time, t1.pick_deadline + from goods_order t1 + left join order_status t2 on t1.vendor_order_id = t2.vendor_order_id + where t1.jx_store_id = ? and t2.order_type = ? and t2.status = ? and DATE_FORMAT(t1.order_finished_at, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d') + ` + sqlParams := []interface{}{ + storeID, + 1, + model.OrderStatusFinishedPickup, + } + return orderList, GetRows(db, &orderList, sql, sqlParams...) +} + +func GetDailyBadCommentOrderCount(db *DaoDB, storeID int) (num int64, err error) { + sql := `select count(*) from jx_bad_comments where DATE_FORMAT(createtime, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d') and jxstoreid = ?` + sqlParams := []interface{}{ + storeID, + } + return ExecuteSQL(db, sql, sqlParams) +} + +func GetDailyUnFinishOrderCount(db *DaoDB, storeID int) (num int64, err error) { + return GetDailyEndOrderCount(db, storeID, []int{model.OrderStatusCanceled}) +} + +func GetDailyFinishOrderCount(db *DaoDB, storeID int) (num int64, err error) { + return GetDailyEndOrderCount(db, storeID, []int{model.OrderStatusFinished}) +} + +func GetDailyEndOrderCount(db *DaoDB, storeID int, statusList []int) (num int64, err error) { + sql := `select count(*) from goods_order + where DATE_FORMAT(order_finished_at, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d') + and jx_store_id = ? + and status in (` + GenQuestionMarks(len(statusList)) + `)` + sqlParams := []interface{}{ + storeID, + } + sqlParams = append(sqlParams, statusList) + return ExecuteSQL(db, sql, sqlParams) +} diff --git a/business/model/dao/store_score.go b/business/model/dao/store_score.go index 71aea890e..4f4f5735f 100644 --- a/business/model/dao/store_score.go +++ b/business/model/dao/store_score.go @@ -6,18 +6,8 @@ import ( "git.rosy.net.cn/jx-callback/business/model" ) -func InsertStoreScore(storeID int, scores map[string]int) error { - storeScore := &model.StoreScore{CreatedAt: time.Now(), StoreID: storeID} - storeScore.StoreOpenTime = scores["storeOpenTime"] - storeScore.SaleSkuCount = scores["saleSkuCount"] - storeScore.AveragePickupTime = scores["averagePickupTime"] - storeScore.BadReviewOrder = scores["badReviewOrder"] - storeScore.UnfinishOrder = scores["unfinishOrder"] - storeScore.LackStockOrder = scores["lackStockOrder"] - storeScore.PromotionSku = scores["promotionSku"] - storeScore.FullVendor = scores["fullVendor"] - storeScore.StoreRange = scores["storeRange"] - storeScore.SaleSkuPrice = scores["saleSkuPrice"] - +// func InsertStoreScore(storeID int, scores map[string]int) error { +func InsertStoreScore(storeScore *model.StoreScore) error { + storeScore.CreatedAt = time.Now() return CreateEntity(nil, storeScore) } diff --git a/business/model/store_score.go b/business/model/store_score.go index 0277557d7..a32c21fcf 100644 --- a/business/model/store_score.go +++ b/business/model/store_score.go @@ -10,9 +10,9 @@ type StoreScore struct { StoreOpenTime int `orm:"column(store_open_time)" json:"storeOpenTime"` SaleSkuCount int `orm:"column(sale_sku_count)" json:"saleSkuCount"` AveragePickupTime int `orm:"column(average_pickup_time)" json:"averagePickupTime"` - BadReviewOrder int `orm:"column(bad_review_order)" json:"badReviewOrder"` + BadCommentOrder int `orm:"column(bad_comment_order)" json:"badCommentOrder"` UnfinishOrder int `orm:"column(unfinish_order)" json:"unfinishOrder"` - LackStockOrder int `orm:"column(lack_stock_order)" json:"lackStockOrder"` + AbsentGoodsOrder int `orm:"column(absent_Goods_order)" json:"absentGoodsOrder"` PromotionSku int `orm:"column(promotion_sku)" json:"promotionSku"` FullVendor int `orm:"column(full_vendor)" json:"fullVendor"` StoreRange int `orm:"column(store_range)" json:"storeRange"`