package defsch import ( "fmt" "math/rand" "sync" "time" "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/msghub" "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 = 15 * time.Minute // 自动拣货等待时间,这个只有在没有PickDeadline信息才有用,否则会根据PickDeadline设置 second2AutoPickupGap = 60 //随机60秒 time2AutoPickupAhead = 20 * time.Second // 有最后拣货时间的提前值 // (把pending order timerout 在-pendingOrderTimerMinMinSecond至pendingOrderTimerMaxSecond映射到pendingOrderTimerMinSecond至pendingOrderTimerMaxSecond) pendingOrderTimerMinMinSecond = 5 * 60 // 5分钟 pendingOrderTimerMinSecond = 2 pendingOrderTimerMaxSecond = 5 maxWaybillRetryCount = 3 orderMapStoreMaxTime = 4 * 24 * time.Hour // cache最长存储时间 time2Schedule3rdCarrierKey = "waitminute4mt" dingShiDaAheadTime = 30 * time.Minute // 定时达订单开始召唤配送的提前时间 minAddWaybillTipMinute = 20 // 最少开始加小费分钟(距离拣货完成) addWaybillTipGap = 15 // 加一元小费间隔的分钟数 maxWaybillTipMoney = 400 // 最大小费 baseWaybillFee = 600 // 基本运费 ebaiCancelWaybillMaxFee = 1000 // 饿百取消运单最高运费 ) const ( maxAddFee = 300 // 最大增加费用,单位为分,超过不发三方配送了 ) var ( FixedScheduler *DefScheduler ) type WatchOrderInfo struct { order *model.GoodsOrder // order里的信息是保持更新的 autoPickupTimeoutMinute int // 0表示禁用,1表示用缺省值time2AutoPickupMin,其它表示分钟数 storeDeliveryType int isDeliveryCompetition bool pmWaybillCreatedAt time.Time isNeedCreate3rdWaybill bool isAddWaybillTipDisabled bool waybills map[int]*model.Waybill // 这个waybills里的状态信息是不真实的,只使用id相关的信息 timerStatusType int // 0表示订单,1表示运单 timerStatus int timer *time.Timer timerTime time.Time 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) 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, storeDeliveryType: scheduler.StoreDeliveryTypeCrowdSourcing, waybills: map[int]*model.Waybill{}, } retVal.SetOrder(order) return retVal } func (s *WatchOrderInfo) SetOrder(order *model.GoodsOrder) (retVal *model.GoodsOrder) { retVal = s.order if s.order != order { if order != nil { s.updateOrderStoreFeature(order) } s.order = order } return retVal } func (s *WatchOrderInfo) updateOrderStoreFeature(order *model.GoodsOrder) (err error) { globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s", order.VendorOrderID) jxStoreID := jxutils.GetSaleStoreIDFromOrder(order) if jxStoreID > 0 { db := dao.GetDB() storeMap, err2 := dao.FakeGetStoreMapByStoreID(db, jxStoreID, order.VendorID) if err = err2; err != nil { return err } s.autoPickupTimeoutMinute = int(storeMap.AutoPickup) s.storeDeliveryType = FixedScheduler.GetStoreDeliveryType(order, storeMap) s.isDeliveryCompetition = storeMap.DeliveryCompetition != 0 globals.SugarLogger.Debugf("updateOrderStoreFeature orderID:%s, s.storeDeliveryType:%d", order.VendorOrderID, s.storeDeliveryType) } return err } func (s *WatchOrderInfo) GetWaybillVendorIDs() (vendorIDs []int) { for vendorID := range s.waybills { vendorIDs = append(vendorIDs, vendorID) } return vendorIDs } 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.GetPurchasePlatformFromVendorID(order.VendorID).GetOrder(order.VendorOrderID); err2 == nil && 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 { utils.CallFuncAsync(func() { netprinter.PrintOrderByOrder(jxcontext.AdminCtx, order) weixinmsg.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.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 savedOrderInfo.autoPickupTimeoutMinute > 0 { 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 }, }, model.OrderStatusFinishedPickup: &StatusActionConfig{ StatusActionParams: partner.StatusActionParams{ TimerType: partner.TimerTypeBaseStatusTime, Timeout: 1 * time.Second, TimeoutGap: 0, }, TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { // 自配送商家使用 return sch.createWaybillOn3rdProviders(savedOrderInfo, 0, nil) } return nil }, ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { return savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore }, }, }, 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) { // 饿百转自送的时机不太清楚,暂时禁用超时转自送,在饿百运单取消时还是会自动创建 if savedOrderInfo.isDeliveryCompetition && savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && savedOrderInfo.order.VendorID == bill.WaybillVendorID && savedOrderInfo.order.VendorID != model.VendorIDEBAI { // 非自配送商家使用 return sch.createWaybillOn3rdProviders(savedOrderInfo, 0, nil) } return nil }, ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { return savedOrderInfo.isDeliveryCompetition && savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && savedOrderInfo.order.VendorID == bill.WaybillVendorID && savedOrderInfo.order.VendorID != model.VendorIDEBAI }, }, //* model.WaybillStatusCanceled: &StatusActionConfig{ StatusActionParams: partner.StatusActionParams{ TimerType: partner.TimerTypeBaseNow, Timeout: 5 * time.Second, }, TimeoutAction: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) (err error) { order := savedOrderInfo.order if (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && savedOrderInfo.isDeliveryCompetition && savedOrderInfo.order.VendorID == bill.WaybillVendorID && savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && order.VendorID == model.VendorIDEBAI { // 非自配送商家使用 return sch.createWaybillOn3rdProviders(savedOrderInfo, ebaiCancelWaybillMaxFee, nil) } return nil }, ShouldSetTimer: func(savedOrderInfo *WatchOrderInfo, bill *model.Waybill) bool { order := savedOrderInfo.order return (order.Status >= model.OrderStatusFinishedPickup && order.Status < model.OrderStatusEndBegin) && savedOrderInfo.isDeliveryCompetition && savedOrderInfo.order.VendorID == bill.WaybillVendorID && savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && order.VendorID == model.VendorIDEBAI }, }, //*/ }, } } 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不动作,这里补一下 utils.CallFuncAsync(func() { netprinter.PrintOrderByOrder(jxcontext.AdminCtx, order) weixinmsg.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) savedOrderInfo.SetOrder(order) // if status.Status == model.OrderStatusNew { // if !isPending { // utils.CallFuncAsync(func() { // weixinmsg.NotifyNewOrder(savedOrderInfo.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 order.LockStatus != model.OrderStatusUnknown { s.stopTimer(savedOrderInfo) } if !isPending { if status.Status == model.OrderStatusFinishedPickup || status.Status == model.OrderStatusCanceled { 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.OrderStatusApplyCancel { utils.CallFuncAsync(func() { weixinmsg.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) isFirst := utils.IsTimeZero(savedOrderInfo.pmWaybillCreatedAt) savedOrderInfo.pmWaybillCreatedAt = bill.StatusTime savedOrderInfo.isAddWaybillTipDisabled = false if isFirst { duration := savedOrderInfo.pmWaybillCreatedAt.Add(minAddWaybillTipMinute * time.Minute).Sub(time.Now()) if duration <= 0 { duration = 1 * time.Second } utils.AfterFuncWithRecover(duration, func() { jxutils.CallMsgHandlerAsync(func() { s.handleWaybillTip(savedOrderInfo) }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) }) } } } 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) { savedOrderInfo.isAddWaybillTipDisabled = true if bill.Status == model.WaybillStatusDelivering { // 强制将订单状态置为配送中? order.Status = model.OrderStatusDelivering partner.CurOrderManager.UpdateOrderStatusAndDeliveryFlag(order) } } else { if savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore || model.IsSpecialOrderPlatformWaybill(bill) { if err := s.SelfDeliverDelivering(savedOrderInfo.order, bill.CourierMobile); err != nil { partner.CurOrderManager.OnOrderMsg(order, "自送出设置失败", err.Error()) } utils.CallFuncAsync(func() { weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate) }) } else { s.swtich2SelfDeliverWithRetry(savedOrderInfo, bill, 2, 10*time.Second) } } } 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) { utils.CallFuncAsync(func() { weixinmsg.NotifyWaybillStatus(bill, order, isBillAlreadyCandidate) }) } flag2Clear := model.WaybillVendorID2Mask(bill.WaybillVendorID) order.Flag &= ^model.OrderFlagMaskFailedGetGoods order.DeliveryFlag &= ^flag2Clear 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 savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore { err2 = s.SelfDeliverDelivered(order, "") } else { err2 = s.Swtich2SelfDelivered(order, "") } } else if model.IsSpecialOrderPlatformWaybill(bill) { err2 = s.SelfDeliverDelivered(savedOrderInfo.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 { utils.CallFuncAsync(func() { weixinmsg.NotifyWaybillStatus(bill, order, false) }) } // case model.WaybillStatusNeverSend: // 平台不配送,直接创建三方运单 // s.resetTimer(savedOrderInfo, bill, isPending) // s.removeWaybillFromMap(savedOrderInfo, bill.WaybillVendorID) // if order.WaybillVendorID == model.VendorIDUnknown { // s.createWaybillOn3rdProviders(savedOrderInfo, 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 = s.getMaxDeliveryFee(savedOrderInfo) } 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, 1000, 1000, 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 { utils.CallFuncAsync(func() { weixinmsg.NotifyWaybillStatus(bill, order, 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) 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(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.TimeoutAction(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.TimeoutAction(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 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(curStatusType, curStatus, statusType, status int) bool { // 拣货完成及之前的订单事件TIMER不能覆盖运单TIMER(一般是消息错序引起的) if curStatusType == scheduler.TimerStatusTypeWaybill && statusType == scheduler.TimerStatusTypeOrder && status <= model.OrderStatusFinishedPickup { return false } 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.GetPurchasePlatformFromVendorID(order.VendorID).GetStatusActionTimeout(order, statusType, status) } // 自动拣货TIMER if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusAccepted { if savedOrderInfo.autoPickupTimeoutMinute > 0 { if utils.IsTimeZero(order.PickDeadline) { // 没有最后拣货时间,正推,以订单中的配置为准 if utils.IsTimeZero(order.ExpectedDeliveredTime) || order.BusinessType == model.BusinessTypeImmediate { // 立即达或没有最后送达时间 // 以缺省配置或平台相关中的配置为准 } else { // 非立即达且有最后送达时间 timeout := time2AutoPickupMin if savedOrderInfo.autoPickupTimeoutMinute > 1 { timeout = time.Duration(savedOrderInfo.autoPickupTimeoutMinute) * time.Minute } vendorActionParams = &partner.StatusActionParams{ TimerType: partner.TimerTypeBaseNow, Timeout: order.ExpectedDeliveredTime.Add(-time2Delivered).Sub(time.Now()) + timeout, } } } else { // 有最后拣货时间,反推 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 { handleType := 0 if userMobile != "" { if db == nil { db = orm.NewOrm() } user := &legacymodel.BlackClient{ Mobile: userMobile, } if err := db.Read(user, "Mobile"); err != nil { if err != orm.ErrNoRows { globals.SugarLogger.Errorf("read data error:%v, data:%v, vendorID:%d", err, user, vendorID) } // 在访问数据库出错的情况下,也需要自动接单 handleType = 1 } else { // 强制拒单 globals.SugarLogger.Infof("force reject order:%s, vendorID:%d", orderID, vendorID) handleType = -1 } } else { globals.SugarLogger.Infof("order:%s, vendorID:%d, mobile is empty, should accept order", orderID, vendorID) handleType = 1 } if handleType == 1 { handler(true) } else if handleType == -1 { handler(false) } return handleType } func (s *DefScheduler) 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) { if err = s.PickupGoods(savedOrderInfo.order, savedOrderInfo.storeDeliveryType == scheduler.StoreDeliveryTypeByStore, ""); err == nil { order := savedOrderInfo.order order.DeliveryFlag |= model.OrderDeliveryFlagMaskAutoPickup partner.CurOrderManager.UpdateOrderFields(order, []string{"DeliveryFlag"}) } else if err == scheduler.ErrOrderStatusAlreadySatisfyCurOperation { err = nil } return err } func (s *DefScheduler) getWaybillTip(savedOrderInfo *WatchOrderInfo) (tipFee int64) { order := savedOrderInfo.order if order.Status == model.OrderStatusFinishedPickup && !utils.IsTimeZero(savedOrderInfo.pmWaybillCreatedAt) { startTime := savedOrderInfo.pmWaybillCreatedAt.Add(minAddWaybillTipMinute * time.Minute) // if order.DeliveryFlag&model.OrderDeliveryFlagMaskAutoPickup != 0 { // startTime = startTime.Add(5 * 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 (s *DefScheduler) getMaxDeliveryFee(savedOrderInfo *WatchOrderInfo) (maxDeliveryFee int64) { maxDeliveryFee = baseWaybillFee + savedOrderInfo.order.DistanceFreightMoney + s.getWaybillTip(savedOrderInfo) return maxDeliveryFee } func (s *DefScheduler) handleWaybillTip(savedOrderInfo *WatchOrderInfo) { if savedOrderInfo.storeDeliveryType != scheduler.StoreDeliveryTypeByStore && savedOrderInfo.isDeliveryCompetition { order2 := savedOrderInfo.order if order, err := partner.CurOrderManager.LoadOrder(order2.VendorOrderID, order2.VendorID); err == nil { savedOrderInfo.SetOrder(order) if order.Status == model.OrderStatusFinishedPickup && order.DeliveryFlag&model.OrderDeliveryFlagMaskScheduleDisabled == 0 { if !utils.IsTimeZero(savedOrderInfo.pmWaybillCreatedAt) && !savedOrderInfo.isAddWaybillTipDisabled { if tipFee := s.getWaybillTip(savedOrderInfo); tipFee > 0 { if handler, ok := partner.GetPurchasePlatformFromVendorID(order.VendorID).(partner.IAddWaybillTip); ok && handler != nil { var remark string 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 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) } } } utils.AfterFuncWithRecover(5*time.Minute, func() { jxutils.CallMsgHandlerAsync(func() { s.handleWaybillTip(savedOrderInfo) }, jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID)) }) } } } } 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) } } }