Order.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. <?php
  2. namespace app\admin\controller\shopro\order;
  3. use addons\shopro\library\Export;
  4. use addons\shopro\model\Config as ModelConfig;
  5. use app\admin\model\shopro\order\OrderExpress;
  6. use app\admin\model\shopro\order\Verify;
  7. use think\Db;
  8. use think\Config;
  9. use app\common\controller\Backend;
  10. use app\admin\controller\shopro\Base;
  11. use think\exception\PDOException;
  12. use think\exception\ValidateException;
  13. use Exception;
  14. use app\admin\model\shopro\order\OrderItem;
  15. /**
  16. * 订单管理
  17. *
  18. * @icon fa fa-circle-o
  19. */
  20. class Order extends Base
  21. {
  22. protected $noNeedRight = ['getType', 'getExpress'];
  23. /**
  24. * Order模型对象
  25. * @var \app\admin\model\shopro\order\Order
  26. */
  27. protected $model = null;
  28. public function _initialize()
  29. {
  30. parent::_initialize();
  31. // 手动加载语言包
  32. $this->loadlang('shopro/order/order_item');
  33. $this->loadlang('shopro/dispatch/dispatch');
  34. $this->loadlang('shopro/goods/goods');
  35. $this->model = new \app\admin\model\shopro\order\Order;
  36. $this->storeModel = new \app\admin\model\shopro\store\Store;
  37. $this->view->assign("typeList", $this->model->getTypeList());
  38. $this->view->assign("statusList", $this->model->getStatusList());
  39. $this->view->assign("payTypeList", $this->model->getPayTypeList());
  40. $this->view->assign("platformList", $this->model->getPlatformList());
  41. }
  42. /**
  43. * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
  44. * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
  45. * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
  46. */
  47. /**
  48. * 查看列表
  49. */
  50. public function index()
  51. {
  52. //当前是否为关联查询
  53. $this->relationSearch = true;
  54. //设置过滤方法
  55. $this->request->filter(['strip_tags', 'trim']);
  56. if ($this->request->isAjax()) {
  57. //如果发送的来源是Selectpage,则转发到Selectpage
  58. if ($this->request->request('keyField')) {
  59. return $this->selectpage();
  60. }
  61. $nobuildfields = ['status', 'aftersale_sn', 'dispatch_type', 'goods_type', 'nickname', 'user_phone', 'goods_title', 'store_id'];
  62. list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(null, $nobuildfields);
  63. $total = $this->buildSearchOrder()
  64. ->where($where)
  65. ->removeOption('soft_delete')
  66. ->order($sort, $order)
  67. ->count();
  68. $list = $this->buildSearchOrder()
  69. ->where($where)
  70. ->with(['user', 'item'])
  71. ->order($sort, $order)
  72. ->limit($offset, $limit)
  73. ->select();
  74. $list = collection($list)->toArray();
  75. $items = [];
  76. foreach ($list as $key => $od) {
  77. // 处理 未支付订单 的 订单 item status_code 状态
  78. $list[$key] = $this->model->setOrderItemStatusByOrder($od);
  79. $items = array_merge($items, $od['item']);
  80. }
  81. $store_ids = array_unique(array_column($items, 'store_id'));
  82. $stores = $this->storeModel->where('id', 'in', $store_ids)->select();
  83. $stores = collection($stores)->toArray();
  84. $stores = array_column($stores, null, 'id');
  85. foreach ($list as $key => $od) {
  86. foreach ($od['item'] as $k => $it) {
  87. $list[$key]['item'][$k]['store'] = $stores[$it['store_id']] ?? null;
  88. }
  89. }
  90. $result = array("total" => $total, "rows" => $list);
  91. return $this->success('操作成功', null, $result);
  92. }
  93. return $this->view->fetch();
  94. }
  95. // 订单导出
  96. public function export()
  97. {
  98. $nobuildfields = ['status', 'aftersale_sn', 'dispatch_type', 'goods_type', 'nickname', 'user_phone', 'goods_title'];
  99. list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(null, $nobuildfields);
  100. $expCellName = [
  101. 'order_id' => 'Id',
  102. 'order_sn' => '订单号',
  103. 'type_text' => '订单类型',
  104. 'status_text' => '订单状态',
  105. 'pay_type_text' => '支付类型',
  106. 'paytime_text' => '支付时间',
  107. 'platform_text' => '交易平台',
  108. 'user_nickname' => '用户姓名',
  109. 'user_phone' => '手机号',
  110. 'store_info' => '门店信息',
  111. 'total_amount' => '订单总金额',
  112. 'discount_fee' => '优惠金额',
  113. 'pay_fee' => '实际支付金额',
  114. 'score_fee' => '积分支付数量',
  115. 'consignee_info' => '收货信息',
  116. 'remark' => '用户备注',
  117. 'activity_type_text' => '营销类型',
  118. 'goods_title' => '商品名称',
  119. 'goods_original_price' => '商品原价',
  120. 'goods_price' => '商品价格',
  121. 'goods_sku_text' => '商品规格',
  122. 'goods_num' => '购买数量',
  123. 'dispatch_status_text' => '发货状态',
  124. 'dispatch_fee' => '发货费用',
  125. 'dispatch_type_text' => '发货方式',
  126. 'aftersale_refund' => '售后/退款',
  127. 'comment_status_text' => '评价状态',
  128. 'refund_fee' => '退款金额',
  129. 'refund_msg' => '退款原因',
  130. 'express_name' => '快递公司',
  131. 'express_no' => '快递单号',
  132. ];
  133. $export = new Export();
  134. $spreadsheet = null;
  135. $sheet = null;
  136. $total = $this->buildSearchOrder()->where($where)->order($sort, $order)->count();
  137. $current_total = 0; // 当前已循环条数
  138. $page_size = 2000;
  139. $total_page = intval(ceil($total / $page_size));
  140. $newList = [];
  141. $total_amount = 0; // 订单总金额
  142. $discount_fee = 0; // 优惠总金额
  143. $pay_fee = 0; // 实际支付总金额
  144. $score_fee = 0; // 支付总积分
  145. if ($total == 0) {
  146. $this->error('导出数据为空');
  147. }
  148. for ($i = 0; $i < $total_page; $i++) {
  149. $page = $i + 1;
  150. $is_last_page = ($page == $total_page) ? true : false;
  151. $list = $this->buildSearchOrder()
  152. ->where($where)
  153. ->with(['user', 'item'])
  154. ->order($sort, $order)
  155. ->limit(($i * $page_size), $page_size)
  156. ->select();
  157. $list = collection($list)->toArray();
  158. $items = [];
  159. foreach ($list as $key => $od) {
  160. // 处理 未支付订单 的 订单 item status_code 状态
  161. $list[$key] = $this->model->setOrderItemStatusByOrder($od);
  162. $items = array_merge($items, $od['item']);
  163. }
  164. $store_ids = array_unique(array_column($items, 'store_id'));
  165. $stores = $this->storeModel->where('id', 'in', $store_ids)->select();
  166. $stores = collection($stores)->toArray();
  167. $stores = array_column($stores, null, 'id');
  168. foreach ($list as $key => $od) {
  169. foreach ($od['item'] as $k => $it) {
  170. $list[$key]['item'][$k]['store'] = $stores[$it['store_id']] ?? null;
  171. }
  172. }
  173. $newList = [];
  174. foreach ($list as $key => $ord) {
  175. $data = [
  176. 'order_id' => $ord['id'],
  177. 'order_sn' => $ord['order_sn'],
  178. 'type_text' => $ord['type_text'],
  179. 'status_text' => $ord['status_text'],
  180. 'pay_type_text' => $ord['pay_type_text'],
  181. 'paytime_text' => $ord['paytime_text'],
  182. 'platform_text' => $ord['platform_text'],
  183. 'user_nickname' => $ord['user'] ? (strpos($ord['user']['nickname'], '=') === 0 ? ' ' . $ord['user']['nickname'] : $ord['user']['nickname']) : '',
  184. 'user_phone' => $ord['user'] ? $ord['user']['mobile'] . ' ' : '',
  185. 'total_amount' => $ord['total_amount'],
  186. 'discount_fee' => $ord['discount_fee'],
  187. 'pay_fee' => $ord['pay_fee'],
  188. 'score_fee' => $ord['score_fee'],
  189. 'consignee_info' => ($ord['consignee'] ? ($ord['consignee'] . ':' . $ord['phone'] . '-') : '') . ($ord['province_name'] . '-' . $ord['city_name'] . '-' . $ord['area_name']) . ' ' . $ord['address'],
  190. 'remark' => $ord['remark']
  191. ];
  192. foreach ($ord['item'] as $k => $item) {
  193. $itemData = [
  194. 'store_info' => $item['store'] ? $item['store']['name'] : '',
  195. 'activity_type_text' => $item['activity_type_text'],
  196. 'goods_title' => strpos($item['goods_title'], '=') === 0 ? ' ' . $item['goods_title'] : $item['goods_title'],
  197. 'goods_original_price' => $item['goods_original_price'],
  198. 'goods_price' => $item['goods_price'],
  199. 'goods_sku_text' => $item['goods_sku_text'],
  200. 'goods_num' => $item['goods_num'],
  201. 'dispatch_status_text' => $item['dispatch_status_text'],
  202. 'dispatch_fee' => $item['dispatch_fee'],
  203. 'dispatch_type_text' => $item['dispatch_type_text'],
  204. 'aftersale_refund' => $item['aftersale_status_text'] . '/' . $item['refund_status_text'],
  205. 'comment_status_text' => $item['comment_status_text'],
  206. 'refund_fee' => $item['refund_fee'],
  207. 'refund_msg' => $item['refund_msg'],
  208. 'express_name' => $item['express_name'],
  209. 'express_no' => $item['express_no'],
  210. ];
  211. $newList[] = array_merge($data, $itemData);
  212. }
  213. $total_amount += $ord['total_amount']; // 订单总金额
  214. $discount_fee += $ord['discount_fee']; // 优惠总金额
  215. $pay_fee += $ord['pay_fee']; // 实际支付总金额
  216. $score_fee += $ord['score_fee']; // 支付总积分
  217. }
  218. if ($is_last_page) {
  219. $newList[] = [
  220. 'order_id' => "订单总数:" . $total . ";订单总金额:¥" . $total_amount . ";优惠总金额:¥" . $discount_fee . ";实际支付总金额:¥" . $pay_fee . ";支付总积分:" . $score_fee
  221. ];
  222. }
  223. $current_total += count($newList); // 当前循环总条数
  224. $export->exportExcel('订单列表-' . date('Y-m-d H:i:s'), $expCellName, $newList, $spreadsheet, $sheet, [
  225. 'page' => $page,
  226. 'page_size' => $page_size, // 如果传了 current_total 则 page_size 就不用了
  227. 'current_total' => $current_total, // page_size 是 order 的,但是 newList 其实是 order_item 的
  228. 'is_last_page' => $is_last_page
  229. ]);
  230. }
  231. }
  232. // 导出发货单
  233. public function exportDelivery()
  234. {
  235. $nobuildfields = ['status', 'aftersale_sn', 'dispatch_type', 'goods_type', 'nickname', 'user_phone', 'goods_title'];
  236. list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(null, $nobuildfields);
  237. $expCellName = [
  238. 'order_id' => 'Id',
  239. 'order_sn' => '订单号',
  240. 'order_item_id' => '子订单Id',
  241. 'type_text' => '订单类型',
  242. 'consignee_info' => '收货信息',
  243. 'remark' => '用户备注',
  244. 'goods_title' => '商品名称',
  245. 'goods_sku_text' => '商品规格',
  246. 'goods_num' => '购买数量',
  247. 'dispatch_fee' => '发货费用',
  248. 'dispatch_type_text' => '发货方式',
  249. 'aftersale_refund' => '售后/退款',
  250. 'express_no' => '快递单号',
  251. ];
  252. $export = new Export();
  253. $spreadsheet = null;
  254. $sheet = null;
  255. $total = $this->buildSearchOrder()->where($where)->order($sort, $order)->count();
  256. $current_total = 0; // 当前已循环条数
  257. $page_size = 2000;
  258. $total_page = intval(ceil($total / $page_size));
  259. $newList = [];
  260. $orderCount = 0;
  261. if ($total == 0) {
  262. $this->error('导出数据为空');
  263. }
  264. for ($i = 0; $i < $total_page; $i++) {
  265. $page = $i + 1;
  266. $is_last_page = ($page == $total_page) ? true : false;
  267. $list = $this->buildSearchOrder()
  268. ->where($where)
  269. ->with(['user', 'item'])
  270. ->order($sort, $order)
  271. ->limit(($i * $page_size), $page_size)
  272. ->select();
  273. $list = collection($list)->toArray();
  274. foreach ($list as $key => $od) {
  275. // 处理 未支付订单 的 订单 item status_code 状态
  276. $list[$key] = $this->model->setOrderItemStatusByOrder($od);
  277. }
  278. $newList = [];
  279. foreach ($list as $key => $ord) {
  280. if ($ord['status_code'] == 'groupon_ing') {
  281. // 拼团正在进行中,不发货
  282. continue;
  283. }
  284. $data = [
  285. 'order_id' => $ord['id'],
  286. 'order_sn' => $ord['order_sn'],
  287. 'type_text' => $ord['type_text'],
  288. 'consignee_info' => ($ord['consignee'] ? ($ord['consignee'] . ':' . $ord['phone'] . '-') : '') . ($ord['province_name'] . '-' . $ord['city_name'] . '-' . $ord['area_name']) . ' ' . $ord['address'],
  289. 'remark' => $ord['remark']
  290. ];
  291. $existItem = false; // 是否有符合发货的item
  292. foreach ($ord['item'] as $k => $item) {
  293. // 未发货,并且未退款,并且未在申请售后中,并且是快递物流的
  294. if (
  295. $item['dispatch_status'] == OrderItem::DISPATCH_STATUS_NOSEND
  296. && !in_array($item['refund_status'], [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])
  297. && $item['aftersale_status'] != OrderItem::AFTERSALE_STATUS_AFTERING
  298. && $item['dispatch_type'] == 'express'
  299. ) {
  300. $itemData = [
  301. 'order_item_id' => $item['id'],
  302. 'goods_title' => strpos($item['goods_title'], '=') === 0 ? ' ' . $item['goods_title'] : $item['goods_title'],
  303. 'goods_sku_text' => $item['goods_sku_text'],
  304. 'goods_num' => $item['goods_num'],
  305. 'dispatch_fee' => $item['dispatch_fee'],
  306. 'dispatch_type_text' => $item['dispatch_type_text'],
  307. 'aftersale_refund' => $item['aftersale_status_text'] . '/' . $item['refund_status_text'],
  308. 'express_no' => $item['express_no'],
  309. ];
  310. $newList[] = array_merge($data, $itemData);
  311. $existItem = true;
  312. }
  313. }
  314. if ($existItem) {
  315. $orderCount++;
  316. }
  317. }
  318. if ($is_last_page) {
  319. $newList[] = [
  320. 'order_id' => "订单总数:" . $orderCount . ";(备注:同一订单中不同包裹请勿填写相同运单号)"
  321. ];
  322. }
  323. $current_total += count($newList); // 当前循环总条数
  324. $export->exportExcel('发货单列表-' . date('Y-m-d H:i:s'), $expCellName, $newList, $spreadsheet, $sheet, [
  325. 'page' => $page,
  326. 'page_size' => $page_size, // 如果传了 current_total 则 page_size 就不用了
  327. 'current_total' => $current_total, // page_size 是 order 的,但是 newList 其实是 order_item 的
  328. 'is_last_page' => $is_last_page
  329. ]);
  330. }
  331. }
  332. // 获取要查询的订单类型
  333. public function getType()
  334. {
  335. $type = $this->model->getTypeList();
  336. $pay_type = $this->model->getPayTypeList();
  337. $platform = $this->model->getPlatformList();
  338. $dispatch_type = (new \app\admin\model\shopro\dispatch\Dispatch)->getTypeList();
  339. $activity_type = (new \app\admin\model\shopro\activity\Activity)->getTypeList();
  340. $goods_type = (new \app\admin\model\shopro\goods\Goods)->getTypeList();
  341. $result = [
  342. 'type' => $type,
  343. 'pay_type' => $pay_type,
  344. 'platform' => $platform,
  345. 'dispatch_type' => $dispatch_type,
  346. 'activity_type' => $activity_type,
  347. 'goods_type' => $goods_type
  348. ];
  349. $data = [];
  350. foreach ($result as $key => $list) {
  351. $data[$key][] = ['name' => '全部', 'type' => 'all'];
  352. foreach ($list as $k => $v) {
  353. $data[$key][] = [
  354. 'name' => $v,
  355. 'type' => $k
  356. ];
  357. }
  358. }
  359. return $this->success('操作成功', null, $data);
  360. }
  361. // 订单详情
  362. public function detail($id)
  363. {
  364. if ($this->request->isAjax()) {
  365. $row = $this->model->withTrashed()->with(['user', 'item.store'])->where('id', $id)->find();
  366. if (!$row) {
  367. $this->error(__('No Results were found'));
  368. }
  369. $row->express = OrderExpress::with(['item' => function ($query) use ($id) {
  370. return $query->where('order_id', $id);
  371. }, 'log'])->where('order_id', $id)->select();
  372. // 处理未支付 item status_code
  373. $row = $this->model->setOrderItemStatusByOrder($row);
  374. // 返回 核销码
  375. $dispatchTypes = array_column($row['item'], 'dispatch_type');
  376. if (in_array('selfetch', $dispatchTypes)) {
  377. $verifies = Verify::where('order_id', $row['id'])->select();
  378. foreach ($row['item'] as &$item) {
  379. $item['verifies'] = [];
  380. if ($item['dispatch_type'] == 'selfetch') {
  381. foreach ($verifies as $verify) {
  382. if ($verify['order_item_id'] == $item['id']) {
  383. $item['verifies'][] = $verify;
  384. }
  385. }
  386. }
  387. }
  388. }
  389. return $this->success('获取成功', null, [
  390. 'order' => $row,
  391. 'item' => $row['item'],
  392. 'express' => $row['express']
  393. ]);
  394. }
  395. $this->assignconfig('id', $id);
  396. return $this->view->fetch();
  397. }
  398. // API一键推单(默认快递鸟)
  399. public function deliverByApi($id, $item_ids = 'all', $express_id = 0)
  400. {
  401. list($order, $orderExpress, $item_lists) = $this->handleDeliveryBefore($id, $item_ids, $express_id);
  402. $type = 'kdniao';
  403. if ($type === 'kdniao') {
  404. try {
  405. $expressLib = new \addons\shopro\library\Express();
  406. $result = $expressLib->eorder($order, $item_lists);
  407. $express_code = $result['Order']['ShipperCode'];
  408. $express_no = $result['Order']['LogisticCode'];
  409. $express = \app\admin\model\shopro\Express::where('code', $express_code)->find();
  410. $express_name = $express ? $express['name'] : '';
  411. } catch (\Exception $e) {
  412. $this->error($e->getMessage());
  413. }
  414. }
  415. list($orderExpress, $needSubscribe) = $this->handleDeliveryAfter($order, $item_lists, $orderExpress, $express_name, $express_code, $express_no);
  416. if ($needSubscribe) {
  417. $this->subscribeExpressInfo($express_code, $express_no, $orderExpress, $order);
  418. }
  419. return $this->success('发货成功', null);
  420. }
  421. // 批量发货(导入发货模板)
  422. public function deliverByUploadTemplate()
  423. {
  424. $file = $this->request->request('file');
  425. $express_name = $this->request->request('express_name');
  426. $express_code = $this->request->request('express_code');
  427. if (!$file) {
  428. $this->error(__('Parameter %s can not be empty', 'file'));
  429. }
  430. $filePath = ROOT_PATH . DS . 'public' . DS . $file;
  431. if (!is_file($filePath)) {
  432. $this->error(__('No results were found'));
  433. }
  434. //实例化reader
  435. $ext = pathinfo($filePath, PATHINFO_EXTENSION);
  436. if ($ext !== 'xlsx') {
  437. $this->error(__('Unknown data format'));
  438. }
  439. $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
  440. //导入文件首行类型,默认是注释,如果需要使用字段名称请使用name
  441. $importHeadType = isset($this->importHeadType) ? $this->importHeadType : 'comment';
  442. //加载文件
  443. $insert = [];
  444. try {
  445. if (!$PHPExcel = $reader->load($filePath)) {
  446. $this->error(__('Unknown data format'));
  447. }
  448. $currentSheet = $PHPExcel->getSheet(0); //读取文件中的第一个工作表
  449. $allColumn = $currentSheet->getHighestDataColumn(); //取得最大的列号
  450. $allRow = $currentSheet->getHighestRow(); //取得一共有多少行
  451. $maxColumnNumber = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($allColumn);
  452. if ($allRow <= 2) {
  453. $this->error('您的发货列表为空');
  454. }
  455. $orderList = [];
  456. for ($currentRow = 2; $currentRow <= $allRow - 1; $currentRow++) {
  457. $order = [];
  458. $order['id'] = $currentSheet->getCellByColumnAndRow(1, $currentRow)->getValue();
  459. $order['item_id'] = $currentSheet->getCellByColumnAndRow(3, $currentRow)->getValue();
  460. $order['express_no'] = $currentSheet->getCellByColumnAndRow(13, $currentRow)->getValue();
  461. $orderList[] = $order;
  462. }
  463. } catch (Exception $exception) {
  464. $this->error($exception->getMessage());
  465. }
  466. if (!$orderList) {
  467. $this->error(__('No rows were updated'));
  468. }
  469. $expressArray = [];
  470. foreach ($orderList as $v) {
  471. $express_no = $v['express_no']; // 使用快递单号作为键名
  472. if(!$express_no) {
  473. $this->error('请填写正确的运单号');
  474. }
  475. if(isset($expressArray[$express_no])) {
  476. if($expressArray[$express_no]['order_id'] != $v['id']) {
  477. $this->error('不同订单勿使用同一运单号');
  478. }
  479. $expressArray[$express_no]['item_ids'][] = $v['item_id'];
  480. }else {
  481. $expressArray[$express_no]['order_id'] = $v['id'];
  482. $expressArray[$express_no]['item_ids'][] = $v['item_id'];
  483. }
  484. }
  485. // 开始发货
  486. if(!$expressArray) $this->error('您的发货列表为空');
  487. foreach($expressArray as $k => $express) {
  488. $express_no = $k;
  489. $order_id = $express['order_id'];
  490. $item_ids = implode(',', $express['item_ids']);
  491. list($order, $orderExpress, $item_lists) = $this->handleDeliveryBefore($order_id, $item_ids, '');
  492. list($orderExpress, $needSubscribe) = $this->handleDeliveryAfter($order, $item_lists, $orderExpress, $express_name, $express_code, $express_no);
  493. if ($needSubscribe) {
  494. $this->subscribeExpressInfo($express_code, $express_no, $orderExpress, $order);
  495. }
  496. }
  497. $this->success('发货列表', null, $insert);
  498. }
  499. /**
  500. * 手动发货
  501. *
  502. * @param int $id 订单ID
  503. * @param string $item_ids 订单中需发货的商品
  504. * @param int $express_id 发货包裹ID
  505. * @param string $express_name 快递公司名称
  506. * @param string $express_code 物流公司编号
  507. * @param string $express_no 运单号
  508. */
  509. public function deliverByInput($id, $item_ids = '', $express_id = null)
  510. {
  511. list($order, $orderExpress, $item_lists) = $this->handleDeliveryBefore($id, $item_ids, $express_id);
  512. $express_name = $this->request->post('express_name', '');
  513. $express_code = $this->request->post('express_code', '');
  514. $express_no = $this->request->post('express_no', '');
  515. list($orderExpress, $needSubscribe) = $this->handleDeliveryAfter($order, $item_lists, $orderExpress, $express_name, $express_code, $express_no);
  516. if ($needSubscribe) {
  517. $this->subscribeExpressInfo($express_code, $express_no, $orderExpress, $order);
  518. }
  519. return $this->success('发货成功');
  520. }
  521. /**
  522. * 处理发货前置检查流程
  523. *
  524. * @param int $id 订单ID
  525. * @param string $item_ids 订单中需发货的商品
  526. * @param int $express_id 发货包裹ID
  527. * @param string $express_name 快递公司名称
  528. * @param string $express_code 物流公司编号
  529. * @param string $express_no 运单号
  530. */
  531. private function handleDeliveryBefore($id, $item_ids, $express_id)
  532. {
  533. $order = $this->model->payed()->where('id', $id)->find();
  534. if (!$order) {
  535. $this->error('订单不存在或未支付');
  536. }
  537. if ($item_ids == '') {
  538. $this->error('请选择待发货的商品');
  539. }
  540. if ($order->status_code === 'groupon_ing') {
  541. $this->error('该商品还未拼团成功,暂不能发货');
  542. }
  543. // 查询要发货的商品
  544. if ($item_ids == 'all') { // 选中所有商品
  545. $where = [
  546. 'order_id' => $id,
  547. 'dispatch_type' => 'express' // 必须是物流快递的商品
  548. ];
  549. } else {
  550. $where = [ // 选中分包裹商品
  551. 'order_id' => $id,
  552. 'id' => ['in', $item_ids],
  553. 'dispatch_type' => 'express'
  554. ];
  555. }
  556. // 订单包裹
  557. $orderExpress = null;
  558. if ($express_id) {
  559. // 修改包裹
  560. $orderExpress = OrderExpress::where('id', $express_id)->find();
  561. if (!$orderExpress) {
  562. $this->error('包裹不存在');
  563. }
  564. }
  565. $dispatchWhere[] = \app\admin\model\shopro\order\OrderItem::DISPATCH_STATUS_NOSEND;
  566. if ($express_id) {
  567. // 可以修改已发货的商品
  568. $dispatchWhere[] = \app\admin\model\shopro\order\OrderItem::DISPATCH_STATUS_SENDED;
  569. }
  570. $where['dispatch_status'] = ['in', $dispatchWhere];
  571. $item_lists = \app\admin\model\shopro\order\OrderItem::where($where)->select();
  572. if (!$item_lists) {
  573. $this->error('没有待发货的商品');
  574. }
  575. return [$order, $orderExpress, $item_lists];
  576. }
  577. /**
  578. * 处理发货后置流程
  579. *
  580. * @param object $order 订单
  581. * @param array $item_lists 订单中待发货的商品
  582. * @param object $orderExpress 发货包裹
  583. * @param string $express_name 快递公司名称
  584. * @param string $express_code 物流公司编号
  585. * @param string $express_no 运单号
  586. */
  587. private function handleDeliveryAfter($order, $item_lists, $orderExpress, $express_name, $express_code, $express_no)
  588. {
  589. if (!$express_name || !$express_code || !$express_no) {
  590. $this->error('请填写完整发货信息');
  591. }
  592. $needSubscribe = true;
  593. if ($orderExpress && $orderExpress->express_no == $express_no && $orderExpress->express_code == $express_code) {
  594. // 没有编辑快递信息,不需要重新订阅快递
  595. $needSubscribe = false;
  596. }
  597. $orderExpress = Db::transaction(function () use ($order, $item_lists, $orderExpress, $express_name, $express_code, $express_no) {
  598. foreach ($item_lists as $key => $item) {
  599. $order->sendItem($order, $item, [
  600. "express_name" => $express_name,
  601. "express_code" => $express_code,
  602. "express_no" => $express_no,
  603. "oper" => $this->auth->getUserInfo(),
  604. "oper_type" => 'admin',
  605. ]);
  606. }
  607. if (!$orderExpress) {
  608. // 添加包裹
  609. $orderExpress = new OrderExpress();
  610. $orderExpress->user_id = $order->user_id;
  611. $orderExpress->order_id = $order->id;
  612. } else {
  613. // 查询选择的包裹中未被选中的商品,改为未发货
  614. \app\admin\model\shopro\order\OrderItem::where('order_id', $order['id'])
  615. ->where('id', 'not in', array_column($item_lists, 'id'))
  616. ->where('express_no', $orderExpress->express_no)
  617. ->where('express_code', $orderExpress->express_code)->update([
  618. 'express_name' => null,
  619. 'express_code' => null,
  620. 'express_no' => null,
  621. 'dispatch_status' => \app\admin\model\shopro\order\OrderItem::DISPATCH_STATUS_NOSEND
  622. ]);
  623. }
  624. $orderExpress->express_name = $express_name;
  625. $orderExpress->express_code = $express_code;
  626. $orderExpress->express_no = $express_no;
  627. $orderExpress->save();
  628. // 查询已经没有商品的包裹,并且删除
  629. OrderExpress::whereNotExists(function ($query) use ($order) {
  630. $order_table_name = (new OrderExpress())->getQuery()->getTable();
  631. $table_name = (new \app\admin\model\shopro\order\OrderItem())->getQuery()->getTable();
  632. $query->table($table_name)->where($table_name . '.express_no=' . $order_table_name . '.express_no')
  633. ->where($table_name . '.express_code=' . $order_table_name . '.express_code')
  634. ->where('order_id', $order['id']);
  635. })->where('order_id', $order['id'])->delete();
  636. return $orderExpress;
  637. });
  638. return [$orderExpress, $needSubscribe];
  639. }
  640. /**
  641. * 订阅物流消息通知(默认快递鸟)
  642. *
  643. * @param string $express_code 物流公司编号
  644. * @param string $express_no 运单号
  645. * @param object $orderExpress 发货包裹
  646. * @param object $order 订单
  647. */
  648. public function subscribeExpressInfo($express_code, $express_no, $orderExpress, $order)
  649. {
  650. $type = 'kdniao';
  651. if ($type === 'kdniao') {
  652. try {
  653. $expressLib = new \addons\shopro\library\Express();
  654. $expressLib->subscribe([
  655. 'express_code' => $express_code,
  656. 'express_no' => $express_no
  657. ], $orderExpress, $order);
  658. } catch (\Exception $e) {
  659. return $e->getMessage();
  660. }
  661. }
  662. }
  663. public function sendStore($id, $item_id = '')
  664. {
  665. if ($this->request->isAjax()) {
  666. $item_id = $item_id ? explode(',', $item_id) : [];
  667. $order = $this->model->payed()->where('id', $id)->find();
  668. if (!$order) {
  669. $this->error('订单不存在或未支付');
  670. }
  671. // 查询要发货的商品
  672. $where = [
  673. 'order_id' => $id,
  674. 'id' => ['in', $item_id],
  675. 'dispatch_type' => ['in', ['store', 'selfetch']], // 必须是商家配送,和自提
  676. 'dispatch_status' => ['in', [\app\admin\model\shopro\order\OrderItem::DISPATCH_STATUS_NOSEND]],
  677. ];
  678. $itemList = \app\admin\model\shopro\order\OrderItem::where($where)->select();
  679. if (!$itemList) {
  680. $this->error('没有要发货的订单商品');
  681. }
  682. // 对选择的 item 进行发货
  683. Db::transaction(function () use ($order, $itemList) {
  684. $order->adminStoreOrderSend($order, $itemList, ['oper_type' => 'admin', 'oper' => $this->auth->getUserInfo()]);
  685. });
  686. // 重新获取订单
  687. $order = $this->model->with(['item'])->where('id', $id)->find();
  688. return $this->success('发货成功', null, $order);
  689. }
  690. }
  691. /**
  692. * 获取物流快递信息
  693. */
  694. public function getExpress($express_id = 0)
  695. {
  696. $type = $this->request->get('type');
  697. // 获取包裹
  698. $orderExpress = OrderExpress::with('order')->where('id', $express_id)->find();
  699. if (!$orderExpress) {
  700. return $this->error('包裹不存在');
  701. }
  702. $expressLib = new \addons\shopro\library\Express();
  703. try {
  704. if ($type == 'subscribe') {
  705. // 重新订阅
  706. $expressLib->subscribe([
  707. 'express_code' => $orderExpress['express_code'],
  708. 'express_no' => $orderExpress['express_no']
  709. ], $orderExpress, $orderExpress->order);
  710. } else {
  711. // 手动查询
  712. $result = $expressLib->search([
  713. 'express_code' => $orderExpress['express_code'],
  714. 'express_no' => $orderExpress['express_no']
  715. ], $orderExpress, $orderExpress->order);
  716. // 差异更新物流信息
  717. $expressLib->checkAndAddTraces($orderExpress, $result);
  718. }
  719. $order = $this->model->with(['item'])->where('id', $orderExpress['order_id'])->find();
  720. } catch (\Exception $e) {
  721. return $this->error(($type == 'subscribe' ? '订阅失败' : '刷新失败') . $e->getMessage());
  722. }
  723. return $this->success(($type == 'subscribe' ? '订阅成功' : '刷新成功'), null, $order);
  724. }
  725. /**
  726. * 同意退款
  727. */
  728. public function refund($id = 0, $item_id = 0)
  729. {
  730. if ($this->request->isAjax()) {
  731. $refund_money = round($this->request->post('refund_money', 0), 2);
  732. if ($refund_money < 0) {
  733. $this->error('退款金额不能小于 0');
  734. }
  735. $order = $this->model->where(
  736. 'status',
  737. 'in',
  738. [
  739. \app\admin\model\shopro\order\Order::STATUS_PAYED,
  740. \app\admin\model\shopro\order\Order::STATUS_FINISH
  741. ]
  742. )
  743. ->with('item')->where('id', $id)->find();
  744. if (!$order) {
  745. $this->error('订单不存在或不可退款');
  746. }
  747. $items = $order->item;
  748. $items = array_column($items, null, 'id');
  749. // 当前订单已退款总金额
  750. $refunded_money = array_sum(array_column($items, 'refund_fee'));
  751. // 剩余可退款金额
  752. $refund_surplus_money = $order->pay_fee - $refunded_money;
  753. // 如果退款金额大于订单支付总金额
  754. if ($refund_money > $refund_surplus_money) {
  755. $this->error('退款总金额不能大于实际支付金额');
  756. }
  757. if ($item_id) {
  758. $item = $items[$item_id];
  759. if (!$item || in_array($item['refund_status'], [
  760. \app\admin\model\shopro\order\OrderItem::REFUND_STATUS_OK,
  761. \app\admin\model\shopro\order\OrderItem::REFUND_STATUS_FINISH,
  762. ])) {
  763. $this->error('订单商品已退款,不能重复退款');
  764. }
  765. } else {
  766. $is_refund = false;
  767. foreach ($items as $key => $it) {
  768. if (in_array($it['refund_status'], [
  769. \app\admin\model\shopro\order\OrderItem::REFUND_STATUS_OK,
  770. \app\admin\model\shopro\order\OrderItem::REFUND_STATUS_FINISH,
  771. ])) {
  772. // 已退款
  773. unset($items[$key]);
  774. } else {
  775. $is_refund = true;
  776. }
  777. }
  778. $items = array_values($items);
  779. if (!$is_refund) {
  780. $this->error('订单已退款,不能重复退款');
  781. }
  782. }
  783. Db::transaction(function () use ($order, $items, $item_id, $refund_money, $refund_surplus_money) {
  784. if ($item_id) {
  785. // 单个商品退款
  786. $item = $items[$item_id];
  787. \app\admin\model\shopro\order\Order::startRefund($order, $item, $refund_money, $this->auth->getUserInfo(), '管理员操作退款');
  788. } else {
  789. // 全部退款
  790. // 未退款 item 商品总金额
  791. $goods_total_amount = 0;
  792. foreach ($items as $ke => $it) {
  793. $goods_total_amount += ($it['goods_price'] * $it['goods_num']);
  794. }
  795. $current_refunded_money = 0;
  796. for ($i = 0; $i < count($items); $i++) {
  797. if ($i == (count($items) - 1)) {
  798. // 最后一条,全部退完
  799. $current_refund_money = $refund_money - $current_refunded_money;
  800. } else {
  801. // 按比例计算当前 item 应退金额
  802. $current_refund_money = round($refund_money * (($items[$i]['goods_price'] * $it['goods_num']) / $goods_total_amount), 2);
  803. }
  804. if (($current_refunded_money + $current_refund_money) > $refund_money) {
  805. $current_refund_money = $refund_money - $current_refunded_money;
  806. }
  807. if ($current_refund_money >= 0) { // 支付金额或者退款金额 为 0 也能退
  808. $current_refunded_money += $current_refund_money;
  809. \app\admin\model\shopro\order\Order::startRefund($order, $items[$i], $current_refund_money, $this->auth->getUserInfo(), '管理员操作退款');
  810. }
  811. }
  812. }
  813. });
  814. $item_list = \app\admin\model\shopro\order\OrderItem::where(['order_id' => $id])->select();
  815. return $this->success('操作成功', null, $item_list);
  816. }
  817. }
  818. // 取消订单
  819. public function cancel($id)
  820. {
  821. if ($this->request->isAjax()) {
  822. $order = $this->model->where('id', $id)->nopay()->find();
  823. if (!$order) {
  824. $this->error('订单不存在或已取消');
  825. }
  826. $order = $order->doCancel($order, $this->auth->getUserInfo(), 'admin');
  827. return $this->success('操作成功', null, $order);
  828. }
  829. }
  830. // 修改收货人信息
  831. public function editConsignee($id)
  832. {
  833. if ($this->request->isAjax()) {
  834. $params = $this->request->post();
  835. extract($params);
  836. $row = $this->model->withTrashed()->where('id', $id)->find();
  837. if (!$row) {
  838. $this->error('订单不存在');
  839. }
  840. try {
  841. if ($this->modelValidate) {
  842. $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
  843. $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
  844. $row->validateFailException(true)->validate($validate);
  845. }
  846. $result = $row->save([
  847. 'consignee' => $consignee,
  848. 'phone' => $phone,
  849. 'province_id' => $province_id,
  850. 'province_name' => $province_name,
  851. 'city_id' => $city_id,
  852. 'city_name' => $city_name,
  853. 'area_id' => $area_id,
  854. 'area_name' => $area_name,
  855. 'address' => $address,
  856. ], ['id' => $id]);
  857. } catch (ValidateException $e) {
  858. $this->error($e->getMessage());
  859. } catch (PDOException $e) {
  860. $this->error($e->getMessage());
  861. } catch (Exception $e) {
  862. $this->error($e->getMessage());
  863. }
  864. if ($result !== false) {
  865. $order = $this->model->with('user')->where('id', $id)->find();
  866. $this->success('修改成功', null, $order);
  867. } else {
  868. $this->error(__('No rows were updated'));
  869. }
  870. }
  871. }
  872. // 编辑商家备注
  873. public function editMemo($id)
  874. {
  875. if ($this->request->isAjax()) {
  876. $memo = $this->request->post('memo');
  877. $order = $this->model->withTrashed()->where('id', $id)->find();
  878. if (!$order) {
  879. $this->error('订单不存在');
  880. }
  881. $order->memo = $memo;
  882. $order->save();
  883. \addons\shopro\model\OrderAction::operAdd($order, null, $this->auth->getUserInfo(), 'admin', "修改备注:" . $memo);
  884. return $this->success('操作成功', null, $order);
  885. }
  886. }
  887. // 获取订单操作记录
  888. public function actions($id)
  889. {
  890. $actions = \app\admin\model\shopro\order\OrderAction::with('oper')->where('order_id', $id)->order('id', 'desc')->select();
  891. foreach ($actions as $key => $action) {
  892. $action = $action->toArray();
  893. if ($action['oper_type'] == 'admin') {
  894. $oper = [
  895. 'id' => $action['oper_id'],
  896. 'name' => $action['oper'] ? $action['oper']['nickname'] : ''
  897. ];
  898. } else if ($action['oper_type'] == 'user') {
  899. $oper = [
  900. 'id' => $action['oper_id'],
  901. 'name' => '用户'
  902. ];
  903. } else if ($action['oper_type'] == 'system') {
  904. $oper = [
  905. 'id' => $action['oper_id'],
  906. 'name' => '系统'
  907. ];
  908. } else {
  909. $oper = null;
  910. }
  911. $action['oper'] = $oper;
  912. $actions[$key] = $action;
  913. }
  914. return $this->success('操作成功', null, $actions);
  915. }
  916. // 构建查询条件
  917. private function buildSearchOrder()
  918. {
  919. $filter = $this->request->get("filter", '');
  920. $filter = (array)json_decode($filter, true);
  921. $filter = $filter ? $filter : [];
  922. $dispatch_type = isset($filter['dispatch_type']) ? $filter['dispatch_type'] : 'all';
  923. $status = isset($filter['status']) ? $filter['status'] : 'all';
  924. $goods_type = isset($filter['goods_type']) ? $filter['goods_type'] : 'all';
  925. $aftersale_sn = isset($filter['aftersale_sn']) ? $filter['aftersale_sn'] : '';
  926. $nickname = isset($filter['nickname']) ? $filter['nickname'] : '';
  927. $mobile = isset($filter['user_phone']) ? $filter['user_phone'] : '';
  928. $goods_title = isset($filter['goods_title']) ? $filter['goods_title'] : '';
  929. $store_id = isset($filter['store_id']) ? $filter['store_id'] : 'all';
  930. $name = $this->model->getQuery()->getTable();
  931. $tableName = $name . '.';
  932. $orders = $this->model->withTrashed();
  933. if ($nickname || $mobile) {
  934. $orders = $orders->whereExists(function ($query) use ($nickname, $mobile, $tableName) {
  935. $userTableName = (new \app\admin\model\User())->getQuery()->getTable();
  936. $query = $query->table($userTableName)->where($userTableName . '.id=' . $tableName . 'user_id');
  937. if ($nickname) {
  938. $query = $query->where('nickname', 'like', "%{$nickname}%");
  939. }
  940. if ($mobile) {
  941. $query = $query->where('mobile', 'like', "%{$mobile}%");
  942. }
  943. return $query;
  944. });
  945. }
  946. // 售后单号
  947. if ($aftersale_sn) {
  948. $orders = $orders->whereExists(function ($query) use ($aftersale_sn, $tableName) {
  949. $itemTableName = (new \app\admin\model\shopro\order\Aftersale())->getQuery()->getTable();
  950. $query->table($itemTableName)->where($itemTableName . '.order_id=' . $tableName . 'id')
  951. ->where('aftersale_sn', $aftersale_sn);
  952. });
  953. }
  954. // 快递方式 || 商品类型 (同一个表,写在一起)
  955. if ($dispatch_type != 'all' || $goods_type != 'all' || $goods_title || $store_id != 'all') {
  956. $orders = $orders->whereExists(function ($query) use ($dispatch_type, $goods_type, $goods_title, $store_id, $tableName) {
  957. $itemTableName = (new \app\admin\model\shopro\order\OrderItem())->getQuery()->getTable();
  958. $query = $query->table($itemTableName)->where($itemTableName . '.order_id=' . $tableName . 'id');
  959. if ($dispatch_type != 'all') {
  960. $query = $query->where('dispatch_type', $dispatch_type);
  961. }
  962. if ($goods_type != 'all') {
  963. $query = $query->where('goods_type', $goods_type);
  964. }
  965. if ($goods_title) {
  966. $query = $query->where('goods_title', 'like', "%{$goods_title}%");
  967. }
  968. if ($store_id != 'all') {
  969. // 门店订单
  970. if ($store_id) {
  971. $query = $query->where('store_id', $store_id);
  972. } else {
  973. $query = $query->where('store_id', '<>', 0);
  974. }
  975. }
  976. return $query;
  977. });
  978. }
  979. // 订单状态
  980. if ($status != 'all') {
  981. if (in_array($status, ['invalid', 'cancel', 'nopay', 'nosend', 'noget', 'nocomment', 'aftersale', 'refund', 'payed', 'finish'])) {
  982. if (in_array($status, ['nosend', 'noget', 'nocomment', 'aftersale', 'refund'])) {
  983. $orders = $orders->payed();
  984. }
  985. $status = $status == 'refund' ? 'refundStatus' : $status;
  986. if ($store_id != 'all' && in_array($status, ['nosend', 'noget', 'nocomment', 'aftersale', 'refundStatus'])) {
  987. // 查询门店订单,需要增加 store_id 进行查询
  988. $status = 'store' . ucfirst($status);
  989. $orders = $orders->{$status}($store_id);
  990. } else {
  991. // 所有订单
  992. $orders = $orders->{$status}();
  993. }
  994. }
  995. }
  996. return $orders;
  997. }
  998. }