DiscountService.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. <?php
  2. namespace app\common\Service;
  3. use think\Db;
  4. use think\Model;
  5. use app\common\Enum\StatusEnum;
  6. use app\common\Enum\ActivityEnum;
  7. use app\common\Enum\GoodsEnum;
  8. /**
  9. * 折扣活动服务类
  10. *
  11. * 外层折扣字段说明:
  12. * - has_discount: 是否有折扣活动
  13. * - discount_price: 折扣价格(单规格商品直接使用,多规格商品显示最低价)
  14. * - discount: 折扣率(单规格商品直接使用,多规格商品显示最低折扣)
  15. * - discount_amount: 折扣金额(原价-折扣价,仅单规格商品)
  16. * - min_discount_price: 最低折扣价格
  17. * - max_discount_price: 最高折扣价格
  18. * - min_discount: 最低折扣率
  19. * - max_discount: 最高折扣率
  20. * - price_range: 价格区间文本(如:¥99 或 ¥99-199)
  21. */
  22. class DiscountService
  23. {
  24. /**
  25. * 查询当前时间段内的商品折扣信息
  26. * @param array $goodsIds 商品ID数组,为空则查询所有参与折扣的商品
  27. * @param int $limit 限制数量
  28. * @return array
  29. */
  30. public static function getCurrentDiscountGoods($goodsIds = [], $limit = 0)
  31. {
  32. // 第一步:查询当前时间有效的活动ID
  33. $activeActivityIds = self::getActiveActivityIds();
  34. if (empty($activeActivityIds)) {
  35. return [];
  36. }
  37. // 第二步:查询这些活动的SKU数据
  38. $skuData = Db::table('shop_activity_sku')
  39. ->where('activity_id', 'in', $activeActivityIds)
  40. ->where('stocks', '>', 0) // 有折扣库存的商品
  41. ->field([
  42. 'id as activity_sku_id',
  43. 'goods_id',
  44. 'sku_id',
  45. 'activity_id',
  46. 'discount',
  47. 'discount_price',
  48. 'stocks as discount_stocks',
  49. 'sale_quantity'
  50. ])
  51. ->order('discount', 'asc')
  52. ->select();
  53. if (empty($skuData)) {
  54. return [];
  55. }
  56. // 收集所有商品ID和SKU ID
  57. $skuDataArray = collection($skuData)->toArray();
  58. $allGoodsIds = array_unique(array_column($skuDataArray, 'goods_id'));
  59. $allSkuIds = array_filter(array_unique(array_column($skuDataArray, 'sku_id')));
  60. // 如果指定了商品ID,则过滤
  61. if (!empty($goodsIds)) {
  62. $allGoodsIds = array_intersect($allGoodsIds, $goodsIds);
  63. if (empty($allGoodsIds)) {
  64. return [];
  65. }
  66. }
  67. // 第三步:批量查询商品信息
  68. $goodsInfo = Db::table('shop_goods')
  69. ->where('id', 'in', $allGoodsIds)
  70. ->where('status', GoodsEnum::STATUS_ON_SALE) // 已上架的商品
  71. ->column('id,title,sub_title,image,price,market_price,stocks,sales,spec_type', 'id');
  72. // 第四步:批量查询SKU信息(如果有多规格商品)
  73. $skuInfo = [];
  74. if (!empty($allSkuIds)) {
  75. $skuInfo = Db::table('shop_goods_sku')
  76. ->where('id', 'in', $allSkuIds)
  77. ->column('id,price,image', 'id');
  78. }
  79. // 第五步:批量查询活动信息
  80. $activityInfo = Db::table('shop_activity')
  81. ->where('id', 'in', $activeActivityIds)
  82. ->column('id,name,start_time,end_time', 'id');
  83. // 过滤掉不存在的商品
  84. $filteredSkuData = array_filter($skuDataArray, function($item) use ($goodsInfo) {
  85. return isset($goodsInfo[$item['goods_id']]);
  86. });
  87. // 如果设置了限制数量
  88. if ($limit > 0) {
  89. $filteredSkuData = array_slice($filteredSkuData, 0, $limit);
  90. }
  91. // 处理返回数据
  92. $processedData = [];
  93. foreach ($filteredSkuData as $item) {
  94. $goodsId = $item['goods_id'];
  95. $skuId = $item['sku_id'];
  96. $activityId = $item['activity_id'];
  97. // 获取商品信息
  98. $goods = $goodsInfo[$goodsId];
  99. // 如果商品还没有在结果中,初始化
  100. if (!isset($processedData[$goodsId])) {
  101. // 构造商品基础信息
  102. $goodsItem = [
  103. 'goods_id' => $goodsId,
  104. 'title' => $goods['title'],
  105. 'sub_title' => $goods['sub_title'],
  106. 'image' => $goods['image'],
  107. 'original_price' => $goods['price'],
  108. 'market_price' => $goods['market_price'],
  109. 'goods_stocks' => $goods['stocks'],
  110. 'sales' => $goods['sales'],
  111. 'spec_type' => $goods['spec_type'],
  112. 'activity_id' => $activityId,
  113. 'activity_name' => $activityInfo[$activityId]['name'] ?? '',
  114. 'start_time' => $activityInfo[$activityId]['start_time'] ?? null,
  115. 'end_time' => $activityInfo[$activityId]['end_time'] ?? null,
  116. ];
  117. $processedData[$goodsId] = self::initGoodsDiscountStructure($goodsItem);
  118. }
  119. // 添加折扣信息
  120. $discountInfo = [
  121. 'activity_sku_id' => $item['activity_sku_id'],
  122. 'sku_id' => $skuId,
  123. 'discount' => $item['discount'],
  124. 'discount_price' => $item['discount_price'],
  125. 'discount_stocks' => $item['discount_stocks'],
  126. 'sale_quantity' => $item['sale_quantity']
  127. ];
  128. // 如果是多规格商品,添加规格信息
  129. if ($skuId > 0 && isset($skuInfo[$skuId])) {
  130. $discountInfo['sku_price'] = $skuInfo[$skuId]['price'];
  131. $discountInfo['sku_image'] = $skuInfo[$skuId]['image'];
  132. }
  133. $processedData[$goodsId]['discount_info'][] = $discountInfo;
  134. }
  135. // 处理外层折扣价格字段
  136. $processedData = self::processGoodsDiscountData($processedData);
  137. return array_values($processedData);
  138. }
  139. /**
  140. * 新出一个方法 通过传入商品id 数组 查询 shop_activity 表中的 goods_ids 来查询是否有在进行中的 活动
  141. * @param mixed $goodsIds
  142. * @return mixed|string|null
  143. */
  144. public static function getActivityByGoodsIds($goodsIds = [])
  145. {
  146. if (empty($goodsIds)) {
  147. return null;
  148. }
  149. // 获取所有进行中的活动
  150. $activities = Db::table('shop_activity')
  151. ->where('start_time', '<=', time())
  152. ->where('end_time', '>=', time())
  153. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  154. ->select();
  155. // 在PHP中过滤匹配的活动
  156. foreach ($activities as $activity) {
  157. if (empty($activity['goods_ids'])) {
  158. continue;
  159. }
  160. // 解析JSON数组
  161. $activityGoodsIds = json_decode($activity['goods_ids'], true);
  162. if (!is_array($activityGoodsIds)) {
  163. continue;
  164. }
  165. // 检查是否有商品ID匹配
  166. $intersection = array_intersect($goodsIds, $activityGoodsIds);
  167. if (!empty($intersection)) {
  168. return $activity;
  169. }
  170. }
  171. return null;
  172. }
  173. /**
  174. * 获取商品ID与活动的映射关系
  175. * @param array $goodsIds 商品ID数组
  176. * @return array 返回商品ID与活动信息的映射关系
  177. */
  178. public static function getGoodsActivityMapping($goodsIds = [])
  179. {
  180. if (empty($goodsIds)) {
  181. return [];
  182. }
  183. // 获取所有进行中的活动
  184. $activities = Db::table('shop_activity')
  185. ->where('start_time', '<=', time())
  186. ->where('end_time', '>=', time())
  187. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  188. ->select();
  189. $goodsActivityMapping = [];
  190. // 为每个商品ID初始化空的活动信息
  191. foreach ($goodsIds as $goodsId) {
  192. $goodsActivityMapping[$goodsId] = null;
  193. }
  194. // 在PHP中匹配商品与活动
  195. foreach ($activities as $activity) {
  196. if (empty($activity['goods_ids'])) {
  197. continue;
  198. }
  199. // 解析JSON数组
  200. $activityGoodsIds = json_decode($activity['goods_ids'], true);
  201. if (!is_array($activityGoodsIds)) {
  202. continue;
  203. }
  204. // 为匹配的商品ID设置活动信息
  205. foreach ($activityGoodsIds as $activityGoodsId) {
  206. if (in_array($activityGoodsId, $goodsIds)) {
  207. $goodsActivityMapping[$activityGoodsId] = $activity;
  208. }
  209. }
  210. }
  211. return $goodsActivityMapping;
  212. }
  213. /**
  214. * 查询单个商品是否参与正在进行的活动
  215. * @param int $goodsId 商品ID
  216. * @return array|null 返回活动信息,如果没有参与活动则返回null
  217. *
  218. * 使用示例:
  219. * $goodsId = 47;
  220. * $activity = DiscountService::getGoodsActivity($goodsId);
  221. * if ($activity) {
  222. * echo "商品参与活动:" . $activity['activity_name'];
  223. * echo "活动ID:" . $activity['id'];
  224. * echo "活动类型:" . $activity['activity_type'];
  225. * } else {
  226. * echo "商品未参与任何活动";
  227. * }
  228. */
  229. public static function getGoodsActivity($goodsId)
  230. {
  231. if (empty($goodsId)) {
  232. return null;
  233. }
  234. // 获取所有进行中的活动
  235. $activities = Db::table('shop_activity')
  236. ->where('start_time', '<=', time())
  237. ->where('end_time', '>=', time())
  238. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  239. ->select();
  240. // 在PHP中查找匹配的活动
  241. foreach ($activities as $activity) {
  242. if (empty($activity['goods_ids'])) {
  243. continue;
  244. }
  245. // 解析JSON数组
  246. $activityGoodsIds = json_decode($activity['goods_ids'], true);
  247. if (!is_array($activityGoodsIds)) {
  248. continue;
  249. }
  250. // 检查商品ID是否在活动的商品列表中
  251. if (in_array($goodsId, $activityGoodsIds)) {
  252. return $activity;
  253. }
  254. }
  255. return null;
  256. }
  257. /**
  258. * 检查单个商品是否参与正在进行的活动(简化版本,只返回true/false)
  259. * @param int $goodsId 商品ID
  260. * @return bool 是否参与活动
  261. *
  262. * 使用示例:
  263. * $goodsId = 47;
  264. * if (DiscountService::hasActivity($goodsId)) {
  265. * echo "商品正在参与活动";
  266. * } else {
  267. * echo "商品未参与活动";
  268. * }
  269. */
  270. public static function hasActivity($goodsId)
  271. {
  272. return self::getGoodsActivity($goodsId) !== null;
  273. }
  274. /**
  275. * 查询单个商品的折扣信息和对应的SKU折扣信息
  276. * @param int $goodsId 商品ID
  277. * @return array|null 商品折扣信息,包含所有SKU的折扣详情,如果没有折扣则返回null
  278. */
  279. public static function getGoodsDiscountDetail($goodsId)
  280. {
  281. // 第一步:查询当前时间有效的活动ID
  282. $activeActivityIds = self::getActiveActivityIds();
  283. if (empty($activeActivityIds)) {
  284. return null;
  285. }
  286. // 第二步:查询该商品的SKU折扣数据
  287. $result = Db::table('shop_activity_sku')
  288. ->alias('sku')
  289. ->join('shop_goods goods', 'sku.goods_id = goods.id')
  290. ->join('shop_goods_sku spec', 'sku.sku_id = spec.id', 'left')
  291. ->join(['shop_activity'=> 'activity'], 'sku.activity_id = activity.id')
  292. ->where('sku.goods_id', $goodsId)
  293. ->where('sku.activity_id', 'in', $activeActivityIds)
  294. ->where('goods.status', GoodsEnum::STATUS_ON_SALE) // 已上架的商品
  295. ->where('sku.stocks', '>', 0) // 有折扣库存的商品
  296. ->field([
  297. 'goods.id as goods_id',
  298. 'goods.title',
  299. 'goods.sub_title',
  300. 'goods.image',
  301. 'goods.price as original_price',
  302. 'goods.market_price',
  303. 'goods.stocks as goods_stocks',
  304. 'goods.sales',
  305. 'goods.spec_type',
  306. 'sku.id as activity_sku_id',
  307. 'sku.sku_id',
  308. 'sku.discount',
  309. 'sku.discount_price',
  310. 'sku.stocks as discount_stocks',
  311. 'sku.sale_quantity',
  312. 'activity.id as activity_id',
  313. 'activity.name as activity_name',
  314. 'activity.start_time',
  315. 'activity.end_time',
  316. 'spec.price as sku_price',
  317. 'spec.image as sku_image',
  318. // 'spec.specs as sku_specs'
  319. ])
  320. ->order('sku.discount', 'asc') // 按折扣力度排序,折扣越大越靠前
  321. ->select();
  322. if (empty($result)) {
  323. return null;
  324. }
  325. // 初始化商品折扣结构
  326. $goodsDiscountData = self::initGoodsDiscountStructure($result[0]);
  327. // 处理所有SKU的折扣信息
  328. foreach ($result as $item) {
  329. $skuId = $item['sku_id'];
  330. // 添加折扣信息
  331. $discountInfo = [
  332. 'activity_sku_id' => $item['activity_sku_id'],
  333. 'sku_id' => $skuId,
  334. 'discount' => $item['discount'],
  335. 'discount_price' => $item['discount_price'],
  336. 'discount_stocks' => $item['discount_stocks'],
  337. 'sale_quantity' => $item['sale_quantity']
  338. ];
  339. // 如果是多规格商品,添加规格信息
  340. if ($skuId > 0) {
  341. $discountInfo['sku_price'] = $item['sku_price'];
  342. $discountInfo['sku_image'] = $item['sku_image'];
  343. // $discountInfo['sku_specs'] = $item['sku_specs'];
  344. }
  345. $goodsDiscountData['discount_info'][] = $discountInfo;
  346. }
  347. // 处理外层折扣价格字段
  348. $discountPrices = [];
  349. $discounts = [];
  350. // 收集所有折扣价格和折扣率
  351. foreach ($goodsDiscountData['discount_info'] as $discount) {
  352. $discountPrices[] = $discount['discount_price'];
  353. $discounts[] = $discount['discount'];
  354. }
  355. if (!empty($discountPrices)) {
  356. // 计算最低和最高折扣价格
  357. $goodsDiscountData['min_discount_price'] = min($discountPrices);
  358. $goodsDiscountData['max_discount_price'] = max($discountPrices);
  359. $goodsDiscountData['min_discount'] = min($discounts);
  360. $goodsDiscountData['max_discount'] = max($discounts);
  361. // 单规格商品处理
  362. if ($goodsDiscountData['spec_type'] == 0) {
  363. $goodsDiscountData['discount_price'] = $goodsDiscountData['min_discount_price'];
  364. $goodsDiscountData['discount'] = $goodsDiscountData['min_discount'];
  365. $goodsDiscountData['discount_amount'] = round($goodsDiscountData['original_price'] - $goodsDiscountData['discount_price'], 2);
  366. $goodsDiscountData['price_range'] = '¥' . $goodsDiscountData['discount_price'];
  367. }
  368. // 多规格商品处理
  369. else {
  370. // 设置价格区间
  371. if ($goodsDiscountData['min_discount_price'] == $goodsDiscountData['max_discount_price']) {
  372. $goodsDiscountData['price_range'] = '¥' . $goodsDiscountData['min_discount_price'];
  373. } else {
  374. $goodsDiscountData['price_range'] = '¥' . $goodsDiscountData['min_discount_price'] . '-' . $goodsDiscountData['max_discount_price'];
  375. }
  376. // 对于多规格,使用最低价格作为主要显示价格
  377. $goodsDiscountData['discount_price'] = $goodsDiscountData['min_discount_price'];
  378. $goodsDiscountData['discount'] = $goodsDiscountData['min_discount'];
  379. }
  380. }
  381. $goodsDiscountData['remaining_time'] = self::formatRemainingTime($goodsDiscountData['end_time']);
  382. return $goodsDiscountData;
  383. }
  384. /**
  385. * 限时查询一个当前活动的信息和商品信息 活动信息直接加一个商品数组字段
  386. * @param mixed $activityId
  387. * @return array|bool|Model|string|\PDOStatement|null
  388. */
  389. public static function getCurrentActivity()
  390. {
  391. $currentTime = time();
  392. // 查询活动信息
  393. $activity = Db::table('shop_activity')
  394. // ->where('id', $activityId)
  395. ->where('start_time', '<=', $currentTime)
  396. ->where('end_time', '>=', $currentTime)
  397. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  398. ->where('status', StatusEnum::ENABLED)
  399. ->find();
  400. if (!$activity) {
  401. return null;
  402. }
  403. // 查询该活动下的商品信息
  404. $goods = Db::table('shop_activity_sku')
  405. ->alias('sku')
  406. ->join('shop_goods goods', 'sku.goods_id = goods.id')
  407. ->join('shop_goods_sku spec', 'sku.sku_id = spec.id', 'left')
  408. ->where('sku.activity_id', $activity['id'])
  409. ->where('goods.status', GoodsEnum::STATUS_ON_SALE)
  410. ->where('sku.stocks', '>', 0)
  411. ->field([
  412. 'goods.id as goods_id',
  413. 'goods.title',
  414. 'goods.sub_title',
  415. 'goods.image',
  416. 'goods.price as original_price',
  417. 'goods.market_price',
  418. 'goods.stocks as goods_stocks',
  419. 'goods.sales',
  420. 'goods.spec_type',
  421. 'sku.id as activity_sku_id',
  422. 'sku.sku_id',
  423. 'sku.discount',
  424. 'sku.discount_price',
  425. 'sku.stocks as discount_stocks',
  426. 'sku.sale_quantity',
  427. 'spec.price as sku_price',
  428. 'spec.image as sku_image'
  429. ])
  430. ->order('sku.discount', 'asc')
  431. ->select();
  432. // 处理商品数据
  433. $processedGoods = [];
  434. foreach ($goods as $item) {
  435. $goodsId = $item['goods_id'];
  436. if (!isset($processedGoods[$goodsId])) {
  437. $processedGoods[$goodsId] = self::initGoodsDiscountStructure($item);
  438. }
  439. $discountInfo = [
  440. 'activity_sku_id' => $item['activity_sku_id'],
  441. 'sku_id' => $item['sku_id'],
  442. 'discount' => $item['discount'],
  443. 'discount_price' => $item['discount_price'],
  444. 'discount_stocks' => $item['discount_stocks'],
  445. 'sale_quantity' => $item['sale_quantity']
  446. ];
  447. if ($item['sku_id'] > 0) {
  448. $discountInfo['sku_price'] = $item['sku_price'];
  449. $discountInfo['sku_image'] = $item['sku_image'];
  450. }
  451. $processedGoods[$goodsId]['discount_info'][] = $discountInfo;
  452. }
  453. // 处理外层折扣价格字段
  454. foreach ($processedGoods as &$goodsItem) {
  455. $discountPrices = [];
  456. $discounts = [];
  457. // 收集所有折扣价格和折扣率
  458. foreach ($goodsItem['discount_info'] as $discount) {
  459. $discountPrices[] = $discount['discount_price'];
  460. $discounts[] = $discount['discount'];
  461. }
  462. if (!empty($discountPrices)) {
  463. // 计算最低和最高折扣价格
  464. $goodsItem['min_discount_price'] = min($discountPrices);
  465. $goodsItem['max_discount_price'] = max($discountPrices);
  466. $goodsItem['min_discount'] = min($discounts);
  467. $goodsItem['max_discount'] = max($discounts);
  468. // 单规格商品处理
  469. if ($goodsItem['spec_type'] == 0) {
  470. $goodsItem['discount_price'] = $goodsItem['min_discount_price'];
  471. $goodsItem['discount'] = $goodsItem['min_discount'];
  472. $goodsItem['discount_amount'] = max(0, round($goodsItem['original_price'] - $goodsItem['discount_price'], 2));
  473. $goodsItem['price_range'] = '¥' . $goodsItem['discount_price'];
  474. }
  475. // 多规格商品处理
  476. else {
  477. // 设置价格区间
  478. if ($goodsItem['min_discount_price'] == $goodsItem['max_discount_price']) {
  479. $goodsItem['price_range'] = '¥' . $goodsItem['min_discount_price'];
  480. } else {
  481. $goodsItem['price_range'] = '¥' . $goodsItem['min_discount_price'] . '-' . $goodsItem['max_discount_price'];
  482. }
  483. // 对于多规格,使用最低价格作为主要显示价格
  484. $goodsItem['discount_price'] = $goodsItem['min_discount_price'];
  485. $goodsItem['discount'] = $goodsItem['min_discount'];
  486. }
  487. }
  488. }
  489. // 处理图像的
  490. foreach ($processedGoods as &$goodsItem) {
  491. $goodsItem['image'] = cdnurl($goodsItem['image'], true);
  492. }
  493. // 在活动信息中添加商品数组字段
  494. $activity['goods'] = array_values($processedGoods);
  495. return $activity;
  496. }
  497. /**
  498. * 根据商品ID和SKU ID获取折扣信息
  499. * @param int $goodsId 商品ID
  500. * @param int $skuId SKU ID,单规格商品传0
  501. * @return array|null
  502. */
  503. public static function getGoodsDiscountInfo($goodsId, $skuId = 0)
  504. {
  505. $currentTime = time();
  506. // 第一步:查询当前时间有效的活动ID
  507. $activeActivityIds = Db::table('shop_activity')
  508. ->where('start_time', '<=', $currentTime)
  509. ->where('end_time', '>=', $currentTime)
  510. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  511. ->where('status', StatusEnum::ENABLED)
  512. ->column('id');
  513. if (empty($activeActivityIds)) {
  514. return null;
  515. }
  516. // 第二步:查询指定商品的折扣信息
  517. $discountInfo = Db::table('shop_activity_sku')
  518. ->alias('sku')
  519. ->join('shop_activity activity', 'sku.activity_id = activity.id')
  520. ->where('sku.goods_id', $goodsId)
  521. ->where('sku.sku_id', $skuId)
  522. ->where('sku.activity_id', 'in', $activeActivityIds)
  523. ->where('sku.status', StatusEnum::ENABLED)
  524. ->where('sku.stocks', '>', 0)
  525. ->field([
  526. 'sku.id as activity_sku_id',
  527. 'sku.discount',
  528. 'sku.discount_price',
  529. 'sku.stocks as discount_stocks',
  530. 'sku.sale_quantity',
  531. 'activity.id as activity_id',
  532. 'activity.name as activity_name',
  533. 'activity.start_time',
  534. 'activity.end_time'
  535. ])
  536. ->find();
  537. return $discountInfo ?: null;
  538. }
  539. /**
  540. * 批量获取商品的折扣信息(JOIN关联查询版本)
  541. * @param array $goodsIds 商品ID数组
  542. * @return array 键为商品ID,值为折扣信息数组
  543. */
  544. public static function getBatchGoodsDiscountInfo($goodsIds)
  545. {
  546. if (empty($goodsIds)) {
  547. return [];
  548. }
  549. $currentTime = time();
  550. // 直接使用JOIN关联查询,一次性完成时间和状态验证
  551. $result = Db::table('shop_activity_sku')
  552. ->alias('sku')
  553. ->join(['shop_activity' => 'activity'], 'sku.activity_id = activity.id')
  554. ->where('sku.goods_id', 'in', $goodsIds)
  555. ->where('activity.start_time', '<=', $currentTime)
  556. ->where('activity.end_time', '>=', $currentTime)
  557. ->where('activity.activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  558. ->where('activity.status', StatusEnum::ENABLED)
  559. ->where('sku.status', StatusEnum::ENABLED)
  560. ->where('sku.stocks', '>', 0)
  561. ->field([
  562. 'sku.goods_id',
  563. 'sku.sku_id',
  564. 'sku.activity_id',
  565. 'sku.id as activity_sku_id',
  566. 'sku.discount',
  567. 'sku.discount_price',
  568. 'sku.stocks as discount_stocks',
  569. 'sku.sale_quantity',
  570. 'activity.name as activity_name'
  571. ])
  572. ->order('sku.discount', 'asc')
  573. ->select();
  574. if (empty($result)) {
  575. return [];
  576. }
  577. // 转换为数组并按商品ID分组
  578. $resultArray = collection($result)->toArray();
  579. $discountData = [];
  580. foreach ($resultArray as $item) {
  581. $goodsId = $item['goods_id'];
  582. if (!isset($discountData[$goodsId])) {
  583. $discountData[$goodsId] = [];
  584. }
  585. $discountData[$goodsId][] = $item;
  586. }
  587. return $discountData;
  588. }
  589. /**
  590. * 检查商品是否参与当前折扣活动
  591. * @param int $goodsId 商品ID
  592. * @param int $skuId SKU ID
  593. * @return bool
  594. */
  595. public static function hasActiveDiscount($goodsId, $skuId = 0)
  596. {
  597. $discountInfo = self::getGoodsDiscountInfo($goodsId, $skuId);
  598. return !empty($discountInfo);
  599. }
  600. /**
  601. * 计算折扣后的价格
  602. * @param float $originalPrice 原价
  603. * @param float $discount 折扣(如9.0表示9折)
  604. * @return float
  605. */
  606. public static function calculateDiscountPrice($originalPrice, $discount)
  607. {
  608. return round($originalPrice * $discount / 10, 2);
  609. }
  610. /**
  611. * 获取活动剩余时间(秒)
  612. * @param int $endTime 活动结束时间戳
  613. * @return int
  614. */
  615. public static function getActivityRemainingTime($endTime)
  616. {
  617. return max(0, $endTime - time());
  618. }
  619. /**
  620. * 格式化活动剩余时间
  621. * @param int $endTime 活动结束时间戳
  622. * @return array
  623. */
  624. public static function formatRemainingTime($endTime)
  625. {
  626. $remaining = self::getActivityRemainingTime($endTime);
  627. if ($remaining <= 0) {
  628. return ['days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 0];
  629. }
  630. $days = floor($remaining / 86400);
  631. $hours = floor(($remaining % 86400) / 3600);
  632. $minutes = floor(($remaining % 3600) / 60);
  633. $seconds = $remaining % 60;
  634. return [
  635. 'days' => $days,
  636. 'hours' => $hours,
  637. 'minutes' => $minutes,
  638. 'seconds' => $seconds
  639. ];
  640. }
  641. /**
  642. * 处理商品的折扣价格信息(公共方法)
  643. * @param array $goodsData 商品数据数组
  644. * @return array 处理后的商品数据
  645. */
  646. private static function processGoodsDiscountData($goodsData)
  647. {
  648. foreach ($goodsData as &$goods) {
  649. $discountPrices = [];
  650. $discounts = [];
  651. // 收集所有折扣价格和折扣率
  652. foreach ($goods['discount_info'] as $discount) {
  653. $discountPrices[] = $discount['discount_price'];
  654. $discounts[] = $discount['discount'];
  655. }
  656. if (!empty($discountPrices)) {
  657. // 计算最低和最高折扣价格
  658. $goods['min_discount_price'] = min($discountPrices);
  659. $goods['max_discount_price'] = max($discountPrices);
  660. $goods['min_discount'] = min($discounts);
  661. $goods['max_discount'] = max($discounts);
  662. // 单规格商品处理
  663. if ($goods['spec_type'] == 0) {
  664. $goods['discount_price'] = $goods['min_discount_price'];
  665. $goods['discount'] = $goods['min_discount'];
  666. $goods['discount_amount'] = round($goods['original_price'] - $goods['discount_price'], 2);
  667. $goods['price_range'] = '¥' . $goods['discount_price'];
  668. }
  669. // 多规格商品处理
  670. else {
  671. // 设置价格区间
  672. if ($goods['min_discount_price'] == $goods['max_discount_price']) {
  673. $goods['price_range'] = '¥' . $goods['min_discount_price'];
  674. } else {
  675. $goods['price_range'] = '¥' . $goods['min_discount_price'] . '-' . $goods['max_discount_price'];
  676. }
  677. // 对于多规格,使用最低价格作为主要显示价格
  678. $goods['discount_price'] = $goods['min_discount_price'];
  679. $goods['discount'] = $goods['min_discount'];
  680. }
  681. }
  682. }
  683. return $goodsData;
  684. }
  685. /**
  686. * 获取当前有效的活动ID列表
  687. * @return array
  688. */
  689. private static function getActiveActivityIds()
  690. {
  691. $currentTime = time();
  692. return Db::table('shop_activity')
  693. ->where('start_time', '<=', $currentTime)
  694. ->where('end_time', '>=', $currentTime)
  695. ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
  696. ->where('status', StatusEnum::ENABLED)
  697. ->column('id');
  698. }
  699. /**
  700. * 初始化商品折扣数据结构
  701. * @param array $item 商品数据
  702. * @return array
  703. */
  704. private static function initGoodsDiscountStructure($item)
  705. {
  706. return [
  707. 'goods_id' => $item['goods_id'],
  708. 'title' => $item['title'],
  709. 'sub_title' => $item['sub_title'],
  710. 'image' => $item['image'],
  711. 'original_price' => $item['original_price'],
  712. 'market_price' => $item['market_price'],
  713. 'goods_stocks' => $item['goods_stocks'],
  714. 'sales' => $item['sales'],
  715. 'spec_type' => $item['spec_type'],
  716. 'activity_id' => $item['activity_id'] ?? null,
  717. 'activity_name' => $item['activity_name'] ?? null,
  718. 'start_time' => $item['start_time'] ?? null,
  719. 'end_time' => $item['end_time'] ?? null,
  720. 'has_discount' => true,
  721. 'discount_info' => [],
  722. // 初始化折扣价格相关字段
  723. 'min_discount_price' => null,
  724. 'max_discount_price' => null,
  725. 'min_discount' => null,
  726. 'max_discount' => null,
  727. 'discount_price' => null,
  728. 'discount' => null,
  729. 'discount_amount' => null,
  730. 'price_range' => ''
  731. ];
  732. }
  733. }