Files
jx-callback/business/jxcallback/scheduler/defsch/defsch.go

1357 lines
60 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/ddmsg"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/jx-callback/business/jxutils/netprinter"
"git.rosy.net.cn/jx-callback/business/jxutils/smsmsg"
"git.rosy.net.cn/jx-callback/business/msghub"
"git.rosy.net.cn/baseapi/platformapi/dingdingapi"
"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做调整
minute2Schedule3rdCarrier4Ebai = 30 // 饿百的最少转自配送需要的时间(分钟)
minMinute2Schedule3rdCarrier = 5 // 转三方配送最少等待时间(分钟)
time2AutoPickupMin = 14 * time.Minute // 自动拣货等待时间这个只有在没有PickDeadline信息才有用否则会根据PickDeadline设置
second2AutoPickupGap = 60 //随机60秒
time2AutoPickupAhead = 20 * time.Second // 有最后拣货时间的提前值
switch2SelfDeliverRetryGap = 3 * time.Second // 转自送失败尝试的时间间隙
switch2SelfDeliverRetryCount = 2 // 转自送失败尝试次数
// 把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 // 定时达订单开始召唤配送的提前时间
minAddWaybillTipMinute = 20 // 最少开始加小费分钟(距离拣货完成)
addWaybillTipGap = 15 // 加一元小费间隔的分钟数
maxWaybillTipMoney = 400 // 最大小费
baseWaybillFee = 600 // 基本运费
ebaiCancelWaybillMaxFee = 1000 // 饿百取消运单最高运费
maxJxStoreDeliveryFee = 2000
)
const (
maxAddFee = 300 // 最大增加费用,单位为分,超过不发三方配送了
)
var (
FixedScheduler *DefScheduler
)
type tTimerInfo struct {
statusType int
vendorID int
status int
timer *time.Timer
timerTime time.Time
}
type WatchOrderInfo struct {
order *model.GoodsOrder // order里的信息是保持更新的
autoPickupTimeoutMinute int // 0表示禁用1表示用缺省值time2AutoPickupMin其它表示分钟数
isDeliveryCompetition bool
isNeedCreate3rdWaybill bool
watchWabillStartAt *time.Time
waybills map[int]*model.Waybill // 这个waybills里的状态信息是不真实的只使用id相关的信息
// timerStatusType int // 0表示订单1表示运单
// timerStatus int
// timer *time.Timer
// timerTime time.Time
timerList []*tTimerInfo
retryCount int // 失败后尝试的次数,调试阶段可能出现死循化,阻止这种情况发生
}
type StatusActionConfig struct {
partner.StatusActionParams
TimeoutAction func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) // 超时后需要执行的动作为nil表示此状态不需要执行监控 nil在GetStatusActionConfig返回时表示不修改缺省
ShouldSetTimer func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool
}
func (c *StatusActionConfig) CallTimeoutAction(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) {
if c.TimeoutAction != nil &&
c.CallShouldSetTimer(savedOrderInfo, bill) {
err = c.TimeoutAction(savedOrderInfo, bill)
}
return err
}
func (c *StatusActionConfig) CallShouldSetTimer(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
if c.ShouldSetTimer != nil {
return c.ShouldSetTimer(savedOrderInfo, bill)
}
return true
}
// 重要:此调度器要求同一定单的处理逻辑必须是序列化了的,不然会有并发问题
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,
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()
storeDetail, err2 := dao.GetStoreDetail(db, jxStoreID, order.VendorID)
if err = err2; err != nil {
return err
}
s.autoPickupTimeoutMinute = int(storeDetail.AutoPickup)
s.isDeliveryCompetition = storeDetail.DeliveryCompetition != 0
}
return err
}
func (s *WatchOrderInfo) GetWaybillVendorIDs() (vendorIDs []int) {
for vendorID := range s.waybills {
vendorIDs = append(vendorIDs, vendorID)
}
return vendorIDs
}
// orderType-1全部
// vendorID-1全部
// status-1全部
func (w *WatchOrderInfo) StopTimer(statusType, vendorID, status int) {
var newTimerList []*tTimerInfo
for _, timerInfo := range w.timerList {
if (statusType == -1 || statusType == timerInfo.statusType) &&
(vendorID == -1 || vendorID == timerInfo.vendorID) &&
(status == -1 || status >= timerInfo.status) {
if timerInfo.timer != nil {
timerInfo.timer.Stop()
timerInfo.timer = nil
}
} else {
newTimerList = append(newTimerList, timerInfo)
}
}
w.timerList = newTimerList
}
func (w *WatchOrderInfo) AddTimer(timerInfo *tTimerInfo) {
w.timerList = append(w.timerList, timerInfo)
}
func (w *WatchOrderInfo) GetCreateWaybillTimeout() (timeoutSecond int) {
// if w.timerStatusType == scheduler.TimerStatusTypeWaybill && w.timerStatus == model.WaybillStatusNew {
// timeoutSecond = int(w.timerTime.Sub(time.Now()) / time.Second)
// }
for _, timerInfo := range w.timerList {
if timerInfo.statusType == scheduler.TimerStatusTypeWaybill &&
timerInfo.status == model.WaybillStatusNew {
timeoutSecond = int(timerInfo.timerTime.Sub(time.Now()) / time.Second)
break
}
}
return timeoutSecond
}
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, bill *model.Waybill) (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 {
partner.CurOrderManager.OnOrderMsg(order, "自动接单失败", err.Error())
// 为了解决京东新消息与接单消息乱序的问题
if errWithCode, ok := err.(*utils.ErrorWithCode); ok && errWithCode.Level() == 1 && errWithCode.IntCode() == -1 {
if order2, err2 := partner.GetPurchaseOrderHandlerFromVendorID(order.VendorID).GetOrder(order.VendorOrgCode, order.VendorOrderID); err2 == nil {
if order2.Status > order.Status {
order.Status = order2.Status
jxutils.CallMsgHandlerAsync(func() {
sch.OnOrderStatusChanged(order, model.Order2Status(order2), false)
}, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID))
err = nil
}
} else {
err = err2
}
}
}
if isAcceptIt {
if err == nil {
sch.notifyNewOrder(order)
msghub.OnNewOrder(order)
}
} else {
partner.CurOrderManager.OnOrderMsg(order, "黑名单拒单", "")
}
return err
})
return nil
},
ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
return savedOrderInfo.order.LockStatus == model.LockStatusUnlocked && savedOrderInfo.order.Status == model.OrderStatusNew
},
},
model.OrderStatusAccepted: &StatusActionConfig{ // 自动拣货
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: time2AutoPickupMin,
TimeoutGap: second2AutoPickupGap,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) {
if err = sch.autoPickupGood(savedOrderInfo); err != nil {
partner.CurOrderManager.OnOrderMsg(savedOrderInfo.order, "自动拣货失败", err.Error())
}
return nil
},
ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
return savedOrderInfo.autoPickupTimeoutMinute > 0 && savedOrderInfo.order.Status < model.OrderStatusFinishedPickup
},
},
model.OrderStatusFinishedPickup: &StatusActionConfig{
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: 1 * time.Second,
TimeoutGap: 0,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) {
// 启动抢单TIMER
sch.saveDeliveryFeeFromAndStartWatch(savedOrderInfo, savedOrderInfo.order.StatusTime)
return sch.createWaybillOn3rdProviders(savedOrderInfo, 0, nil)
},
ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
return model.IsOrderDeliveryByStore(savedOrderInfo.order) // 自配送商家使用
},
},
},
map[int]*StatusActionConfig{
// todo 平台物流二次创建运单的话这个TIMER有问题
model.WaybillStatusNew: &StatusActionConfig{
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseStatusTime,
Timeout: minute2Schedule3rdCarrier * time.Minute,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) {
return sch.createWaybillOn3rdProviders(savedOrderInfo, 0, nil)
},
ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
// 饿百转自送的时机不太清楚,暂时禁用超时转自送,在饿百运单取消时还是会自动创建
// 非自配送商家使用
order := savedOrderInfo.order
return order.VendorID != model.VendorIDEBAI &&
order.VendorID == bill.WaybillVendorID &&
savedOrderInfo.isDeliveryCompetition &&
model.IsOrderDeliveryByPlatform(order) &&
isOrderCanSwitch2SelfDeliver(order) &&
(order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin)
},
},
//*
model.WaybillStatusCanceled: &StatusActionConfig{
StatusActionParams: partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: 1 * time.Second,
},
TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) {
return sch.createWaybillOn3rdProviders(savedOrderInfo, ebaiCancelWaybillMaxFee, nil)
},
ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool {
order := savedOrderInfo.order
// 非自配送商家使用
return order.VendorID == model.VendorIDEBAI &&
order.VendorID == bill.WaybillVendorID &&
savedOrderInfo.isDeliveryCompetition &&
model.IsOrderDeliveryByPlatform(order) &&
isOrderCanSwitch2SelfDeliver(order) &&
(order.Status >= model.OrderStatusAccepted && order.Status < model.OrderStatusEndBegin) // 运单与订单时间有错序的情况放开订单条件至OrderStatusAccepted
},
},
//*/
},
}
}
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)
savedOrderInfo.SetOrder(order)
if order.Status >= model.OrderStatusNew {
s.resetTimer(savedOrderInfo, nil, isPending)
if !isPending && order.Status >= model.OrderStatusAccepted { // 有订单消息错序先收到接单消息再收到新订单消息导致接单TIMER不动作这里补一下
s.notifyNewOrder(order)
msghub.OnNewOrder(order)
}
}
return err
}
// todo 这个接口应该可以直接传order的因为在OrderManager中每次都生成了
func (s *DefScheduler) OnOrderStatusChanged(order *model.GoodsOrder, status *model.OrderStatus, isPending bool) (err error) {
if status.Status > model.OrderStatusMsg && status.Status != model.OrderStatusUnknown {
globals.SugarLogger.Debugf("OnOrderStatusChanged orderID:%s %s, status:%v", status.VendorOrderID, model.OrderStatusName[status.Status], status)
if order == nil {
globals.SugarLogger.Warnf("OnOrderStatusChanged order is nil, status:%s", utils.Format4Output(status, true))
} else if order.Status > model.OrderStatusUnknown && status.Status > model.OrderStatusUnknown && order.Status != status.Status {
globals.SugarLogger.Warnf("OnOrderStatusChanged strange order:%s, status:%s", utils.Format4Output(order, true), utils.Format4Output(status, true))
}
savedOrderInfo := s.loadSavedOrderFromMap(status, false) // 可能会有重复的消息(比如订单取消)
statusChanged := savedOrderInfo.order == nil || savedOrderInfo.order.Status != order.Status
savedOrderInfo.SetOrder(order)
if (model.IsOrderUnlockStatus(status.Status)) ||
(order.LockStatus == model.OrderStatusUnknown && (status.Status > model.OrderStatusUnknown || status.Status == model.OrderStatusRefuseFailedGetGoods)) { // 只处理状态转换,一般消息不处理
if status.Status == model.OrderStatusRefuseFailedGetGoods && order.Status != model.OrderStatusFinishedPickup && !model.IsOrderFinalStatus(order.Status) {
order.Status = model.OrderStatusFinishedPickup
partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order)
}
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.OrderStatusFinished {
if curWaybill != nil && !model.IsWaybillPlatformOwn(curWaybill) {
globals.SugarLogger.Infof("OnOrderStatusChanged [运营2]订单orderID:%s可能被手动点击送达会对程序状态产生不利影响请通知门店不要这样操作", status.VendorOrderID)
}
}
}
s.cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo, curWaybill, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrOrderAlreadyFinished)
if status.Status >= model.OrderStatusEndBegin {
s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(status))
}
}
if !isPending {
if status.Status == model.OrderStatusAgreeFailedGetGoods || status.Status == model.OrderStatusDeliverFailed {
s.updateOrderByBill(order, nil, status.Status != model.OrderStatusAgreeFailedGetGoods)
s.removeWaybillFromMap(savedOrderInfo, order.VendorID)
}
}
}
if order.LockStatus != model.OrderStatusUnknown {
s.stopTimer(savedOrderInfo)
}
if !isPending {
if order.Flag&model.OrderFlagMaskFake != 0 && status.Status == model.OrderStatusAccepted {
s.autoPickupGood(savedOrderInfo)
} else if status.Status == model.OrderStatusFinishedPickup || status.Status == model.OrderStatusCanceled {
if statusChanged && status.Status == model.OrderStatusCanceled {
s.notifyOrderCanceled(savedOrderInfo.order)
}
msghub.OnFinishedPickup(savedOrderInfo.order)
} else if status.Status == model.OrderStatusApplyCancel || //model.IsOrderLockStatus(status.Status) ||
status.Status == model.OrderStatusApplyFailedGetGoods || //model.IsOrderUnlockStatus(status.Status) ||
status.Status == model.OrderStatusAgreeFailedGetGoods ||
status.Status == model.OrderStatusDeliverFailed {
if status.Status == model.OrderStatusApplyFailedGetGoods {
dao.ClearOrderFlag(dao.GetDB(), model.AdminName, order.VendorOrderID, order.VendorID, ^(model.OrderFlagMaskFailedGetGoods | model.OrderFlagMaskCallPMCourier))
}
if status.Status == model.OrderStatusApplyCancel {
s.notifyUserApplyCancel(savedOrderInfo.order, status.Remark)
}
msghub.OnKeyOrderStatusChanged(savedOrderInfo.order)
}
}
}
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 model.IsOrderHaveWaybill(order) {
globals.SugarLogger.Debugf("OnWaybillStatusChanged multiple waybill created, bill:%v", bill)
if model.IsWaybillPlatformOwn(bill) { // 是购物平台运单
if !model.IsOrderHaveOwnWaybill(order) { // 既有运单不是购物平台运单
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)
}
}
s.updateOrderByBill(order, nil, false)
if time.Now().Sub(order.OrderCreatedAt) < 2*time.Minute { // 京东一些门店设置成了接单即拣货完成,这种情况下自动调用拣货完成
s.autoPickupGood(savedOrderInfo)
}
} else {
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
}
}
flag2Clear := model.WaybillVendorID2Mask(bill.WaybillVendorID)
if order.DeliveryFlag&flag2Clear != 0 {
order.DeliveryFlag &= ^flag2Clear
err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order)
}
}
// 购物平台的新运单消息会启动抢单TIMER
if model.IsWaybillPlatformOwn(bill) {
s.resetTimer(savedOrderInfo, bill, isPending)
s.saveDeliveryFeeFromAndStartWatch(savedOrderInfo, bill.StatusTime)
}
} else {
isBillExist := s.updateBillsInfo(savedOrderInfo, bill)
if !isBillExist {
// s.addWaybill2Map(savedOrderInfo, bill) // updateBillsInfo中会添加
globals.SugarLogger.Debugf("OnWaybillStatusChanged bill not exist! orderID:%s, bill:%v", bill.VendorOrderID, bill)
}
switch bill.Status {
case model.WaybillStatusAccepted, model.WaybillStatusCourierArrived, model.WaybillStatusDelivering:
s.resetTimer(savedOrderInfo, bill, isPending)
if (isBillExist || bill.WaybillVendorID != model.VendorIDDada) && !isPending { // todo 达达运单有错序的情况,临时看看
isBillAlreadyCandidate := s.isBillCandidate(order, bill)
// todo 购买平台的运单优先级最高但这样写也可能带来问题即在这个时间因为之前3方已经接单已经发出了转自送请求而且可能成功了所以加个状态判断
if !model.IsOrderHaveWaybill(order) ||
(model.IsWaybillPlatformOwn(bill) && !model.IsOrderHaveOwnWaybill(order) && (order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled) == 0) {
if model.IsOrderHaveWaybill(order) {
// 进到这里的原因是在这个时间点购物平台物流已经抢单但抢单消息还没有被收到比如818810379000941
globals.SugarLogger.Infof("OnWaybillStatusChanged orderID:%s purchase platform waybill arrvied later, may cause problem", order.VendorOrderID)
}
s.updateOrderByBill(order, bill, false)
s.cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
if model.IsWaybillPlatformOwn(bill) {
if bill.Status == model.WaybillStatusDelivering && order.Status < model.OrderStatusEndBegin {
// 强制将订单状态置为配送中?
order.Status = model.OrderStatusDelivering
partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order)
}
} else {
if model.IsOrderDeliveryByStore(savedOrderInfo.order) {
if err := s.SelfDeliverDelivering(savedOrderInfo.order, bill.CourierMobile); err != nil {
partner.CurOrderManager.OnOrderMsg(order, "自送出设置失败", err.Error())
}
s.notify3rdPartyWaybill(order, bill, isBillAlreadyCandidate)
} else {
s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, switch2SelfDeliverRetryCount, switch2SelfDeliverRetryGap)
}
}
} 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 isBillAlreadyCandidate && !s.isWaybillCourierSame(savedOrderInfo, bill) && !model.IsWaybillPlatformOwn(bill) {
s.notify3rdPartyWaybill(order, bill, isBillAlreadyCandidate)
}
order.Flag &= ^model.OrderFlagMaskFailedGetGoods
err = partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order)
}
case model.WaybillStatusAcceptCanceled:
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if s.isBillCandidate(order, bill) {
s.resetTimer(savedOrderInfo, bill, isPending)
if !isPending {
s.updateOrderByBill(order, nil, true)
}
} else if model.IsOrderHaveWaybill(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.WaybillStatusCanceled, model.WaybillStatusFailed:
s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
if s.isBillCandidate(order, bill) || order.WaybillVendorID == model.VendorIDUnknown {
s.resetTimer(savedOrderInfo, bill, isPending)
if !isPending {
if model.IsOrderHaveWaybill(order) {
s.updateOrderByBill(order, nil, true)
}
// 3方的运单取消才会重新发起创建3方订单购物平台的运单取消后它本身还会再创建新运单(NewWaybill事件有相应TIMER)),至少京东是这样的,暂时按京东的行为来
// 现在发现饿百取消订单后不会再创建运单了,所以饿百运单取消也允许直接创建三方运单
// 之前的条件是order.Status < model.OrderStatusDelivering但像订单902322817000122确实有在配送中取消状态改成非订单结束状态都可以
// OrderStatusFinishedPickup状态的订单依赖于TIMER重新建运单
if bill.DeliveryFlag&model.WaybillDeliveryFlagMaskActiveCancel == 0 {
if (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && (bill.WaybillVendorID != order.VendorID /* || bill.WaybillVendorID == model.VendorIDEBAI*/) {
s.createWaybillOn3rdProviders(savedOrderInfo, 0, 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 !isPending {
var err2 error
if !model.IsWaybillPlatformOwn(bill) {
if model.IsOrderDeliveryByStore(order) {
err2 = s.SelfDeliverDelivered(order, "")
} else if model.IsOrderDeliveryByPlatform(order) {
err2 = s.Swtich2SelfDelivered(order, "")
}
}
if err2 != nil {
partner.CurOrderManager.OnOrderMsg(order, "送达设置失败", err2.Error())
}
}
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 !model.IsOrderHaveWaybill(order) {
s.updateOrderByBill(order, bill, false)
}
}
if !isPending {
s.notify3rdPartyWaybill(order, bill, false)
}
// case model.WaybillStatusNeverSend: // 平台不配送,直接创建三方运单
// s.resetTimer(savedOrderInfo, bill, isPending)
// s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID)
// if order.WaybillVendorID == model.VendorIDUnknown {
// s.createWaybillOn3rdProviders(savedOrderInfo, 0, nil)
// }
default:
s.resetTimer(savedOrderInfo, bill, isPending)
}
// 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 !model.IsWaybillPlatformOwn(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, maxDeliveryFee int64, excludeBill *model.Waybill) (err error) {
order := savedOrderInfo.order
globals.SugarLogger.Debugf("createWaybillOn3rdProviders, orderID:%s, status:%d, maxDeliveryFee:%d, excludeBill:%v", order.VendorOrderID, order.Status, maxDeliveryFee, excludeBill)
if order.VendorID == model.VendorIDELM {
return nil
}
if maxDeliveryFee == 0 {
maxDeliveryFee = getMaxDeliveryFee(order)
}
if err = s.canOrderCreateWaybillNormally(order); err == nil {
if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
if savedOrderInfo.retryCount <= maxWaybillRetryCount {
savedOrderInfo.isNeedCreate3rdWaybill = true
if _, err = s.CreateWaybillOnProviders4SavedOrder(jxcontext.AdminCtx, savedOrderInfo, nil, savedOrderInfo.GetWaybillVendorIDs(), false, maxDeliveryFee); err == nil {
savedOrderInfo.retryCount++
}
} else {
err = fmt.Errorf("订单:%s已经自动创建过了%d次运单请人工处理", order.VendorOrderID, savedOrderInfo.retryCount)
globals.SugarLogger.Infof("createWaybillOn3rdProviders [运营2]同一订单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, "自动创建三方运单失败", err.Error())
}
} else {
err = nil
}
return err
}
func (s *DefScheduler) cancelOtherWaybillsCheckOrderDeliveryFlag(savedOrderInfo *WatchOrderInfo, bill2Keep *model.Waybill, cancelReasonID int, cancelReason string) (err error) {
globals.SugarLogger.Debugf("cancelOtherWaybillsCheckOrderDeliveryFlag, orderID:%s, bill:%v", savedOrderInfo.order.VendorOrderID, bill2Keep)
if (savedOrderInfo.order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
err = s.cancelOtherWaybills(savedOrderInfo, bill2Keep, cancelReasonID, cancelReason)
} else {
globals.SugarLogger.Debugf("cancelOtherWaybillsCheckOrderDeliveryFlag, orderID:%s, bill:%v stop schedule", savedOrderInfo.order.VendorOrderID, bill2Keep)
}
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)
for _, v := range savedOrderInfo.waybills {
if v.Status < model.WaybillStatusEndBegin &&
!model.IsWaybillPlatformOwn(v) &&
(bill2Keep == nil || !(v.WaybillVendorID == bill2Keep.WaybillVendorID && v.VendorWaybillID == bill2Keep.VendorWaybillID)) {
err2 := s.CancelWaybill(v, cancelReasonID, cancelReason)
if err2 == nil {
// 在这里就从map里删除而不是等收到运单结束事件才删除可避免不必要的重复取消第二次取消还会失败
s.removeWaybillFromMap(savedOrderInfo, v.WaybillVendorID)
} else {
// 至少返回一个错误
if err == nil {
err = err2
}
partner.CurOrderManager.OnOrderMsg(savedOrderInfo.order, "取消三方运单失败", err2.Error())
}
}
}
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.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)
}, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID))
})
} else {
errStr := fmt.Sprintf("订单:%s转自配送失败, 错误信息:%v", order.VendorOrderID, err)
globals.SugarLogger.Info(errStr)
if s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonSwitch2SelfFailed, partner.CancelWaybillReasonStrSwitch2SelfFailed) == nil {
// 转自送失败的取消,要将订单中的运单状态更新
if s.isBillCandidate(order, bill) {
s.updateOrderByBill(order, nil, false)
}
}
// todo 之前这里为什么要设置OrderDeliveryFlagMaskScheduleDisabled标志呢
// order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled
// partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"})
partner.CurOrderManager.OnOrderMsg(order, "转商家自配送失败", errStr)
}
} else {
s.notify3rdPartyWaybill(order, bill, false)
s.removeWaybillFromMap(savedOrderInfo, order.VendorID)
partner.CurOrderManager.OnOrderMsg(order, "转自送成功", "")
}
} else {
s.cancelOtherWaybills(savedOrderInfo, nil, partner.CancelWaybillReasonOther, partner.CancelWaybillReasonStrNotAcceptIntime)
order.DeliveryFlag |= model.OrderDeliveryFlagMaskScheduleDisabled
partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"})
// 进到这里的原因是,在这个时间点,购物平台物流已经抢单(但抢单消息还没有被收到),所以转自送会失败 比如818810379000941更好的做法应该是判断Swtich2SelfDeliver的返回值这种情况下就不得试了
globals.SugarLogger.Infof("swtich2SelfDeliverWithRetry orderID:%s status is wrong(maybe purchase platform accepted waybill)", order.VendorOrderID)
partner.CurOrderManager.OnOrderMsg(order, "转商家自配送失败", "平台物流已接单")
// globals.SugarLogger.Warnf("swtich2SelfDeliverWithRetry orderID:%s status is wrong, order details:%v", order.VendorOrderID, order)
}
}
// 这个函数这样写的原因是适应一些消息错序
func (s *DefScheduler) loadSavedOrderFromMap(status *model.OrderStatus, isForceLoad 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)
} else {
realSavedInfo = NewWatchOrderInfo(nil)
s.orderMap.StoreWithTimeout(universalOrderID, realSavedInfo, orderMapStoreMaxTime)
}
if isForceLoad {
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) resetTimer(savedOrderInfo *WatchOrderInfo, bill *model.Waybill, isPending bool) {
order := savedOrderInfo.order
status := order.Status
statusType := scheduler.TimerStatusTypeOrder
vendorID := order.VendorID
statusTime := order.StatusTime
if bill != nil {
status = bill.Status
statusType = scheduler.TimerStatusTypeWaybill
vendorID = bill.WaybillVendorID
statusTime = bill.StatusTime
}
globals.SugarLogger.Debugf("resetTimer, orderID:%s statusType:%d status:%d", order.VendorOrderID, statusType, status)
config := s.mergeOrderStatusConfig(savedOrderInfo, statusTime, statusType, status)
stopStatusType := statusType
stopStatus := status
if statusType == scheduler.TimerStatusTypeOrder {
if status >= model.OrderStatusDelivering {
stopStatusType = -1
stopStatus = -1
}
}
if config == nil || config.TimerType != partner.TimerTypeByPass {
savedOrderInfo.StopTimer(stopStatusType, -1, stopStatus)
}
if config != nil && config.TimeoutAction != nil && config.TimerType != partner.TimerTypeByPass {
if config.CallShouldSetTimer(savedOrderInfo, bill) {
timeout := config.GetRefTimeout(statusTime, order.OrderCreatedAt)
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.CallTimeoutAction(savedOrderInfo, bill)
} else {
timerName := ""
if statusType == model.OrderTypeOrder {
timerName = model.OrderStatusName[status]
} else if statusType == model.OrderTypeWaybill {
timerName = model.WaybillStatusName[status]
}
timerInfo := &tTimerInfo{
statusType: statusType,
vendorID: vendorID,
status: status,
timerTime: time.Now().Add(timeout),
}
timerInfo.timer = utils.AfterFuncWithRecover(timeout, func() {
jxutils.CallMsgHandlerAsync(func() {
globals.SugarLogger.Debugf("fire timer:%s, orderID:%s", timerName, order.VendorOrderID)
ts := s.loadSavedOrderFromMap(model.Order2Status(order), true)
config.CallTimeoutAction(ts, bill)
timerInfo.timer = nil
ts.StopTimer(statusType, vendorID, status)
}, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID))
})
savedOrderInfo.AddTimer(timerInfo)
}
globals.SugarLogger.Debugf("resetTimer, orderID:%s, statusType:%d, status:%d, timeout:%v", order.VendorOrderID, statusType, status, timeout)
} else {
globals.SugarLogger.Debugf("resetTimer, orderID:%s, statusType:%d, status:%d, should not set timer", order.VendorOrderID, statusType, status)
}
} else {
globals.SugarLogger.Debugf("resetTimer bypass2, orderID:%s statusType:%d status:%v, config:%s", order.VendorOrderID, statusType, status, utils.Format4Output(config, true))
}
}
func (s *DefScheduler) stopTimer(savedOrderInfo *WatchOrderInfo) {
savedOrderInfo.StopTimer(-1, -1, -1)
}
// func (s *DefScheduler) stopTimer(savedOrderInfo *WatchOrderInfo) {
// if savedOrderInfo.timer != nil {
// globals.SugarLogger.Debugf("stopTimer orderID:%s", savedOrderInfo.order.VendorOrderID)
// savedOrderInfo.timer.Stop()
// savedOrderInfo.timerStatus = model.OrderStatusUnknown
// 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:%d", order.VendorOrderID, statusType, status)
// if isStatusNewer(order.VendorID, savedOrderInfo.timerStatusType, savedOrderInfo.timerStatus, statusType, status) { // 新设置的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 {
// if config.CallShouldSetTimer(savedOrderInfo, bill) {
// timeout := config.GetRefTimeout(statusTime, order.OrderCreatedAt)
// 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.CallTimeoutAction(savedOrderInfo, bill)
// } 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.timerTime = time.Now().Add(timeout)
// savedOrderInfo.timer = utils.AfterFuncWithRecover(timeout, func() {
// jxutils.CallMsgHandlerAsync(func() {
// globals.SugarLogger.Debugf("fire timer:%s, orderID:%s", timerName, order.VendorOrderID)
// savedOrderInfo := s.loadSavedOrderFromMap(model.Order2Status(order), true)
// config.CallTimeoutAction(savedOrderInfo, bill)
// savedOrderInfo.timerStatus = 0
// savedOrderInfo.timerStatusType = scheduler.TimerStatusTypeUnknown
// }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID))
// })
// }
// globals.SugarLogger.Debugf("resetTimer, orderID:%s, statusType:%d, status:%d, timeout:%v", order.VendorOrderID, statusType, status, timeout)
// } else {
// globals.SugarLogger.Debugf("resetTimer, orderID:%s, statusType:%d, status:%d, should not set timer", order.VendorOrderID, statusType, status)
// }
// } else {
// globals.SugarLogger.Debugf("resetTimer bypass2, orderID:%s statusType:%d status:%v, config:%s", order.VendorOrderID, statusType, status, utils.Format4Output(config, true))
// }
// } else {
// globals.SugarLogger.Debugf("resetTimer bypass1, orderID:%s statusType:%d status:%v", order.VendorOrderID, statusType, status)
// }
// }
// func isStatusNewer(vendorID int, curStatusType, curStatus, statusType, status int) bool {
// // 拣货完成及之前的订单事件TIMER不能覆盖运单TIMER一般是消息错序引起的
// // 美团订单在接单后就会收到新运单事件因当前只支持一个TIMER暂时舍弃三方配送调度而要自动拣货调度
// if vendorID != model.VendorIDMTWM {
// if curStatusType == scheduler.TimerStatusTypeWaybill && statusType == scheduler.TimerStatusTypeOrder && status <= model.OrderStatusFinishedPickup {
// return false
// }
// } else {
// return statusType == scheduler.TimerStatusTypeOrder && status >= curStatus
// }
// if curStatusType == scheduler.TimerStatusTypeWaybill {
// return curStatus != status
// }
// return curStatusType != statusType || status >= curStatus
// }
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, order.OrderCreatedAt) < 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.GetPurchaseOrderHandlerFromVendorID(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 { // 有最后拣货时间,反推
timeout := order.PickDeadline.Sub(time.Now()) - (time2AutoPickupAhead + second2AutoPickupGap*time.Second)
realSecond2AutoPickupGap := second2AutoPickupGap
if realSecond2AutoPickupGap > int(timeout/time.Second) {
realSecond2AutoPickupGap = int(timeout / time.Second)
if realSecond2AutoPickupGap < 0 {
realSecond2AutoPickupGap = 0
}
}
vendorActionParams = &partner.StatusActionParams{
TimerType: partner.TimerTypeBaseNow,
Timeout: timeout,
TimeoutGap: realSecond2AutoPickupGap,
}
}
}
}
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 {
globals.SugarLogger.Debugf("handleAutoAcceptOrder order:%s, vendorID:%d", orderID, vendorID)
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("handleAutoAcceptOrder orderID:%s, read data error:%v, data:%v, vendorID:%d", orderID, err, user, vendorID)
}
// 在访问数据库出错的情况下,也需要自动接单
handleType = 1
} else {
// 强制拒单
globals.SugarLogger.Infof("handleAutoAcceptOrder force reject order:%s, vendorID:%d", orderID, vendorID)
handleType = -1
}
} else {
globals.SugarLogger.Infof("handleAutoAcceptOrder 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) updateOrderByBill(order *model.GoodsOrder, bill *model.Waybill, revertStatus bool) {
if order.Status > model.OrderStatusEndBegin {
return
}
updateFields := []string{
"WaybillVendorID",
"VendorWaybillID",
}
if bill == nil {
order.WaybillVendorID = model.VendorIDUnknown
order.VendorWaybillID = ""
} else {
order.WaybillVendorID = bill.WaybillVendorID
order.VendorWaybillID = bill.VendorWaybillID
}
if revertStatus {
order.Status = model.OrderStatusFinishedPickup
updateFields = append(updateFields, "Status")
}
partner.CurOrderManager.UpdateOrderFields(order, updateFields)
}
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
// }
}
savedOrderInfo.waybills[bill.WaybillVendorID] = bill
}
return isBillExist
}
func (s *DefScheduler) autoPickupGood(savedOrderInfo *WatchOrderInfo) (err error) {
order := savedOrderInfo.order
if err = s.PickupGoods(order, model.IsOrderDeliveryByStore(order), ""); err == nil {
order.DeliveryFlag |= model.OrderDeliveryFlagMaskAutoPickup
partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"})
} else if err == scheduler.ErrOrderStatusAlreadySatisfyCurOperation {
err = nil
}
return err
}
func getWaybillTip(order *model.GoodsOrder) (tipFee int64) {
if !utils.IsPtrTimeZero(order.DeliveryFeeFrom) {
startTime := order.DeliveryFeeFrom.Add(minAddWaybillTipMinute * time.Minute)
timeGap1 := time.Now().Sub(startTime)
if timeGap1 > 0 {
timeGap := int64(timeGap1/(addWaybillTipGap*time.Minute)) + 1
tipFee = timeGap * 100
if tipFee > maxWaybillTipMoney {
tipFee = maxWaybillTipMoney
}
}
}
return tipFee
}
func getMaxDeliveryFee(order *model.GoodsOrder) (maxDeliveryFee int64) {
if order.VendorID == model.VendorIDJX {
maxDeliveryFee = maxJxStoreDeliveryFee
} else {
maxDeliveryFee = baseWaybillFee + order.DistanceFreightMoney + getWaybillTip(order)
}
if maxDeliveryFee < ebaiCancelWaybillMaxFee &&
order.DeliveryType == model.OrderDeliveryTypeStoreSelf {
maxDeliveryFee = ebaiCancelWaybillMaxFee
}
return maxDeliveryFee
}
func isNeedWatchWaybillTip(order *model.GoodsOrder) bool {
return order.DeliveryFlag&model.OrderDeliveryFlagMaskPurcahseDisabled == 0 && // 没有转出
order.DeliveryType == model.OrderDeliveryTypePlatform && // 订单配送类型为平台
!utils.IsPtrTimeZero(order.DeliveryFeeFrom) // 已经有了开始计费时间
}
func isNeedWatch3rdWaybill(order *model.GoodsOrder) bool {
return (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusDelivering) && // 订单状态
order.DeliveryFlag&model.OrderDeliveryFlagMaskScheduleDisabled == 0 && // 没有禁止调度
!model.IsOrderHaveWaybill(order) // 没有有效运单
}
func (s *DefScheduler) setWatchOrderWaybills(savedOrderInfo *WatchOrderInfo, duration time.Duration) {
if utils.IsPtrTimeZero(savedOrderInfo.watchWabillStartAt) {
savedOrderInfo.watchWabillStartAt = utils.Time2Pointer(time.Now())
utils.AfterFuncWithRecover(5*time.Minute, func() {
jxutils.CallMsgHandlerAsync(func() {
savedOrderInfo.watchWabillStartAt = nil
s.watchOrderWaybills(savedOrderInfo)
}, jxutils.ComposeUniversalOrderID(savedOrderInfo.order.VendorOrderID, savedOrderInfo.order.VendorID))
})
}
}
func (s *DefScheduler) saveDeliveryFeeFromAndStartWatch(savedOrderInfo *WatchOrderInfo, statusTime time.Time) {
order := savedOrderInfo.order
if utils.IsPtrTimeZero(order.DeliveryFeeFrom) {
order.DeliveryFeeFrom = utils.Time2Pointer(statusTime)
partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFeeFrom"})
}
duration := order.DeliveryFeeFrom.Add(minAddWaybillTipMinute * time.Minute).Sub(time.Now())
if duration <= 0 {
duration = 5 * time.Second
}
s.setWatchOrderWaybills(savedOrderInfo, duration)
}
func (s *DefScheduler) watchOrderWaybills(savedOrderInfo *WatchOrderInfo) {
order2 := savedOrderInfo.order
if model.IsOrderDeliveryByPlatform(order2) && savedOrderInfo.isDeliveryCompetition ||
model.IsOrderDeliveryByStore(order2) {
if order, err := partner.CurOrderManager.LoadOrder(order2.VendorOrderID, order2.VendorID); err == nil {
savedOrderInfo.SetOrder(order)
if isNeedWatch3rdWaybill(order) {
if isNeedWatchWaybillTip(order) {
if handler, ok := partner.GetPurchaseOrderHandlerFromVendorID(order.VendorID).(partner.IAddWaybillTip); ok && handler != nil {
var remark string
tipFee := getWaybillTip(order)
vendorStatus := fmt.Sprintf("设置小费:%s", jxutils.IntPrice2StandardCurrencyString(tipFee))
if curTipFee, err := handler.GetWaybillTip(jxcontext.AdminCtx, order); err == nil {
tipFee2Add := tipFee - curTipFee
vendorStatus += fmt.Sprintf(", 本次添加:%s", jxutils.IntPrice2StandardCurrencyString(tipFee2Add))
if false { //tipFee2Add > 0 {
err := handler.AddWaybillTip(jxcontext.AdminCtx, order, tipFee2Add)
if err == nil {
vendorStatus += "成功"
order.WaybillTipMoney += tipFee2Add
partner.CurOrderManager.UpdateOrderFields(order, []string{"WaybillTipMoney"})
} else {
vendorStatus += "失败"
remark = fmt.Sprint(err)
}
} else {
vendorStatus += "空操作"
}
} else {
vendorStatus += "失败"
remark = fmt.Sprint(err)
}
partner.CurOrderManager.OnOrderMsg(order, vendorStatus, remark)
}
}
if savedOrderInfo.isNeedCreate3rdWaybill {
s.createWaybillOn3rdProviders(savedOrderInfo, 0, nil)
}
s.setWatchOrderWaybills(savedOrderInfo, 5*time.Minute)
}
}
}
}
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 {
if err = s.CancelWaybill(bill, cancelReasonID, cancelReason); err != nil {
partner.CurOrderManager.OnOrderMsg(order, "取消三方运单失败", err.Error())
}
return err
}
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 setFakeActualPayPrice(order *model.GoodsOrder) (newOrder *model.GoodsOrder) {
orderCopy := *order
storeDetail, err := dao.GetStoreDetail(dao.GetDB(), order.JxStoreID, order.VendorID)
if err == nil {
if storeDetail.PayPercentage < 50 {
orderCopy.ActualPayPrice = order.TotalShopMoney * (100 - int64(storeDetail.PayPercentage/2)) * 100
} else {
orderCopy.ActualPayPrice = order.EarningPrice
}
}
newOrder = &orderCopy
return newOrder
}
func (s *DefScheduler) notifyNewOrder(order *model.GoodsOrder) {
if order.Flag&model.OrderFlagMaskFake == 0 {
utils.CallFuncAsync(func() {
order = setFakeActualPayPrice(order)
netprinter.PrintOrderByOrder(jxcontext.AdminCtx, order)
weixinmsg.NotifyNewOrder(order)
smsmsg.NotifyNewOrder(order)
OrderProfitWarning(order)
})
}
}
func (s *DefScheduler) notifyUserApplyCancel(order *model.GoodsOrder, cancelReason string) {
utils.CallFuncAsync(func() {
order = setFakeActualPayPrice(order)
weixinmsg.NotifyUserApplyCancel(order, cancelReason)
})
}
func (s *DefScheduler) notifyOrderCanceled(order *model.GoodsOrder) {
if order.Flag&model.OrderFlagMaskFake == 0 {
utils.CallFuncAsync(func() {
order = setFakeActualPayPrice(order)
weixinmsg.NotifyOrderCanceled(order)
smsmsg.NotifyOrderCanceled(order)
})
}
}
func (s *DefScheduler) notify3rdPartyWaybill(order *model.GoodsOrder, bill *model.Waybill, isBillAlreadyCandidate bool) {
if order.Flag&model.OrderFlagMaskFake == 0 {
utils.CallFuncAsync(func() {
weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate)
})
}
}
func isOrderCanSwitch2SelfDeliver(order *model.GoodsOrder) (isCan bool) {
isCan = true
if handler := partner.GetPurchaseOrderHandlerFromVendorID(order.VendorID); handler != nil {
isCan, _ = handler.CanSwitch2SelfDeliver(order)
}
return isCan
}
//订单预计利润若低于0则向门店运营负责人发送钉钉消息
func OrderProfitWarning(order *model.GoodsOrder) {
var (
operatorName string
operatorPhone string
noticeMsg string
profit float64
storeID int
)
db := dao.GetDB()
if order == nil {
return
}
storeID = jxutils.GetShowStoreIDFromOrder(order)
storeDetail, err := dao.GetStoreDetail(db, storeID, order.VendorID)
if storeDetail != nil && err == nil {
payPercentage := storeDetail.PayPercentage
if payPercentage >= 50 {
profit = utils.Str2Float64(utils.Int64ToStr(order.TotalShopMoney-order.EarningPrice-order.DistanceFreightMoney-order.WaybillTipMoney)) / 100
} else {
profit = utils.Str2Float64(utils.Int64ToStr(order.TotalShopMoney*int64(payPercentage)/200)) / 100
}
if profit < 0 {
if storeDetail.OperatorPhone != "" {
operatorName = storeDetail.OperatorName
operatorPhone = storeDetail.OperatorPhone
} else if storeDetail.OperatorPhone2 != "" {
operatorName = storeDetail.OperatorName2
operatorPhone = storeDetail.OperatorPhone2
}
noticeMsg = fmt.Sprintf("订单号:[%v],利润 [%v],运营负责人:[%v]", order.VendorOrderID, profit, operatorName)
user, err := dao.GetUserByID(db, "mobile", operatorPhone)
if user != nil && err == nil {
ddmsg.SendUserMessage(dingdingapi.MsgTyeText, user.UserID, "警告此订单利润低于0", noticeMsg)
}
}
}
}