batchUpdate('order_packages', $params); } /** @var OrderTrackingService $orderTrackingService */ public $orderTrackingService; /** * @param string $logistic_number * @param array $values * @return OrderPackage $package */ public function firstOrCreate($logistic_number, array $values) { /** @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 OrderPackage::query()->with('order') ->whereIn('order_id', function ($query) use ($order_nos) { $query->from('orders')->select('id')->whereIn('code', $order_nos); })->get(); } public function create(array $params) { if (count($params) == 0) return null; try { $this->insert($params); app('LogService')->log(__METHOD__, __FUNCTION__, '批量生成 orderPackage' . count($params) . json_encode($params)); } catch (\Exception $e) { app('LogService')->log(__METHOD__, __FUNCTION__, '批量生成 orderPackage error ' . json_encode($params) . $e->getMessage() . $e->getTraceAsString()); } finally { $logistic_numbers = data_get($params, '*.logistic_number'); unset($params); return OrderPackage::query()->whereIn('logistic_number', $logistic_numbers)->get(); } } public function getByOrderNos($orderNos) { return OrderPackage::query()->with('order') ->whereIn('order_id', function ($query) use ($orderNos) { $query->from('orders')->select('id')->whereIn('code', $orderNos); })->get(); } 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, 5000); foreach ($inner_array as $params) { $bool = $this->insert($params); $bool ? LogService::log(__METHOD__, __FUNCTION__, '批量添加 OrderPackage ' . count($inner_params) . ' || ' . json_encode($inner_params)) : null; } } catch (\Exception $e) { LogService::log(__METHOD__, __FUNCTION__, '批量添加 OrderPackage error ' . count($inner_params) . ' || ' . json_encode($e->getMessage()) . json_encode($inner_params) . json_encode($e->getTraceAsString())); } } } public function getInnerParams($orderHeader, $order, $packages_maps, $logistic): array { /** * @var DataHandlerService $dataHandlerService */ $dataHandlerService = app('DataHandlerService'); $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; } foreach ($logistic_numbers as $logistic_number) { $package = $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) { /** * @var DataHandlerService $dataHandlerService * @var OrderTrackingService $orderTrackingService * @var OrderPackageCommoditiesService $orderPackageCommoditiesService */ $dataHandlerService = app(DataHandlerService::class); $orderPackageCommoditiesService = app('OrderPackageCommoditiesService'); $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') { $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 = $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 = $dataHandlerService->getKeyValue(['logistic_number' => $number], $packages_maps); if ($package ?? false) $packages[] = $package->id; } /** 删除 OrderPackage 和 OrderPackageCommodities*/ if (count($packages) == 0) return; try { $bool = OrderPackage::query()->whereIn('id', $packages)->delete(); $orderPackageCommodities = OrderPackageCommodities::query()->whereIn('order_package_id', $packages)->get(); $orderPackageCommoditiesService->deleteOrderCommodities($orderPackageCommodities); $bool ? LogService::log(__METHOD__, __FUNCTION__, '删除多余包裹 ids:' . json_encode($packages)) : null; } catch (\Exception $e) { LogService::log(__METHOD__, __FUNCTION__, '删除多余包裹 ids:' . json_encode($packages) . $e->getMessage()); } } private function updatePackage($orderHeaders, $packages) { $map = $this->getSentAtMap($orderHeaders); $update_params = []; $update_params[] = ['id', 'sent_at']; foreach ($packages as $package) { if ($package->sent_at) continue; try { $checktime = $map[$package->logistic_number]; } catch (\Exception $e) { continue; } if ($checktime) { $this->checkingAndProcess($package); //检查和处理揽收 $update_params[] = [ 'id' => $package->id, 'sent_at' => $checktime, ]; } } $this->batchUpdate($update_params); } /** * 检查和处理揽收 * * @param OrderPackage $package */ public function checkingAndProcess(OrderPackage $package) { //TODO 揽收与分配回馈时间不定 这里应该推进队列处理 //检查快递商来判断该快递商是否允许自动揽收发货 //检查揽收标记来判断是否已被揽收过 } /** * @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->checktime; } } return $map; } /** * 删除取消的订单 * @param $orderHeaders */ public function processCancelOrderPackages(&$orderHeaders) { $this->instant($this->orderTrackingService, 'OrderTrackingService'); $cancelOrder = $orderHeaders->filter(function ($orderHeader) { return $orderHeader->sostatus == '90'; }); $orderPackages = OrderPackage::query()->whereIn('order_id', function ($query) use ($cancelOrder) { /** @var Builder $query */ $query->from((new Order())->getTable())->selectRaw('id')->whereIn('code', data_get($cancelOrder, '*.orderno')); })->get(); if ($orderPackages->count() == 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 ($orderPackages) { /** @var Builder $query */ $query->from((new OrderPackageCommodities)->getTable())->selectRaw('id')->whereIn('order_package_id', data_get($orderPackages, '*.id')); })->get(); $this->orderTrackingService->deleteOrderTracings($items); } } /** * 中通一键揽收上传 * 由于中通接口只支持100条的操作,本接口支持100以上 * 如果中途调用中通接口发生异常,本方法不会停止,但在最后会返回错误信息,将执行失败的单号和原因返回 * @param $logistic_numbers array * @return array */ public function collectUpload(array $logistic_numbers = []): array { //参数校验 if (empty($logistic_numbers)) { return [ 'success' => false, 'message' => '输入快递单号为空', ]; } //根据环境获取中通接口参数 $url = env('APP_ENV') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.url') : config('api_logistic.collectUpload.ZTO.test.url'); $xAppKey = env('APP_ENV') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.x-appKey') : config('api_logistic.collectUpload.ZTO.test.x-appKey'); $appSecret = env('APP_ENV') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.appSecret') : config('api_logistic.collectUpload.ZTO.test.appSecret'); $appId = env('APP_ENV') === 'production' ? config('api_logistic.collectUpload.ZTO.prod.appId') : config('api_logistic.collectUpload.ZTO.test.appId'); //中通接口最大支持100条 $logistic_numbers_chunked = array_chunk($logistic_numbers, 100); //中通接口返回异常信息数组 $errorMessage = []; foreach ($logistic_numbers_chunked as $logistic_numbers_chunked_items) { $orderPackages = OrderPackage::query() ->select('weight', 'logistic_number') ->whereIn('logistic_number', $logistic_numbers_chunked_items)->get(); //中通接口请求body $collectUploadDTOS = []; foreach ($orderPackages as $orderPackage) { $collectUploadDTOS[] = [ 'billCode' => $orderPackage->logistic_number, 'weight' => $orderPackage->weight ?? 0, 'appId' => $appId, 'importDate' => now()->toDateTimeString(), ]; } try { //调用中通接口 $response = $this->sentReqToZOP($collectUploadDTOS, $appSecret, $xAppKey, $url); //接口返回的异常包装 $responseBody = json_decode($response->body()); if ($responseBody->statusCode === 'S210' ||//无权限 $responseBody->statusCode === 'PARAM_ERROR' ||//揽收上传信息为空 $responseBody->statusCode === 'SYSTEM_ERROR'//系统异常,请联系系统管理员 ) { //有异常包装 $errorMessage[] = [ 'status_code' => $responseBody->statusCode, 'message' => $responseBody->message, 'logistic_number' => $logistic_numbers_chunked_items, ]; } else { //没有异常将对应包裹标记为手动揽收 OrderPackage::query() ->select('weight', 'logistic_number') ->whereIn('logistic_number', $logistic_numbers_chunked_items)->update([ 'collecting_status' => 1, ]); } } catch (\Exception $e) { //调用异常,构建异常返回体 $errorMessage[] = [ 'status_code' => '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, ]; return Http::withHeaders($headers)->withBody($body, 'application/json')->post($url); } }