OrderService.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. <?php
  2. namespace app\common\Service;
  3. use app\common\Enum\GoodsEnum;
  4. use app\common\model\Order;
  5. use app\common\model\OrderGoods;
  6. use app\common\model\OrderAction;
  7. use app\common\model\Address;
  8. use app\common\model\UserCoupon;
  9. use app\common\model\Goods;
  10. use app\common\model\Sku;
  11. use app\common\model\Freight;
  12. use app\common\model\Coupon;
  13. use app\common\model\Carts;
  14. use think\Db;
  15. use think\Exception;
  16. use app\common\model\OrderAddress;
  17. use app\common\Enum\OrderEnum;
  18. use app\common\exception\BusinessException;
  19. use app\common\Service\Order\OrderActionService;
  20. use app\common\Enum\OrderActionEnum;
  21. /**
  22. * 订单服务类
  23. * 封装订单创建相关逻辑
  24. */
  25. class OrderService
  26. {
  27. /**
  28. * 根据商品列表计算订单明细
  29. * @param array $orderInfo 订单基础信息
  30. * @param array $goods_list 商品列表
  31. * @param int $user_id 用户ID
  32. * @param int $area_id 地区ID
  33. * @param int $user_coupon_id 优惠券ID
  34. * @return array
  35. * @throws
  36. */
  37. protected static function computeGoods(&$orderInfo, $goodsList, $userId, $areaId, $userCouponId = 0)
  38. {
  39. $config = get_addon_config('shop');
  40. $orderInfo['amount'] = 0;
  41. $orderInfo['goods_price'] = 0;
  42. $orderInfo['goods_num'] = 0;
  43. $orderInfo['express_fee'] = 0;
  44. $orderInfo['discount_fee'] = 0;
  45. $orderInfo['coupon_discount_fee'] = 0;
  46. $orderInfo['order_amount'] = 0;
  47. $orderItem = [];
  48. $shippingTemp = [];
  49. $userCoupon = null;
  50. $processedGoodsList = []; // 处理后的商品列表,避免与参数$goodsList冲突
  51. // 校验优惠券
  52. if ($userCouponId) {
  53. $userCouponModel = new UserCoupon();
  54. $userCoupon = $userCouponModel->checkUserOrUse($userCouponId, $userId);
  55. $orderInfo['user_coupon_id'] = $userCouponId;
  56. }
  57. // 提取所有商品ID和SKU ID,进行批量查询
  58. $goodsIds = array_column($goodsList, 'goods_id');
  59. $skuIds = [];
  60. foreach ($goodsList as $index => $item) {
  61. if (isset($item['goods_sku_id']) && $item['goods_sku_id'] > 0) {
  62. $skuIds[] = $item['goods_sku_id'];
  63. }
  64. }
  65. // 批量查询商品信息
  66. $goodsData = [];
  67. if (!empty($goodsIds)) {
  68. $goodsCollection = Goods::with(['brand'])
  69. ->where('id', 'in', $goodsIds)
  70. ->where('status', GoodsEnum::STATUS_ON_SALE)
  71. ->select();
  72. foreach ($goodsCollection as $goods) {
  73. $goodsData[$goods->id] = $goods;
  74. }
  75. }
  76. // 批量查询SKU信息
  77. $skuData = [];
  78. $multiSpecSkuIds = []; // 用于存储多规格商品的SKU ID
  79. if (!empty($skuIds)) {
  80. $skuCollection = Sku::where('id', 'in', $skuIds)->select();
  81. foreach ($skuCollection as $sku) {
  82. $skuData[$sku->id] = $sku;
  83. // 过滤出有规格值的SKU ID(spec_value_ids不为空)
  84. if (!empty($sku->spec_value_ids)) {
  85. $multiSpecSkuIds[] = $sku->id;
  86. }
  87. }
  88. }
  89. // 批量查询规格属性字符串(只查询多规格商品的SKU)
  90. $skuAttrData = [];
  91. if (!empty($multiSpecSkuIds)) {
  92. $skuAttrData = \app\common\Service\SkuSpec::getSkuAttrs($multiSpecSkuIds);
  93. }
  94. // 验证并构建商品数据
  95. foreach ($goodsList as $item) {
  96. $goods_id = $item['goods_id'];
  97. $goods_sku_id = $item['goods_sku_id']; // 现在所有商品都应该有SKU ID
  98. $nums = $item['nums'];
  99. if ($nums <= 0) {
  100. throw new BusinessException("商品数量必须大于0");
  101. }
  102. // 检查商品是否存在
  103. if (!isset($goodsData[$goods_id])) {
  104. throw new ("商品已下架");
  105. }
  106. $goods = $goodsData[$goods_id];
  107. // 所有商品都必须有SKU(包括单规格商品的默认SKU)
  108. if (empty($skuData) || !isset($skuData[$goods_sku_id])) {
  109. throw new Exception("商品规格不存在");
  110. }
  111. $sku = $skuData[$goods_sku_id];
  112. // 验证SKU是否属于该商品
  113. if ($sku->goods_id != $goods_id) {
  114. throw new Exception("商品规格不匹配");
  115. }
  116. // 获取规格属性字符串(单规格商品的sku_attr为空)
  117. $sku_attr = $skuAttrData[$goods_sku_id] ?? '';
  118. // 构建商品对象,模拟购物车数据结构
  119. $goodsItem = (object)[
  120. 'goods_id' => $goods_id,
  121. 'goods_sku_id' => $goods_sku_id,
  122. 'nums' => $nums,
  123. 'goods' => $goods,
  124. 'sku' => $sku,
  125. 'sku_attr' => $sku_attr
  126. ];
  127. $processedGoodsList[] = $goodsItem;
  128. }
  129. // 计算商品价格和运费(统一使用SKU进行计算)
  130. foreach ($processedGoodsList as $item) {
  131. $goodsItemData = [];
  132. if (empty($item->goods) || empty($item->sku)) {
  133. throw new Exception("商品已下架");
  134. }
  135. // 库存验证(统一使用SKU库存)
  136. if ($item->sku->stocks < $item->nums) {
  137. throw new Exception("商品库存不足,请重新修改数量");
  138. }
  139. // 统一使用SKU数据进行计算
  140. $goodsItemData['image'] = !empty($item->sku->image) ? $item->sku->image : $item->goods->image;
  141. $goodsItemData['price'] = $item->sku->price;
  142. // $goodsItemData['lineation_price'] = $item->sku->lineation_price;
  143. $goodsItemData['sku_sn'] = $item->sku->sku_sn;
  144. $amount = bcmul($item->sku->price, $item->nums, 2);
  145. $goodsItemData['amount'] = $amount;
  146. // 订单应付金额
  147. $orderInfo['amount'] = bcadd($orderInfo['amount'], $amount, 2);
  148. // 商品总价
  149. $orderInfo['goods_price'] = bcadd($orderInfo['goods_price'], $amount, 2);
  150. // 商品数量累计
  151. $orderInfo['goods_num'] += $item->nums;
  152. $freight_id = $item->goods->express_template_id;
  153. // 计算邮费【合并运费模板】
  154. if (!isset($shippingTemp[$freight_id])) {
  155. $shippingTemp[$freight_id] = [
  156. 'nums' => $item->nums,
  157. 'weight' => $item->sku->weight,
  158. 'amount' => $amount
  159. ];
  160. } else {
  161. $shippingTemp[$freight_id] = [
  162. 'nums' => bcadd($shippingTemp[$freight_id]['nums'], $item->nums, 2),
  163. 'weight' => bcadd($shippingTemp[$freight_id]['weight'], $item->sku->weight, 2),
  164. 'amount' => bcadd($shippingTemp[$freight_id]['amount'], $amount, 2)
  165. ];
  166. }
  167. // 创建订单商品数据 (基于正确的表结构)
  168. $orderItemData = [
  169. 'user_id' => $userId,
  170. 'order_sn' => '', // 将在订单创建后补充
  171. 'goods_sn' => $item->goods->goods_sn ?: '', // 商品货号
  172. 'sku_sn' => $item->sku->sku_sn ?: '', // SKU编号
  173. 'goods_type' => $item->goods->type ?: 0, // 商品类型
  174. 'goods_id' => $item->goods->id,
  175. 'goods_sku_attr' => $item->sku->sku_attr,
  176. 'goods_spec_value_ids' => $item->sku->spec_value_ids,
  177. 'goods_sku_id' => $item->sku->id,
  178. 'goods_title' => $item->goods->title,
  179. 'goods_market_price' => $item->sku->market_price ?: 0, // 市场价
  180. 'goods_original_price' => $item->sku->price, // 商城售价
  181. 'goods_price' => $amount, // 实付金额
  182. 'goods_image' => $goodsItemData['image'],
  183. 'goods_weight' => $item->sku->weight ?: 0,
  184. 'nums' => $item->nums,
  185. 'sale_status' => 0, // 销售状态:0=待申请
  186. 'comment_status' => 0, // 评论状态:0=未评论
  187. 'status' => 1, // 状态
  188. 'supplier_id' => $item->goods->supplier_id,
  189. ];
  190. $orderItem[] = $orderItemData;
  191. }
  192. // 按运费模板计算
  193. foreach ($shippingTemp as $key => $item) {
  194. $shippingfee = Freight::calculate($key, $areaId, $item['nums'], $item['weight'], $item['amount']);
  195. $orderInfo['express_fee'] = bcadd($orderInfo['express_fee'], $shippingfee, 2);
  196. }
  197. // 订单金额(商品价格+运费)
  198. $orderInfo['order_amount'] = bcadd($orderInfo['goods_price'], $orderInfo['express_fee'], 2);
  199. // 订单应付金额(暂时等于订单金额,后续会减去优惠)
  200. $orderInfo['amount'] = $orderInfo['order_amount'];
  201. // if (!empty($userCoupon)) {
  202. // // 校验优惠券
  203. // $goods_ids = array_column($orderItem, 'goods_id');
  204. // $category_ids = array_column($orderItem, 'category_id');
  205. // $brand_ids = array_column($orderItem, 'brand_id');
  206. // $couponModel = new Coupon();
  207. // $coupon = $couponModel->getCoupon($userCoupon['coupon_id'])
  208. // ->checkCoupon()
  209. // ->checkOpen()
  210. // ->checkUseTime($userCoupon['createtime'])
  211. // ->checkConditionGoods($goods_ids, $userId, $category_ids, $brand_ids);
  212. // // 计算折扣金额,判断是使用不含运费,还是含运费的金额
  213. // $amount = !isset($config['shippingfeecoupon']) || $config['shippingfeecoupon'] == 0 ? $orderInfo['goods_price'] : $orderInfo['order_amount'];
  214. // list($new_money, $coupon_money) = $coupon->doBuy($amount);
  215. // // 判断优惠金额是否超出总价,超出则直接设定优惠金额为总价
  216. // $orderInfo['coupon_discount_fee'] = $coupon_money > $amount ? $amount : $coupon_money;
  217. // $orderInfo['discount_fee'] = $orderInfo['coupon_discount_fee'];
  218. // }
  219. // 计算最终应付金额【订单金额减去折扣】
  220. $orderInfo['amount'] = max(0, bcsub($orderInfo['order_amount'], $orderInfo['discount_fee'], 2));
  221. $orderInfo['pay_amount'] = $orderInfo['amount']; // 实际付款金额等于应付金额
  222. $orderInfo['discount_fee'] = bcadd($orderInfo['discount_fee'], 0, 2);
  223. return [
  224. $orderItem,
  225. $processedGoodsList,
  226. $userCoupon
  227. ];
  228. }
  229. /**
  230. * 统一的创建订单方法
  231. * @param int $address_id 地址ID
  232. * @param int $user_id 用户ID
  233. * @param array $goods_list 标准化的商品列表
  234. * @param int $user_coupon_id 优惠券ID
  235. * @param string $memo 备注
  236. * @param array $cart_ids 购物车ID数组(如果是购物车模式需要清空)
  237. * @return Order
  238. * @throws BusinessException
  239. */
  240. public static function createOrder($addressId, $userId, $goodsList, $userCouponId = 0, $remark = '',$supplierId = 0)
  241. {
  242. $address = Address::get($addressId);
  243. if (!$address || $address['user_id'] != $userId) {
  244. throw new BusinessException("地址未找到");
  245. }
  246. if (empty($goodsList)) {
  247. throw new BusinessException("商品列表不能为空");
  248. }
  249. $config = get_addon_config('shop');
  250. $orderSn = date("Ymdhis") . sprintf("%08d", $userId) . mt_rand(1000, 9999);
  251. // 订单主表信息 (基于新表结构)
  252. $orderInfo = [
  253. 'type' => 1, // 1:普通订单
  254. 'order_sn' => $orderSn,
  255. 'user_id' => $userId,
  256. 'amount' => 0, // 订单应付金额
  257. 'goods_price' => 0, // 商品总费用
  258. 'goods_num' => 0, // 商品数量
  259. 'discount_fee' => 0, // 优惠金额
  260. 'coupon_discount_fee' => 0, // 优惠券金额
  261. 'promo_discount_fee' => 0, // 营销金额
  262. 'order_amount' => 0, // 订单金额 + 运费
  263. 'express_fee' => 0, // 配送费用
  264. 'expire_time' => time() + $config['order_timeout'], // 过期时间
  265. 'order_status' => OrderEnum::STATUS_CREATE, // 待付款
  266. 'invoice_status' => 0, // 发票开具状态
  267. 'remark' => $remark, // 用户备注
  268. 'user_coupon_id' => $userCouponId ?: null,
  269. 'ip' => request()->ip(), // IP地址
  270. 'status' => 'normal',
  271. ];
  272. $orderInfo['platform'] = request()->header('platform', 'H5');
  273. // 通过商品列表计算订单明细
  274. list($orderItem, $calculatedGoodsList, $userCoupon) = self::computeGoods($orderInfo, $goodsList, $userId, $address->area_id, $userCouponId);
  275. $orderInfo['pay_amount'] = bcsub($orderInfo['order_amount'], $orderInfo['discount_fee'], 2);
  276. $orderInfo['pay_original_amount'] = $orderInfo['pay_amount'];
  277. $orderInfo['pay_remain_amount'] = $orderInfo['pay_amount'];
  278. // 创建订单
  279. $order = self::createOrderWithTransaction($orderInfo, $orderItem, $calculatedGoodsList, $userCoupon, $address);
  280. return $order;
  281. }
  282. /**
  283. * 在事务中创建订单
  284. * @param array $orderInfo 订单信息
  285. * @param array $orderItem 订单商品列表
  286. * @param array $goodsList 商品列表
  287. * @param object $userCoupon 优惠券
  288. * @param object $address 地址信息
  289. * @return Order
  290. * @throws Exception
  291. */
  292. protected static function createOrderWithTransaction($orderInfo, $orderItem, $goodsList, $userCoupon, $address)
  293. {
  294. $order = null;
  295. Db::startTrans();
  296. try {
  297. // 创建订单
  298. $order = Order::create($orderInfo, true);
  299. // 为每个订单商品添加订单ID和订单号
  300. foreach ($orderItem as &$item) {
  301. $item['order_id'] = $order->id;
  302. $item['order_sn'] = $order->order_sn;
  303. // 移除临时字段
  304. unset($item['category_id'], $item['brand_id']);
  305. }
  306. unset($item);
  307. // 创建订单地址信息
  308. $orderAddressData = [
  309. 'order_id' => $order->id,
  310. 'user_id' => $orderInfo['user_id'],
  311. 'consignee' => $address->receiver,
  312. 'mobile' => $address->mobile,
  313. 'province_name' => $address->province->name ?? '',
  314. 'city_name' => $address->city->name ?? '',
  315. 'district_name' => $address->area->name ?? '',
  316. 'address' => $address->address,
  317. 'province_id' => $address->province_id,
  318. 'city_id' => $address->city_id,
  319. 'district_id' => $address->area_id,
  320. ];
  321. OrderAddress::create($orderAddressData);
  322. // 减库存
  323. foreach ($goodsList as $index => $item) {
  324. if ($item->sku) {
  325. $item->sku->setDec('stocks', $item->nums);
  326. }
  327. $item->goods->setDec("stocks", $item->nums);
  328. }
  329. // 计算单个商品折扣后的价格 (基于新字段名)
  330. $saleamount = bcsub($order['amount'], $order['express_fee'], 2);
  331. $saleratio = $order['goods_price'] > 0 ? bcdiv($saleamount, $order['goods_price'], 10) : 1;
  332. $saleremains = $saleamount;
  333. foreach ($orderItem as $index => &$item) {
  334. if (!isset($orderItem[$index + 1])) {
  335. $saleprice = $saleremains;
  336. } else {
  337. $saleprice = $order['discount_fee'] == 0 ? bcmul($item['goods_original_price'], $item['nums'], 2) : bcmul(bcmul($item['goods_original_price'], $item['nums'], 2), $saleratio, 2);
  338. }
  339. $saleremains = bcsub($saleremains, $saleprice, 2);
  340. $item['goods_price'] = $saleprice;
  341. }
  342. unset($item);
  343. // 批量创建订单商品数据
  344. if (!empty($orderItem)) {
  345. (new OrderGoods())->saveAll($orderItem);
  346. }
  347. // 修改地址使用次数
  348. if ($address) {
  349. $address->setInc('used_nums');
  350. }
  351. // 优惠券已使用
  352. if (!empty($userCoupon)) {
  353. $userCoupon->save(['is_used' => 2]);
  354. }
  355. // 提交事务
  356. Db::commit();
  357. } catch (Exception $e) {
  358. Db::rollback();
  359. throw new Exception($e->getMessage());
  360. }
  361. // 记录操作
  362. OrderActionService::recordUserAction(
  363. $orderInfo['order_sn'],
  364. OrderActionEnum::ACTION_CREATE,
  365. $orderInfo['user_id'],
  366. '创建订单',
  367. $orderInfo['user_id']
  368. );
  369. // 订单应付金额为0时直接结算
  370. if ($order['amount'] == 0) {
  371. // Order::settle($order->order_sn, 0);
  372. // $order = Order::get($order->id);
  373. return $order;
  374. }
  375. return $order;
  376. }
  377. /**
  378. * 验证商品规格参数
  379. * @param array $goods_list 商品列表
  380. * @throws Exception
  381. */
  382. public static function validateGoodsList($goods_list)
  383. {
  384. if (empty($goods_list) || !is_array($goods_list)) {
  385. throw new Exception("商品列表不能为空");
  386. }
  387. foreach ($goods_list as $item) {
  388. if (!isset($item['goods_id']) || !is_numeric($item['goods_id']) || $item['goods_id'] <= 0) {
  389. throw new Exception("商品ID无效");
  390. }
  391. if (!isset($item['nums']) || !is_numeric($item['nums']) || $item['nums'] <= 0) {
  392. throw new Exception("商品数量必须大于0");
  393. }
  394. if (isset($item['goods_sku_id']) && !is_numeric($item['goods_sku_id'])) {
  395. throw new Exception("商品规格ID无效");
  396. }
  397. }
  398. }
  399. /**
  400. * 统一的订单计算方法(用于预览订单)
  401. * @param array $goods_list 标准化的商品列表
  402. * @param int $user_id 用户ID
  403. * @param int $area_id 地区ID
  404. * @param int $user_coupon_id 优惠券ID
  405. * @return array
  406. * @throws Exception
  407. */
  408. public static function calculateOrder($goodsList, $userId, $areaId = 0, $userCouponId = 0)
  409. {
  410. if (empty($goodsList)) {
  411. throw new Exception("商品列表不能为空");
  412. }
  413. // 验证商品列表格式
  414. self::validateGoodsList($goodsList);
  415. // 订单基础信息(用于计算,不包含订单号)
  416. $orderInfo = [
  417. 'amount' => 0, // 应付金额
  418. 'goods_price' => 0, // 商品金额 (不含运费)
  419. 'goods_num' => 0, // 商品数量
  420. 'discount_fee' => 0, // 总优惠金额
  421. 'coupon_discount_fee' => 0, // 优惠券金额
  422. 'promo_discount_fee' => 0, // 营销金额
  423. 'order_amount' => 0, // 总金额 (含运费)
  424. 'express_fee' => 0, // 运费
  425. ];
  426. // 计算商品明细
  427. list($orderItem, $calculatedGoodsList, $userCoupon) = self::computeGoods($orderInfo, $goodsList, $userId, $areaId, $userCouponId);
  428. return [
  429. 'orderItem' => $orderItem,
  430. 'goodsList' => $calculatedGoodsList,
  431. 'orderInfo' => $orderInfo,
  432. 'userCoupon' => $userCoupon
  433. ];
  434. }
  435. /**
  436. * 订单列表
  437. *
  438. * @param $param
  439. * @return \think\Paginator
  440. */
  441. public static function getOrderList($userId = 0, $param =[],$status = [],$supplierId = 0)
  442. {
  443. $pageSize = 10;
  444. if (!empty($param['pageSize'])) {
  445. $pageSize = $param['pageSize'];
  446. }
  447. return Order::with(['orderGoods'])
  448. ->where(function ($query) use ($param,$userId,$status) {
  449. if (!empty($userId)) {
  450. $query->where('user_id', $userId);
  451. }
  452. if (!empty($status)) {
  453. $query->whereIn('order_status', $status );
  454. }
  455. if (isset($param['keywords']) && $param['keywords'] != '') {
  456. $query->where('order_sn', 'in', function ($query) use ($param) {
  457. return $query->name('shop_order_goods')->where('order_sn|goods_title', 'like', '%' . $param['q'] . '%')->field('order_sn');
  458. });
  459. }
  460. if (!empty($supplierId)) {
  461. $query->where('order_sn', 'in', function ($query) use ($supplierId) {
  462. return $query->name('shop_order_goods')->where('supplier_id', $supplierId)->field('order_sn');
  463. });
  464. }
  465. })
  466. ->order('createtime desc')
  467. ->paginate($pageSize, false, ['query' => request()->get()]);
  468. }
  469. /**
  470. *
  471. * @ 订单信息
  472. * @param $orderId
  473. * @param $userId
  474. * @return array|false|\PDOStatement|string|Model
  475. */
  476. public static function getDetail($orderId = 0, $userId = 0)
  477. {
  478. return Order::with(['orderGoods'])
  479. ->where('id', $orderId)
  480. ->where(function($query) use ($userId){
  481. if($userId > 0){
  482. $query->where('user_id', $userId);
  483. }
  484. })
  485. ->find();
  486. }
  487. public static function getDetailByOrderSn($orderSn)
  488. {
  489. return Order::with(['orderGoods'])
  490. ->where('order_sn', $orderSn)
  491. ->find();
  492. }
  493. // 查询地址信息
  494. public static function getAddressInfo($orderId)
  495. {
  496. return OrderAddress::where('order_id', $orderId)->find();
  497. }
  498. /**
  499. * 判断订单是否失效
  500. * @param $order_sn
  501. * @return bool
  502. */
  503. public static function isExpired($orderSn)
  504. {
  505. $orderInfo = self::getByOrderSn($orderSn);
  506. //订单过期
  507. if (!$orderInfo['orderstate'] && !$orderInfo['paystate'] && time() > $orderInfo['expiretime']) {
  508. // 启动事务
  509. Db::startTrans();
  510. try {
  511. $orderInfo->save(['orderstate' => 2]);
  512. //库存恢复
  513. OrderGoods::setGoodsStocksInc($orderInfo->order_sn);
  514. //恢复优惠券
  515. UserCoupon::resetUserCoupon($orderInfo->user_coupon_id, $orderInfo->order_sn);
  516. // 提交事务
  517. Db::commit();
  518. } catch (\Exception $e) {
  519. // 回滚事务
  520. Db::rollback();
  521. }
  522. return true;
  523. }
  524. return false;
  525. }
  526. public static function getByOrderSn($orderSn)
  527. {
  528. return Order::where('order_sn', $orderSn)->find();
  529. }
  530. public static function getByOrderId($orderId)
  531. {
  532. return Order::where('id', $orderId)->find();
  533. }
  534. // 获取状态订单统计
  535. public static function getOrderStatusCount($userId = 0)
  536. {
  537. $info = [];
  538. $info['unpay'] = Order::where('user_id', $userId)->where('order_status',OrderEnum::STATUS_CREATE)->count();
  539. $info['unsend'] = Order::where('user_id', $userId)->where('order_status',OrderEnum::STATUS_INSPECTION_PASS)->count();
  540. $info['unrec'] = Order::where('user_id', $userId)->where('order_status',OrderEnum::STATUS_SHIP)->count();
  541. $info['uneva'] = Order::where('user_id', $userId)->where('order_status',OrderEnum::STATUS_CONFIRM)->count();
  542. $info['inspect'] = Order::where('user_id', $userId)
  543. ->whereIn('order_status',[
  544. OrderEnum::STATUS_PAY,
  545. OrderEnum::STATUS_INSPECTION,
  546. OrderEnum::STATUS_INSPECTION_FAIL])
  547. ->count();
  548. return $info;
  549. }
  550. // 更新订单状态
  551. public static function updateOrderStatus($orderId = 0, $userId = 0, $status = 0)
  552. {
  553. $order = self::getDetail($orderId, $userId);
  554. if (!$order) {
  555. throw new BusinessException('订单不存在!');
  556. }
  557. // 验证状态
  558. if (!OrderEnum::isValidOrderStatus($status)) {
  559. throw new BusinessException('状态不合法!');
  560. }
  561. // 要处理每个状态对应的时间字段在枚举类中
  562. $timeField = OrderEnum::STATUS_TIME_MAP[$status];
  563. $updateData = [
  564. 'order_status' => $status,
  565. $timeField => time()
  566. ];
  567. Order::where('id', $orderId)->update($updateData);
  568. return $order;
  569. }
  570. /**
  571. * 获取供应商订单列表 - 关联查询方式
  572. * @param int $supplierId 供应商ID
  573. * @param int $userId 用户ID(可选)
  574. * @param array $param 查询参数
  575. * @param array $status 订单状态筛选
  576. * @return \think\Paginator
  577. */
  578. public static function getSupplierOrderList($supplierId, $userId = 0, $param = [], $status = [])
  579. {
  580. $pageSize = $param['page_size'] ?? 10;
  581. return Order::alias('o')
  582. ->join('shop_order_goods og', 'o.order_sn = og.order_sn', 'inner')
  583. ->with(['orderGoods' => function($query) use ($supplierId) {
  584. $query->where('supplier_id', $supplierId);
  585. }])
  586. ->where('og.supplier_id', $supplierId)
  587. ->where(function ($query) use ($param, $userId, $status) {
  588. if (!empty($userId)) {
  589. $query->where('o.user_id', $userId);
  590. }
  591. if (!empty($status)) {
  592. $query->whereIn('o.order_status', $status);
  593. }
  594. if (isset($param['keywords']) && $param['keywords'] != '') {
  595. $query->where(function($subQuery) use ($param) {
  596. $subQuery->where('o.order_sn', 'like', '%' . $param['keywords'] . '%')
  597. ->whereOr('og.goods_title', 'like', '%' . $param['keywords'] . '%');
  598. });
  599. }
  600. })
  601. ->field('o.*')
  602. ->group('o.id')
  603. ->order('o.createtime desc')
  604. ->paginate($pageSize, false, ['query' => request()->get()]);
  605. }
  606. /**
  607. * 获取供应商订单详情 - 支持订单ID和订单号两种方式
  608. * @param mixed $orderParam 订单ID或订单号
  609. * @param int $supplierId 供应商ID
  610. * @param int $userId 用户ID(可选,用于权限验证)
  611. * @return array|null
  612. */
  613. public static function getSupplierOrderDetail($orderParam, $supplierId, $userId = 0)
  614. {
  615. // 根据参数类型判断是订单ID还是订单号
  616. if (is_numeric($orderParam)) {
  617. // 数字类型,当作订单ID处理
  618. $query = Order::where('id', $orderParam);
  619. } else {
  620. // 字符串类型,当作订单号处理
  621. $query = Order::where('order_sn', $orderParam);
  622. }
  623. // 如果指定了用户ID,添加用户权限验证
  624. if (!empty($userId)) {
  625. $query->where('user_id', $userId);
  626. }
  627. $order = $query->find();
  628. if (!$order) {
  629. return null;
  630. }
  631. // 获取该供应商在此订单中的商品
  632. $orderGoods = OrderGoods::where('order_sn', $order->order_sn)
  633. ->where('supplier_id', $supplierId)
  634. ->select();
  635. // 如果该供应商在此订单中没有商品,返回null
  636. if (empty($orderGoods)) {
  637. return null;
  638. }
  639. $order->order_goods = $orderGoods;
  640. return $order;
  641. }
  642. /**
  643. * 获取供应商订单统计
  644. * @param int $supplierId 供应商ID
  645. * @param int $userId 用户ID(可选)
  646. * @return array
  647. */
  648. public static function getSupplierOrderStatusCount($supplierId, $userId = 0)
  649. {
  650. $baseQuery = function($status) use ($supplierId, $userId) {
  651. $query = Order::alias('o')
  652. ->join('shop_order_goods og', 'o.order_sn = og.order_sn', 'inner')
  653. ->where('og.supplier_id', $supplierId)
  654. ->where('o.order_status', $status);
  655. if (!empty($userId)) {
  656. $query->where('o.user_id', $userId);
  657. }
  658. return $query->count('DISTINCT o.id');
  659. };
  660. return [
  661. 'unpay' => $baseQuery(OrderEnum::STATUS_CREATE), // 待付款
  662. 'unsend' => $baseQuery(OrderEnum::STATUS_PAY), // 待发货
  663. 'unrec' => $baseQuery(OrderEnum::STATUS_SHIP), // 待收货
  664. 'uneva' => $baseQuery(OrderEnum::STATUS_CONFIRM), // 待评价
  665. ];
  666. }
  667. /**
  668. * 验证订单和订单商品是否存在
  669. * @param int $orderId 订单ID
  670. * @param int $orderGoodsId 订单商品ID
  671. * @return array|false 返回订单和订单商品信息,不存在返回false
  672. */
  673. public static function validateOrderAndOrderGoods($orderId, $orderGoodsId)
  674. {
  675. // 查询订单
  676. $order = Order::where('id', $orderId)->find();
  677. if (!$order) {
  678. return false;
  679. }
  680. // 查询订单商品
  681. $orderGoods = OrderGoods::where('id', $orderGoodsId)
  682. ->where('order_id', $orderId)
  683. ->find();
  684. if (!$orderGoods) {
  685. return false;
  686. }
  687. return [
  688. 'order' => $order,
  689. 'order_goods' => $orderGoods
  690. ];
  691. }
  692. /**
  693. * 更新订单商品的验收状态
  694. * @param int $orderGoodsId 订单商品ID
  695. * @param int $inspectStatus 验收状态 0:待验收 1:验收通过 2:验收不通过
  696. * @return bool
  697. */
  698. public static function updateOrderGoodsInspectStatus($orderGoodsId, $inspectStatus)
  699. {
  700. return OrderGoods::where('id', $orderGoodsId)->update([
  701. 'inspect_status' => $inspectStatus,
  702. 'inspect_time' => time()
  703. ]);
  704. }
  705. /**
  706. * 检查订单中所有商品的验收状态
  707. * @param int $orderId 订单ID
  708. * @return array 返回验收状态统计
  709. */
  710. public static function checkOrderInspectStatus($orderId)
  711. {
  712. $orderGoods = OrderGoods::where('order_id', $orderId)->select();
  713. $statusCount = [
  714. 'total' => count($orderGoods), // 总商品数
  715. 'pending' => 0, // 待验收
  716. 'passed' => 0, // 验收通过
  717. 'failed' => 0 // 验收不通过
  718. ];
  719. foreach ($orderGoods as $goods) {
  720. switch ($goods['inspect_status']) {
  721. case 0:
  722. $statusCount['pending']++;
  723. break;
  724. case 1:
  725. $statusCount['passed']++;
  726. break;
  727. case 2:
  728. $statusCount['failed']++;
  729. break;
  730. }
  731. }
  732. return $statusCount;
  733. }
  734. /**
  735. * 根据验收状态决定是否更新订单状态
  736. * @param int $orderId 订单ID
  737. * @param int $userId 用户ID
  738. * @return bool
  739. */
  740. public static function updateOrderStatusByInspectResult($orderId, $userId = 0)
  741. {
  742. $inspectStatus = self::checkOrderInspectStatus($orderId);
  743. // 如果所有商品都验收完成(没有待验收的商品)
  744. if ($inspectStatus['pending'] == 0) {
  745. // 如果所有商品都验收通过
  746. if ($inspectStatus['failed'] == 0 && $inspectStatus['passed'] > 0) {
  747. // 更新订单状态为验收通过
  748. return self::updateOrderStatus($orderId, $userId, OrderEnum::STATUS_INSPECTION_PASS);
  749. }
  750. // 如果所有商品都验收不通过
  751. elseif ($inspectStatus['failed'] > 0 && $inspectStatus['passed'] == 0) {
  752. // 更新订单状态为验收失败
  753. return self::updateOrderStatus($orderId, $userId, OrderEnum::STATUS_INSPECTION_FAIL);
  754. }
  755. // 如果部分商品验收通过,部分不通过
  756. elseif ($inspectStatus['failed'] > 0 && $inspectStatus['passed'] > 0) {
  757. // 混合状态,由业务人员手动处理,不自动更新订单状态
  758. return true;
  759. }
  760. }
  761. return true;
  762. }
  763. /**
  764. * 更新订单商品的发货状态
  765. * @param int $orderGoodsId 订单商品ID
  766. * @param string $expressName 快递公司
  767. * @param string $expressNo 快递单号
  768. * @param array $expressImage 快递图片
  769. * @return bool
  770. */
  771. public static function updateOrderGoodsDeliveryStatus($orderGoodsId, $expressName, $expressNo, $expressImage = [])
  772. {
  773. $updateData = [
  774. 'express_name' => $expressName,
  775. 'express_no' => $expressNo,
  776. 'express_image' => is_array($expressImage) ? json_encode($expressImage) : $expressImage,
  777. 'delivery_status' => 1, // 已发货
  778. 'updatetime' => time()
  779. ];
  780. return OrderGoods::where('id', $orderGoodsId)->update($updateData);
  781. }
  782. /**
  783. * 检查订单中所有商品的发货状态
  784. * @param int $orderId 订单ID
  785. * @return array 返回发货状态统计
  786. */
  787. public static function checkOrderDeliveryStatus($orderId)
  788. {
  789. $orderGoods = OrderGoods::where('order_id', $orderId)->select();
  790. $statusCount = [
  791. 'total' => count($orderGoods), // 总商品数
  792. 'pending' => 0, // 待发货
  793. 'delivered' => 0, // 已发货
  794. ];
  795. foreach ($orderGoods as $goods) {
  796. if ($goods['delivery_status'] == 1) {
  797. $statusCount['delivered']++;
  798. } else {
  799. $statusCount['pending']++;
  800. }
  801. }
  802. return $statusCount;
  803. }
  804. /**
  805. * 根据发货状态决定是否更新订单状态
  806. * @param int $orderId 订单ID
  807. * @param int $userId 用户ID
  808. * @return bool
  809. */
  810. public static function updateOrderStatusByDeliveryResult($orderId, $userId = 0)
  811. {
  812. $deliveryStatus = self::checkOrderDeliveryStatus($orderId);
  813. // 如果所有商品都已发货(没有待发货的商品)
  814. if ($deliveryStatus['pending'] == 0 && $deliveryStatus['delivered'] > 0) {
  815. // 更新订单状态为已发货
  816. return self::updateOrderStatus($orderId, $userId, OrderEnum::STATUS_SHIP);
  817. }
  818. return true;
  819. }
  820. /**
  821. * 验证订单商品是否可以发货
  822. * @param int $orderId 订单ID
  823. * @param int $orderGoodsId 订单商品ID
  824. * @return array|false 返回订单和订单商品信息,验证失败返回false
  825. */
  826. public static function validateOrderGoodsForDelivery($orderId, $orderGoodsId)
  827. {
  828. // 验证订单和订单商品是否存在
  829. $orderData = self::validateOrderAndOrderGoods($orderId, $orderGoodsId);
  830. if (!$orderData) {
  831. return false;
  832. }
  833. $orderGoods = $orderData['order_goods'];
  834. // 检查订单商品的状态是都验货通过才可以发货
  835. if ($orderGoods['inspect_status'] !== 1) {
  836. return false;
  837. }
  838. // 检查该订单商品是否已经发货
  839. if ($orderGoods['delivery_status'] == 1) {
  840. return false;
  841. }
  842. return $orderData;
  843. }
  844. }