Files
jx-callback/business/jxcallback/scheduler/defsch/defsch.go
2019-04-01 21:00:15 +08:00

876 lines
40 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package defsch
import (
"fmt"
"math/rand"
"sync"
"time"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/jxcallback/scheduler"
"git.rosy.net.cn/jx-callback/business/jxcallback/scheduler/basesch"
"git.rosy.net.cn/jx-callback/business/jxutils"
"git.rosy.net.cn/jx-callback/business/jxutils/configindb"
"git.rosy.net.cn/jx-callback/business/jxutils/weixinmsg"
"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/legacymodel"
"git.rosy.net.cn/jx-callback/business/partner"
"git.rosy.net.cn/jx-callback/globals"
"github.com/astaxie/beego/orm"
)
const (
time2Delivered = 1 * time.Hour // 正常从下单到送达的时间。
minute2Schedule3rdCarrier = 20 // 收到平台方自有配送的新运单消息后等待创建三方配送运单的时间分钟如果是定时达会再根据ExpectedDeliveredTime与dingShiDaAheadTime做调整
minMinute2Schedule3rdCarrier = 5 // 转三方配送最少等待时间(分钟)
time2AutoPickupMin = 15 * time.Minute // 自动拣货等待时间这个只有在没有PickDeadline信息才有用否则会根据PickDeadline设置
second2AutoPickupGap = 60 //随机60秒
time2AutoPickupAhead = 20 * time.Second // 有最后拣货时间的提前值
// 把pending order timerout 在-pendingOrderTimerMinMinSecond至pendingOrderTimerMaxSecond映射到pendingOrderTimerMinSecond至pendingOrderTimerMaxSecond
pendingOrderTimerMinMinSecond = 5 * 60 // 5分钟
pendingOrderTimerMinSecond = 2
pendingOrderTimerMaxSecond = 5
maxWaybillRetryCount = 3
orderMapStoreMaxTime = 4 * 24 * time.Hour // cache最长存储时间
time2Schedule3rdCarrierKey = "waitminute4mt"
dingShiDaAheadTime = 30 * time.Minute // 定时达订单开始召唤配送的提前时间
)
const (
maxAddFee = 300 // 最大增加费用,单位为分,超过不发三方配送了
)
var (
FixedScheduler *DefScheduler
)
type WatchOrderInfo struct {
autoPickupTimeoutMinute int // 0表示禁用1表示用缺省值time2AutoPickupMin其它表示分钟数
storeDeliveryType int
order *model.GoodsOrder // order里的信息是保持更新的
waybills map[int]*model.Waybill // 这个waybills里的状态信息是不真实的只使用id相关的信息
timerStatusType int // 0表示订单1表示运单
timerStatus int
timer *time.Timer
retryCount int // 失败后尝试的次数,调试阶段可能出现死循化,阻止这种情况发生
}
type StatusActionConfig struct {
partner.StatusActionParams
TimeoutAction func(savedOrderInfo *WatchOrderInfo) (err error) // 超时后需要执行的动作为nil表示此状态不需要执行监控 nil在GetStatusActionConfig返回时表示不修改缺省
}
// 重要:此调度器要求同一定单的处理逻辑必须是序列化了的,不然会有并发问题
type DefScheduler struct {
basesch.BaseScheduler
locker sync.RWMutex
defWorkflowConfig []map[int]*StatusActionConfig
orderMap jxutils.SyncMapWithTimeout
}
func NewWatchOrderInfo(order *model.GoodsOrder) (retVal *WatchOrderInfo) {
retVal = &WatchOrderInfo{
autoPickupTimeoutMinute: 1,
storeDeliveryType: scheduler.StoreDeliveryTypeCrowdSourcing,
waybills: map[int]*model.Waybill{},
}
retVal.SetOrder(order)
return retVal
}
func (s *WatchOrderInfo) SetOrder(order *model.GoodsOrder) (retVal *model.GoodsOrder) {
retVal = s.order
if s.order != order {
if order != nil {
s.updateOrderStoreFeature(order)
}
s.order = order
}
return retVal
}
func (s *WatchOrderInfo) updateOrderStoreFeature(order *model.GoodsOrder) (err error) {
globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s", order.VendorOrderID)
jxStoreID := jxutils.GetSaleStoreIDFromOrder(order)
if jxStoreID > 0 {
db := dao.GetDB()
storeMap, err2 := dao.FakeGetStoreMapByStoreID(db, jxStoreID, order.VendorID)
if err = err2; err != nil {
return err
}
s.autoPickupTimeoutMinute = int(storeMap.AutoPickup)
s.storeDeliveryType = FixedScheduler.GetStoreDeliveryType(order, storeMap)
if s.storeDeliveryType == scheduler.StoreDeliveryTypeByStore {
order.DeliveryFlag |= model.OrderDeliveryFlagMaskPurcahseDisabled
}
_, err = dao.UpdateEntity(db, order, "DeliveryFlag")
globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s, s.storeDeliveryType:%d, order.DeliveryFlag:%d", order.VendorOrderID, s.storeDeliveryType, order.DeliveryFlag)
}
return err
}
func init() {
sch := &DefScheduler{}
basesch.FixedBaseScheduler = &sch.BaseScheduler
FixedScheduler = sch
sch.IsReallyCallPlatformAPI = globals.ReallyCallPlatformAPI
scheduler.CurrentScheduler = sch
sch.defWorkflowConfig = []map[int]*StatusActionConfig{
map[int]*StatusActionConfig{
model.OrderStatusNew: &StatusActionConfig{ // 自动接单
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: 10 * time.Millisecond,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) {
order := savedOrderInfo.order
mobile := order.ConsigneeMobile
if order.ConsigneeMobile2 != "" {
mobile = order.ConsigneeMobile2
}
_ = sch.handleAutoAcceptOrder(order.VendorOrderID, order.VendorID, mobile, jxutils.GetSaleStoreIDFromOrder(order), nil, func(isAcceptIt bool) error {
if err = sch.AcceptOrRefuseOrder(order, isAcceptIt, ""); err != nil && err != scheduler.ErrOrderStatusAlreadySatisfyCurOperation {
// 为了解决京东新消息与接单消息乱序的问题
if errWithCode, ok := err.(*utils.ErrorWithCode); ok && errWithCode.Level() == 1 && errWithCode.IntCode() == -1 {
if order2, err2 := partner.GetPurchasePlatformFromVendorID(order.VendorID).GetOrder(order.VendorOrderID); err2 == nil && order2.Status > order.Status {
sch.OnOrderStatusChanged(model.Order2Status(order2), false)
err = nil
} else {
err = err2
}
}
if isAcceptIt {
sch.PrintOrderByOrder(jxcontext.AdminCtx, order)
}
}
return err
})
return nil
},
},
model.OrderStatusAccepted: &StatusActionConfig{ // 自动拣货
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: time2AutoPickupMin,
TimeoutGap: second2AutoPickupGap,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) {
if savedOrderInfo.autoPickupTimeoutMinute > 0 {
return sch.autoPickupGood(savedOrderInfo)
}
return nil
},
},
model.OrderStatusFinishedPickup: &StatusActionConfig{
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: 1 * time.Second,
TimeoutGap: 0,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) {
if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { // 自配送商家使用
return sch.createWaybillOn3rdProviders(savedOrderInfo, nil)
}
return nil
},
},
},
map[int]*StatusActionConfig{
model.WaybillStatusNew: &StatusActionConfig{
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: minute2Schedule3rdCarrier * time.Minute,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo) (err error) {
if savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore { // 非自配送商家使用
return sch.createWaybillOn3rdProviders(savedOrderInfo, nil)
}
return nil
},
},
},
}
}
func Init() {
configindb.WatchConfigChange(time2Schedule3rdCarrierKey, OnDefSchConfChanged)
if configTime, err := configindb.GetConfig(time2Schedule3rdCarrierKey, utils.Int2Str(minute2Schedule3rdCarrier)); err == nil {
OnDefSchConfChanged(time2Schedule3rdCarrierKey, configTime)
} else {
globals.SugarLogger.Errorf("defsch Init, error:%v", err)
}
}
// 以下是订单
func (s *DefScheduler) OnOrderNew(order *model.GoodsOrder, isPending bool) (err error) {
globals.SugarLogger.Debugf("OnOrderNew orderID:%s", order.VendorOrderID)
savedOrderInfo := s.loadSavedOrderFromMap(model.Order2Status(order), false)
if savedOrderInfo == nil {
savedOrderInfo = NewWatchOrderInfo(order)
s.orderMap.StoreWithTimeout(jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID), savedOrderInfo, orderMapStoreMaxTime)
} else {
savedOrderInfo.SetOrder(order) // 调整单或消息错序都可能进到这里来
}
if order.Status >= model.OrderStatusNew {
s.resetTimer(savedOrderInfo, nil, isPending)
if !isPending {
utils.CallFuncAsync(func() {
weixinmsg.NotifyNewOrder(order)
})
}
}
return err
}
// todo 这个接口应该可以直接传order的因为在OrderManager中每次都生成了
func (s *DefScheduler) OnOrderStatusChanged(status *model.OrderStatus, isPending bool) (err error) {
if status.LockStatus != model.OrderStatusUnknown || status.Status > model.OrderStatusUnknown {
globals.SugarLogger.Debugf("OnOrderStatusChanged orderID:%s %s, status:%v", status.VendorOrderID, model.OrderStatusName[status.Status], status)
savedOrderInfo := s.loadSavedOrderFromMap(status, true)
// if status.Status == model.OrderStatusNew {
// if !isPending {
// utils.CallFuncAsync(func() {
// weixinmsg.NotifyNewOrder(savedOrderInfo.order)
// })
// }
// }
s.updateOrderByStatus(savedOrderInfo.order, status)
if status.LockStatus == model.OrderStatusUnknown && status.Status > model.OrderStatusUnknown { // 只处理状态转换,一般消息不处理
s.resetTimer(savedOrderInfo, nil, isPending)
if status.Status >= model.OrderStatusDelivering {
// 会出现创建运单后门店自己转自送了例如828400083000222当前逻辑会导致此运单不会被取消有三个可能的修改方法考虑1
// 1在订单相应事件中取消不是candidate的运单
// 2在接收到运单接单时根据状态判断马上取消些运单
// 3在转自送失败或状态已经是自送时取消运单
var curWaybill *model.Waybill
if !(status.Status == model.OrderStatusCanceled) { // 订单取消时,取消所有运单
curWaybill = savedOrderInfo.waybills[savedOrderInfo.order.WaybillVendorID]
if status.Status == model.OrderStatusDelivered || status.Status == model.OrderStatusFinished {
if curWaybill != nil && curWaybill.WaybillVendorID != curWaybill.OrderVendorID {
globals.SugarLogger.Infof("OnOrderStatusChanged [运营2]订单orderID:%s可能被手动点击送达会对程序状态产生不利影响请通知门店不要这样操作", status.VendorOrderID)
}
}
}
s.cancelOtherWaybills(savedOrderInfo, curWaybill, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrOrderAlreadyFinished)
if status.Status >= model.OrderStatusEndBegin {
s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(status))
}
}
} else if status.LockStatus != model.OrderStatusUnknown {
s.stopTimer(savedOrderInfo)
}
} else if status.Status == model.OrderStatusApplyCancel {
globals.SugarLogger.Debugf("OnOrderStatusChanged orderID:%s %s, status:%v", status.VendorOrderID, model.OrderStatusName[status.Status], status)
savedOrderInfo := s.loadSavedOrderFromMap(status, true)
utils.CallFuncAsync(func() {
weixinmsg.NotifyUserApplyCancel(savedOrderInfo.order, status.Remark)
})
}
return err
}
// 以下是运单
func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill, isPending bool) (err error) {
if bill.Status > model.WaybillStatusUnknown {
globals.SugarLogger.Debugf("OnWaybillStatusChanged orderID:%s %s, bill:%v", bill.VendorOrderID, model.WaybillStatusName[bill.Status], bill)
savedOrderInfo := s.loadSavedOrderFromMap(model.Waybill2Status(bill), true)
order := savedOrderInfo.order
// todo 当前收到的事件顺序有时是乱的,不能严格限制,暂时放开
// if order.Status < model.OrderStatusFinishedPickup || order.Status > model.OrderStatusEndBegin { // 如果当前order状态是不应该出现运单状态
// globals.SugarLogger.Infof("OnWaybillStatusChanged orderID:%s status:%s is not suitable for waybill", order.VendorOrderID, model.OrderStatusName[order.Status])
// s.ProxyCancelWaybill(order, bill)
// s.stopTimer(savedOrderInfo)
// s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(model.Order2Status(order)))
// return nil
// }
// if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 { // 如果被停止调度,整个不动作
if bill.Status == model.WaybillStatusNew {
s.addWaybill2Map(savedOrderInfo, bill)
if !isPending {
if order.Status > model.OrderStatusEndBegin {
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
} else if s.IsOrderHasWaybill(order) {
globals.SugarLogger.Debugf("OnWaybillStatusChanged multiple waybill created, bill:%v", bill)
if s.IsOrderPlatformWaybill(bill) { // 是购物平台运单
if order.VendorID != order.WaybillVendorID { // 既有运单不是购物平台运单
globals.SugarLogger.Infof("OnWaybillStatusChanged bill:%v purchase platform bill came later than others, strange!!!", bill)
oldBill := savedOrderInfo.waybills[order.WaybillVendorID]
if oldBill != nil {
s.ProxyCancelWaybill(order, oldBill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
} else {
globals.SugarLogger.Warnf("OnWaybillStatusChanged bill:%v, oldBill is null, strange!!!", bill)
}
}
bill.WaybillVendorID = model.VendorIDUnknown
s.updateOrderByBill(order, bill, false)
} else {
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
}
}
}
// 只有购物平台的新运单消息才会启动抢单TIMER
if s.IsOrderPlatformWaybill(bill) {
s.resetTimer(savedOrderInfo, bill, isPending)
}
} else {
isBillExist := s.updateBillsInfo(savedOrderInfo, bill)
if !isBillExist {
globals.SugarLogger.Debugf("OnWaybillStatusChanged bill not exist! orderID:%s, bill:%v", bill.VendorOrderID, bill)
}
switch bill.Status {
case model.WaybillStatusAccepted:
s.resetTimer(savedOrderInfo, bill, isPending)
if (isBillExist || bill.WaybillVendorID != model.VendorIDDada) && !isPending { // todo 达达运单有错序的情况,临时看看
isBillAlreadyCandidate := s.isBillCandidate(order, bill)
// todo 购买平台的运单优先级最高但这样写也可能带来问题即在这个时间因为之前3方已经接单已经发出了转自送请求而且可能成功了所以加个状态判断
if order.WaybillVendorID == model.VendorIDUnknown ||
(s.IsOrderPlatformWaybill(bill) && order.VendorID != order.WaybillVendorID && (order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled) == 0) {
if s.IsOrderHasWaybill(order) {
// 进到这里的原因是在这个时间点购物平台物流已经抢单但抢单消息还没有被收到比如818810379000941
globals.SugarLogger.Infof("OnWaybillStatusChanged orderID:%s purchase platform waybill arrvied later, may case problem", order.VendorOrderID)
}
s.updateOrderByBill(order, bill, false)
s.cancelOtherWaybills(savedOrderInfo, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
if !s.IsOrderPlatformWaybill(bill) {
if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore {
s.SelfDeliverDelivering(savedOrderInfo.order, "")
} else {
s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, 2, 10*time.Second)
}
} else if s.IsSpecialOrderPlatformWaybill(bill) {
s.SelfDeliverDelivering(savedOrderInfo.order, "")
}
} else if !s.isBillCandidate(order, bill) && bill.WaybillVendorID != order.VendorID {
// 发生这种情况的原因就是两个接单事件几乎同时到达(来不及取消),也算正常
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
globals.SugarLogger.Infof("OnWaybillStatusChanged Accepted orderID:%s got multiple bill:%v", order.VendorOrderID, bill)
}
if s.isBillCandidate(order, bill) && order.WaybillVendorID != order.VendorID {
if !isBillAlreadyCandidate || !s.isWaybillCourierSame(savedOrderInfo, bill) {
utils.CallFuncAsync(func() {
weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate)
})
}
}
}
case model.WaybillStatusAcceptCanceled:
if s.isBillCandidate(order, bill) {
s.resetTimer(savedOrderInfo, bill, isPending)
if !isPending {
bill.WaybillVendorID = model.VendorIDUnknown
s.updateOrderByBill(order, bill, false)
// 取消抢单应该不需要发3方运单
// s.createWaybillOn3rdProviders(savedOrderInfo, bill)
}
} else if s.IsOrderHasWaybill(order) {
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
if !isPending {
globals.SugarLogger.Warnf("OnWaybillStatusChanged AcceptCanceled orderID:%s got multiple bill:%v, order details:%v", order.VendorOrderID, bill, order)
}
}
case model.WaybillStatusCourierArrived: // do nothing
s.resetTimer(savedOrderInfo, bill, isPending)
if s.isBillCandidate(order, bill) {
} else {
// s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
globals.SugarLogger.Infof("OnWaybillStatusChanged CourierArrived order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill)
}
case model.WaybillStatusFailed: // todo WaybillStatusFailed理解成订单整个失败了不需要再尝试创建运单了注意这里应该加个zabbix日志的报警
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if s.isBillCandidate(order, bill) {
s.resetTimer(savedOrderInfo, bill, isPending)
if !isPending {
globals.SugarLogger.Infof("OnWaybillStatusChanged WaybillStatusFailed, bill:%v", bill)
bill.WaybillVendorID = model.VendorIDUnknown
s.updateOrderByBill(order, bill, false)
}
} else {
// 创建运单失败时可能到这里来比如818874313000121
globals.SugarLogger.Infof("OnWaybillStatusChanged Failed bill:%v shouldn't got here, order details:%v", bill, order)
}
case model.WaybillStatusCanceled:
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if s.isBillCandidate(order, bill) || order.WaybillVendorID == model.VendorIDUnknown {
s.resetTimer(savedOrderInfo, bill, isPending)
if !isPending {
if s.IsOrderHasWaybill(order) {
bill.WaybillVendorID = model.VendorIDUnknown
s.updateOrderByBill(order, bill, false)
}
// 3方的运单取消才会重新发起创建3方订单购物平台的运单取消后它本身还会再创建新运单(NewWaybill事件有相应TIMER)),至少京东是这样的,暂时按京东的行为来
// 现在发现饿百取消订单后不会再创建运单了,所以饿百运单取消也允许直接创建三方运单
// 之前的条件是order.Status < model.OrderStatusDelivering但像订单902322817000122确实有在配送中取消状态改成非订单结束状态都可以
if order.Status < model.OrderStatusEndBegin && (bill.WaybillVendorID != order.VendorID || order.VendorID == model.VendorIDEBAI) {
s.createWaybillOn3rdProviders(savedOrderInfo, nil)
}
}
}
case model.WaybillStatusDelivering:
s.resetTimer(savedOrderInfo, bill, isPending)
if s.isBillCandidate(order, bill) {
// do nothing
} else {
// s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
globals.SugarLogger.Infof("OnWaybillStatusChanged Delivering order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill)
}
case model.WaybillStatusDelivered:
s.resetTimer(savedOrderInfo, bill, isPending)
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if !s.IsOrderPlatformWaybill(bill) && !isPending {
if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore {
s.SelfDeliverDelivered(order, "")
} else {
s.Swtich2SelfDelivered(order, "")
}
} else if s.IsSpecialOrderPlatformWaybill(bill) {
s.SelfDeliverDelivered(savedOrderInfo.order, "")
}
if !s.isBillCandidate(order, bill) {
// 一般只会消息乱序才会到这里,即新订单消息在运单接单消息后到达
// 典型的一个1223633660228537567
globals.SugarLogger.Infof("OnWaybillStatusChanged Delivered order(%d, %s) bill(%d, %s), bill:%v shouldn't get here", order.WaybillVendorID, order.VendorWaybillID, bill.WaybillVendorID, bill.VendorWaybillID, bill)
if order.WaybillVendorID == model.VendorIDUnknown {
s.updateOrderByBill(order, bill, false)
}
}
if !isPending {
utils.CallFuncAsync(func() {
weixinmsg.NotifyWaybillStatus(bill, order, false)
})
}
case model.WaybillStatusNeverSend: // 平台不配送,直接创建三方运单
s.resetTimer(savedOrderInfo, bill, isPending)
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if order.WaybillVendorID == model.VendorIDUnknown {
s.createWaybillOn3rdProviders(savedOrderInfo, nil)
}
}
s.updateBillsInfo(savedOrderInfo, bill) // 更新可能的运单状态变化
}
// }
}
return err
}
func (s *DefScheduler) isWaybillCourierSame(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
return savedOrderInfo.waybills[bill.WaybillVendorID] != nil && savedOrderInfo.waybills[bill.WaybillVendorID].CourierMobile == bill.CourierMobile
}
func (s *DefScheduler) addWaybill2Map(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) {
if _, ok := savedOrderInfo.waybills[bill.WaybillVendorID]; ok {
if !s.IsOrderPlatformWaybill(bill) { // 购买平台重复发相同号的新运单是正常的,京东就是
globals.SugarLogger.Warnf("addWaybill2Map bill:%v already exists", bill)
}
}
savedOrderInfo.waybills[bill.WaybillVendorID] = bill
}
func (s *DefScheduler) removeWaybillFromMap(savedOrderInfo *WatchOrderInfo, waybillVendorID int) {
if _, ok := savedOrderInfo.waybills[waybillVendorID]; ok {
delete(savedOrderInfo.waybills, waybillVendorID)
}
}
func (s *DefScheduler) createWaybillOn3rdProviders(savedOrderInfo *WatchOrderInfo, excludeBill *model.Waybill) (err error) {
order := savedOrderInfo.order
globals.SugarLogger.Debugf("createWaybillOn3rdProviders, orderID:%s, status:%d, excludeBill:%v", order.VendorOrderID, order.Status, excludeBill)
if order.VendorID == model.VendorIDELM {
return nil
}
if err = s.canOrderCreateWaybillNormally(order); err == nil {
if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
if savedOrderInfo.retryCount <= maxWaybillRetryCount {
_, err = s.CreateWaybillOnProviders4SavedOrder(jxcontext.NewWithOnlyUserName("admin"), savedOrderInfo, false)
} else {
err = fmt.Errorf("订单:%s已经自动创建过了%d次运单请人工处理", order.VendorOrderID, savedOrderInfo.retryCount)
globals.SugarLogger.Infof("createWaybillOn3rdProviders [运营]同一订单orderID:%s尝试了%d次创建运单失败, 停止调度,如果还需要发单,请人工处理", order.VendorOrderID, savedOrderInfo.retryCount)
}
} else {
globals.SugarLogger.Debugf("createWaybillOn3rdProviders, orderID:%s, store:%d dont't support 3rd delivery platform", order.VendorOrderID, jxutils.GetSaleStoreIDFromOrder(order))
}
if err != nil {
partner.CurOrderManager.OnOrderMsg(order, "自动创建三方运单", utils.LimitUTF8StringLen(err.Error(), 255))
}
} else {
err = nil
}
return err
}
func (s *DefScheduler) cancelOtherWaybills(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) {
globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep)
if (savedOrderInfo.order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
err = s.cancelOtherWaybills2(savedOrderInfo, bill2Keep, cancelReasonID, cancelReason)
} else {
globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v stop schedule", savedOrderInfo.order.VendorOrderID, bill2Keep)
}
return err
}
func (s *DefScheduler) cancelOtherWaybills2(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) {
globals.SugarLogger.Debugf("cancelOtherWaybills2, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep)
toBeDeleted := []*model.Waybill{}
for _, v := range savedOrderInfo.waybills {
if !s.IsOrderPlatformWaybill(v) && (bill2Keep == nil || !(v.WaybillVendorID == bill2Keep.WaybillVendorID && v.VendorWaybillID == bill2Keep.VendorWaybillID)) {
err2 := s.ProxyCancelWaybill(savedOrderInfo.order, v, cancelReasonID, cancelReason)
if err2 == nil {
toBeDeleted = append(toBeDeleted, v)
}
// 至少返回一个错误
if err == nil && err2 != nil {
err = err2
}
}
}
if len(toBeDeleted) > 0 {
// todo 这里为什么要删除运单,应该只需要在运单完成,取消或失败时才删除
// for _, v := range toBeDeleted {
// s.removeWaybillFromMap(savedOrderInfo, v.WaybillVendorID)
// }
} else {
globals.SugarLogger.Debugf("cancelOtherWaybills, orderID:%s, bill:%v cancel 0 bills", savedOrderInfo.order.VendorOrderID, bill2Keep)
}
return err
}
func (s *DefScheduler) swtich2SelfDeliverWithRetry(savedOrderInfo *WatchOrderInfo, bill *model.Waybill, retryCount int, duration time.Duration) {
order := savedOrderInfo.order
globals.SugarLogger.Debugf("swtich2SelfDeliverWithRetry orderID:%s", order.VendorOrderID)
if (order.DeliveryFlag & model.OrderDeliveryFlagMaskPurcahseDisabled) == 0 {
if order.WaybillVendorID != order.VendorID {
if err := s.Swtich2SelfDeliver(order, ""); err != nil && err != scheduler.ErrOrderStatusAlreadySatisfyCurOperation {
globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry failed, bill:%v, err:%v", bill, err)
if retryCount > 0 {
utils.AfterFuncWithRecover(duration, func() {
jxutils.CallMsgHandlerAsync(func() {
s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, retryCount-1, duration)
}, order.VendorOrderID)
})
} else {
globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry finally failed, orderID:%s bill:%v, err:%v", order.VendorOrderID, bill, err)
if s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonSwitch2SelfFailed, partner.CancelWaybillReasonStrSwitch2SelfFailed) == nil {
// 转自送失败的取消,要将订单中的运单状态更新
if s.isBillCandidate(order, bill) {
bill.WaybillVendorID = model.VendorIDUnknown
s.updateOrderByBill(order, bill, false)
}
}
}
} else {
s.removeWaybillFromMap(savedOrderInfo, order.VendorID)
partner.CurOrderManager.UpdateOrderStatusAndFlag(order)
}
} else {
// 进到这里的原因是,在这个时间点,购物平台物流已经抢单(但抢单消息还没有被收到),所以转自送会失败 比如818810379000941更好的做法应该是判断Swtich2SelfDeliver的返回值这种情况下就不得试了
globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry orderID:%s status is wrong(maybe purchase platform accepted waybill)", order.VendorOrderID)
// globals.SugarLogger.Warnf("swtich2SelfDeliverWithRetry orderID:%s status is wrong, order details:%v", order.VendorOrderID, order)
}
}
}
// 这个函数这样写的原因是适应一些消息错序
func (s *DefScheduler) loadSavedOrderFromMap(status *model.OrderStatus, isAutoLoad bool) *WatchOrderInfo {
globals.SugarLogger.Debugf("loadSavedOrderFromMap status:%v", status)
universalOrderID := jxutils.ComposeUniversalOrderID(status.RefVendorOrderID, status.RefVendorID)
var realSavedInfo *WatchOrderInfo
if savedInfo, ok := s.orderMap.Load(universalOrderID); ok {
realSavedInfo = savedInfo.(*WatchOrderInfo)
}
if isAutoLoad && (realSavedInfo == nil || !model.IsOrderSolid(realSavedInfo.order)) {
if realSavedInfo == nil {
realSavedInfo = NewWatchOrderInfo(nil)
s.orderMap.StoreWithTimeout(universalOrderID, realSavedInfo, orderMapStoreMaxTime)
} else {
globals.SugarLogger.Infof("loadSavedOrderFromMap order is incomplete, orderID:%s, load it", status.RefVendorOrderID)
}
if order, err := partner.CurOrderManager.LoadOrder(status.RefVendorOrderID, status.RefVendorID); err == nil {
realSavedInfo.SetOrder(order)
} else {
realSavedInfo.SetOrder(&model.GoodsOrder{
VendorOrderID: status.RefVendorOrderID,
VendorID: status.RefVendorID,
Status: status.Status,
StatusTime: status.StatusTime,
OrderCreatedAt: status.StatusTime,
WaybillVendorID: model.VendorIDUnknown,
})
globals.SugarLogger.Infof("loadSavedOrderFromMap can not load order orderID:%s with error:%v", status.RefVendorOrderID, err)
}
}
return realSavedInfo
}
func (s *DefScheduler) stopTimer(savedOrderInfo *WatchOrderInfo) {
if savedOrderInfo.timer != nil {
globals.SugarLogger.Debugf("stopTimer orderID:%s", savedOrderInfo.order.VendorOrderID)
savedOrderInfo.timer.Stop()
savedOrderInfo.timerStatus = 0
savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown
savedOrderInfo.timer = nil
}
}
func (s *DefScheduler) resetTimer(savedOrderInfo *WatchOrderInfo, bill *model.Waybill, isPending bool) {
order := savedOrderInfo.order
status := order.Status
statusType := scheduler.TimerStatusTypeOrder
statusTime := order.StatusTime
if bill != nil {
status = bill.Status
statusType = scheduler.TimerStatusTypeWaybill
statusTime = bill.StatusTime
}
globals.SugarLogger.Debugf("resetTimer, orderID:%s statusType:%d status:%v", order.VendorOrderID, statusType, status)
if statusType != savedOrderInfo.timerStatusType || status >= savedOrderInfo.timerStatus { // 新设置的TIMER不能覆盖状态在其后的TIMER如果状态回绕需要注意
config := s.mergeOrderStatusConfig(savedOrderInfo, statusTime, statusType, status)
if config == nil || config.TimerType != partner.TimerTypeByPass {
s.stopTimer(savedOrderInfo)
}
if config != nil && config.TimeoutAction != nil && config.TimerType != partner.TimerTypeByPass {
timeout := config.GetRefTimeout(statusTime)
if config.TimeoutGap != 0 {
timeout += time.Duration(rand.Intn(int(config.TimeoutGap))) * time.Second
}
if isPending && timeout < pendingOrderTimerMaxSecond*time.Second { // 如果是PENDING的订单则将其分布到2--5秒内让后续事件有机会执行
timeout = time.Duration(jxutils.MapValue2Scope(int64(timeout), -pendingOrderTimerMinMinSecond*1000, pendingOrderTimerMaxSecond*1000, pendingOrderTimerMinSecond*1000, pendingOrderTimerMaxSecond*1000)) * time.Millisecond
} else if timeout < 0 {
timeout = 0
}
if timeout == 0 {
config.TimeoutAction(savedOrderInfo)
} else {
timerName := ""
if statusType == scheduler.TimerStatusTypeOrder {
timerName = model.OrderStatusName[status]
} else if statusType == scheduler.TimerStatusTypeWaybill {
timerName = model.WaybillStatusName[status]
}
savedOrderInfo.timerStatusType = statusType
savedOrderInfo.timerStatus = status
savedOrderInfo.timer = utils.AfterFuncWithRecover(timeout, func() {
jxutils.CallMsgHandlerAsync(func() {
globals.SugarLogger.Debugf("fire timer:%s, orderID:%s", timerName, order.VendorOrderID)
config.TimeoutAction(savedOrderInfo)
savedOrderInfo.timerStatus = 0
savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown
}, order.VendorOrderID)
})
}
globals.SugarLogger.Debugf("resetTimer, orderID:%s, status:%d, timeout:%v", order.VendorOrderID, status, timeout)
}
}
}
func (s *DefScheduler) mergeOrderStatusConfig(savedOrderInfo *WatchOrderInfo, statusTime time.Time, statusType, status int) (retVal *StatusActionConfig) {
s.locker.RLock()
defer func() {
s.locker.RUnlock()
}()
defConfig := s.defWorkflowConfig[statusType][status]
if defConfig == nil {
return nil
}
retVal = &StatusActionConfig{}
*retVal = *defConfig
order := savedOrderInfo.order
var vendorActionParams *partner.StatusActionParams
// 非立即达订单timer设置
if model.IsOrderSolid(order) {
if order.BusinessType != model.BusinessTypeImmediate { // 非立即达订单
if utils.IsTimeZero(order.ExpectedDeliveredTime) {
globals.SugarLogger.Warnf("mergeOrderStatusConfig orderID:%s 非立即达订单没有预计送达时间, orderDetail:%s", order.VendorOrderID, utils.Format4Output(order, false))
} else if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusFinishedPickup { // 这个只针对自配送门店才有效
vendorActionParams = &partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: order.ExpectedDeliveredTime.Sub(time.Now()) - dingShiDaAheadTime,
TimeoutGap: -1,
}
timeout := statusTime.Sub(time.Now()) + minMinute2Schedule3rdCarrier*time.Minute
if vendorActionParams.GetRefTimeout(statusTime) < timeout { // 如果非立即达订单根据ExpectedDeliveredTime算出来的timeout太早
vendorActionParams.Timeout = timeout
vendorActionParams.TimeoutGap = 0
}
} else if statusType == scheduler.TimerStatusTypeWaybill && status == model.WaybillStatusNew { // 因为有些平台(比如美团外卖)的定时达单,很早就创建运单了
vendorActionParams = &partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: order.ExpectedDeliveredTime.Sub(time.Now()) - dingShiDaAheadTime,
TimeoutGap: -1,
}
}
}
}
if vendorActionParams == nil {
vendorActionParams = partner.GetPurchasePlatformFromVendorID(order.VendorID).GetStatusActionTimeout(order, statusType, status)
}
// 自动拣货TIMER
if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusAccepted {
if savedOrderInfo.autoPickupTimeoutMinute > 0 {
if utils.IsTimeZero(order.PickDeadline) { // 没有最后拣货时间,正推,以订单中的配置为准
if utils.IsTimeZero(order.ExpectedDeliveredTime) || order.BusinessType == model.BusinessTypeImmediate { // 立即达或没有最后送达时间
// 以缺省配置或平台相关中的配置为准
} else { // 非立即达且有最后送达时间
timeout := time2AutoPickupMin
if savedOrderInfo.autoPickupTimeoutMinute > 1 {
timeout = time.Duration(savedOrderInfo.autoPickupTimeoutMinute) * time.Minute
}
vendorActionParams = &partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: order.ExpectedDeliveredTime.Add(-time2Delivered).Sub(time.Now()) + timeout,
}
}
} else { // 有最后拣货时间,反推
vendorActionParams = &partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: order.PickDeadline.Sub(time.Now()) - time2AutoPickupAhead - second2AutoPickupGap*time.Second,
TimeoutGap: second2AutoPickupGap,
}
}
}
}
if vendorActionParams != nil {
retVal.Timeout = vendorActionParams.Timeout
if vendorActionParams.TimeoutGap >= 0 {
retVal.TimeoutGap = vendorActionParams.TimeoutGap
}
if vendorActionParams.TimerType != partner.TimerTypeNoOverride {
retVal.TimerType = vendorActionParams.TimerType
}
}
return retVal
}
func (s *DefScheduler) handleAutoAcceptOrder(orderID string, vendorID int, userMobile string, jxStoreID int, db orm.Ormer, handler func(accepted bool) error) int {
handleType := 0
if userMobile != "" {
if db == nil {
db = orm.NewOrm()
}
user := &legacymodel.BlackClient{
Mobile: userMobile,
}
if err := db.Read(user, "Mobile"); err != nil {
if err != orm.ErrNoRows {
globals.SugarLogger.Errorf("read data error:%v, data:%v, vendorID:%d", err, user, vendorID)
}
// 在访问数据库出错的情况下,也需要自动接单
handleType = 1
} else {
// 强制拒单
globals.SugarLogger.Infof("force reject order:%s, vendorID:%d", orderID, vendorID)
handleType = -1
}
} else {
globals.SugarLogger.Infof("order:%s, vendorID:%d, mobile is empty, should accept order", orderID, vendorID)
handleType = 1
}
if handleType == 1 {
handler(true)
} else if handleType == -1 {
handler(false)
}
return handleType
}
func (s *DefScheduler) updateOrderByStatus(order *model.GoodsOrder, status *model.OrderStatus) (retVal *model.GoodsOrder) {
order.Status = status.Status
order.VendorStatus = status.VendorStatus
order.StatusTime = status.StatusTime
order.LockStatus = status.LockStatus
return order
}
func (s *DefScheduler) updateOrderByBill(order *model.GoodsOrder, bill *model.Waybill, revertStatus bool) {
if order.Status > model.OrderStatusEndBegin {
return
}
if bill.WaybillVendorID == model.VendorIDUnknown {
bill.VendorWaybillID = ""
}
partner.CurOrderManager.UpdateWaybillVendorID(bill, revertStatus)
order.WaybillVendorID = bill.WaybillVendorID
order.VendorWaybillID = bill.VendorWaybillID
if revertStatus {
order.Status = model.OrderStatusFinishedPickup
}
}
func (s *DefScheduler) updateBillsInfo(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (isBillExist bool) {
if savedOrderInfo != nil {
if savedBill := savedOrderInfo.waybills[bill.WaybillVendorID]; savedBill != nil {
isBillExist = true
if savedBill.Status > bill.Status {
bill.Status = savedBill.Status
} else if bill.Status > savedBill.Status {
savedBill.Status = bill.Status
}
}
}
return isBillExist
}
func (s *DefScheduler) autoPickupGood(savedOrderInfo *WatchOrderInfo) (err error) {
err = s.PickupGoods(savedOrderInfo.order, savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore, "")
if err == scheduler.ErrOrderStatusAlreadySatisfyCurOperation {
err = nil
}
return err
}
func (s *DefScheduler) isBillCandidate(order *model.GoodsOrder, bill *model.Waybill) bool {
return order.WaybillVendorID == bill.WaybillVendorID && order.VendorWaybillID == bill.VendorWaybillID
}
func (s *DefScheduler) ProxyCancelWaybill(order *model.GoodsOrder, bill *model.Waybill, cancelReasonID int, cancelReason string) (err error) {
globals.SugarLogger.Debugf("ProxyCancelWaybill orderID:%s", order.VendorOrderID)
if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
return s.CancelWaybill(bill, cancelReasonID, cancelReason)
}
globals.SugarLogger.Debugf("ProxyCancelWaybill orderID:%s stop schedule, bypass CancelWaybill", order.VendorOrderID)
return nil
}
func OnDefSchConfChanged(key, value string) {
if key == time2Schedule3rdCarrierKey {
waitMinutes := int(utils.Str2Int64WithDefault(value, minute2Schedule3rdCarrier))
if waitMinutes >= 0 {
FixedScheduler.locker.Lock()
defer func() {
FixedScheduler.locker.Unlock()
}()
conf := FixedScheduler.defWorkflowConfig[1][model.WaybillStatusNew]
conf.Timeout = time.Duration(waitMinutes) * time.Minute
globals.SugarLogger.Debugf("defsch wait miniutes 4 3rd delivery changed to:%d", waitMinutes)
}
}
}
// 判断是否是购买平台自有物流
// 对于京东,饿百来说,就是其自有的物流,对于微商城来说,是达达
func (s *DefScheduler) IsOrderPlatformWaybill(bill *model.Waybill) bool {
return bill.OrderVendorID == bill.WaybillVendorID || s.IsSpecialOrderPlatformWaybill(bill)
}
// 是否是特殊物流
func (s *DefScheduler) IsSpecialOrderPlatformWaybill(bill *model.Waybill) bool {
return (bill.OrderVendorID == model.VendorIDWSC && bill.WaybillVendorID == model.VendorIDDada)
}
func (s *DefScheduler) IsOrderHasWaybill(order *model.GoodsOrder) bool {
return order.WaybillVendorID != model.VendorIDUnknown
}