package defsch import ( "fmt" "math/rand" "sync" "time" "github.com/astaxie/beego" "git.rosy.net.cn/jx-callback/business/jxstore/cms" "git.rosy.net.cn/jx-callback/business/authz" "git.rosy.net.cn/jx-callback/business/authz/autils" "git.rosy.net.cn/jx-callback/business/jxutils/ddmsg" "git.rosy.net.cn/jx-callback/business/partner/purchase/jdshop" "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 = 120 * 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 // 失败后尝试的次数,调试阶段可能出现死循化,阻止这种情况发生 storeDetail *dao.StoreDetail } 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 storeDetail, err := dao.GetStoreDetail(dao.GetDB(), jxutils.GetSaleStoreIDFromOrder(order), order.VendorID) if err == nil { savedOrderInfo.storeDetail = storeDetail } 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) } s.sendCourierOrderSMS(bill) } 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 order.VendorID == model.VendorIDJDShop { s.solutionJdsOrder(bill) } 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) sendCourierOrderSMS(bill *model.Waybill) (err error) { err = smsmsg.NotifyNewCourierOrder(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, savedOrderInfo); err == nil { if (order.DeliveryFlag & model.OrderDeliveryFlagMaskScheduleDisabled) == 0 { if savedOrderInfo.retryCount <= maxWaybillRetryCount { savedOrderInfo.isNeedCreate3rdWaybill = true excludeVendorIDs := savedOrderInfo.GetWaybillVendorIDs() //TODO 取消京西不自动发美团 2020-06-02 // 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) solutionJdsOrder(bill *model.Waybill) (err error) { if len(bill.VendorOrderID) > 12 { //表示此订单是京东商城2次转移的订单,不用出库,但要去修改运单号 if utils.Str2Int(bill.VendorOrderID[12:len(bill.VendorOrderID)]) > 2 { err = jdshop.CurPurchaseHandler.OrderTransfer(jxcontext.AdminCtx, bill.VendorOrderID, bill.VendorWaybillID, true) if err != nil { globals.SugarLogger.Errorf("京东商城订单自动转移失败!", err) } } else { err = jdshop.CurPurchaseHandler.OrderExport(jxcontext.AdminCtx, bill.VendorOrderID, bill.VendorWaybillID, true) } } 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 bill.Status == model.WaybillStatusDelivered { storeDetail, _ := partner.CurOrderManager.LoadStoreDetail(jxutils.GetSaleStoreIDFromOrder(order), order.VendorID) if storeDetail != nil { jxutils.RefreshOrderEarningPrice3(order, storeDetail.PayPercentage, bill) updateFields = append(updateFields, "NewEarningPrice") } } } 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) //目前暂且认为AdjustCount > 0 就是调整单 if order.AdjustCount > 0 { weixinmsg.NotifyAdjustOrder(order) } else { weixinmsg.NotifyNewOrder(order) } OrderProfitWarning(order) if order.VendorID != model.VendorIDJDShop { smsmsg.NotifyNewOrder(order) smsmsg.NotifyNewUserOrder(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 beego.BConfig.RunMode == "jxgy" { 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 } } else { if payPercentage >= 50 { profit = utils.Str2Float64(utils.Int64ToStr(order.TotalShopMoney-order.ShopPrice)) / 100 } else { profit = utils.Str2Float64(utils.Int64ToStr(order.TotalShopMoney*int64(payPercentage)/200)) / 100 } } if profit < 0 { operatorPhone, operatorName := getOrderOperatorInfo(order, storeDetail) operatorRole := getOrderOperatorRoleInfo(order, storeDetail) if operatorPhone != "" { var ( roleList []*authz.RoleInfo userIDs []string flag = false ) roleList = append(roleList, autils.NewRole(operatorRole, 0)) userIDMap, err := cms.GetRolesUserList(jxcontext.AdminCtx, roleList) 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 { for _, v := range userIDMap { for _, vv := range v { userIDs = append(userIDs, vv) } } for _, v := range userIDs { if v == user.UserID { flag = true } } if !flag { userIDs = append(userIDs, user.UserID) } for _, v := range userIDs { ddmsg.SendUserMessage(dingdingapi.MsgTyeText, v, "警告!此订单利润低于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 } func getOrderOperatorRoleInfo(order *model.GoodsOrder, storeDetail *dao.StoreDetail) (roleName string) { switch order.VendorID { case model.VendorIDJD: roleName = storeDetail.OperatorRole case model.VendorIDMTWM: roleName = storeDetail.OperatorRole2 case model.VendorIDEBAI: roleName = storeDetail.OperatorRole3 } return roleName }