ShopLogic.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. namespace App\Module\Shop\Logics;
  3. use App\Module\Shop\Models\ShopCategory;
  4. use App\Module\Shop\Models\ShopItem;
  5. use App\Module\Shop\Models\ShopPromotion;
  6. use App\Module\Shop\Models\ShopPromotionItem;
  7. use App\Module\Shop\Models\ShopPurchaseLog;
  8. use Carbon\Carbon;
  9. use Illuminate\Database\Eloquent\Collection;
  10. /**
  11. * 商店逻辑类
  12. *
  13. * 处理商店相关的业务逻辑,包括获取商品列表、检查购买限制等。
  14. * 该类是商店模块的核心业务逻辑处理类,由ShopService调用。
  15. */
  16. class ShopLogic
  17. {
  18. /**
  19. * 获取商店商品列表
  20. *
  21. * @param array $filters 过滤条件
  22. * @return Collection 商品列表
  23. */
  24. public function getShopItems(array $filters = []): Collection
  25. {
  26. $query = ShopItem::with([
  27. 'category',
  28. 'consumeGroup.consumeItems',
  29. 'rewardGroup.rewardItems',
  30. 'promotions' => function ($q) {
  31. $now = Carbon::now();
  32. $q->where('is_active', true)
  33. ->where(function ($q) use ($now) {
  34. $q->whereNull('start_time')
  35. ->orWhere('start_time', '<=', $now);
  36. })
  37. ->where(function ($q) use ($now) {
  38. $q->whereNull('end_time')
  39. ->orWhere('end_time', '>=', $now);
  40. })
  41. ->orderBy('sort_order', 'asc');
  42. }
  43. ])
  44. ->where('is_active', true)
  45. ->where(function ($q) {
  46. $now = Carbon::now();
  47. $q->where(function ($q) use ($now) {
  48. $q->whereNull('start_time')
  49. ->whereNull('end_time');
  50. })->orWhere(function ($q) use ($now) {
  51. $q->where('start_time', '<=', $now)
  52. ->where('end_time', '>=', $now);
  53. })->orWhere(function ($q) use ($now) {
  54. $q->where('start_time', '<=', $now)
  55. ->whereNull('end_time');
  56. })->orWhere(function ($q) use ($now) {
  57. $q->whereNull('start_time')
  58. ->where('end_time', '>=', $now);
  59. });
  60. });
  61. // 应用过滤条件
  62. if (isset($filters['category_id'])) {
  63. $query->where('category_id', $filters['category_id']);
  64. }
  65. // 注意:由于商品结构已改为消耗组和奖励组模式,不再支持按item_id过滤
  66. // if (isset($filters['item_id'])) {
  67. // $query->where('item_id', $filters['item_id']);
  68. // }
  69. if (isset($filters['keyword'])) {
  70. $keyword = $filters['keyword'];
  71. $query->where(function ($q) use ($keyword) {
  72. $q->where('name', 'like', "%{$keyword}%")
  73. ->orWhere('description', 'like', "%{$keyword}%");
  74. // 注意:由于商品结构已改为消耗组和奖励组模式,不再支持按关联物品名称搜索
  75. // ->orWhereHas('item', function ($q) use ($keyword) {
  76. // $q->where('name', 'like', "%{$keyword}%");
  77. // });
  78. });
  79. }
  80. // 是否只获取促销商品
  81. if (isset($filters['only_promotion']) && $filters['only_promotion']) {
  82. $query->whereHas('promotions', function ($q) {
  83. $now = Carbon::now();
  84. $q->where('is_active', true)
  85. ->where(function ($q) use ($now) {
  86. $q->whereNull('start_time')
  87. ->orWhere('start_time', '<=', $now);
  88. })
  89. ->where(function ($q) use ($now) {
  90. $q->whereNull('end_time')
  91. ->orWhere('end_time', '>=', $now);
  92. });
  93. });
  94. }
  95. // 按促销活动过滤
  96. if (isset($filters['promotion_id'])) {
  97. $query->whereHas('promotions', function ($q) use ($filters) {
  98. $q->where('id', $filters['promotion_id']);
  99. });
  100. }
  101. // 排序
  102. $query->orderBy('sort_order', 'asc')
  103. ->orderBy('id', 'asc');
  104. // 获取商品列表
  105. $items = $query->get();
  106. // 注意:由于商品结构已改为消耗组和奖励组模式,不再有固定价格
  107. // 折扣价格计算需要根据消耗组和促销活动重新实现
  108. // foreach ($items as $item) {
  109. // $item->original_price = $item->price;
  110. // $item->discounted_price = $item->getDiscountedPrice();
  111. // $item->has_discount = $item->discounted_price < $item->original_price;
  112. // $item->discount_percentage = $item->original_price > 0
  113. // ? round((1 - $item->discounted_price / $item->original_price) * 100)
  114. // : 0;
  115. // }
  116. return $items;
  117. }
  118. /**
  119. * 获取商店分类列表
  120. *
  121. * @param bool $onlyActive 是否只获取激活的分类
  122. * @return Collection 分类列表
  123. */
  124. public function getShopCategories(bool $onlyActive = true): Collection
  125. {
  126. $query = ShopCategory::query();
  127. if ($onlyActive) {
  128. $query->where('is_active', true);
  129. }
  130. return $query->orderBy('sort_order', 'asc')->get();
  131. }
  132. /**
  133. * 获取商店分类树
  134. *
  135. * @param bool $onlyActive 是否只获取激活的分类
  136. * @return array 分类树
  137. */
  138. public function getShopCategoryTree(bool $onlyActive = true): array
  139. {
  140. return ShopCategory::getCategoryTree($onlyActive);
  141. }
  142. /**
  143. * 检查用户购买限制
  144. *
  145. * @param int $userId 用户ID
  146. * @param ShopItem $shopItem 商品
  147. * @param int $quantity 购买数量
  148. * @return array [是否可购买, 错误消息]
  149. */
  150. public function checkBuyLimit(int $userId, ShopItem $shopItem, int $quantity): array
  151. {
  152. // 检查商品是否激活
  153. if (!$shopItem->is_active) {
  154. return [false, '该商品已下架'];
  155. }
  156. // 检查商品是否在有效期内
  157. $now = Carbon::now();
  158. if (($shopItem->start_time && $now < $shopItem->start_time) ||
  159. ($shopItem->end_time && $now > $shopItem->end_time)) {
  160. return [false, '该商品不在销售时间内'];
  161. }
  162. // 检查购买数量是否有效
  163. if ($quantity <= 0) {
  164. return [false, '购买数量必须大于0'];
  165. }
  166. // 检查购买限制
  167. if ($shopItem->max_buy > 0) {
  168. $boughtCount = $shopItem->getUserBoughtCount($userId);
  169. if ($boughtCount + $quantity > $shopItem->max_buy) {
  170. return [false, "超出购买限制,最多还能购买" . ($shopItem->max_buy - $boughtCount) . "个"];
  171. }
  172. }
  173. // 注意:由于商品结构已改为消耗组和奖励组模式,不再检查单一关联物品
  174. // 需要检查消耗组和奖励组是否存在
  175. if ($shopItem->consume_group_id && !$shopItem->consumeGroup) {
  176. return [false, '商品关联的消耗组不存在'];
  177. }
  178. if ($shopItem->reward_group_id && !$shopItem->rewardGroup) {
  179. return [false, '商品关联的奖励组不存在'];
  180. }
  181. return [true, ''];
  182. }
  183. /**
  184. * 记录购买日志
  185. *
  186. * @param int $userId 用户ID
  187. * @param ShopItem $shopItem 商品
  188. * @param int $quantity 购买数量
  189. * @param int $totalPrice 总价
  190. * @return ShopPurchaseLog 购买日志
  191. */
  192. public function recordPurchase(int $userId, ShopItem $shopItem, int $quantity, int $totalPrice): ShopPurchaseLog
  193. {
  194. return $shopItem->recordPurchase($userId, $quantity, $totalPrice);
  195. }
  196. /**
  197. * 获取用户购买记录
  198. *
  199. * @param int $userId 用户ID
  200. * @param array $filters 过滤条件
  201. * @return Collection 购买记录
  202. */
  203. public function getUserPurchaseHistory(int $userId, array $filters = []): Collection
  204. {
  205. $query = ShopPurchaseLog::with(['shopItem'])
  206. ->where('user_id', $userId);
  207. // 应用过滤条件
  208. if (isset($filters['shop_item_id'])) {
  209. $query->where('shop_item_id', $filters['shop_item_id']);
  210. }
  211. if (isset($filters['item_id'])) {
  212. $query->where('item_id', $filters['item_id']);
  213. }
  214. if (isset($filters['start_date'])) {
  215. $query->where('purchase_time', '>=', $filters['start_date']);
  216. }
  217. if (isset($filters['end_date'])) {
  218. $query->where('purchase_time', '<=', $filters['end_date']);
  219. }
  220. // 排序
  221. $query->orderBy('purchase_time', 'desc');
  222. return $query->get();
  223. }
  224. /**
  225. * 获取促销活动列表
  226. *
  227. * @param array $filters 过滤条件
  228. * @return Collection 促销活动列表
  229. */
  230. public function getPromotions(array $filters = []): Collection
  231. {
  232. $query = ShopPromotion::with('items');
  233. // 是否只获取激活的促销活动
  234. if (isset($filters['only_active']) && $filters['only_active']) {
  235. $query->where('is_active', true);
  236. }
  237. // 是否只获取当前有效的促销活动
  238. if (isset($filters['only_valid']) && $filters['only_valid']) {
  239. $now = Carbon::now();
  240. $query->where('is_active', true)
  241. ->where(function ($q) use ($now) {
  242. $q->whereNull('start_time')
  243. ->orWhere('start_time', '<=', $now);
  244. })
  245. ->where(function ($q) use ($now) {
  246. $q->whereNull('end_time')
  247. ->orWhere('end_time', '>=', $now);
  248. });
  249. }
  250. // 按分类过滤
  251. if (isset($filters['category_id'])) {
  252. $query->whereHas('items', function ($q) use ($filters) {
  253. $q->where('category_id', $filters['category_id']);
  254. });
  255. }
  256. // 按关键词搜索
  257. if (isset($filters['keyword'])) {
  258. $keyword = $filters['keyword'];
  259. $query->where(function ($q) use ($keyword) {
  260. $q->where('name', 'like', "%{$keyword}%")
  261. ->orWhere('description', 'like', "%{$keyword}%");
  262. });
  263. }
  264. // 排序
  265. $query->orderBy('sort_order', 'asc')
  266. ->orderBy('id', 'asc');
  267. return $query->get();
  268. }
  269. /**
  270. * 获取促销活动详情
  271. *
  272. * @param int $promotionId 促销活动ID
  273. * @return ShopPromotion|null 促销活动详情
  274. */
  275. public function getPromotionDetail(int $promotionId): ?ShopPromotion
  276. {
  277. return ShopPromotion::with('items')->find($promotionId);
  278. }
  279. /**
  280. * 添加商品到促销活动
  281. *
  282. * @param int $promotionId 促销活动ID
  283. * @param int $shopItemId 商品ID
  284. * @param int|null $customDiscountValue 自定义折扣值
  285. * @return ShopPromotionItem 促销商品关联
  286. */
  287. public function addItemToPromotion(int $promotionId, int $shopItemId, ?int $customDiscountValue = null): ShopPromotionItem
  288. {
  289. // 检查促销活动是否存在
  290. ShopPromotion::findOrFail($promotionId);
  291. // 检查商品是否存在
  292. ShopItem::findOrFail($shopItemId);
  293. // 检查商品是否已经在促销活动中
  294. $existingItem = ShopPromotionItem::where('promotion_id', $promotionId)
  295. ->where('shop_item_id', $shopItemId)
  296. ->first();
  297. if ($existingItem) {
  298. // 更新自定义折扣值
  299. $existingItem->custom_discount_value = $customDiscountValue;
  300. $existingItem->save();
  301. return $existingItem;
  302. }
  303. // 创建新的关联
  304. $promotionItem = new ShopPromotionItem([
  305. 'promotion_id' => $promotionId,
  306. 'shop_item_id' => $shopItemId,
  307. 'custom_discount_value' => $customDiscountValue,
  308. ]);
  309. $promotionItem->save();
  310. return $promotionItem;
  311. }
  312. /**
  313. * 从促销活动中移除商品
  314. *
  315. * @param int $promotionId 促销活动ID
  316. * @param int $shopItemId 商品ID
  317. * @return bool 是否成功
  318. */
  319. public function removeItemFromPromotion(int $promotionId, int $shopItemId): bool
  320. {
  321. return ShopPromotionItem::where('promotion_id', $promotionId)
  322. ->where('shop_item_id', $shopItemId)
  323. ->delete() > 0;
  324. }
  325. }