ChainOrderService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <?php
  2. namespace addons\StarChain\common\services;
  3. use addons\StarChain\common\enums\ShippingTypeEnum;
  4. use addons\StarChain\common\enums\StarChainEnum;
  5. use addons\StarChain\common\helpers\ArrayHelper;
  6. use addons\StarChain\common\models\Goods;
  7. use addons\StarChain\common\models\GoodsAttr;
  8. use addons\StarChain\common\models\MallOrder;
  9. use addons\StarChain\common\models\Order;
  10. use addons\StarChain\common\models\Region;
  11. use common\enums\StatusEnum;
  12. use common\logic\order\BaseOrderForm;
  13. use common\models\user\UserAddress;
  14. use Exception;
  15. use Yii;
  16. use yii\helpers\Json;
  17. /**
  18. * 供应链订单相关服务
  19. */
  20. class ChainOrderService extends BaseService
  21. {
  22. /**
  23. * 下单前检测
  24. *
  25. * @param BaseOrderForm $form 商城订单数据
  26. * @param boolean $is_new_record
  27. * @return array
  28. */
  29. public function createBeforeCheck($form, $is_new_record = false)
  30. {
  31. // 获取购买的AttrId
  32. $order_goods = $form->order_goods;
  33. $attr_ids = array_column($order_goods, 'goods_attr_id');
  34. // 获取SKU
  35. $sku = GoodsAttr::getSkuByAttrIDs($attr_ids, 'sku_id, mall_goods_id, chain_goods_id, attr_id');
  36. if (empty($sku) || (count($sku) != count($attr_ids))) {
  37. throw new Exception('下单商品SKU有误,请重新下单');
  38. }
  39. // 重组sku 添加数量
  40. $sku = array_map(function ($item) use ($order_goods) {
  41. $goods = ArrayHelper::first($order_goods, function ($goods) use ($item) {
  42. return $goods['goods_attr_id'] == $item['attr_id'];
  43. });
  44. $item['goods_attr_name'] = $goods['goods_attr_name'];
  45. $item['goods_name'] = $goods['goods_name'];
  46. $item['num'] = $goods['num'];
  47. return $item;
  48. }, $sku);
  49. // 获取用户收货地址信息
  50. if (empty($extra['address'])) {
  51. $address = UserAddress::getOne([['id' => $form['address_id']], ['user_id' => $form['user_id']]], true);
  52. $extra['address'] = $address;
  53. }
  54. $group_order_goods = $form->group_order_goods;
  55. // 检测起购
  56. $this->checkBuyStartQty($sku);
  57. // 未选择地址不进行计算运费
  58. if (!empty($address)) {
  59. // 地址编码
  60. $district = Region::getDistrictByRegion($address['province'], $address['city'], $address['district'], $address['town']);
  61. if (empty($district)) {
  62. throw new Exception('地址信息有误,请联系管理员处理');
  63. }
  64. $spuInfoList = array_map(function ($item) {
  65. return [
  66. 'quantity' => $item['num'],
  67. 'spuId' => $item['chain_goods_id'],
  68. ];
  69. }, $sku);
  70. $params = [
  71. 'spuInfoList' => $spuInfoList,
  72. 'cityId' => $district->parent_code,
  73. ];
  74. // 检测可售
  75. if ($is_new_record) {
  76. $this->checkGoodsLimitByRegionCode($sku, $district->region_code);
  77. }
  78. // 库存监测
  79. if ($is_new_record) {
  80. $this->checkGoodsLimitBySkuStock($sku, $district->region_code);
  81. }
  82. // 运费
  83. $freight = $this->api->calculate($params);
  84. $form->marketing['deduct']['shipping_fee'] = [
  85. 'name' => '运费',
  86. 'money' => $freight,
  87. ];
  88. foreach ($group_order_goods as $k => &$v) {
  89. if ($v['mch_order_info']) {
  90. // 标识
  91. $v['mch_order_info']['order_source'] = StarChainEnum::PLUGIN_SIGN;
  92. // 邮费
  93. $v['mch_order_info']['shipping_type'] = ShippingTypeEnum::LOGISTICS; // 默认物流配送
  94. $v['mch_order_info']['shipping_money'] = $freight;
  95. // 收货地址
  96. $v['mch_order_info']['province'] = $address['province_id'];
  97. $v['mch_order_info']['city'] = $address['city_id'];
  98. $v['mch_order_info']['area'] = $address['district_id'];
  99. $v['mch_order_info']['town'] = $address['town_id'];
  100. $v['mch_order_info']['region_name'] = $address['province'] .' '. $address['city'] .' '. $address['district'].' '.$address['town'];
  101. $v['mch_order_info']['address'] = $address['detail'];
  102. $v['mch_order_info']['receiver_name'] = $address['name'];
  103. $v['mch_order_info']['mobile'] = $address['mobile'];
  104. }
  105. }
  106. // 计算总额
  107. $form['total_pay_money'] = bcadd($form['total_pay_money'], $freight, 2);
  108. }
  109. $form->group_order_goods = $group_order_goods;
  110. return $form;
  111. }
  112. /**
  113. * 创建订单
  114. *
  115. * @param integer $order_id
  116. * @param string $order_no
  117. * @return void
  118. */
  119. public static function createOrder($order_id/* , $order_no */)
  120. {
  121. /**
  122. * @var MallOrder|null
  123. */
  124. $order = MallOrder::getOrder($order_id);
  125. if (!$order->isChainOrder()) return false;
  126. if (!$order->pay_status == StatusEnum::ENABLED) return false;
  127. /**
  128. * @var array|null
  129. */
  130. $details = $order->detail;
  131. if (empty($details)) return false;
  132. // 已经创建订单 不执行
  133. // if ($order->hasChainOrder()) return false;
  134. // 下单商品
  135. $attr_ids = array_column($details, 'goods_attr_id');
  136. // 获取SKU
  137. $sku = GoodsAttr::getSkuByAttrIDs($attr_ids, 'sku_id, mall_goods_id, chain_goods_id, attr_id');
  138. if (empty($sku) || (count($sku) != count($attr_ids))) throw new Exception('下单商品SKU有误,请重新下单');
  139. // 重组sku 添加数量
  140. $sku = array_map(function ($item) use ($details) {
  141. $detail = ArrayHelper::first($details, function ($goods) use ($item) {
  142. return $goods['goods_attr_id'] == $item['attr_id'];
  143. });
  144. $item['num'] = $detail['num'];
  145. $item['mall_order_id'] = $detail['order_id'];
  146. $item['order_detail_id'] = $detail['id'];
  147. return $item;
  148. }, $sku);
  149. // 下单数据
  150. $goods_list = array_map(function ($item) {
  151. return [
  152. 'goodsQty' => $item['num'],
  153. 'skuId' => $item['sku_id'],
  154. 'spuId' => $item['chain_goods_id'],
  155. 'mall_order_id' => $item['mall_order_id'],
  156. 'order_detail_id' => $item['order_detail_id'],
  157. ];
  158. }, $sku);
  159. if (empty($goods_list)) throw new Exception('下单数据为空');
  160. // 地址信息
  161. $region = explode(' ', $order->region_name);
  162. $region = Region::getAddress($region);
  163. // 地址编码
  164. $area_code = Region::getAreaCode($region);
  165. if ($area_code === false) throw new Exception('该地址无法下单');
  166. $dto = [
  167. 'orderSource' => 1,
  168. 'goodsList' => $goods_list, // 下单商品
  169. 'shipArea' => trim(implode(',', $region), ','),
  170. 'shipAreaCode' => trim(implode(',', array_reverse(explode(',', $area_code)))),
  171. 'shipName' => $order->receiver_name,
  172. 'shipAddress' => $order->address,
  173. 'shipMobile' => $order->mobile,
  174. 'outOrderSn' => $order->order_no,
  175. 'buyerMemo' => $order->remark,
  176. ];
  177. /**
  178. * @var Order|null $order_model
  179. */
  180. $order_model = null;
  181. // 添加
  182. Yii::$app->db->transaction(function () use ($order, $dto, &$order_model) {
  183. $order_model = Order::createOrder($order, $dto);
  184. });
  185. // 创建订单
  186. try {
  187. $dto['goodsList'] = array_map(function ($item) {
  188. return [
  189. 'goodsQty' => $item['goodsQty'],
  190. 'skuId' => $item['skuId'],
  191. ];
  192. }, $dto['goodsList']);
  193. $order_data = (new self(['mall_id' => $order->mall_id]))->api->submitOrder($dto);
  194. } catch (Exception $e) {
  195. $order_model->orderError($e->getMessage());
  196. throw new Exception($e->getMessage());
  197. }
  198. // 修改状态
  199. Yii::$app->db->transaction(function () use ($order_data, $order_model, $order) {
  200. $order->changeOrderSource();
  201. if ($order_model->orderSuccess($order_data['orderList']) === false) throw new \Exception('修改状态失败');
  202. });
  203. }
  204. /**
  205. * 起购检测
  206. *
  207. * @param array $spu_id
  208. * @return void
  209. */
  210. protected function checkBuyStartQty(array $skus)
  211. {
  212. $data_list = $this->api->listSkuBySpuIds(array_unique(array_column($skus, 'chain_goods_id')));
  213. foreach ($skus as $sku) {
  214. // 供应链端商品信息
  215. $chain_goods = ArrayHelper::first($data_list, function ($item) use ($sku) {
  216. return $sku['chain_goods_id'] == $item['spuId'];
  217. });
  218. // 供应链sku
  219. $chain_sku = ArrayHelper::first($chain_goods['data'], function ($item) use ($sku) {
  220. return $item['skuId'] == $sku['sku_id'];
  221. });
  222. if ($chain_sku['buyStartQty'] > $sku['num']) {
  223. throw new Exception('「' . $sku['goods_attr_name'] . '」最低起购数量为' . $chain_sku['buyStartQty']);
  224. }
  225. }
  226. }
  227. /**
  228. * 商品区域限制检测
  229. *
  230. * @param array $skus
  231. * @param string $region_code
  232. * @return true
  233. * @throws Exception
  234. */
  235. protected function checkGoodsLimitByRegionCode(array $skus, string $region_code)
  236. {
  237. $spu_ids = array_unique(array_column($skus, 'chain_goods_id'));
  238. $goods = Goods::getInfoBySpuIds($spu_ids, 'name, is_limit_area, limit_area_type, limit_area');
  239. try {
  240. foreach ($goods as $item) {
  241. $limit_area = Json::decode($item->limit_area) ?? [];
  242. // 未开启限制
  243. if ($item->is_limit_area == 0) return true;
  244. // 限制类型
  245. if ($item->limit_area_type == 1 && in_array($region_code, $limit_area)) {
  246. throw new Exception('商品: 「' . $item->name . '」, 对区域: 「' . $region_code . '」, 已开启区域限制1, 请选择其他商品');
  247. } else if (!in_array($region_code, $limit_area)) {
  248. throw new Exception('商品: 「' . $item->name . '」, 对区域: 「' . $region_code . '」, 已开启区域限制0, 请选择其他商品');
  249. }
  250. }
  251. } catch (Exception $e) {
  252. // 如果区域编码不是6位,进行处理
  253. if (strlen($region_code) != 6) {
  254. // 区域编码处理
  255. $region_code = substr($region_code, 0, 6);
  256. $this->checkGoodsLimitByRegionCode($skus, $region_code);
  257. }
  258. throw new Exception($e->getMessage());
  259. }
  260. }
  261. /**
  262. * 库存限制坚持
  263. *
  264. * @param array $skus
  265. * @return void
  266. * @throws Exception
  267. */
  268. protected function checkGoodsLimitBySkuStock(array $skus, string $region_code)
  269. {
  270. $data = array_map(function ($item) {
  271. return [
  272. 'skuId' => $item['sku_id'],
  273. 'skuNum' => $item['num'],
  274. ];
  275. }, $skus);
  276. $data = $this->api->checkSkuStock($data, $region_code);
  277. foreach ($data as $item) {
  278. if (!($item['sailState'] && $item['stockState'])) {
  279. throw new Exception($item['msg'] ?? sprintf('「%s」库存限制购买', $item['skuId']));
  280. }
  281. }
  282. }
  283. }