package ebai import ( "math" "time" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" "git.rosy.net.cn/baseapi/platformapi/autonavi" "git.rosy.net.cn/baseapi/platformapi/ebaiapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxcallback/scheduler" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" ) const ( // acceptOrderDelay = 180 * time.Second pickupOrderDelay = 260 * time.Second callDeliveryDelay = 10 * time.Minute callDeliveryDelayGap = 30 fakeUserApplyCancel = "fake_user_apply_cancel" fakeUserUndoApplyCancel = "fake_user_undo_apply_cancel" fakeAcceptOrder = "fake_accept_order" fakeOrderAdjustFinished = "fake_order_adjust_finished" ) // 饿百的接单会直接召唤配送,为了统一将饿百的接单影射成拣货完成,然后模拟一个接单消息 var ( VendorStatus2StatusMap = map[string]int{ ebaiapi.OrderStatusNew: model.OrderStatusNew, fakeAcceptOrder: model.OrderStatusAccepted, ebaiapi.OrderStatusAccepted: model.OrderStatusFinishedPickup, ebaiapi.OrderStatusCourierAccepted: model.OrderStatusDelivering, ebaiapi.OrderStatusCourierPickedup: model.OrderStatusDelivering, ebaiapi.OrderStatusFinished: model.OrderStatusFinished, ebaiapi.OrderStatusCanceled: model.OrderStatusCanceled, fakeOrderAdjustFinished: model.OrderStatusAdjust, fakeUserApplyCancel: model.OrderStatusApplyCancel, fakeUserUndoApplyCancel: model.OrderStatusUndoApplyCancel, } ) func (p *PurchaseHandler) GetStatusFromVendorStatus(vendorStatus string) int { if status, ok := VendorStatus2StatusMap[vendorStatus]; ok { return status } return model.OrderStatusUnknown } func (p *PurchaseHandler) GetOrder(vendorOrderID string) (order *model.GoodsOrder, err error) { order, _, err = p.getOrder(vendorOrderID) return order, err } func (p *PurchaseHandler) getOrder(vendorOrderID string) (order *model.GoodsOrder, orderMap map[string]interface{}, err error) { result, err := api.EbaiAPI.OrderGet(vendorOrderID) if err == nil { order = p.Map2Order(result) } return order, result, err } func (p *PurchaseHandler) GetOrder4PartRefund(vendorOrderID string) (order *model.GoodsOrder, err error) { taskIDs := []int{1, 2} var ( err1, err2 error result1, result2 map[string]interface{} ) task := tasksch.NewParallelTask("GetOrder4PartRefund", nil, jxcontext.AdminCtx, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { taskID := batchItemList[0].(int) if taskID == 1 { result1, err1 = api.EbaiAPI.OrderGet(vendorOrderID) } else if taskID == 2 { result2, err2 = api.EbaiAPI.OrderPartRefundGet(vendorOrderID) } return nil, nil }, taskIDs) task.Run() task.GetResult(0) if err1 == nil { order = p.Map2Order(result1) if err2 == nil { order.Skus = p.partRefund2OrderDetailSkuList(utils.Interface2String(result2["order_id"]), result2["order_detail"]) jxutils.RefreshOrderSkuRelated(order) } else if err2Ext, ok := err2.(*utils.ErrorWithCode); !ok || err2Ext.IntCode() != ebaiapi.ErrOrderIsNotPartRefund { err = err2 } } else { err = err1 } return order, err } func (p *PurchaseHandler) partRefund2OrderDetailSkuList(orderID string, orderDetail2 interface{}) (skuList []*model.OrderSku) { orderDetail := orderDetail2.([]interface{}) for _, product2 := range orderDetail { product := product2.(map[string]interface{}) skuName := product["name"].(string) _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(skuName) number := int(utils.MustInterface2Int64(product["number"])) sku := &model.OrderSku{ VendorOrderID: orderID, VendorID: model.VendorIDEBAI, Count: number, SkuID: int(utils.Str2Int64WithDefault(utils.Interface2String(product[ebaiapi.KeyCustomSkuID]), 0)), VendorSkuID: utils.Interface2String(product[ebaiapi.KeySkuID]), SkuName: skuName, // Weight: int(utils.Interface2Int64WithDefault(product["total_weight"], 0)) / number, // 退单这里的total_weight有BUG,这里的total_weight还是没有退单时的值 SalePrice: utils.MustInterface2Int64(product["product_price"]), } if sku.Weight == 0 { sku.Weight = jxutils.FormatSkuWeight(specQuality, specUnit) // 订单信息里没有重量,只有名字里尝试找 } skuList = append(skuList, sku) } return skuList } func (p *PurchaseHandler) Map2Order(orderData map[string]interface{}) (order *model.GoodsOrder) { result := orderData shopMap := result["shop"].(map[string]interface{}) orderMap := result["order"].(map[string]interface{}) userMap := result["user"].(map[string]interface{}) vendorOrderID := orderMap["order_id"].(string) order = &model.GoodsOrder{ VendorOrderID: vendorOrderID, VendorOrderID2: orderMap["eleme_order_id"].(string), VendorID: model.VendorIDEBAI, VendorStoreID: shopMap["baidu_shop_id"].(string), StoreID: int(utils.Str2Int64WithDefault(utils.Interface2String(shopMap["id"]), 0)), StoreName: shopMap["name"].(string), ConsigneeName: userMap["name"].(string), ConsigneeMobile: jxutils.FormalizeMobile(userMap["phone"].(string)), ConsigneeAddress: userMap["address"].(string), CoordinateType: model.CoordinateTypeBaiDu, BuyerComment: utils.TrimBlankChar(utils.Interface2String(orderMap["remark"])), ExpectedDeliveredTime: getTimeFromInterface(orderMap["send_time"]), PickDeadline: utils.DefaultTimeValue, VendorStatus: utils.Int64ToStr(utils.MustInterface2Int64(orderMap["status"])), OrderSeq: int(utils.Str2Int64(utils.Interface2String(orderMap["order_index"]))), StatusTime: getTimeFromInterface(orderMap["create_time"]), OriginalData: string(utils.MustMarshal(result)), ActualPayPrice: utils.MustInterface2Int64(orderMap["user_fee"]), Skus: []*model.OrderSku{}, } if utils.IsTimeZero(order.PickDeadline) && !utils.IsTimeZero(order.StatusTime) { order.PickDeadline = order.StatusTime.Add(pickupOrderDelay) // 饿百要求在5分钟内拣货,不然订单会被取消 } if order.ConsigneeMobile == "" { if mobileInfo, err := api.EbaiAPI.OrderPrivateInfo(vendorOrderID); err == nil { order.ConsigneeMobile = jxutils.FormalizeMobile(mobileInfo.ShortNumber) } } if order.StoreID > math.MaxInt32 { order.StoreID = 0 } order.Status = p.GetStatusFromVendorStatus(order.VendorStatus) if utils.MustInterface2Int64(orderMap["send_immediately"]) == 1 { order.BusinessType = model.BusinessTypeImmediate } else { order.BusinessType = model.BusinessTypeDingshida if utils.IsTimeZero(order.ExpectedDeliveredTime) { order.ExpectedDeliveredTime = getTimeFromInterface(orderMap["latest_send_time"]) } } deliveryGeo := userMap["coord"].(map[string]interface{}) originalLng := utils.Interface2Float64WithDefault(deliveryGeo["longitude"], 0.0) // 饿百的订单在过一段时间后,经纬度信息会变成字符串"**" originalLat := utils.Interface2Float64WithDefault(deliveryGeo["latitude"], 0.0) lng, lat, err2 := api.AutonaviAPI.CoordinateConvert(originalLng, originalLat, autonavi.CoordSysBaidu) if err2 == nil { originalLng = lng originalLat = lat order.CoordinateType = model.CoordinateTypeMars } order.ConsigneeLng = jxutils.StandardCoordinate2Int(originalLng) order.ConsigneeLat = jxutils.StandardCoordinate2Int(originalLat) products := result["products"].([]interface{})[0].([]interface{}) // discounts := result["discount"].(map[string]interface{}) for _, product2 := range products { product := product2.(map[string]interface{}) skuName := product["product_name"].(string) _, _, _, specUnit, _, specQuality := jxutils.SplitSkuName(skuName) productAmount := int(utils.MustInterface2Int64(product["product_amount"])) sku := &model.OrderSku{ VendorOrderID: order.VendorOrderID, VendorID: model.VendorIDEBAI, Count: productAmount, SkuID: int(utils.Str2Int64WithDefault(utils.Interface2String(product[ebaiapi.KeyCustomSkuID]), 0)), VendorSkuID: utils.Interface2String(product["baidu_product_id"]), SkuName: skuName, Weight: int(utils.Interface2Int64WithDefault(product["total_weight"], 0)) / productAmount, SalePrice: utils.MustInterface2Int64(product["product_price"]), // PromotionType: int(utils.MustInterface2Int64(product["promotionType"])), } if sku.Weight == 0 { sku.Weight = jxutils.FormatSkuWeight(specQuality, specUnit) // 订单信息里没有重量,只有名字里尝试找 } // if product["isGift"].(bool) { // sku.SkuType = 1 // } order.Skus = append(order.Skus, sku) } jxutils.RefreshOrderSkuRelated(order) return order } func (p *PurchaseHandler) AcceptOrRefuseOrder(order *model.GoodsOrder, isAcceptIt bool, userName string) (err error) { globals.SugarLogger.Debugf("ebai AcceptOrRefuseOrder orderID:%s, isAcceptIt:%t", order.VendorOrderID, isAcceptIt) if isAcceptIt { p.postFakeMsg(order.VendorOrderID, fakeAcceptOrder) } else { if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderCancel(order.VendorOrderID, ebaiapi.CancelTypeCustom, "bu") } } return err } func (p *PurchaseHandler) PickupGoods(order *model.GoodsOrder, isSelfDelivery bool, userName string) (err error) { globals.SugarLogger.Debugf("ebai PickupGoods orderID:%s, isSelfDelivery:%t", order.VendorOrderID, isSelfDelivery) if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderConfirm(order.VendorOrderID) } else { p.postFakeMsg(order.VendorOrderID, ebaiapi.OrderStatusAccepted) } return err } func (p *PurchaseHandler) AcceptOrRefuseFailedGetOrder(ctx *jxcontext.Context, order *model.GoodsOrder, isAcceptIt bool) (err error) { return err } func (p *PurchaseHandler) CallCourier(ctx *jxcontext.Context, order *model.GoodsOrder) (err error) { // 拣货失败后再次招唤平台配送 return err } func (p *PurchaseHandler) ConfirmReceiveGoods(ctx *jxcontext.Context, order *model.GoodsOrder) (err error) { // 投递失败后确认收到退货 return err } // 将订单从购物平台配送转为自送 func (p *PurchaseHandler) Swtich2SelfDeliver(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("ebai Swtich2SelfDeliver orderID:%s", order.VendorOrderID) if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderSwitchselfdelivery(order.VendorOrderID) } if err == nil { // 饿百不会发送配送中,模拟发送 p.postFakeMsg(order.VendorOrderID, ebaiapi.OrderStatusCourierAccepted) } return err } // 将订单从购物平台配送转为自送后又送达 func (p *PurchaseHandler) Swtich2SelfDelivered(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("ebai Swtich2SelfDelivered orderID:%s", order.VendorOrderID) // todo 饿百转商家自送后,没有确认送达的概念,空操作 return err } // 完全自送的门店表示开始配送 func (p *PurchaseHandler) SelfDeliverDelivering(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("ebai SelfDeliverDelivering orderID:%s", order.VendorOrderID) if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderSendOut(order.VendorOrderID, userName) } if err == nil { // 饿百不会发送配送中,模拟发送 p.postFakeMsg(order.VendorOrderID, ebaiapi.OrderStatusCourierAccepted) } return err } // 完全自送的门店表示配送完成 func (p *PurchaseHandler) SelfDeliverDelivered(order *model.GoodsOrder, userName string) (err error) { globals.SugarLogger.Debugf("ebai SelfDeliverDelivered orderID:%s", order.VendorOrderID) if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderComplete(order.VendorOrderID, userName) } return err } // func (c *PurchaseHandler) onOrderMsg(msg *ebaiapi.CallbackMsg) (retVal *ebaiapi.CallbackResponse) { if c.isAfsMsg(msg) { retVal = c.OnAfsOrderMsg(msg) } else { if ebaiapi.CmdOrderCreate == msg.Cmd { retVal = c.onOrderNew(msg) } else { var err error status := c.callbackMsg2Status(msg) if status != nil { if status.Status == model.OrderStatusAdjust { var order *model.GoodsOrder if order, err = c.GetOrder4PartRefund(GetOrderIDFromMsg(msg)); err == nil { err = partner.CurOrderManager.OnOrderAdjust(order, status) } } else { err = partner.CurOrderManager.OnOrderStatusChanged(status) } } retVal = api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, nil) } } return retVal } func (c *PurchaseHandler) onOrderNew(msg *ebaiapi.CallbackMsg) (response *ebaiapi.CallbackResponse) { vendorOrderID := GetOrderIDFromMsg(msg) order, orderMap, err := c.getOrder(vendorOrderID) if err == nil { if err = partner.CurOrderManager.OnOrderNew(order, c.callbackMsg2Status(msg)); err == nil { utils.CallFuncAsync(func() { c.OnOrderDetail(orderMap, partner.CreatedPeration) }) } } return api.EbaiAPI.Err2CallbackResponse(msg.Cmd, err, map[string]interface{}{ "source_order_id": vendorOrderID, }) } func (c *PurchaseHandler) callbackMsg2Status(msg *ebaiapi.CallbackMsg) (orderStatus *model.OrderStatus) { orderID := GetOrderIDFromMsg(msg) orderStatus = &model.OrderStatus{ VendorOrderID: orderID, VendorID: model.VendorIDEBAI, OrderType: model.OrderTypeOrder, RefVendorOrderID: orderID, RefVendorID: model.VendorIDEBAI, StatusTime: utils.Timestamp2Time(msg.Timestamp), VendorStatus: msg.Cmd, } if msg.Cmd == ebaiapi.CmdOrderUserCancel { msgType := int(utils.MustInterface2Int64(msg.Body["type"])) cancelType := int(utils.MustInterface2Int64(msg.Body["cancel_type"])) orderStatus.Remark = buildFullReason(utils.Interface2String(msg.Body["cancel_reason"]), utils.Interface2String(msg.Body["addition_reason"])) orderStatus.VendorStatus = msg.Cmd + "-" + utils.Int2Str(msgType) if cancelType == ebaiapi.OrderUserCancelTypeBeforeSale { if msgType == ebaiapi.OrderUserCancelApply || msgType == ebaiapi.OrderUserCancelCSIntervene { orderStatus.VendorStatus = fakeUserApplyCancel } else if msgType == ebaiapi.OrderUserCancelInvalid || msgType == ebaiapi.OrderUserCancelMerchantRefused || msgType == ebaiapi.OrderUserCancelCSRefused { orderStatus.VendorStatus = fakeUserUndoApplyCancel } } } else if msg.Cmd == ebaiapi.CmdOrderPartRefund { msgType := int(utils.MustInterface2Int64(msg.Body["type"])) status := int(utils.MustInterface2Int64(msg.Body["status"])) orderStatus.Remark = buildFullReason(utils.Interface2String(msg.Body["reason"]), utils.Interface2String(msg.Body["addition_reason"])) if msgType == ebaiapi.OrderPartRefuncTypeMerchant && status == ebaiapi.OrderPartRefundSuccess { orderStatus.VendorStatus = fakeOrderAdjustFinished } } else if status, ok := msg.Body["status"]; ok { if vendorStatus, ok := status.(string); ok { orderStatus.VendorStatus = vendorStatus } else { orderStatus.VendorStatus = utils.Int64ToStr(utils.MustInterface2Int64(status)) } orderStatus.Remark = utils.Interface2String(msg.Body["reason"]) } orderStatus.Status = c.GetStatusFromVendorStatus(orderStatus.VendorStatus) return orderStatus } func buildFullReason(reason, addReason string) (fullReason string) { fullReason = reason if addReason != "" { fullReason += ",额外原因:" + addReason } return fullReason } func (c *PurchaseHandler) GetStatusActionTimeout(order *model.GoodsOrder, statusType, status int) (params *partner.StatusActionParams) { if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusAccepted { params = &partner.StatusActionParams{ // PickDeadline没有设置时才有效,饿百要求在5分钟内拣货,不然订单会被取消 Timeout: pickupOrderDelay, } } else if statusType == scheduler.TimerStatusTypeOrder && status == model.OrderStatusFinishedPickup { params = &partner.StatusActionParams{ // 立即达订单有效,自配送延时召唤配送 Timeout: callDeliveryDelay, TimeoutGap: callDeliveryDelayGap, } } return params } func (c *PurchaseHandler) getOrderStoreDeliveryType(order *model.GoodsOrder) (deliveryType int) { sql := ` SELECT * FROM store_map t1 WHERE t1.vendor_store_id = ? AND t1.vendor_id = ? AND t1.deleted_at = ? ` db := dao.GetDB() var storeMap *model.StoreMap if err := dao.GetRow(db, &storeMap, sql, order.VendorStoreID, model.VendorIDEBAI, utils.DefaultTimeValue); err == nil { return int(storeMap.DeliveryType) } else if !dao.IsNoRowsError(err) { globals.SugarLogger.Warnf("getOrderStoreDeliveryType orderID:%s failed with error:%v", order.VendorOrderID, err) } return scheduler.StoreDeliveryTypeByPlatform } func (c *PurchaseHandler) postFakeMsg(vendorOrderID, vendorStatus string) { msg := &ebaiapi.CallbackMsg{ Cmd: ebaiapi.CmdOrderStatus, Timestamp: time.Now().Unix(), Body: map[string]interface{}{ "status": vendorStatus, "order_id": vendorOrderID, }, } utils.CallFuncAsync(func() { OnCallbackMsg(msg) }) } func getTimeFromInterface(timeValue interface{}) time.Time { var timeStamp int64 if timeStr, ok := timeValue.(string); ok { timeStamp = utils.Str2Int64WithDefault(timeStr, 0) } else { timeStamp = utils.Interface2Int64WithDefault(timeValue, 0) } if timeStamp < 1538103149 { // 立即达订单给的是1(而不是空,0),1538103149不是特殊值,只是一个任意之前的时间,这样写可以处理 return utils.DefaultTimeValue } return utils.Timestamp2Time(timeStamp) } func (c *PurchaseHandler) GetOrderRealMobile(ctx *jxcontext.Context, order *model.GoodsOrder) (mobile string, err error) { mobile, err = api.EbaiAPI.GetRealMobile4Order(order.VendorOrderID) return mobile, err } func (c *PurchaseHandler) AgreeOrRefuseCancel(ctx *jxcontext.Context, order *model.GoodsOrder, isAgree bool, reason string) (err error) { if globals.EnableEbaiStoreWrite { if isAgree { err = api.EbaiAPI.OrderAgreeRefund(order.VendorOrderID) } else { err = api.EbaiAPI.OrderDisagreeRefund(order.VendorOrderID, reason) } } return err } func (c *PurchaseHandler) CancelOrder(ctx *jxcontext.Context, order *model.GoodsOrder, reason string) (err error) { if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderCancel(order.VendorOrderID, ebaiapi.CancelTypeCustom, reason) } return err } func (c *PurchaseHandler) AdjustOrder(ctx *jxcontext.Context, order *model.GoodsOrder, removedSkuList []*model.OrderSku, reason string) (err error) { // 饿百必须要确认订单后才能调整单 if order.Status < model.OrderStatusFinished { err = c.PickupGoods(order, false, ctx.GetUserName()) } if err == nil { var skuList []*ebaiapi.RefundSku for _, sku := range removedSkuList { skuList = append(skuList, &ebaiapi.RefundSku{ CustomeSkuID: utils.Int2Str(jxutils.GetSkuIDFromOrderSku(sku)), Number: utils.Int2Str(sku.Count), }) } if globals.EnableEbaiStoreWrite { err = api.EbaiAPI.OrderPartRefund(order.VendorOrderID, skuList) } } return err }