logisticService = $logisticService; $this->dataHandlerService = $dataHandlerService; $this->orderTrackingService = $orderTrackingService; $this->orderPackageCommoditiesService = $orderPackageCommoditiesService; $this->batchUpdateService = $batchUpdateService; } public function batchUpdate(array $params) { return $this->batchUpdateService->batchUpdate('order_packages', $params); } /** * @param string $logistic_number * @param array $values * @return OrderPackage $package */ public function firstOrCreate($logistic_number, array $values): OrderPackage { /** @var $package OrderPackage */ $package = OrderPackage::query()->where('logistic_number', $logistic_number)->first(); if ($package) return $package; /** @var OrderService */ $order = app('OrderService')->logisticNumberFirstOrCreateOrder($logistic_number); if ($order) $values["order_id"] = $order->id; $values["logistic_number"] = $logistic_number; /** @var OrderPackage $package */ $package = OrderPackage::query()->create($values); return $package; } public function createExceptionPaginate($paginate) { return OrderPackage::query()->select('id', 'status', 'logistic_number', 'measuring_machine_id', 'weighed_at', 'weight', 'length', 'width', 'height', 'bulk', 'paper_box_id') ->where('status', '上传异常')->orWhere('status', '测量异常')->orderBy('created_at', 'DESC') ->paginate($paginate); } public function issueExceptionPaginate($paginate) { return OrderPackage::query()->select('id', 'logistic_number', 'created_at', 'batch_number', 'batch_rule') ->where('status', '下发异常')->orWhere('status', '记录异常') ->orWhere('uploaded_to_wms', '异常')->orderBy('created_at', 'DESC') ->paginate($paginate); } public function getByWmsOrders($orderHeaders) { $order_nos = data_get($orderHeaders, '*.orderno'); return $this->getByOrderNos($order_nos); } public function create(array $params) { if (count($params) == 0) return null; try { $this->insert($params); } catch (\Exception $e) { } finally { $logistic_numbers = data_get($params, '*.logistic_number'); unset($params); return OrderPackage::query()->whereIn('logistic_number', $logistic_numbers)->get(); } } public function getByOrderNos($orderNos) { $orderIds = []; $order_chunks = array_chunk($orderNos,150); foreach ($order_chunks as $order_chunk) { $ids = Order::query()->select('id')->whereIn('code',$order_chunk)->get()->map(function($order){ return $order->id; })->toArray(); $orderIds = array_merge($orderIds,$ids); } $orderPackages = null; $orderId_chunks = array_chunk($orderIds,150); foreach ($orderId_chunks as $orderId_chunk){ $items = OrderPackage::query()->with('order.logistic')->whereIn('order_id',$orderId_chunk)->get(); if ($orderPackages == null){ $orderPackages = $items; }else{ $orderPackages = $orderPackages->concat($items); } } return $orderPackages ?? new Collection(); } public function update($orderClientNo, $logisticNumber) { $order = Order::query()->with('packages.commodities.commodity')->where('client_code', $orderClientNo)->first(); if (!$order) { $order = Order::query()->create(['client_code' => $orderClientNo]); } $orderPackage = OrderPackage::query()->where('logistic_number', $logisticNumber)->first(); if ($orderPackage) { return compact('orderPackage', 'order'); } $orderPackage = OrderPackage::query()->create(['order_id' => $order->id, 'logistic_number' => $logisticNumber]); return compact('orderPackage', 'order'); } public function syncOrderPackage(&$orderHeaders) { $this->syncPackageByOrderHeaders($orderHeaders); } public function syncPackageByOrderHeaders(&$orderHeaders) { /** @var OrderService $orderService */ $orderService = app('OrderService'); if (!$orderHeaders) return; $orders = $orderService->getByWmsOrders($orderHeaders); $this->processCancelOrderPackages($orderHeaders); // 取消订单操作 及 过滤 $packages = $this->getByOrderNos(data_get($orderHeaders, '*.orderno')); // 已有 $this->createOrderPackage($orderHeaders, $orders, $packages); // 创建package $this->updatePackage($orderHeaders, $packages); $this->deleteUnnecessaryPackage($orderHeaders, $packages); // 删除package unset($orders, $packages); // 手动清除 } public function createOrderPackage($orderHeaders, $orders, $packages) { if (!$orderHeaders) return; /** * @var DataHandlerService $dataHandlerService * @var LogisticService $logsitcService */ $logisticService = app(LogisticService::class); $dataHandlerService = app(DataHandlerService::class); $orderHeaders_map = $dataHandlerService->dataHeader(['orderno'], $orderHeaders); $packages_maps = $dataHandlerService->dataHeader(['logistic_number'], $packages); $inner_params = []; /** 定制京东快递的订单 */ $logistic = $logisticService->getLogisticByCodes(['JDKD'])->first(); foreach ($orders as $order) { $orderHeader = $dataHandlerService->getKeyValue(['orderno' => $order->code], $orderHeaders_map); if (!$orderHeader) continue; if ($orderHeader->sostatus == 90) continue; $params = $this->getInnerParams($orderHeader, $order, $packages_maps, $logistic); $inner_params = array_merge($inner_params, $params); } /** 批量添加 */ if (count($inner_params) > 0) { try { $inner_array = array_chunk($inner_params, 200); foreach ($inner_array as $params) { $bool = $this->insert($params); } } catch (\Exception $e) { } } } public function getInnerParams($orderHeader, $order, $packages_maps, $logistic): array { $logistic_numbers = array_diff(array_unique(data_get($orderHeader->actAllocationDetails, '*.picktotraceid')), ['', '*']); $date = Carbon::now()->format('Y-m-d H:i:s'); $inner_params = []; $sentAtMap = []; if ($orderHeader['sostatus'] == '90') { if ($orderHeader['soreference5'] == '') $logistic_numbers = [$orderHeader['orderno']]; else $logistic_numbers = [$orderHeader['soreference5']]; } /** sent_at checktime */ foreach ($orderHeader->actAllocationDetails as $item) { $sentAtMap[$item->picktotraceid] = $item; } /** 承运商是京东时的定制操作 */ if ($order['logistic_id'] == $logistic['id'] && count($logistic_numbers) == 1) { $logistic_numbers = [$orderHeader['soreference5']]; $sentAtMap[$orderHeader['soreference5']] = $orderHeader->actAllocationDetails->first() ?? null; } /** 唯品顺丰速运承运商定制 */ if ($orderHeader['userdefine1'] === 'WPSFSY'){ $logistic_numbers = [$orderHeader['soreference5']]; $sentAtMap[$orderHeader['soreference5']] = $orderHeader->actAllocationDetails->first() ?? null; } foreach ($logistic_numbers as $logistic_number) { $package = $this->dataHandlerService->getKeyValue(['logistic_number' => $logistic_number], $packages_maps); if (isset($package)) continue; try { $data = $sentAtMap[$logistic_number]; } catch (\Exception $e) { $data = null; } $inner_params[] = [ 'order_id' => $order->id, 'logistic_number' => $logistic_number, 'created_at' => $date, 'updated_at' => $date, 'status' => '无', 'owner_id' => $order->owner_id, 'sent_at' => $data ? $data->checktime : null, ]; } return $inner_params; } public function deleteUnnecessaryPackage($orderHeaders, $packages) { $logistic_numbers = array(); foreach ($orderHeaders as $orderHeader) { if ($orderHeader['sostatus'] == '90') { if ($orderHeader['soreference5'] == '') $logistic_numbers[$orderHeader['orderno']] = $orderHeader['orderno']; else $logistic_numbers[$orderHeader['soreference5']] = $orderHeader['soreference5']; } elseif ($orderHeader['userdefine1'] == 'JDKD' || $orderHeader['userdefine1'] == 'WPSFSY') { $logistic_numbers[$orderHeader['soreference5']] = $orderHeader['soreference5']; } else { foreach ($orderHeader->actAllocationDetails as $actAllocationDetail) { $logistic_numbers[$actAllocationDetail['picktotraceid']] = $actAllocationDetail['picktotraceid']; } } } /** WMS快递单号唯一化 剔除 '',' ','*'*/ $logistic_numbers = array_unique(array_values($logistic_numbers)); $logistic_numbers = array_diff($logistic_numbers, ['', ' ', '*']); /** WAS数据库中已有的快递单号*/ $packages_maps = $this->dataHandlerService->dataHeader(['logistic_number'], $packages); $exits_number = data_get($packages, '*.logistic_number'); /** WMS快递单号 和 WAS的快递单号 的差集*/ $packages = array(); $diff_number = array_diff($exits_number, $logistic_numbers); /** 记录差集对应的OrderPackage的id*/ foreach ($diff_number as $number) { $package = $this->dataHandlerService->getKeyValue(['logistic_number' => $number], $packages_maps); if ($package ?? false) $packages[] = $package->id; } /** 删除 OrderPackage 和 OrderPackageCommodities*/ if (count($packages) == 0) return; $this->deleteIds($packages); } public function deleteIds($ids) { $deleteIds = array_chunk($ids,150); foreach ($deleteIds as $deleted_ids) { OrderPackage::query()->whereIn('id', $deleted_ids)->delete(); $orderPackageCommodities = OrderPackageCommodities::query()->whereIn('order_package_id', $deleted_ids)->get(); app('OrderPackageCommoditiesService')->deleteOrderCommodities($orderPackageCommodities); } } private function updatePackage($orderHeaders, $packages) { $map = $this->getSentAtMap($orderHeaders); $update_params = []; $update_params[] = ['id', 'sent_at']; foreach ($packages as $package) { if ($package->sent_at || !isset($map[$package->logistic_number])) continue; $allocation = $map[$package->logistic_number]; try { $checktime = $allocation->checktime; } catch (\Exception $e) { continue; } if ($checktime) { //EDISENDFLAG $this->checkingAndProcess($package); //检查和处理揽收 $update_params[] = [ 'id' => $package->id, 'sent_at' => $checktime, ]; } } $this->batchUpdate($update_params); } /** * 检查和处理揽收 * * @param OrderPackage|\stdClass $package */ public function checkingAndProcess(OrderPackage $package) { //校验快递商 订单状态 复核标记 揽收标记 if ($package->collecting_status == '1' || !$package->logistic_number)return; $package->loadMissing("order.logistic"); if (strpos($package->order->logistic->code ?? '','ZTO') === false)return; $statusMapping = array_flip(Order::STATUS); if (($statusMapping[$package->order->wms_status] ?? 90) == 90){ Log::warning("自动揽收跳出",["status"=>$package->order->wms_status]); return; } $tag = Cache::get(self::CACHE_COLLECT_FLAG,[]); if (isset($tag[$package->logistic_number])){ unset($tag[$package->logistic_number]); Cache::put(self::CACHE_COLLECT_FLAG, $tag, self::CACHE_COLLECT_FLAG_TTL); $package->update(["collecting_status"=>1]); }else dispatch(new PackageCollectingAllocation($package)); } /** * @param $orderHeaders * @return array */ private function getSentAtMap($orderHeaders): array { $map = []; foreach ($orderHeaders as $orderHeader) { $actAllocationDetails = $orderHeader->actAllocationDetails; foreach ($actAllocationDetails as $actAllocationDetail) { $logistic_number = $actAllocationDetail->picktotraceid; $map[$logistic_number] = $actAllocationDetail; } } return $map; } /** * 删除取消的订单 * @param $orderHeaders */ public function processCancelOrderPackages(&$orderHeaders) { $cancelOrder = $orderHeaders->filter(function ($orderHeader) { return $orderHeader->sostatus == '90'; }); $orderPackages = $this->getByWmsOrders($cancelOrder); if ($orderPackages == null || count($orderPackages) == 0) return; $orderPackage_list = $orderPackages->chunk(200); foreach ($orderPackage_list as $orderPackageSet) { OrderPackage::query()->whereIn('id', data_get($orderPackageSet, '*.id'))->delete(); $items = OrderTracking::query()->whereIn('order_package_commodity_id', function ($query) use ($orderPackageSet) { /** @var Builder $query */ $query->from((new OrderPackageCommodities)->getTable())->selectRaw('id')->whereIn('order_package_id', data_get($orderPackageSet, '*.id')); })->get(); $this->orderTrackingService->deleteOrderTracings($items); } } /** * 中通一键揽收 * 由于中通接口只支持100条的操作,本接口支持100以上 * 如果中途调用中通接口发生异常,本方法不会停止,但在最后会返回错误信息,将执行失败的单号和原因返回 * @param $logistic_numbers array * @return array */ public function collectUpload(array $logistic_numbers = []): array { $logistic_numbers = array_unique($logistic_numbers); //参数校验 if (empty($logistic_numbers)) { return [ 'success' => false, 'message' => '输入快递单号为空', ]; } //根据环境获取中通接口参数 list($url, $xAppKey, $appSecret, $appId) = $this->getZOPCollectUploadApiParameters(); //中通接口最大支持100条 $logistic_numbers_chunked = array_chunk($logistic_numbers, 100); //中通接口返回异常信息数组 $errorMessage = []; foreach ($logistic_numbers_chunked as $logistic_numbers_chunked_items) { //根据单号获取包裹信息 $collectUploadDTOS = $this->getCollectUploadDTOS($logistic_numbers_chunked_items, $appId); try { //调用中通接口 $response = $this->sentReqToZOP($collectUploadDTOS, $appSecret, $xAppKey, $url); //接口返回的异常包装 $responseBody = json_decode($response->body()); //异常的接口返回 $errorResponseBody = $responseBody->statusCode === 'S210' ||//无权限 $responseBody->statusCode === 'PARAM_ERROR' ||//揽收上传信息为空 $responseBody->statusCode === 'SYSTEM_ERROR';//系统异常,请联系系统管理员 if ($errorResponseBody) { //有异常包装 $errorMessage[] = [ 'status_code' => $responseBody->statusCode, 'message' => $responseBody->message, 'logistic_number' => $logistic_numbers_chunked_items, ]; } else { //没有异常将对应包裹标记为手动揽收 $this->setCollectingStatusOrCache($logistic_numbers_chunked_items, 1); } } catch (\Exception $e) { //调用异常,构建异常返回体 $errorMessage[] = [ 'status_code' => 'HTTP_REQUEST_ERROR', 'message' => [$e->getMessage()], 'logistic_number' => $logistic_numbers_chunked_items, ]; } } if (empty($errorMessage)) { return ['success' => true, 'message' => '一键揽收上传成功']; } else { return ['success' => false, 'message' => $errorMessage]; } } /** * 调用中通接口封装 * @param array $requestBody 请求体 * @param $appSecret * @param $xAppKey * @param $url * @return Response */ private function sentReqToZOP(array $requestBody, $appSecret, $xAppKey, $url): Response { $body = json_encode([ 'collectUploadDTOS' => $requestBody, ], JSON_UNESCAPED_UNICODE); $data_digest = base64_encode(md5($body . $appSecret, TRUE)); $headers = [ 'Content-Type' => 'application/json; charset=UTF-8', 'x-companyid' => $xAppKey, 'x-datadigest' => $data_digest, ]; Log::info("中通自动揽收", [json_encode($headers), $body]); return Http::withHeaders($headers)->withBody($body, 'application/json')->post($url); } /** * 根据环境获取中通一键揽收接口参数 * @return array */ private function getZOPCollectUploadApiParameters(): array { $url = config('app.env') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.url') : config('api_logistic.collectUpload.ZTO.test.url'); $xAppKey = config('app.env') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.x-appKey') : config('api_logistic.collectUpload.ZTO.test.x-appKey'); $appSecret = config('app.env') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.appSecret') : config('api_logistic.collectUpload.ZTO.test.appSecret'); $appId = config('app.env') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.appId') : config('api_logistic.collectUpload.ZTO.test.appId'); return array($url, $xAppKey, $appSecret, $appId); } /** * @param $logistic_numbers_chunked_items * @param $appId * @return array */ private function getCollectUploadDTOS($logistic_numbers_chunked_items, $appId): array { //中通接口请求body $collectUploadDTOS = []; foreach ($logistic_numbers_chunked_items as $logistic_number) { $collectUploadDTOS[] = [ 'billCode' => $logistic_number, 'weight' => self::ZT_COLLECT_UPLOAD_DEFAULT_WEIGHT, 'appId' => $appId, 'importDate' => now()->toDateTimeString(), ]; } return $collectUploadDTOS; } /** * 根据快递单号设置揽收状态 如果orderPackage暂时有未同步的数据,将结果放入缓存中 * @param array $logistic_numbers * @param int $status */ public function setCollectingStatusOrCache(array $logistic_numbers, int $status): void { OrderPackage::query() ->whereIn('logistic_number', $logistic_numbers) ->update([ 'collecting_status' => $status, ]); //更新成功的数据 $updated_logistic_numbers = OrderPackage::query() ->select('logistic_number') ->whereIn('logistic_number', $logistic_numbers) ->where('collecting_status', 1) ->pluck('logistic_number'); //没有更新成功的单号 $order_packages_missing_logistic_numbers = array_diff($logistic_numbers, $updated_logistic_numbers->toArray()); $order_packages_missing_logistic_numbers = array_fill_keys($order_packages_missing_logistic_numbers, true); //旧缓存的数据 $old_cache = Cache::get(self::CACHE_COLLECT_FLAG); //新缓存的数据 $new_cache = $order_packages_missing_logistic_numbers; //合并新旧缓存 if (!empty($old_cache)) { foreach ($old_cache as $key=>$item) { $new_cache[$key] = $item; } } Cache::put(self::CACHE_COLLECT_FLAG, $new_cache, self::CACHE_COLLECT_FLAG_TTL); } /** * 根据快递单号查询orderno * @param Collection $logisticNums * @return Collection */ public function getOrderCodesByLogisticNumbers(Collection $logisticNums): Collection { return OrderPackage::query() ->whereIn('logistic_number', $logisticNums) ->leftJoin('orders', 'order_id', '=', 'orders.id') ->select('orders.code') ->pluck('code'); } }