package sfps import ( "errors" "fmt" "time" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/business/partner/delivery" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/baseapi/platformapi/mtpsapi" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/globals/api" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/baseapi/platformapi/sfps2" "git.rosy.net.cn/jx-callback/business/model" ) type DeliveryHandler struct { } var ( curDeliveryHandler *DeliveryHandler ) func init() { if api.SfPsAPI != nil { curDeliveryHandler = new(DeliveryHandler) partner.RegisterDeliveryPlatform(curDeliveryHandler, true) } } func (d DeliveryHandler) GetVendorID() int { return model.VendorIDSFPS } func (d DeliveryHandler) CreateStore(ctx *jxcontext.Context, storeDetail *dao.StoreDetail2) (vendorStoreID string, status int, err error) { return "", 0, fmt.Errorf("顺丰派送暂不支持此操作") } func (d DeliveryHandler) GetStore(ctx *jxcontext.Context, storeID int, vendorStoreID string) (storeDetail *dao.StoreDetail2, err error) { store, err := dao.GetStoreDetail(dao.GetDB(), utils.Str2Int(vendorStoreID), model.VendorIDSFPS, "") if err == nil { storeDetail = &dao.StoreDetail2{ Store: model.Store{ Name: store.Name, Tel1: store.Tel1, Address: store.Address, Lat: store.Lat, Lng: store.Lng, }, VendorID: model.VendorIDSFPS, VendorStoreID: utils.Int2Str(storeID), CourierStatus: model.StoreStatusOpened, } } return storeDetail, err } func (d DeliveryHandler) IsErrStoreNotExist(err error) bool { return false } func (d DeliveryHandler) IsErrStoreExist(err error) bool { return false } func (d DeliveryHandler) CreateWaybill(order *model.GoodsOrder, maxDeliveryFee int64) (bill *model.Waybill, err error) { vendorOrgCode, err := dao.GetVendorOrgCode(dao.GetDB(), model.VendorIDSFPS, "", model.VendorOrgTypeDelivery) if err != nil { return nil, err } if len(vendorOrgCode) > 0 && vendorOrgCode[0].IsOpen == model.YES { return nil, fmt.Errorf("此平台配送已被系统关闭,暂不发配送 [%v]", vendorOrgCode[0].Comment) } store, err := dao.GetStoreDetail(dao.GetDB(), getReallyStoreID(order.StoreID, order.JxStoreID), 0, "") if err != nil { return nil, err } var ( weight int productDetail []*sfps2.ProductDetail ) if order.Weight >= 49500 { weight = 49500 } else { weight = order.Weight } param := &sfps2.CreateOrderReq{ ShopId: sfps2.SFShopStoreID, ShopOrderId: order.VendorOrderID, OrderSource: GetVendorSource(order.VendorID), OrderTime: order.CreatedAt.Unix(), ShopType: sfps2.OrderTypeSF, LbsType: sfps2.LbsTypeGD, RiderPickMethod: sfps2.RiderPickUpMethodSTU, Receive: &sfps2.ReceiveAddress{ UserName: order.ConsigneeName, UserPhone: order.ConsigneeMobile, UserAddress: order.ConsigneeAddress, UserLat: utils.Float64ToStr(jxutils.IntCoordinate2Standard(order.ConsigneeLat)), UserLng: utils.Float64ToStr(jxutils.IntCoordinate2Standard(order.ConsigneeLng)), }, Shop: &sfps2.SfShopInfo{ ShopName: order.StoreName, ShopPhone: store.Tel1, ShopAddress: store.Address, ShopLat: utils.Float64ToStr(jxutils.IntCoordinate2Standard(store.Lat)), ShopLng: utils.Float64ToStr(jxutils.IntCoordinate2Standard(store.Lng)), }, OrderDetail: &sfps2.OrderDetail{ TotalPrice: order.ActualPayPrice, ProductType: sfps2.ProductTypeFresh, WeightGram: int64(weight), ProductNum: int64(order.GoodsCount), ProductTypeNum: int64(order.SkuCount), }, } for _, v := range order.Skus { productDetail = append(productDetail, &sfps2.ProductDetail{ ProductName: v.SkuName, ProductNum: int64(v.Count), }) } param.OrderDetail.ProductDetail = productDetail sfOrderID, sfBillID, sfTotalPrice, sfReallyPrice, err := api.SfPsAPI.CreateOrder(param) if err != nil { return nil, err } //+0.2 desiredFee := utils.Float64TwoInt(sfTotalPrice) + utils.WayBillDeliveryMarkUp bill = &model.Waybill{ VendorOrderID: order.VendorOrderID, OrderVendorID: order.VendorID, VendorWaybillID: sfOrderID, VendorWaybillID2: sfBillID, WaybillVendorID: model.VendorIDSFPS, DesiredFee: int64(desiredFee), ActualFee: utils.Float64TwoInt64(sfReallyPrice), } delivery.OnWaybillCreated(bill) //todo 模拟新运单回调 if _, err = partner.CurOrderManager.LoadWaybill(sfOrderID, model.VendorIDSFPS); fmt.Sprintf("%s", err) == "找不到相应运单" { resp := OnWaybillMsg(sfps2.UrlIndexRiderStatus, sfps2.RiderStatus{ ShopId: utils.Str2Float64(sfps2.SFShopStoreID), SFOrderID: sfOrderID, ShopOrderID: order.VendorOrderID, UrlIndex: sfps2.UrlIndexRiderStatus, OperatorName: "", OperatorPhone: "", RiderLng: 0, RiderLat: 0, OrderStatus: sfps2.OrderStatusNewOrder, StatusDesc: sfps2.OrderStatusNewOrderDesc, SFUCode: "", PushTime: int(time.Now().Unix()), }) if resp.ErrorCode != sfps2.SuccessCode { err = fmt.Errorf("%v,%s", err, resp.ErrorMsg) } } return bill, err } func (d DeliveryHandler) CancelWaybill(bill *model.Waybill, cancelReasonID int, cancelReason string) (err error) { if err = api.SfPsAPI.CancelOrder(bill.VendorWaybillID); err != nil { return err } bill.Status = model.WaybillStatusCanceled bill.Remark = cancelReason partner.CurOrderManager.OnWaybillStatusChanged(bill) return nil } func (d DeliveryHandler) GetWaybillFee(order *model.GoodsOrder) (deliveryFeeInfo *partner.WaybillFeeInfo, err error) { var ( weight int //productDetail []*sfps2.ProductDetail ) if order.Weight >= 49500 { weight = 49500 } else { weight = order.Weight } store, err := dao.GetStoreDetail(dao.GetDB(), getReallyStoreID(order.StoreID, order.JxStoreID), 0, "") if err != nil { return nil, err } param := &sfps2.PreCreateOrderReq{ ShopId: sfps2.SFShopStoreID, UserLng: utils.Float64ToStr(jxutils.IntCoordinate2Standard(order.ConsigneeLng)), UserLat: utils.Float64ToStr(jxutils.IntCoordinate2Standard(order.ConsigneeLat)), UserAddress: order.ConsigneeAddress, Weight: int64(weight), ProductType: sfps2.ProductTypeFresh, ShopType: sfps2.OrderTypeSF, LbsType: sfps2.LbsTypeGD, RiderPickMethod: sfps2.RiderPickUpMethodSTU, Shop: &sfps2.SfShopInfo{ ShopName: order.StoreName, ShopPhone: store.Tel1, ShopAddress: store.Address, ShopLat: utils.Float64ToStr(jxutils.IntCoordinate2Standard(store.Lat)), ShopLng: utils.Float64ToStr(jxutils.IntCoordinate2Standard(store.Lng)), }, } deliveryFeeInfo = &partner.WaybillFeeInfo{} price, err := api.SfPsAPI.PreCreateOrder(param) //+0.2 //deliveryFee := utils.Float64TwoInt(price) + utils.WayBillDeliveryMarkUp deliveryFeeInfo.DeliveryFee = utils.Float64TwoInt64(price) globals.SugarLogger.Debugf("GetWaybillFee deliveryFeeInfo.DeliveryFee=%d", deliveryFeeInfo.DeliveryFee) return deliveryFeeInfo, err } func (d DeliveryHandler) ComplaintRider(bill *model.Waybill, resonID int, resonContent string) (err error) { return nil } // GetDeliverLiquidatedDamages 获取取消运单违约金 func (d DeliveryHandler) GetDeliverLiquidatedDamages(orderId string, deliverId string) (money int64, err error) { waybill, err := dao.GetWaybills(dao.GetDB(), orderId, []int64{model.VendorIDSFPS}) if len(waybill) == 0 || err != nil { return 0, err } deductionFee, err := api.SfPsAPI.PreCancelOrder(waybill[0].VendorWaybillID) if deductionFee == 0 || err != nil { return 0, err } money = utils.Float64TwoInt64(deductionFee) return money, nil } func (d DeliveryHandler) GetRiderInfo(orderId string, deliveryId int64, mtPeisongId string) (rider *mtpsapi.RiderInfo, err error) { order, err := dao.GetWaybills(dao.GetDB(), orderId, []int64{model.VendorIDSFPS}) if len(order) == 0 || err != nil { return nil, errors.New("顺丰 订单id无效,请检查") } //获取顺丰运单详情 sfOrder, err := api.SfPsAPI.GetOrderStatus(order[0].VendorWaybillID) //获取顺丰骑手实时位置 sfRider, err := api.SfPsAPI.GetRiderLatestPosition(sfOrder.OrderID) result := &mtpsapi.RiderInfo{ OrderId: orderId, ThirdCarrierOrderId: sfOrder.OrderID, CourierName: sfOrder.RiderName, CourierPhone: sfOrder.RiderPhone, LogisticsProviderCode: mtpsapi.SFPSCode, LogisticsStatus: utils.Float64TwoInt(sfOrder.OrderStatus), // 默认正在配送中 Latitude: sfRider.RiderLat, Longitude: sfRider.RiderLng, } switch sfOrder.OrderStatus { case sfps2.OrderStatusNewOrder: result.LogisticsStatus = model.WaybillStatusNew result.LogisticsContext = model.RiderWaitRider case sfps2.OrderStatusTakeOrder: result.LogisticsStatus = model.WaybillStatusCourierAssigned result.LogisticsContext = model.RiderGetOrder case sfps2.OrderStatusArrivedStore: result.LogisticsStatus = model.WaybillStatusCourierArrived result.LogisticsContext = model.RiderToStore case sfps2.OrderStatusRiderArriving: result.LogisticsStatus = model.WaybillStatusDelivering result.LogisticsContext = model.RiderGetOrderDelivering case sfps2.OrderStatusFinished: result.LogisticsStatus = model.WaybillStatusDelivered result.LogisticsContext = model.RiderGetOrderDelivered case sfps2.OrderStatusOrderCancel: result.LogisticsStatus = model.WaybillStatusCanceled result.LogisticsContext = model.RiderGetOrderCanceled case sfps2.OrderStatusError: result.LogisticsStatus = model.WaybillStatusDeliverFailed result.LogisticsContext = model.RiderGetOrderDeliverFailed default: result.LogisticsStatus = 0 result.LogisticsContext = model.RiderGetOrderDeliverOther } return result, nil } func (c *DeliveryHandler) GetWaybillTip(ctx *jxcontext.Context, vendorOrgCode, vendorStoreID, vendorOrderID, vendorWaybillID, vendorWaybillID2 string) (tipFee int64, err error) { return api.SfPsAPI.QueryTipFee(vendorWaybillID) } func (c *DeliveryHandler) UpdateWaybillTip(ctx *jxcontext.Context, vendorOrgCode, vendorStoreID, vendorOrderID, vendorWaybillID, vendorWaybillID2, cityCode string, tipFee int64) (err error) { return api.SfPsAPI.AddTipFee(vendorWaybillID, tipFee) } // OnWaybillMsg 配送状态更改回调 func OnWaybillMsg(urlIndex string, msg interface{}) (resp *sfps2.CallbackResponse) { order := GetWaybillByStatus(urlIndex, msg) //多次取消回调只取第一次 tempStatus := utils.Str2Int(order.VendorStatus) if tempStatus == sfps2.OrderStatusOrderCancel || tempStatus == sfps2.OrderStatusRiderCancel { bill, err := partner.CurOrderManager.LoadWaybill(order.VendorWaybillID, model.VendorIDSFPS) if err != nil { return sfps2.Err2CallbackResponse(err) } if bill.Status == model.OrderStatusCanceled { return sfps2.Err2CallbackResponse(nil) } } //获取实时订单信息 sfOrder, err := api.SfPsAPI.GetOrderStatus(order.VendorWaybillID) if err != nil { return sfps2.Err2CallbackResponse(err) } globals.SugarLogger.Debugf("SFPS OnWaybillMsg sfOrder=%s", utils.Format4Output(sfOrder, false)) if order.CourierMobile == "" { order.CourierName = sfOrder.RiderName } if order.CourierName == "" { order.CourierMobile = sfOrder.RiderPhone } //+0.2 sfTotalPrice := utils.Float64TwoInt(sfOrder.TotalPrice) + utils.WayBillDeliveryMarkUp sfActualPrice := utils.Float64TwoInt64(sfOrder.RealPayMoney) globals.SugarLogger.Debugf("SFPS OnWaybillMsg,sfTotalPrice=%d,sfActualPrice=%d", sfTotalPrice, sfActualPrice) var good *model.GoodsOrder sql := `SELECT * FROM goods_order WHERE vendor_order_id = ? ORDER BY order_created_at DESC LIMIT 1 OFFSET 0` sqlParams := []interface{}{order.VendorOrderID} dao.GetRow(dao.GetDB(), &good, sql, sqlParams) order.OrderVendorID = good.VendorID orderStatus := utils.Str2Int64(order.VendorStatus) switch orderStatus { case sfps2.OrderStatusNewOrder: //1:订单创建 order.DesiredFee = int64(sfTotalPrice) order.ActualFee = sfActualPrice order.Status = model.WaybillStatusNew //5 带调度 case sfps2.OrderStatusTakeOrder: //10:配送员接单 order.DesiredFee = int64(sfTotalPrice) order.Status = model.WaybillStatusCourierAssigned //12 order.Remark = order.CourierName + "," + order.CourierMobile case sfps2.OrderStatusArrivedStore: order.DesiredFee = int64(sfTotalPrice) order.Status = model.WaybillStatusCourierArrived case sfps2.OrderStatusRiderArriving: order.DesiredFee = int64(sfTotalPrice) order.Status = model.WaybillStatusDelivering case sfps2.OrderStatusFinished: order.DesiredFee = int64(sfTotalPrice) order.Status = model.WaybillStatusDelivered case sfps2.OrderStatusOrderCancel, sfps2.OrderStatusRiderCancel: order.Status = model.WaybillStatusCanceled case sfps2.OrderStatusError: order.Status = model.WaybillStatusDeliverFailed // 22 default: globals.SugarLogger.Warnf("onWaybillMsg unknown msg:%v", msg) } globals.SugarLogger.Debugf("SFPS OnWaybillMsg order=%s", utils.Format4Output(order, false)) if err := partner.CurOrderManager.OnWaybillStatusChanged(order); err != nil { return sfps2.Err2CallbackResponse(err) } if order.OrderVendorID == model.VendorIDDD { result := &mtpsapi.RiderInfo{ OrderId: order.VendorOrderID, ThirdCarrierOrderId: order.VendorOrderID, CourierName: order.CourierName, CourierPhone: order.CourierMobile, LogisticsProviderCode: "10002", LogisticsStatus: order.Status, OpCode: "", } switch orderStatus { case sfps2.OrderStatusNewOrder: result.LogisticsStatus = model.WaybillStatusNew result.LogisticsContext = model.RiderWaitRider case sfps2.OrderStatusTakeOrder: result.LogisticsStatus = model.WaybillStatusCourierAssigned // 分配骑手 result.LogisticsContext = model.RiderWaitGetGoods case sfps2.OrderStatusRiderArriving: result.LogisticsStatus = model.WaybillStatusDelivering result.LogisticsContext = model.RiderGetOrderDelivering case sfps2.OrderStatusFinished: result.LogisticsStatus = model.WaybillStatusDelivered result.LogisticsContext = model.RiderGetOrderDelivered case sfps2.OrderStatusOrderCancel: result.LogisticsStatus = model.WaybillStatusCanceled result.LogisticsContext = model.RiderGetOrderCanceled case sfps2.OrderStatusError: result.LogisticsStatus = model.WaybillStatusDeliverFailed result.LogisticsContext = model.RiderGetOrderDeliverFailed case sfps2.OrderStatusArrivedStore: result.LogisticsStatus = model.WaybillStatusCourierArrived result.LogisticsContext = model.RiderToStore default: result.LogisticsStatus = 0 result.LogisticsContext = model.RiderGetOrderDeliverOther } delivery.PullTiktokRiderInfo(result) } defer delivery.GetOrderRiderInfoToPlatform(order.VendorOrderID, order.Status) // 骑手位置更新 return sfps2.Err2CallbackResponse(nil) } // OnWaybillExceptSF 异常报备 func OnWaybillExceptSF(msg *sfps2.RiderException) (retVal *sfps2.CallbackResponse) { jxutils.CallMsgHandler(func() { order := &model.Waybill{ VendorWaybillID: msg.SFOrderID, VendorWaybillID2: utils.Int2Str(msg.ExID), WaybillVendorID: model.VendorIDSFPS, CourierName: msg.OperatorName, CourierMobile: "", Status: model.WaybillStatusUnknown, // todo 这里要再确定一下是否只要收到订单异常消息就只简单当成一个消息 VendorStatus: utils.Float64ToStr(msg.OrderStatus), StatusTime: utils.Timestamp2Time(int64(msg.PushTime)), } order.VendorOrderID, order.OrderVendorID = jxutils.SplitUniversalOrderID(msg.ShopOrderID) retVal = sfps2.Err2CallbackResponse(partner.CurOrderManager.OnWaybillStatusChanged(order)) }, jxutils.ComposeUniversalOrderID(msg.ShopOrderID, model.VendorIDSFPS)) return retVal } // GetWaybillByStatus 根据orderStatus 获取waybill结构 func GetWaybillByStatus(urlIndex string, msg interface{}) *model.Waybill { waybill := &model.Waybill{} switch urlIndex { case sfps2.UrlIndexRiderStatus: retVal := msg.(sfps2.RiderStatus) waybill = &model.Waybill{ VendorWaybillID: retVal.SFOrderID, WaybillVendorID: model.VendorIDSFPS, VendorOrderID: retVal.ShopOrderID, CourierName: retVal.OperatorName, CourierMobile: retVal.OperatorPhone, VendorStatus: utils.Float64ToStr(retVal.OrderStatus), StatusTime: utils.Timestamp2Time(int64(retVal.PushTime)), Remark: retVal.StatusDesc, } if retVal.PushTime == 0 { waybill.StatusTime = time.Now() } case sfps2.UrlIndexRiderRecall: retVal := msg.(sfps2.RiderRecall) waybill = &model.Waybill{ VendorWaybillID: retVal.SFOrderID, WaybillVendorID: model.VendorIDSFPS, VendorOrderID: retVal.ShopOrderID, VendorStatus: utils.Float64ToStr(retVal.OrderStatus), StatusTime: utils.Timestamp2Time(int64(retVal.PushTime)), Remark: retVal.StatusDesc, } if retVal.PushTime == 0 { waybill.StatusTime = time.Now() } case sfps2.UrlIndexOrderComplete: retVal := msg.(sfps2.OrderComplete) waybill = &model.Waybill{ VendorWaybillID: retVal.SFOrderID, WaybillVendorID: model.VendorIDSFPS, VendorOrderID: retVal.ShopOrderID, CourierName: retVal.OperatorName, VendorStatus: utils.Float64ToStr(retVal.OrderStatus), StatusTime: utils.Timestamp2Time(int64(retVal.PushTime)), Remark: retVal.StatusDesc, } if retVal.PushTime == 0 { waybill.StatusTime = time.Now() } case sfps2.UrlIndexSFCancel: retVal := msg.(sfps2.SFCancel) waybill = &model.Waybill{ VendorWaybillID: retVal.SFOrderID, WaybillVendorID: model.VendorIDSFPS, VendorOrderID: retVal.ShopOrderID, CourierName: retVal.OperatorName, VendorStatus: utils.Float64ToStr(retVal.OrderStatus), StatusTime: utils.Timestamp2Time(int64(retVal.PushTime)), Remark: retVal.StatusDesc, } if retVal.PushTime == 0 { waybill.StatusTime = time.Now() } default: return nil } return waybill } // GetVendorSource 辅助函数 //获取订单来源标识符 func GetVendorSource(vendorID int) (source string) { switch vendorID { case model.VendorIDMTWM: source = sfps2.OrderSourceMt case model.VendorIDELM: source = sfps2.OrderSourceELM case model.VendorIDEBAI: source = sfps2.OrderSourceEBAI case model.VendorIDDD: source = "抖音" case model.VendorIDJD: source = "京东" case model.VendorIDJX: source = "京西" default: source = "其他" } return source } func getReallyStoreID(storeID, jxStoreID int) int { if storeID == 0 && jxStoreID == 0 { return 0 } if storeID == 0 { return jxStoreID } else { return storeID } }