DeliveryController.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <?php
  2. namespace hd\controllers;
  3. use biz\common\classes\HdNotifyClass;
  4. use Yii;
  5. use biz\ghs\classes\GhsClass;
  6. use bizGhs\express\classes\DeliveryAuthTokenClass;
  7. use bizHd\order\classes\OrderClass;
  8. use bizHd\express\classes\HdDeliveryOrderClass;
  9. use hd\models\delivery\CancelOrderForm;
  10. use hd\models\delivery\CreateOrderForm;
  11. use biz\shop\classes\ShopClass;
  12. use common\components\delivery\services\DispatchService;
  13. use common\components\util;
  14. use yii\helpers\ArrayHelper;
  15. class DeliveryController extends BaseController
  16. {
  17. public function actionList()
  18. {
  19. $platformList = [];
  20. $platformLogos = [
  21. 'dada' => ['name' => '达达', 'logo' => '📦'],
  22. 'shunfeng' => ['name' => '顺丰同城', 'logo' => '🚚'],
  23. 'meituan' => ['name' => '美团', 'logo' => '🍱'],
  24. 'fengniao' => ['name' => '蜂鸟', 'logo' => '🍜'],
  25. 'huolala' => ['name' => '货拉拉跑腿', 'logo' => '🚛'],
  26. 'shansong' => ['name' => '闪送', 'logo' => '⚡'],
  27. 'didi' => ['name' => '滴滴', 'logo' => '🚗'],
  28. 'uu' => ['name' => 'UU跑腿', 'logo' => '🛵']
  29. ];
  30. $mainId = $this->mainId;
  31. $allDeliveries = DeliveryAuthTokenClass::getAllByCondition(['mainId' => $mainId]);
  32. foreach ($allDeliveries as $d) {
  33. $item = [
  34. 'id' => $d['platform'],
  35. 'logo' => $platformLogos[$d['platform']]['logo'],
  36. 'name' => $platformLogos[$d['platform']]['name'],
  37. 'is_authorized' => true,
  38. 'balance' => $d['balance'] / 100.0,
  39. 'access_type' => 'oauth'
  40. ];
  41. $platformList[] = $item;
  42. }
  43. util::success(['platformList' => $platformList]);
  44. }
  45. // 花店发货时 -- 获取多个平台报价
  46. public function actionHdAllDeliveryQuotes()
  47. {
  48. $post = Yii::$app->request->post();
  49. $orderTime = $post['pickupTime'];
  50. $weight = $post['weight'];
  51. $remark = $post['remark'] ?? '';
  52. $adminId = $this->adminId;
  53. util::checkRepeatCommit($adminId, 3);
  54. $orderId = intval($post['orderId']);
  55. $order = OrderClass::getById($orderId);
  56. if (empty($order)) {
  57. util::fail('没有找到订单');
  58. }
  59. if ($order['mainId'] != $this->mainId) {
  60. util::fail('不是你的订单');
  61. }
  62. if (empty($order['address'])) {
  63. util::fail('客户地址缺失');
  64. }
  65. if (empty($order['long']) || empty($order['lat'])) {
  66. util::fail('客户地址信息缺失');
  67. }
  68. if (empty($order['receiveMobile'])) {
  69. util::fail('收货人手机为空');
  70. }
  71. if (empty($order['receiveUserName'])) {
  72. util::fail('收货人名称为空');
  73. }
  74. $shop = ShopClass::getById($this->shopId);
  75. $order['weight'] = $weight;
  76. $order['remark'] = $remark;
  77. $ds = new DispatchService($this->mainId);
  78. $platformQuotes = $ds->getAllPlatformPrice($order, $shop, $orderTime);
  79. if (isset($platformQuotes['error'])) {
  80. util::fail($platformQuotes['error']);
  81. }
  82. // 格式化各平台报价为前端展示格式
  83. $deliveryList = $ds->formatPlatformQuotesForDisplay($platformQuotes);
  84. // 按 price 从低到高排序
  85. ArrayHelper::multisort($deliveryList, 'price', SORT_ASC);
  86. $ret['deliveryList'] = $deliveryList;
  87. util::success($ret, "success");
  88. }
  89. // 花店快捷开单时 -- 获取各跑腿平台报价
  90. public function actionQuickOrderDeliveryQuotes()
  91. {
  92. $post = Yii::$app->request->post();
  93. $orderTime = date('Y-m-d H:i:s');
  94. // --------- $customId ----------
  95. $customId = $post['customId'];
  96. $custom = \bizHd\custom\classes\CustomClass::getById($customId, true);
  97. if (empty($custom)) {
  98. util::fail('获取custom数据失败');
  99. }
  100. // 生成随机订单号,不要跟原来的混起来,跑腿-销售单-
  101. $prefix = 'PT-XSD-' . $this->mainId . '-';
  102. $orderSn = $prefix . round(microtime(true) * 1000);
  103. //构建出 Order 数据
  104. $order = [
  105. 'orderSn' => $orderSn,
  106. 'customName' => $custom['name'],
  107. 'customMobile' => $custom['mobile'],
  108. 'fullAddress' => $custom['fullAddress'],
  109. 'floor' => $custom['floor'],
  110. 'dist' => $custom['dist'],
  111. 'lat' => $custom['lat'],
  112. 'long' => $custom['long'],
  113. 'address' => $custom['address'], //'toAddress' => $order['address'],
  114. 'city' => $custom['city'],
  115. 'weight' => $post['weight'] ?? 1,
  116. 'remark' => $post['remark'] ?? '',
  117. 'prePrice' => floatval($post['totalPrice'] ?? 0),
  118. 'actPrice' => floatval($post['totalPrice'] ?? 0),
  119. ];
  120. $shop = ShopClass::getById($this->shopId);
  121. $ds = new DispatchService($this->mainId);
  122. $ds->validateOrder($order); //对 $order 进行验证
  123. $platformQuotes = $ds->getAllPlatformPrice($order, $shop, $orderTime);
  124. if (isset($platformQuotes['error'])) {
  125. util::fail($platformQuotes['error']);
  126. }
  127. // 格式化各平台报价为前端展示格式
  128. $deliveryList = $ds->formatPlatformQuotesForDisplay($platformQuotes);
  129. // 按 price 从低到高排序
  130. ArrayHelper::multisort($deliveryList, 'price', SORT_ASC);
  131. $ret['deliveryList'] = $deliveryList;
  132. util::success($ret, "success");
  133. }
  134. // 花店向批发买花时 -- 获取各跑腿平台报价
  135. public function actionAllDeliveryQuotes()
  136. {
  137. $post = Yii::$app->request->post();
  138. $orderTime = date('Y-m-d H:i:s');
  139. // --------- $ghsShopId ----------
  140. $ghsId = $post['ghsId'] ?? 0;
  141. $ghs = GhsClass::getById($ghsId, true);
  142. if (empty($ghs)) {
  143. util::fail('获取批发商失败');
  144. }
  145. $ghsShopId = $ghs->shopId;
  146. // --------- $customId
  147. $customId = $ghs->customId ?? 0;
  148. $custom = \bizGhs\custom\classes\CustomClass::getById($customId, true); // 批发的 custom
  149. if (empty($custom)) {
  150. util::fail('获取custom数据失败');
  151. }
  152. // 生成随机订单号,不要跟原来的混起来,跑腿-销售单-
  153. $prefix = 'PT-XSD-' . $ghs->mainId . '-';
  154. $orderSn = $prefix . round(microtime(true) * 1000);
  155. //构建出 Order 数据
  156. $order = [
  157. 'orderSn' => $orderSn,
  158. 'customName' => $custom['name'],
  159. 'customMobile' => $custom['mobile'],
  160. 'fullAddress' => $custom['fullAddress'],
  161. 'floor' => $custom['floor'],
  162. 'dist' => $custom['dist'],
  163. 'lat' => $custom['lat'],
  164. 'long' => $custom['long'],
  165. 'address' => $custom['address'], //'toAddress' => $order['address'],
  166. 'city' => $custom['city'],
  167. 'weight' => $post['weight'] ?? 1,
  168. 'remark' => $post['remark'] ?? '',
  169. 'prePrice' => floatval($post['totalPrice'] ?? 0),
  170. 'actPrice' => floatval($post['totalPrice'] ?? 0),
  171. ];
  172. $ghsShop = ShopClass::getById($ghsShopId);
  173. $ds = new DispatchService($ghs->mainId);
  174. $ds->validateOrder($order); //对 $order 进行验证
  175. $platformQuotes = $ds->getAllPlatformPrice($order, $ghsShop, $orderTime);
  176. if (isset($platformQuotes['error'])) {
  177. util::fail($platformQuotes['error']);
  178. }
  179. $deliveryList = [];
  180. // 格式化各平台报价为前端展示格式
  181. $deliveryList = $ds->formatPlatformQuotesForDisplay($platformQuotes);
  182. // 按 price 从低到高排序
  183. ArrayHelper::multisort($deliveryList, 'price', SORT_ASC);
  184. $ret['deliveryList'] = $deliveryList;
  185. util::success($ret, "success");
  186. }
  187. // 创建订单(真实下单)
  188. public function actionCreateOrder()
  189. {
  190. $form = new CreateOrderForm();
  191. $form->loadAndValidate();
  192. $post = $form->getAttributes();
  193. $orderId = $post['orderId'] ?? 0; // xhOrder.id
  194. $params = $post['allParams'] ?? '';
  195. $platform = $params['en_name'] ?? '';// 平台英文名,$post['platform'] 是中文
  196. if (empty($platform)) {
  197. util::fail('请选择跑腿');
  198. }
  199. $pickupTime = $post['pickupTime'] ?? '';
  200. $params['mainId'] = $this->mainId;
  201. $params['ip'] = Yii::$app->request->userIP;
  202. $params['weight'] = $post['weight'] ?? 1;
  203. $params['remark'] = $post['remark'] ?? '';
  204. $params['total_price_fen'] = $post['price'] ?? '';
  205. $params['pickupTime'] = $pickupTime['value'] ?? '';
  206. $Order = OrderClass::getById($orderId, true);
  207. if (empty($Order)) {
  208. util::fail('没有找到订单');
  209. }
  210. if ($Order->mainId != $this->mainId) {
  211. util::fail('不是你的订单');
  212. }
  213. if (!in_array($Order['sendStatus'], [-4, -2, -1])) {
  214. Yii::error('订单已经发过跑腿');
  215. util::fail('订单已经发过跑腿');
  216. }
  217. //避免连续点击的重复提交 ssh
  218. $adminId = $this->adminId;
  219. util::checkRepeatCommit($adminId, 3);
  220. $ds = new DispatchService($this->mainId, $platform);
  221. $ret = $ds->createOrder($Order, 'xhOrder', $this->shopId, $params);
  222. if (isset($ret['code']) && $ret['code'] == 0) {
  223. $gdo = HdDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'orderId' => $ret['data']['orderId']]);
  224. if (empty($gdo)) {
  225. $data = [
  226. 'mainId' => $this->mainId,
  227. 'shopId' => $this->shopId,
  228. 'deliveryId' => $ret['platform'],
  229. 'hdOrderId' => $orderId,
  230. 'orderId' => $ret['data']['orderId'],
  231. 'distance' => $ret['data']['distance'],
  232. 'fee' => $ret['data']['fee'],
  233. 'orderStatus' => 0
  234. ];
  235. // createdAt 中保存了滴滴跑腿订单的后半截数据
  236. if($platform == 'didi'){
  237. $data['createdAt'] = date('Y-m-d H:i:s', $ret['data']['timeStamp']);
  238. }
  239. HdDeliveryOrderClass::add($data);
  240. } else {
  241. Yii::info('重复创建订单');
  242. }
  243. //更新订单
  244. $Order->sendDistance = $ret['data']['distance'];
  245. $Order->sendStatus = 0;
  246. $Order->deliveryId = $platform;
  247. $Order->sendType = 2;// 2指跑腿
  248. $Order->status = 3;// 3配送中
  249. $save = $Order->save();
  250. if ($save) {
  251. util::success($ret, "success");
  252. } else {
  253. util::fail('创建跑腿失败(更新订单失败)');
  254. }
  255. } else {
  256. if (isset($ret['msg'])) {
  257. util::fail($ret['msg']);
  258. }
  259. util::fail('创建跑腿失败');
  260. }
  261. }
  262. // 取消订单
  263. public function actionCancelOrder()
  264. {
  265. $form = new CancelOrderForm();
  266. $form->loadAndValidate();
  267. $post = $form->getAttributes();
  268. $deliveryOrderId = $post['orderId'] ?? 0; // 平台的orderId,
  269. $platform = $post['platform'] ?? '';
  270. $hdOrderId = $post['hdOrderId'];
  271. // 先获取配送订单,确保 $gdo 始终可用
  272. $hdo = HdDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'hdOrderId' => $hdOrderId, 'orderStatus' => ['not in', [-1, -2]]], true, 'id DESC');
  273. if (empty($hdo)) {
  274. util::fail('未找到对应订单: ' . $hdOrderId);
  275. }
  276. $orderId = $hdo->hdOrderId;
  277. // 如果没有提供平台和订单ID,从配送订单中获取
  278. if ($platform == '' || $deliveryOrderId == 0) {
  279. $deliveryOrderId = $hdo->orderId;
  280. $platform = $hdo->deliveryId;
  281. }
  282. $ds = new DispatchService($this->mainId, $platform);
  283. $cancelCostCent = 0;
  284. if ($platform == 'fengniao') {
  285. //请求订单预取消接口,获取是否可取消。如果运单状态不允许取消,则直接返回
  286. $cancelParams = ['order_id' => $deliveryOrderId, 'order_cancel_code' => $post['order_cancel_code']];
  287. $res = $ds->getAdapter()->preCancelOrder($cancelParams);
  288. if ($res['code'] != 0) {
  289. util::fail('运单状态不允许取消');
  290. }
  291. $cancelCostCent = $res['data']['actual_cancel_cost_cent']; //取消实际需金额(单位:分)
  292. }
  293. if ($platform == 'shunfeng') {
  294. //请求订单预取消接口,获取是否可取消。如果运单状态不允许取消,则直接返回
  295. $cancelParams = ['order_id' => $deliveryOrderId, 'order_cancel_code' => $post['order_cancel_code']];
  296. $res = $ds->getAdapter()->preCancelOrder($cancelParams);
  297. if ($res['code'] != 0) {
  298. util::fail('运单状态不允许取消');
  299. }
  300. $post['order_type'] = 1; //查询订单ID类型 1、顺丰订单号 2、商家订单号
  301. }
  302. if ($platform == 'dada') {
  303. $order = OrderClass::getById($orderId, false, 'id,orderSn');
  304. $deliveryOrderId = $order['orderSn'];
  305. }
  306. if ($platform == 'didi') {
  307. $order = OrderClass::getById($orderId, false, 'id,orderSn');
  308. $timeStamp = strtotime($hdo->createdAt);
  309. $post['outOrderNo'] = \common\components\delivery\platform\didi\Didi::generateUniOroder($order['orderSn'], $timeStamp);
  310. $post['cancelSource'] = 1; // TODO 要增加取消码判断
  311. }
  312. $post['order_cancel_role'] = 1; //1:商户取消, 2:用户取消
  313. $ret = $ds->cancelOrder($deliveryOrderId, $post);
  314. if ($ret['code'] == 0) {
  315. $Order = OrderClass::getById($hdOrderId, true, 'id, sendDistance, sendStatus, deliveryId');
  316. $Order->sendDistance = 0;
  317. if(in_array($platform, ['shunfeng', 'fengniao'])){ // 测试发现取消配送没回调的平台,统一在这主动更新配送状态
  318. $Order->sendStatus = -1;
  319. // 更新跑腿订单与批发订单状态
  320. $hdo->orderStatus = -1;
  321. HdNotifyClass::ptErrorNotify($Order->id, '门店主动取消');
  322. }
  323. $hdo->cancelCost = $ret['platform'] !== 'fengniao' ? $ret['data']['deductionFee'] : $cancelCostCent; // 由于蜂鸟平台返回的扣费不一样造成
  324. if (!$hdo->save()) {
  325. Yii::error('保存配送订单失败: ' . json_encode($hdo->errors));
  326. util::fail('保存配送订单失败');
  327. }
  328. $Order->status = 2;
  329. //$Order->deliveryId = '';
  330. //$Order->sendTip = 0; //零售xhOrder表没这个字段
  331. if (!$Order->save()) {
  332. Yii::error('保存批发订单失败: ' . json_encode($Order->errors));
  333. util::fail('保存批发订单失败');
  334. }
  335. util::success($ret['data'], "success");
  336. }
  337. util::fail('失败:'.$ret['msg']);
  338. }
  339. /**
  340. * 获取订单取消原因
  341. */
  342. public function actionCancelReason()
  343. {
  344. $post = Yii::$app->request->post();
  345. $hdOrderId = $post['hdOrderId'];
  346. $platform = $post['platform'];
  347. $hdo = HdDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'hdOrderId' => $hdOrderId, 'orderStatus!=' => -1], true);
  348. if (!empty($hdo)) {
  349. $orderId = $hdo->orderId;
  350. //$platform = $gdo->deliveryId;
  351. } else {
  352. util::fail('未找到对应订单: ' . $hdOrderId);
  353. }
  354. $ds = new DispatchService($this->mainId, $platform);
  355. $ret = $ds->getCancelReasonList($orderId);
  356. if ($ret['code'] == 0) {
  357. util::success($ret['data']);
  358. } else {
  359. Yii::error('获取订单取消原因:' . json_encode($ret));
  360. util::fail($ret['msg']);
  361. }
  362. }
  363. public function actionAddress()
  364. {
  365. $post = Yii::$app->request->post();
  366. $adminId = $this->adminId;
  367. util::checkRepeatCommit($adminId, 4);
  368. $orderId = intval($post['orderId']);
  369. $order = OrderClass::getById($orderId, false, 'fullAddress, long, lat');
  370. $shop = ShopClass::getById($this->shopId, false, 'fullAddress, long, lat');
  371. $ret = [
  372. "sender" => [
  373. 'name' => '门店',
  374. 'address' => $shop['fullAddress'],
  375. 'long' => $shop['long'],
  376. 'lat' => $shop['lat'],
  377. ],
  378. "receiver" => [
  379. 'name' => '客户',
  380. 'address' => $order['fullAddress'],
  381. 'long' => $order['long'],
  382. 'lat' => $order['lat'],
  383. ],
  384. ];
  385. util::success($ret, "success");
  386. }
  387. public function actionCancelAuth()
  388. {
  389. // 有平台有对应的取消授权接口,有的没有。
  390. // 有的就对接下,然后再删除数据。没有的直接删除数据就行。
  391. $platform = Yii::$app->request->get('platform');
  392. $delivery = DeliveryAuthTokenClass::getByCondition(['mainId' => $this->mainId, 'platform' => $platform]);
  393. if ($delivery) {
  394. if ($platform == 'shansong') {
  395. $auth = new \common\components\delivery\platform\shansong\Auth();
  396. $result = $auth->cancelAuthorization($delivery['accessToken']);
  397. if ($result['success']) {
  398. // 授权已取消
  399. } else {
  400. if ($result['message'] == '商户未授权,请授权后再次尝试') {
  401. Yii::info($this->mainId . '--商户未授权,请授权后再次尝试');
  402. } else {
  403. util::fail('取消授权失败:' . $result['message']);
  404. }
  405. }
  406. }
  407. } else {
  408. util::fail('取消授权失败,数据未找到');
  409. }
  410. $ret = DeliveryAuthTokenClass::deleteByCondition(['mainId' => $this->mainId, 'platform' => $platform]);
  411. if ($ret > 0) {
  412. util::success(['message' => '取消授权成功']);
  413. } else {
  414. util::fail('取消授权失败');
  415. }
  416. }
  417. public function actionGetAuthUrl()
  418. {
  419. $platform = Yii::$app->request->get('platformId');
  420. $Auth = new \common\components\delivery\services\AuthService();
  421. $mainId = $this->mainId;
  422. switch ($platform) {
  423. case 'shansong':
  424. $Auth->shansongAuth($mainId);
  425. break;
  426. case 'huolala':
  427. $Auth->huolalaAuth($mainId);
  428. break;
  429. case 'fengniao':
  430. $Auth->fengniaoAuth($mainId);
  431. break;
  432. case 'dada':
  433. $Auth->dadaAuth($mainId);
  434. break;
  435. case 'shunfeng':
  436. $Auth->shunfengAuth($mainId);
  437. break;
  438. case 'didi':
  439. break;
  440. default:
  441. util::error(-1, $platform . '不存在');
  442. break;
  443. }
  444. }
  445. }