LogisticYDService.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?php
  2. namespace App\Services;
  3. use App\OrderPackage;
  4. use App\Traits\ServiceAppAop;
  5. use Carbon\Carbon;
  6. use Illuminate\Support\Facades\Http;
  7. class LogisticYDService
  8. {
  9. use ServiceAppAop;
  10. private $app_key;
  11. private $app_secret;
  12. private $url;
  13. /**
  14. * 批量订阅接口
  15. * @param $logistic_numbers array
  16. * @return mixed
  17. */
  18. public function registerApi(array $logistic_numbers)
  19. {
  20. $this->app_key = config('api_logistic.YD.prod.app-key', '999999');
  21. $this->app_secret = config('api_logistic.YD.prod.app-secret', '04d4ad40eeec11e9bad2d962f53dda9d');
  22. $this->url = config('api_logistic.YD.prod.register.url');
  23. $sender = [
  24. "address" => "上海市松江区泗泾镇泗砖公路351号",
  25. "city" => "上海市",
  26. "county" => "松江区",
  27. "name" => "施尧",
  28. "phone" => '13761413262',
  29. "province" => "上海市"
  30. ];
  31. $body = [
  32. "orders" => [],
  33. ];
  34. $order_packages = OrderPackage::query()
  35. ->with('order')
  36. ->whereIn('logistic_number', $logistic_numbers)->get();
  37. foreach ($order_packages as $order_package) {
  38. $order = $order_package->order;
  39. $body['orders'][] = [
  40. 'orderid' => $order->client_code,
  41. "mailno" => $order_package->logistic_number,
  42. "receiver" => [
  43. "address" => $order->address,
  44. "city" => $order->city,
  45. "county" => $order->district,
  46. "name" => $order->consignee_name,
  47. "phone" => $order->consignee_phone,
  48. "province" => $order->province
  49. ],
  50. "sender" => $sender
  51. ];
  52. }
  53. $json_body = json_encode($body, JSON_UNESCAPED_UNICODE);
  54. $sign = md5($json_body . '_' . $this->app_secret);
  55. $headers = [
  56. 'app-key' => $this->app_key,
  57. 'sign' => $sign,
  58. 'req-time' => now()->timestamp,
  59. "Content-Type" => "application/json"
  60. ];
  61. $response = Http::withHeaders($headers)->withBody($json_body, 'application/json')->post($this->url);
  62. return json_decode($response);
  63. }
  64. public function query($logistic_number)
  65. {
  66. $this->app_key = config('api_logistic.YD.prod.app-key', '999999');
  67. $this->app_secret = config('api_logistic.YD.prod.app-secret', '04d4ad40eeec11e9bad2d962f53dda9d');
  68. $this->url = config('api_logistic.YD.prod.search.url');
  69. $body = [
  70. "mailno" => $logistic_number
  71. ];
  72. $sign = md5(json_encode($body, JSON_UNESCAPED_UNICODE) . '_' . $this->app_secret);
  73. $headers = [
  74. 'app-key' => $this->app_key,
  75. 'sign' => $sign,
  76. 'req-time' => now()->timestamp,
  77. "Content-Type" => "application/json"
  78. ];
  79. $response = Http::withHeaders($headers)->withBody(json_encode($body, JSON_UNESCAPED_UNICODE), 'application/json')->post($this->url);
  80. return json_decode($response->body());
  81. }
  82. public function format($nativeResponse)
  83. {
  84. $result = [];
  85. if ($nativeResponse->code != '0000') {
  86. return [];
  87. } else {
  88. $nativeData = $nativeResponse->data;
  89. $result['logistic_number'] = $nativeData->mailno;
  90. $nativeRoutes = $nativeData->steps;
  91. if (!empty($nativeRoutes)) {
  92. $lastNativeRoute = $nativeRoutes[count($nativeRoutes) - 1];
  93. $result['status'] = $this->getStatus($nativeData);
  94. if ($result['status'] == '已收件') {
  95. $result['received_at'] = $lastNativeRoute->time;
  96. }
  97. $result['transfer_status'] = $this->getTransferStatus($nativeRoutes);
  98. $exceptionData = $this->setExceptionType($result, $lastNativeRoute ? $lastNativeRoute->time : null);
  99. $result['exception_type'] = $exceptionData['exception_type'];
  100. $result['exception'] = $exceptionData['exception'];
  101. } else {
  102. $result['status'] = null;
  103. $result['transfer_status'] = [];
  104. }
  105. if (!array_key_exists('status', $result)) {
  106. $result['status'] = null;
  107. $result['transfer_status'] = [];
  108. }
  109. //如果没有发现额外的异常,且查询到物流轨迹,将异常置为无
  110. if (!array_key_exists('exception', $result)
  111. && !array_key_exists('exception_type', $result)
  112. && array_key_exists('transfer_status', $result)
  113. ) {
  114. $result['exception_type'] = '无';
  115. $result['exception'] = '否';
  116. }
  117. return $result;
  118. }
  119. }
  120. /**
  121. * @param array $data
  122. * @param $lastRouteDate
  123. * @return array
  124. */
  125. private function setExceptionType(array $data, $lastRouteDate): array
  126. {
  127. $logistic_number = $data['logistic_number'];
  128. /** @var OrderPackage $orderPackage */
  129. $orderPackage = OrderPackage::query()->with('order')->where('logistic_number', $logistic_number)->first();
  130. $delivered_duration = now()->diffInHours(Carbon::parse($orderPackage['sent_at']));
  131. $last_routed_duration = now()->diffInHours(Carbon::parse($lastRouteDate));
  132. $VALID_HOURS = 4;
  133. $SHORT_RESPONSE_HOURS = (function ($province) {
  134. switch ($province) {
  135. case '浙江省':
  136. case '江苏省':
  137. case '上海':
  138. case '安徽省':
  139. return 24;
  140. case '北京':
  141. case '天津':
  142. case '江西省':
  143. case '湖北省':
  144. case '湖南省':
  145. case '广东省':
  146. case '福建省':
  147. case '山东省':
  148. case '河北省':
  149. case '河南省':
  150. case '山西省':
  151. case '四川省':
  152. case '陕西省':
  153. case '重庆':
  154. case '广西壮族自治区':
  155. case '贵州省':
  156. case '云南省':
  157. case '海南省':
  158. case '吉林省':
  159. case '黑龙江省':
  160. case '辽宁省':
  161. return 72;
  162. case '青海省':
  163. case '宁夏回族自治区':
  164. case '甘肃省':
  165. case '内蒙古自治区':
  166. case '新疆维吾尔自治区':
  167. case '西藏自治区':
  168. return 120;
  169. default:
  170. return 24;
  171. }
  172. })($orderPackage->order->province);
  173. $LONG_RESPONSE_HOURS = (function ($province) {
  174. switch ($province) {
  175. case '浙江省':
  176. case '江苏省':
  177. case '上海':
  178. case '安徽省':
  179. return 72;
  180. case '北京':
  181. case '天津':
  182. case '江西省':
  183. case '湖北省':
  184. case '湖南省':
  185. case '广东省':
  186. case '福建省':
  187. case '山东省':
  188. case '河北省':
  189. case '河南省':
  190. case '山西省':
  191. case '四川省':
  192. case '陕西省':
  193. case '重庆':
  194. case '广西壮族自治区':
  195. case '贵州省':
  196. case '云南省':
  197. case '海南省':
  198. case '吉林省':
  199. case '黑龙江省':
  200. case '辽宁省':
  201. return 120;
  202. case '青海省':
  203. case '宁夏回族自治区':
  204. case '甘肃省':
  205. case '内蒙古自治区':
  206. case '新疆维吾尔自治区':
  207. case '西藏自治区':
  208. return 168;
  209. default:
  210. return 72;
  211. }
  212. })($orderPackage->order->province);
  213. $SENDING_RESPONSE_HOURS = 48;
  214. $IS_ROUTED = 1; //0000 0001 有路由信息
  215. $IS_IN_VALID_TIME = 2; //0000 0010 大于4小时
  216. $IS_WEIGHED = 4; //0000 0100 称重过
  217. $IS_RECEIVED = 8; //0000 1000 已经收货
  218. $IS_SENDING = 16; //0001 0000 正在派送
  219. $IS_SHORT_NO_RESPONSE = 32; //0010 0000 中转异常
  220. $IS_LONG_NO_RESPONSE = 64; //0010 0000 疑似丢件
  221. $IS_SENDING_NO_RESPONSE = 128; //0010 0000 派送异常
  222. $conclusion = (function () use (
  223. $data, $delivered_duration, $last_routed_duration,
  224. $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,
  225. $SHORT_RESPONSE_HOURS, $LONG_RESPONSE_HOURS, $SENDING_RESPONSE_HOURS,
  226. $orderPackage
  227. ) {
  228. $conclusion = 0;
  229. $conclusion |= !empty($data['transfer_status']) ? $IS_ROUTED : 0;
  230. $conclusion |= ($delivered_duration > $VALID_HOURS) ? $IS_IN_VALID_TIME : 0;
  231. $conclusion |= ($orderPackage->weighed_at) ? $IS_WEIGHED : 0;
  232. $conclusion |= ($data['status'] == '已收件') ? $IS_RECEIVED : 0;
  233. $conclusion |= ($data['status'] == '派送中') ? $IS_SENDING : 0;//
  234. $conclusion |= ($last_routed_duration > $SHORT_RESPONSE_HOURS && $last_routed_duration < $LONG_RESPONSE_HOURS) ? $IS_SHORT_NO_RESPONSE : 0;
  235. $conclusion |= ($last_routed_duration > $LONG_RESPONSE_HOURS) ? $IS_LONG_NO_RESPONSE : 0;
  236. $conclusion |= ($last_routed_duration > $SENDING_RESPONSE_HOURS && $data['status'] == '派送中') ? $IS_SENDING_NO_RESPONSE : 0;
  237. return $conclusion;
  238. })();
  239. switch ($conclusion) {
  240. case $IS_IN_VALID_TIME:
  241. $data['exception_type'] = '疑似库内丢件';
  242. break;
  243. case $IS_IN_VALID_TIME | $IS_WEIGHED:
  244. $data['exception_type'] = '揽件异常';
  245. break;
  246. case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE:
  247. case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE | $IS_WEIGHED:
  248. $data['exception_type'] = '中转异常';
  249. break;
  250. case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE:
  251. case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE | $IS_WEIGHED:
  252. $data['exception_type'] = '疑似丢件';
  253. break;
  254. default:
  255. break;
  256. }
  257. if ($conclusion
  258. == ($conclusion | $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SENDING | $IS_SENDING_NO_RESPONSE)) {
  259. $data['exception_type'] = '派件异常';
  260. }
  261. switch ($conclusion) {
  262. case $IS_IN_VALID_TIME:
  263. case $IS_IN_VALID_TIME | $IS_WEIGHED:
  264. case $IS_ROUTED | $IS_SHORT_NO_RESPONSE:
  265. case $IS_LONG_NO_RESPONSE:
  266. $data['exception'] = '是';
  267. break;
  268. default:
  269. break;
  270. }
  271. return [
  272. 'exception_type' => array_key_exists('exception_type', $data) ? $data['exception_type'] : null,
  273. 'exception' => array_key_exists('exception', $data) ? $data['exception'] : null,
  274. ];
  275. }
  276. /**
  277. * @param $nativeData
  278. * @return string
  279. */
  280. private function getStatus($nativeData): string
  281. {
  282. $status = null;
  283. switch ($nativeData->status) {
  284. case 'GOT':
  285. $status = '已揽收';
  286. break;
  287. case 'TRANSIT':
  288. $status = '在途';
  289. break;
  290. case 'SIGNED':
  291. $status = '已收件';
  292. break;
  293. case 'RETURN':
  294. $status = '返回中';
  295. break;
  296. case 'SIGNFAIL':
  297. $status = '无';
  298. break;
  299. default:
  300. $status = '无';
  301. }
  302. return $status;
  303. }
  304. /**
  305. * @param $nativeRoutes
  306. * @return array
  307. */
  308. private function getTransferStatus($nativeRoutes): array
  309. {
  310. $transferStatus = [];
  311. foreach ($nativeRoutes as $nativeRoute) {
  312. $item = [];
  313. $item['accept_time'] = $nativeRoute->time;
  314. $item['accept_address'] = $nativeRoute->description;
  315. $item['remark'] = "";
  316. $transferStatus[] = $item;
  317. }
  318. return $transferStatus;
  319. }
  320. }