select(['logistic_number', 'order_id', 'id']) ->with(['order' => function ($query) { return $query->select(['id', 'logistic_id'])->with('logistic:id,name,code'); }]); if ($is_to_init) {//当前时间小于等于初始化时间 $initDate = Carbon::parse(config('api_logistic.init_date')); //初始化查询一个月的数据,exception为否 $query = $query->where('sent_at', '>=', $initDate) ->whereNull('received_at'); } else {//查询20天以内的数据 $query = $query->where('sent_at', '>=', now()->subDays(config('api_logistic.querying_days'))->startOfDay()) ->whereNull('received_at'); } $query->chunkById(1000, function ($orderPackages) { LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法", json_encode(data_get($orderPackages, '*.logistic_number'))); $logisticNumbers = $this->buildData($orderPackages); //sf if (array_key_exists('SF', $logisticNumbers)) { $SFLogisticNumbers = $logisticNumbers['SF']; LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-SF", json_encode($SFLogisticNumbers)); foreach ($SFLogisticNumbers as $logisticNumber) { LogService::log(OrderPackageReceivedSyncService::class, "同步SF快递单号", $logisticNumber); LogisticSFSync::dispatch($logisticNumber); } } //更新中通 if (array_key_exists('ZTO', $logisticNumbers)) { $ZTOLogisticNumbers = $logisticNumbers['ZTO']; LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-ZTO", json_encode($ZTOLogisticNumbers)); foreach ($ZTOLogisticNumbers as $logisticNumber) { LogService::log(OrderPackageReceivedSyncService::class, "同步ZTO快递单号", $logisticNumber); LogisticZopSync::dispatch($logisticNumber); } } //更新韵达 if (array_key_exists('YUNDA', $logisticNumbers)) { $YDLogisticNumbers = $logisticNumbers['YUNDA']; LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-YUNDA", json_encode($YDLogisticNumbers)); foreach ($YDLogisticNumbers as $logistic_number) { LogService::log(OrderPackageReceivedSyncService::class, "同步YUNDA快递单号", $logisticNumber); LogisticYDSync::dispatch($logistic_number); } } //更新圆通 if (array_key_exists('YTO', $logisticNumbers)) { $YTOLogisticNumbers = $logisticNumbers['YTO']; LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-YTO", json_encode($YTOLogisticNumbers)); foreach ($YTOLogisticNumbers as $logistic_number) { if ($logistic_number)LogisticYTOSync::dispatch($logistic_number); } } }); } public function syncLogisticRouteByAliJiSu() { ini_set('max_execution_time', 2 * 60 * 60); $query = OrderPackage::query() ->select(['logistic_number', 'order_id']) ->whereIn('order_id', function ($query) { $query->from('orders')->selectRaw('id')->whereIn('logistic_id', function ($builder) { $builder->from('logistics')->selectRaw('id')->where('type', '!=', '物流')->whereNotIn('belong_company', ['顺丰', '中通', '韵达', '圆通', '京东']); }); }); $query = $query->where('sent_at', '>=', now()->subDays(config('api_logistic.querying_days'))) ->whereNull('received_at'); $query->chunkById(200, function ($orderPackages) { LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-阿里公用接口", json_encode($orderPackages)); foreach ($orderPackages as $orderPackage) { if ($orderPackage && $orderPackage->logistic_number) LogisticAliJiSuSync::dispatch($orderPackage->logistic_number); } }); $this->syncLogisticRouteJD(); } public function syncLogisticRouteJD() { ini_set('max_execution_time', 60); $query = OrderPackage::query() ->select(['logistic_number', 'order_id']) ->whereIn('order_id', function ($query) { $query->from('orders')->selectRaw('id')->whereIn('logistic_id', function ($builder) { $builder->from('logistics')->selectRaw('id')->where('type', '!=', '物流')->where('belong_company', '京东'); }); }); $query = $query->where('created_at', '>=', now()->subDays(20)) ->whereNull('received_at')->where('logistic_number', 'like', 'JD%'); $query->chunk(200, function ($orderPackages) { LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-JD", json_encode($orderPackages)); foreach ($orderPackages as $orderPackage) { if ($orderPackage && $orderPackage->logistic_number) LogisticAliJiSuSync::dispatch($orderPackage->logistic_number); } }); } public function syncLogisticRouteYTO() { ini_set('max_execution_time', 120); $query = OrderPackage::query() ->select(['logistic_number', 'order_id']) ->whereIn('order_id', function ($query) { $query->from('orders')->selectRaw('id')->whereIn('logistic_id', function ($builder) { $builder->from('logistics')->selectRaw('id')->where('type', '!=', '物流')->where('belong_company', '圆通'); }); }); $query = $query->where('sent_at', '>=', now()->subDays(20)) ->whereNull('received_at'); $query->chunk(1000, function ($orderPackages) { LogService::log(OrderPackageReceivedSyncService::class, "同步快递信息定时方法-YTO", json_encode($orderPackages)); foreach ($orderPackages as $orderPackage) { if ($orderPackage && $orderPackage->logistic_number) LogisticYTOSync::dispatch($orderPackage->logistic_number); } }); } /** * 根据传递的承运商与快递单号更新快递信息 * @param array $logisticNumbers 快递单号 * example: ['SF' => ['SF1038651915891', 'SF1038651413847', 'SF1038611050071'],'ZT'=>['75424148714142','548464120822', '75424147834290']....] * @throws Exception 快递接口调用或者返回的信息有误,无法更新指定的快递路由信息 */ public function syncLogisticRouteApi(array $logisticNumbers) { $this->update($this->getLogisticRoutes($logisticNumbers)); } /** * 获取快件揽收信息 * @param array $request [ * 'SF' => ['SF1038651915891', 'SF1038651413847', 'SF1038611050071'], * 'ZT'=>['75424148714142','548464120822', '75424147834290'] * ] * @return array * @throws Exception */ public function getLogisticRoutes(array $request): array { $this->logisticSFService = new LogisticSFService(); $resultSF = []; $resultYD = []; $resultYT = []; $resultOther = []; foreach ($request as $key => $logisticNums) { switch ($key) { case "SF": $resultSF = $this->logisticSFService->get($logisticNums); break; case "YD": $resultYD = []; break; case "YT": $resultYT = []; break; default: $resultOther = []; break; } } return array_merge($resultSF, $resultYD, $resultYT, $resultOther); } /** * 根据快递单号更新状态 * @param array $logisticResponses */ public function update(array $logisticResponses) { foreach ($logisticResponses as $logisticResponse) { if (empty($logisticResponse)) continue; $orderPackage = OrderPackage::query()->where('logistic_number', $logisticResponse['logistic_number'])->first(); try { if ($orderPackage->order && $orderPackage->order->issue) { unset($logisticResponse['exception_type']); unset($logisticResponse['exception']); } } catch (Exception $e) { LogService::log(OrderPackageReceivedSyncService::class, "标记问题件不需要更新异常状态失败", $logisticResponse['logistic_number'] . '-' . json_encode($e)); } //如果已经收货,将异常更新为正常 if ($logisticResponse['received_at']??false) { $logisticResponse['exception_type'] = '无'; $logisticResponse['exception'] = '否'; } if (isset($logisticResponse['status'])) $orderPackage->status = $logisticResponse['status']; if (isset($logisticResponse['received_at'])) $orderPackage->received_at = $logisticResponse['received_at']; if (isset($logisticResponse['exception'])) $orderPackage->exception = $logisticResponse['exception']; if (isset($logisticResponse['transfer_status']) && !empty($logisticResponse['transfer_status'])) $orderPackage->transfer_status = $logisticResponse['transfer_status']; if (isset($logisticResponse['exception_type'])) $orderPackage->exception_type = $logisticResponse['exception_type']; $orderPackage->save(); } } /** * 将orderPackage集合分类并摘取指定数据 * @param Collection $orderPackages * @return array */ private function buildData(Collection $orderPackages): array { $data = []; foreach ($orderPackages as $orderPackage) { try { $logisticCode = $orderPackage->order->logistic->code; } catch (Exception $e) { LogService::log(OrderPackageReceivedSyncService::class, "快递同步按照承运商分组异常", json_encode($orderPackage ?? [])); continue; } $key = config('api_logistic.logistic.' . $logisticCode); if (!isset($data[$key])) { $data[$key] = []; } $data[$key][] = $orderPackage->logistic_number; } return $data; } /** * @param array $data * @param $lastRouteDate * @return array */ public function setExceptionType(array $data, $lastRouteDate): array { //设置默认异常为否 $data['exception_type'] = '无'; $data['exception'] = '否'; $logistic_number = $data['logistic_number']; /** @var OrderPackage $orderPackage */ $orderPackage = OrderPackage::query()->with('order')->where('logistic_number', $logistic_number)->first(); $delivered_duration = now()->diffInHours(Carbon::parse($orderPackage['sent_at'])); $last_routed_duration = now()->diffInHours(Carbon::parse($lastRouteDate)); $VALID_HOURS = 4; $SHORT_RESPONSE_HOURS = (function ($province) { switch ($province) { case '浙江省': case '江苏省': case '上海': case '安徽省': return 24; case '北京': case '天津': case '江西省': case '湖北省': case '湖南省': case '广东省': case '福建省': case '山东省': case '河北省': case '河南省': case '山西省': case '四川省': case '陕西省': case '重庆': case '广西壮族自治区': case '贵州省': case '云南省': case '海南省': case '吉林省': case '黑龙江省': case '辽宁省': return 72; case '青海省': case '宁夏回族自治区': case '甘肃省': case '内蒙古自治区': case '新疆维吾尔自治区': case '西藏自治区': return 120; default: return 24; } })($orderPackage->order->province); $LONG_RESPONSE_HOURS = (function ($province) { switch ($province) { case '浙江省': case '江苏省': case '上海': case '安徽省': return 72; case '北京': case '天津': case '江西省': case '湖北省': case '湖南省': case '广东省': case '福建省': case '山东省': case '河北省': case '河南省': case '山西省': case '四川省': case '陕西省': case '重庆': case '广西壮族自治区': case '贵州省': case '云南省': case '海南省': case '吉林省': case '黑龙江省': case '辽宁省': return 120; case '青海省': case '宁夏回族自治区': case '甘肃省': case '内蒙古自治区': case '新疆维吾尔自治区': case '西藏自治区': return 168; default: return 72; } })($orderPackage->order->province); $SENDING_RESPONSE_HOURS = 48; $HAVEN_SECOND_GOT_HOURS = 24; $IS_ROUTED = 1; //0000 0001 有路由信息 $IS_IN_VALID_TIME = 2; //0000 0010 大于4小时 $IS_WEIGHED = 4; //0000 0100 称重过 $IS_RECEIVED = 8; //0000 1000 已经收货 $IS_SENDING = 16; //0001 0000 正在派送 $IS_SHORT_NO_RESPONSE = 32; //0010 0000 中转异常 $IS_LONG_NO_RESPONSE = 64; //0010 0000 疑似丢件 $IS_SENDING_NO_RESPONSE = 128; //0010 0000 派送异常 $IS_SECOND_ROUTE_HAVE = 256; //0100 0000 揽件异常 $conclusion = (function () use ( $data, $delivered_duration, $last_routed_duration, $VALID_HOURS, $IS_ROUTED, $IS_IN_VALID_TIME, $IS_WEIGHED, $IS_RECEIVED, $IS_SENDING, $IS_SHORT_NO_RESPONSE, $IS_LONG_NO_RESPONSE, $IS_SENDING_NO_RESPONSE, $SHORT_RESPONSE_HOURS, $LONG_RESPONSE_HOURS, $SENDING_RESPONSE_HOURS, $HAVEN_SECOND_GOT_HOURS, $IS_SECOND_ROUTE_HAVE, $orderPackage ) { $conclusion = 0; $conclusion |= !empty($data['transfer_status']) ? $IS_ROUTED : 0; $conclusion |= ($delivered_duration > $VALID_HOURS) ? $IS_IN_VALID_TIME : 0; $conclusion |= ($orderPackage->weighed_at) ? $IS_WEIGHED : 0; $conclusion |= ($data['status'] == '已收件') ? $IS_RECEIVED : 0; $conclusion |= ($data['status'] == '派送中') ? $IS_SENDING : 0;// $conclusion |= ($last_routed_duration > $SHORT_RESPONSE_HOURS && $last_routed_duration < $LONG_RESPONSE_HOURS) ? $IS_SHORT_NO_RESPONSE : 0; $conclusion |= ($last_routed_duration > $LONG_RESPONSE_HOURS) ? $IS_LONG_NO_RESPONSE : 0; $conclusion |= ($last_routed_duration > $SENDING_RESPONSE_HOURS && $data['status'] == '派送中') ? $IS_SENDING_NO_RESPONSE : 0; $conclusion |= ($delivered_duration > $HAVEN_SECOND_GOT_HOURS && $data['routes_length'] < 3) ? $IS_SECOND_ROUTE_HAVE : 0;//和出库时间比较 超过指定时间,路由信息小于三条 return $conclusion; })(); switch ($conclusion) { case $IS_IN_VALID_TIME: $data['exception_type'] = '疑似库内丢件'; break; case $IS_IN_VALID_TIME | $IS_WEIGHED: $data['exception_type'] = '揽件异常'; break; case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE: case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE | $IS_WEIGHED: $data['exception_type'] = '中转异常'; break; case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE: case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE | $IS_WEIGHED: $data['exception_type'] = '疑似丢件'; break; default: break; } if ($conclusion == ($conclusion | $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SENDING | $IS_SENDING_NO_RESPONSE)) { $data['exception_type'] = '派件异常'; } if ($conclusion == ($conclusion | $IS_SECOND_ROUTE_HAVE)) { $data['exception_type'] = '揽件异常'; $data['exception'] = '是'; } switch ($conclusion) { case $IS_IN_VALID_TIME: case $IS_IN_VALID_TIME | $IS_WEIGHED: case $IS_ROUTED | $IS_SHORT_NO_RESPONSE: case $IS_LONG_NO_RESPONSE: $data['exception'] = '是'; break; default: break; } return [ 'exception_type' => array_key_exists('exception_type', $data) ? $data['exception_type'] : null, 'exception' => array_key_exists('exception', $data) ? $data['exception'] : null, ]; } }