OrderService.php 54 KB

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