OrderService.php 42 KB

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