1471 lines
66 KiB
Go
1471 lines
66 KiB
Go
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
|
||
baseWaybillAddFee = 200 //增加运费上限2块
|
||
|
||
ebaiCancelWaybillWarningMinute = 8
|
||
)
|
||
|
||
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
|
||
}
|
||
globals.SugarLogger.Debugf("updateOrderStoreFeature2 orderID:%s, isDeliveryCompetition:%t", order.VendorOrderID, s.isDeliveryCompetition)
|
||
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 savedOrderInfo.isDeliveryCompetition && 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.onEbaiWaybillCanceled(savedOrderInfo, nil)
|
||
// 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.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, false) //status.Status != model.OrderStatusAgreeFailedGetGoods)
|
||
s.removeWaybillFromMap(savedOrderInfo, order.VendorID)
|
||
clearFlag := 0
|
||
if status.Status == model.OrderStatusAgreeFailedGetGoods {
|
||
clearFlag = model.OrderFlagMaskFailedGetGoods
|
||
} else if status.Status == model.OrderFlagMaskFailedDeliver {
|
||
clearFlag = model.OrderFlagMaskFailedDeliver
|
||
}
|
||
if order.Flag > clearFlag {
|
||
dao.ClearOrderFlag2(dao.GetDB(), model.AdminName, order.VendorOrderID, order.VendorID, clearFlag|model.OrderFlagMaskPrinted)
|
||
} else {
|
||
dao.ClearOrderFlag(dao.GetDB(), model.AdminName, order.VendorOrderID, order.VendorID, clearFlag)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
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 {
|
||
needAddTip := order.WaybillTipMoney > 0 && !model.IsWaybillPlatformOwn(bill)
|
||
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 {
|
||
needAddTip = false
|
||
s.ProxyCancelWaybill(order, bill, partner.CancelWaybillReasonNotAcceptIntime, partner.CancelWaybillReasonStrNotAcceptIntime)
|
||
}
|
||
}
|
||
if needAddTip {
|
||
if handler := partner.GetWaybillTipUpdater(bill.WaybillVendorID); handler != nil {
|
||
if storeDetail, err2 := dao.GetStoreDetail(dao.GetDB(), jxutils.GetSaleStoreIDFromOrder(order), order.VendorID); err2 == nil {
|
||
handler.UpdateWaybillTip(jxcontext.AdminCtx, order.VendorOrgCode, order.VendorStoreID, order.VendorOrderID, bill.VendorWaybillID, bill.VendorWaybillID2, utils.Int2Str(storeDetail.CityCode), order.WaybillTipMoney)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
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) && savedOrderInfo.order.StoreID != 666666 { // 购买平台重复发相同号的新运单是正常的,京东就是
|
||
globals.SugarLogger.Warnf("addWaybill2Map bill:%v already exists", bill)
|
||
}
|
||
}
|
||
if bill.ID == 0 {
|
||
globals.SugarLogger.Infof("addWaybill2Map bill id is 0:%s", utils.Format4Output(bill, true))
|
||
}
|
||
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) onEbaiWaybillCanceled(savedOrderInfo *WatchOrderInfo, excludeBill *model.Waybill) (err error) {
|
||
order := savedOrderInfo.order
|
||
timeout := time.Duration(ebaiCancelWaybillWarningMinute) * time.Minute
|
||
utils.AfterFuncWithRecover(timeout, func() {
|
||
jxutils.CallMsgHandlerAsync(func() {
|
||
ts := s.loadSavedOrderFromMap(model.Order2Status(order), true)
|
||
order := ts.order
|
||
globals.SugarLogger.Debugf("fire timer ebai waybill cancled, orderID:%s, status:%d", order.VendorOrderID, order.Status)
|
||
if order.Status < model.OrderStatusDelivering && order.LockStatus == model.OrderStatusLocked {
|
||
msgContent := fmt.Sprintf("门店:%d,订单:%s的平台运单被取消了,可能导致订单被取消,请重点关注,必要的话可以先手动转自送", jxutils.GetSaleStoreIDFromOrder(order), order.VendorOrderID)
|
||
s.notifyOrderStakeHolder(order, "", msgContent)
|
||
}
|
||
}, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID))
|
||
})
|
||
err = s.createWaybillOn3rdProviders(savedOrderInfo, 0, excludeBill)
|
||
return err
|
||
}
|
||
|
||
func (s *DefScheduler) createWaybillOn3rdProviders(savedOrderInfo *WatchOrderInfo, maxDeliveryFee int64, excludeBill *model.Waybill) (err error) {
|
||
order := savedOrderInfo.order
|
||
if order.VendorID == model.VendorIDELM {
|
||
return nil
|
||
}
|
||
if maxDeliveryFee == 0 {
|
||
maxDeliveryFee = getMaxDeliveryFee(order)
|
||
}
|
||
if !savedOrderInfo.isDeliveryCompetition {
|
||
globals.SugarLogger.Debugf("createWaybillOn3rdProviders orderID:%s门店没有设置配送竞争", order.VendorOrderID)
|
||
}
|
||
globals.SugarLogger.Debugf("createWaybillOn3rdProviders, orderID:%s, status:%d, maxDeliveryFee:%d, excludeBill:%v", order.VendorOrderID, order.Status, maxDeliveryFee, excludeBill)
|
||
if err = s.canOrderCreateWaybillNormally(order); err == nil {
|
||
if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 {
|
||
if savedOrderInfo.retryCount <= maxWaybillRetryCount {
|
||
savedOrderInfo.isNeedCreate3rdWaybill = true
|
||
excludeVendorIDs := savedOrderInfo.GetWaybillVendorIDs()
|
||
if order.VendorID == model.VendorIDJX {
|
||
excludeVendorIDs = append(excludeVendorIDs, model.VendorIDMTPS)
|
||
}
|
||
if _, err = s.CreateWaybillOnProviders4SavedOrder(jxcontext.AdminCtx, savedOrderInfo, nil, excludeVendorIDs, false, maxDeliveryFee); err == nil {
|
||
savedOrderInfo.retryCount++
|
||
}
|
||
} else {
|
||
errMsg := fmt.Sprintf("订单:%s已经自动创建过了%d次运单,仍然无法配送,请关注!", order.VendorOrderID, savedOrderInfo.retryCount)
|
||
err = fmt.Errorf(errMsg)
|
||
s.notifyOrderStakeHolder(order, "", errMsg)
|
||
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 {
|
||
orderBaseFreightMoney := order.BaseFreightMoney
|
||
if orderBaseFreightMoney == 0 {
|
||
orderBaseFreightMoney = baseWaybillFee
|
||
}
|
||
maxDeliveryFee = orderBaseFreightMoney + order.DistanceFreightMoney + getWaybillTip(order) + baseWaybillAddFee
|
||
if maxDeliveryFee < ebaiCancelWaybillMaxFee &&
|
||
(order.VendorID == model.VendorIDEBAI ||
|
||
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) && // 没有有效运单
|
||
order.LockStatus == model.OrderStatusUnknown
|
||
}
|
||
|
||
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) {
|
||
// tipFee := getWaybillTip(order)
|
||
// if tipFee > order.WaybillTipMoney {
|
||
// vendorStatus := fmt.Sprintf("应设置小费:%s", jxutils.IntPrice2StandardCurrencyString(tipFee))
|
||
// remark := ""
|
||
// if false {
|
||
// err = s.SetOrderWaybillTipByOrder(jxcontext.AdminCtx, order, tipFee)
|
||
// if err == nil {
|
||
// vendorStatus += "成功"
|
||
// } else {
|
||
// vendorStatus += "失败"
|
||
// remark = fmt.Sprint(err)
|
||
// }
|
||
// } else {
|
||
// vendorStatus += "空操作"
|
||
// }
|
||
// partner.CurOrderManager.OnOrderMsg(order, vendorStatus, remark)
|
||
// }
|
||
|
||
// 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))
|
||
// tipFee2Add := tipFee - order.WaybillTipMoney
|
||
// vendorStatus += fmt.Sprintf(", 本次添加:%s", jxutils.IntPrice2StandardCurrencyString(tipFee2Add))
|
||
// if false { //tipFee2Add > 0 {
|
||
// err := handler.UpdateWaybillTip(jxcontext.AdminCtx, order.VendorOrgCode, order.VendorStoreID, order.VendorOrderID, order.VendorOrderID, "", "", tipFee)
|
||
// if err == nil {
|
||
// vendorStatus += "成功"
|
||
// order.WaybillTipMoney = tipFee
|
||
// partner.CurOrderManager.UpdateOrderFields(order, []string{"WaybillTipMoney"})
|
||
// } else {
|
||
// vendorStatus += "失败"
|
||
// remark = fmt.Sprint(err)
|
||
// }
|
||
// } else {
|
||
// vendorStatus += "空操作"
|
||
// }
|
||
// 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 profit float64
|
||
|
||
db := dao.GetDB()
|
||
if order == nil {
|
||
return
|
||
}
|
||
if order.TotalShopMoney == 0 {
|
||
globals.SugarLogger.Debugf("OrderProfitWarning TotalShopMoney=0 orderID:%s", order.VendorOrderID)
|
||
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)) / 100
|
||
} else {
|
||
profit = utils.Str2Float64(utils.Int64ToStr(order.TotalShopMoney*int64(payPercentage)/200)) / 100
|
||
}
|
||
if profit < 0 {
|
||
operatorPhone, operatorName := getOrderOperatorInfo(order, storeDetail)
|
||
if operatorPhone != "" {
|
||
noticeMsg := fmt.Sprintf("利润 :[%v],运营负责人:[%v],门店ID:[%v],平台门店ID[%v],门店名:[%v],订单序号:[%v],订单号(点击进入详情):%v", profit, operatorName, order.StoreID, order.VendorStoreID, order.StoreName, order.OrderSeq, globals.BackstageHost+"/#/ordermanager/"+order.VendorOrderID)
|
||
user, err := dao.GetUserByID(db, "mobile", operatorPhone)
|
||
if user != nil && err == nil {
|
||
ddmsg.SendUserMessage(dingdingapi.MsgTyeText, user.UserID, "警告!此订单利润低于0", noticeMsg)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (s *DefScheduler) notifyOrderStakeHolder(order *model.GoodsOrder, msgTitle, msgContent string) (err error) {
|
||
userMobiles := []string{
|
||
// "18180948107",
|
||
}
|
||
db := dao.GetDB()
|
||
storeDetail, err := dao.GetStoreDetail(db, jxutils.GetSaleStoreIDFromOrder(order), order.VendorID)
|
||
if err == nil {
|
||
operatorPhone, _ := getOrderOperatorInfo(order, storeDetail)
|
||
if operatorPhone != "" {
|
||
userMobiles = append(userMobiles, operatorPhone)
|
||
}
|
||
}
|
||
if len(userMobiles) > 0 {
|
||
if msgTitle == "" {
|
||
msgTitle = fmt.Sprintf("%s平台订单%s异常", model.VendorChineseNames[order.VendorID], order.VendorOrderID)
|
||
}
|
||
users, _, err := dao.GetUsers(db, 0, "", nil, nil, userMobiles, 0, 0)
|
||
if err == nil && len(users) > 0 {
|
||
var userIDs []string
|
||
for _, v := range users {
|
||
userIDs = append(userIDs, v.UserID)
|
||
}
|
||
ddmsg.SendUsersMessage(dingdingapi.MsgTyeText, userIDs, msgTitle, msgContent)
|
||
}
|
||
}
|
||
return err
|
||
}
|
||
|
||
func getOrderOperatorInfo(order *model.GoodsOrder, storeDetail *dao.StoreDetail) (operatorPhone, operatorName string) {
|
||
switch order.VendorID {
|
||
case model.VendorIDJD:
|
||
operatorPhone = storeDetail.OperatorPhone
|
||
operatorName = storeDetail.OperatorName
|
||
case model.VendorIDMTWM:
|
||
operatorPhone = storeDetail.OperatorPhone2
|
||
operatorName = storeDetail.OperatorName2
|
||
case model.VendorIDEBAI:
|
||
operatorPhone = storeDetail.OperatorPhone3
|
||
operatorName = storeDetail.OperatorName3
|
||
}
|
||
return operatorPhone, operatorName
|
||
}
|