OrderDispatch.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <?php
  2. namespace addons\shopro\service\order;
  3. use app\admin\model\shopro\order\Order;
  4. use app\admin\model\shopro\order\OrderItem;
  5. use app\admin\model\shopro\order\Action as OrderAction;
  6. use app\admin\model\shopro\order\Express as OrderExpress;
  7. use app\admin\model\shopro\activity\Groupon as ActivityGroupon;
  8. use app\admin\model\shopro\activity\GrouponLog as ActivityGrouponLog;
  9. use app\admin\model\shopro\dispatch\Dispatch as DispatchModel;
  10. use app\admin\model\shopro\dispatch\DispatchAutosend;
  11. use addons\shopro\library\express\Express as ExpressLib;
  12. class OrderDispatch
  13. {
  14. public $order = null;
  15. public function __construct($params = [])
  16. {
  17. if (isset($params['order_id']) && !empty($params['order_id'])) {
  18. $this->order = Order::find($params['order_id']);
  19. if (!$this->order) {
  20. error_stop('订单不存在');
  21. }
  22. }
  23. }
  24. /**
  25. * 执行发货
  26. *
  27. * @return object
  28. */
  29. public function confirm($params)
  30. {
  31. $admin = auth_admin();
  32. $method = $params['method'] ?? '';
  33. if (!in_array($method, ['input', 'api', 'upload'])) {
  34. error_stop('请使用正确的发货方式');
  35. }
  36. if ($this->order->status !== Order::STATUS_PAID && !$this->order->isOffline($this->order)) {
  37. error_stop("该订单{$this->order->status_text},不能发货");
  38. }
  39. if ($this->order->apply_refund_status === Order::APPLY_REFUND_STATUS_APPLY) {
  40. error_stop("该订单已申请退款,暂不能发货");
  41. }
  42. switch ($method) {
  43. case 'api':
  44. list($orderItems, $express) = $this->doByApi($params);
  45. break;
  46. case 'input':
  47. list($orderItems, $express) = $this->doByInput($params);
  48. break;
  49. case 'upload':
  50. list($orderItems, $express) = $this->doByUpload($params);
  51. break;
  52. }
  53. // 添加包裹信息
  54. $orderExpress = OrderExpress::create([
  55. 'user_id' => $this->order->user_id,
  56. 'order_id' => $this->order->id,
  57. 'express_name' => $express['name'],
  58. 'express_code' => $express['code'],
  59. 'express_no' => $express['no'],
  60. 'method' => $method,
  61. 'driver' => $express['driver'] ?? null,
  62. 'ext' => $express['ext'] ?? null
  63. ]);
  64. // 修改订单商品发货状态
  65. foreach ($orderItems as $orderItem) {
  66. $orderItem->order_express_id = $orderExpress->id;
  67. $orderItem->dispatch_status = OrderItem::DISPATCH_STATUS_SENDED;
  68. $orderItem->ext = array_merge($orderItem->ext, ['send_time' => time()]); // item 发货时间
  69. $orderItem->save();
  70. OrderAction::add($this->order, $orderItem, $admin, 'admin', "商品{$orderItem->goods_title}已发货");
  71. //发货,冗余到bill表
  72. Db::name('bill')->where([
  73. 'table_name' => 'shopro_order',
  74. 'table_id' => $this->order->id,
  75. ])->update([
  76. 'status' => 2, //待收货
  77. ])
  78. }
  79. $this->subscribeExpressInfo($orderExpress);
  80. // 订单发货后
  81. $data = [
  82. 'order' => $this->order,
  83. 'items' => $orderItems,
  84. 'express' => $orderExpress,
  85. 'dispatch_type' => 'express',
  86. ];
  87. \think\Hook::listen('order_dispatch_after', $data);
  88. return $express;
  89. }
  90. // 手动发货
  91. private function doByInput($params)
  92. {
  93. $orderItems = $this->getDispatchOrderItems($params, 'express');
  94. $express = $params['express'] ?? null;
  95. if (empty($express['name']) || empty($express['code']) || empty($express['no']) || strpos($express['no'], '=') !== false) {
  96. error_stop('请输入正确的快递信息');
  97. }
  98. return [$orderItems, $express];
  99. }
  100. // API发货
  101. private function doByApi($params)
  102. {
  103. $orderItems = $this->getDispatchOrderItems($params, 'express');
  104. $sender = $params['sender'] ?? null;
  105. $expressLib = new ExpressLib();
  106. $data = [
  107. 'order' => $this->order,
  108. 'sender' => $sender,
  109. 'consignee' => $this->order->address
  110. ];
  111. $express = $expressLib->eOrder($data, $orderItems);
  112. return [$orderItems, $express];
  113. }
  114. // 上传发货模板发货 TODO: 如果发货单比较多,循环更新可能会比较慢,考虑解析完模版信息以后,把数据返回前端,再次执行批量发货流程
  115. private function doByUpload($params)
  116. {
  117. $orderItems = $this->getDispatchOrderItems($params, 'express');
  118. $express = $params['express'] ?? null;
  119. if (empty($express['name']) || empty($express['code']) || empty($express['no'])) {
  120. error_stop('请输入正确的快递信息');
  121. }
  122. return [$orderItems, $express];
  123. }
  124. // 获取可发货的订单商品
  125. public function getDispatchOrderItems($params = null, $dispatch_type = 'express')
  126. {
  127. $orderItemIds = $params['order_item_ids'] ?? [];
  128. $whereCanDispatch['order_id'] = $this->order->id;
  129. $whereCanDispatch['dispatch_status'] = OrderItem::DISPATCH_STATUS_NOSEND;
  130. $whereCanDispatch['aftersale_status'] = ['<>', OrderItem::AFTERSALE_STATUS_ING];
  131. $whereCanDispatch['refund_status'] = OrderItem::REFUND_STATUS_NOREFUND;
  132. $whereCanDispatch['dispatch_type'] = $dispatch_type;
  133. if (empty($orderItemIds)) {
  134. $orderItems = OrderItem::where($whereCanDispatch)->select();
  135. } else {
  136. $orderItems = OrderItem::where('id', 'in', $orderItemIds)->where($whereCanDispatch)->select();
  137. if (count($orderItems) !== count($orderItemIds)) {
  138. error_stop('选中商品暂不能发货');
  139. }
  140. }
  141. if (!$orderItems) {
  142. error_stop('该订单无可发货商品');
  143. }
  144. return $orderItems;
  145. }
  146. /**
  147. * 取消发货
  148. *
  149. */
  150. public function cancel($params)
  151. {
  152. $admin = auth_user();
  153. $order_express_id = $params['order_express_id'] ?? 0;
  154. $orderExpress = OrderExpress::where('id', $order_express_id)->find();
  155. if (!$orderExpress) {
  156. error_stop('未找到发货单');
  157. }
  158. // 1.检测是不是用api发的 有些快递不支持取消接口 所以不判断了,统一手动取消
  159. // if ($orderExpress->method === 'api') {
  160. // // TODO: 走取消运单接口
  161. // $expressLib = new ExpressLib();
  162. // $data = [
  163. // 'express_no' => $orderExpress['express_no'],
  164. // 'express_code' => $orderExpress['express_code'],
  165. // 'order_code' => $orderExpress['ext']['Order']['OrderCode']
  166. // ];
  167. // $express = $expressLib->cancel($data);
  168. // }
  169. // 2. 变更发货状态
  170. $orderItems = OrderItem::where([
  171. 'order_id' => $this->order->id,
  172. 'order_express_id' => $orderExpress->id
  173. ])->where('dispatch_type', 'express')->select();
  174. foreach ($orderItems as $orderItem) {
  175. $orderItem->order_express_id = 0;
  176. $orderItem->dispatch_status = OrderItem::DISPATCH_STATUS_NOSEND;
  177. $orderItem->save();
  178. OrderAction::add($this->order, null, $admin, 'admin', "已取消发货");
  179. }
  180. // 删除发货单
  181. $orderExpress->delete();
  182. return true;
  183. }
  184. /**
  185. * 修改发货信息
  186. *
  187. */
  188. public function change($params)
  189. {
  190. $admin = auth_user();
  191. $order_express_id = $params['order_express_id'] ?? 0;
  192. $orderExpress = OrderExpress::where('id', $order_express_id)->find();
  193. if (!$orderExpress) {
  194. error_stop('未找到发货单');
  195. }
  196. // 1.1 检测是不是用api发的 如果是则提醒取消运单再重新走发货流程 此时什么都不用做
  197. if ($orderExpress->method === 'api') {
  198. error_stop('该发货单已被推送第三方平台,请取消后重新发货');
  199. }
  200. // 1.2 如果不是则手动变更运单信息(快递公司、运单号)
  201. $express = $params['express'] ?? null;
  202. if (empty($express['name']) || empty($express['code']) || empty($express['no']) || strpos($express['no'], '=') !== false) {
  203. error_stop('请输入正确的快递信息');
  204. }
  205. $orderExpress->save([
  206. 'express_name' => $express['name'],
  207. 'express_code' => $express['code'],
  208. 'express_no' => $express['no'],
  209. 'method' => 'input'
  210. ]);
  211. OrderAction::add($this->order, null, $admin, 'admin', "变更发货信息");
  212. $this->subscribeExpressInfo($orderExpress);
  213. // 修改发货信息
  214. $data = [
  215. 'order' => $this->order,
  216. 'express' => $orderExpress,
  217. 'dispatch_type' => 'express',
  218. ];
  219. \think\Hook::listen('order_dispatch_change', $data);
  220. return $express;
  221. }
  222. // 解析批量发货信息,筛选出能发货的订单
  223. public function multiple($params)
  224. {
  225. // 上传发货模板
  226. if (!empty($params['file'])) {
  227. $express = $params['express'];
  228. $file = $params['file']->getPathname();
  229. $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
  230. $PHPExcel = $reader->load($file);
  231. $currentSheet = $PHPExcel->getSheet(0); //读取文件中的第一个工作表
  232. $allRow = $currentSheet->getHighestRow(); //取得一共有多少行
  233. if ($allRow <= 2) {
  234. error_stop('您的发货列表为空');
  235. }
  236. $orderExpressMap = [];
  237. $orderId = 0;
  238. $orderSn = "";
  239. for ($currentRow = 2; $currentRow <= $allRow - 1; $currentRow++) {
  240. $orderId = $currentSheet->getCellByColumnAndRow(1, $currentRow)->getValue() ?? $orderId;
  241. $orderSn = $currentSheet->getCellByColumnAndRow(2, $currentRow)->getValue() ?? $orderSn;
  242. $orderItemId = $currentSheet->getCellByColumnAndRow(8, $currentRow)->getValue();
  243. if (empty($orderItemId)) {
  244. error_stop("发货单格式不正确");
  245. }
  246. $orderExpressNo = $currentSheet->getCellByColumnAndRow(15, $currentRow)->getValue();
  247. if (empty($orderExpressNo)) {
  248. error_stop("请填写 订单ID-{$orderId} 的运单号");
  249. }
  250. $orderExpressMap["$orderId.$orderSn"][$orderExpressNo][] = $orderItemId;
  251. }
  252. $list = [];
  253. foreach ($orderExpressMap as $orderFlag => $orderExpress) {
  254. foreach ($orderExpress as $expressNo => $orderItemIds) {
  255. $order = explode('.', $orderFlag);
  256. $list[] = [
  257. 'order_id' => $order[0],
  258. 'order_sn' => $order[1],
  259. 'order_item_ids' => $orderItemIds,
  260. 'express' => [
  261. 'name' => $express['name'],
  262. 'code' => $express['code'],
  263. 'no' => $expressNo
  264. ]
  265. ];
  266. }
  267. }
  268. return $list;
  269. } else {
  270. $list = [];
  271. $orderIds = $params['order_ids'] ?? [];
  272. if (empty($orderIds)) {
  273. error_stop('请选择发货订单');
  274. }
  275. foreach ($orderIds as $orderId) {
  276. $list[] = [
  277. 'order_id' => $orderId,
  278. 'order_sn' => Order::where('id', $orderId)->value('order_sn'),
  279. 'order_item_ids' => $this->getDispatchOrderItemIds($orderId)
  280. ];
  281. }
  282. return $list;
  283. }
  284. }
  285. // 获取可发货的订单商品
  286. private function getDispatchOrderItemIds($orderId)
  287. {
  288. $whereCanDispatch = [
  289. 'order_id' => $orderId,
  290. 'dispatch_status' => OrderItem::DISPATCH_STATUS_NOSEND,
  291. 'aftersale_status' => ['neq', OrderItem::AFTERSALE_STATUS_ING],
  292. 'refund_status' => OrderItem::REFUND_STATUS_NOREFUND,
  293. 'dispatch_type' => 'express'
  294. ];
  295. $orderItems = OrderItem::where($whereCanDispatch)->column('id');
  296. return $orderItems;
  297. }
  298. // 订阅物流追踪
  299. private function subscribeExpressInfo($orderExpress)
  300. {
  301. try {
  302. $expressLib = new ExpressLib();
  303. $a = $expressLib->subscribe([
  304. 'express_code' => $orderExpress['express_code'],
  305. 'express_no' => $orderExpress['express_no']
  306. ]);
  307. } catch (\Exception $e) {
  308. // Nothing TODO
  309. return;
  310. }
  311. }
  312. /**
  313. * 手动发货
  314. *
  315. * @param array $params
  316. * @return void
  317. */
  318. public function customDispatch($params)
  319. {
  320. $admin = auth_admin();
  321. $custom_type = $params['custom_type'] ?? 'text';
  322. $custom_content = $params['custom_content'] ?? ($custom_type == 'text' ? '' : []);
  323. if ($this->order->status !== Order::STATUS_PAID && !$this->order->isOffline($this->order)) {
  324. error_stop("该订单{$this->order->status_text},不能发货");
  325. }
  326. if ($this->order->apply_refund_status === Order::APPLY_REFUND_STATUS_APPLY) {
  327. error_stop("该订单已申请退款,暂不能发货");
  328. }
  329. // 获取手动发货的 items
  330. $orderItems = $this->getDispatchOrderItems($params, 'custom');
  331. $customExt = [ // 手动发货信息
  332. 'dispatch_content_type' => $custom_type,
  333. 'dispatch_content' => $custom_content
  334. ];
  335. // 修改订单商品发货状态
  336. foreach ($orderItems as $orderItem) {
  337. $orderItem->dispatch_status = OrderItem::DISPATCH_STATUS_SENDED;
  338. $orderItem->ext = array_merge($orderItem->ext, $customExt, ['send_time' => time()]); // item 发货时间
  339. $orderItem->save();
  340. OrderAction::add($this->order, $orderItem, $admin, 'admin', "商品{$orderItem->goods_title}已发货");
  341. }
  342. // 订单发货后
  343. $data = [
  344. 'order' => $this->order,
  345. 'items' => $orderItems,
  346. 'express' => null,
  347. 'dispatch_type' => 'custom',
  348. ];
  349. \think\Hook::listen('order_dispatch_after', $data);
  350. }
  351. /**
  352. * 拼团完成时触发检测自动发货
  353. *
  354. * @return bool
  355. */
  356. public function grouponCheckDispatchAndSend()
  357. {
  358. $this->systemCheckAutoSend();
  359. return true;
  360. }
  361. /**
  362. * 普通商品自动发货
  363. *
  364. * @return bool
  365. */
  366. public function checkDispatchAndSend()
  367. {
  368. // 拼团不自动发货,等成团完成才发货
  369. $orderExt = $this->order['ext'];
  370. $buy_type = ($orderExt && isset($orderExt['buy_type'])) ? $orderExt['buy_type'] : '';
  371. if ($this->order['activity_type'] && strpos($this->order['activity_type'], 'groupon') !== false && $buy_type == 'groupon') {
  372. return true; // 这里不对拼团的订单进行自动发货,等拼团成功在检测
  373. }
  374. // 检测需要自动发货的 item
  375. $this->systemCheckAutoSend();
  376. return true;
  377. }
  378. /**
  379. * 系统检测自动发货
  380. */
  381. private function systemCheckAutoSend()
  382. {
  383. $autosendItems = [];
  384. // 判断订单是否有需要发货的商品,并进行自动发货(autosend)
  385. foreach ($this->order->items as $key => $item) {
  386. // 判断不是未发货状态,或者退款完成,continue
  387. if (
  388. $item['dispatch_status'] == OrderItem::DISPATCH_STATUS_NOSEND
  389. && $item['aftersale_status'] != OrderItem::AFTERSALE_STATUS_ING
  390. && $item['refund_status'] == OrderItem::REFUND_STATUS_NOREFUND
  391. ) {
  392. // 订单可以发货
  393. switch ($item['dispatch_type']) {
  394. case 'autosend':
  395. // 自动发货
  396. $autosendItems[] = $item;
  397. }
  398. }
  399. }
  400. if ($autosendItems) {
  401. $this->autoSendItems($autosendItems, ['oper_type' => 'system']);
  402. }
  403. }
  404. /**
  405. * 当前订单需要自动发货的所有商品
  406. *
  407. * @param object|array $items
  408. * @return void
  409. */
  410. private function autoSendItems($items)
  411. {
  412. foreach ($items as $item) {
  413. $autosendExt = $this->getAutosendContent($item);
  414. $item->dispatch_status = OrderItem::DISPATCH_STATUS_SENDED;
  415. $item->ext = array_merge($item->ext, $autosendExt, ['send_time' => time()]); // item 发货时间
  416. $item->save();
  417. OrderAction::add($this->order, $item, null, 'system', "商品{$item->goods_title}已发货");
  418. }
  419. $data = [
  420. 'order' => $this->order,
  421. 'items' => $items,
  422. 'express' => null,
  423. 'dispatch_type' => 'custom',
  424. ];
  425. // 发货后事件,消息通知
  426. \think\Hook::listen('order_dispatch_after', $data);
  427. }
  428. /**
  429. * 获取商品的自动发货模板数据
  430. *
  431. * @param object|array $item
  432. * @return array
  433. */
  434. private function getAutosendContent($item)
  435. {
  436. // 获取配送模板
  437. $result = [];
  438. $dispatch = DispatchModel::with([$item['dispatch_type']])->show()->where('type', $item['dispatch_type'])->where('id', $item['dispatch_id'])->find();
  439. if ($dispatch && $dispatch->autosend) {
  440. $autosend = $dispatch->autosend;
  441. if (in_array($autosend['type'], ['text', 'params'])) {
  442. $result['dispatch_content_type'] = $autosend['type'];
  443. $result['dispatch_content'] = $autosend['content'];
  444. }
  445. }
  446. return $result;
  447. }
  448. }