OrderDispatch.php 18 KB

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