ShopItem.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <?php
  2. namespace App\Module\Shop\Models;
  3. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  4. use Illuminate\Database\Eloquent\Relations\BelongsToMany;
  5. use Illuminate\Database\Eloquent\Relations\HasMany;
  6. use Illuminate\Support\Facades\DB;
  7. use UCore\ModelCore;
  8. use App\Module\Shop\Models\ShopCategory;
  9. use App\Module\Shop\Models\ShopPromotion;
  10. use App\Module\Shop\Models\ShopPromotionItem;
  11. use App\Module\Shop\Models\ShopPurchaseLog;
  12. /**
  13. * 商店物品模型
  14. *
  15. * field start
  16. * @property int $id 商品ID,主键
  17. * @property string $name 商品名称
  18. * @property string $description 商品描述
  19. * @property int $category_id 分类ID,外键关联kku_shop_categories表
  20. * @property string $category_name 分类名称(字符串格式,区别于现有分类机制)
  21. * @property int $consume_group_id 消耗组ID,外键关联kku_game_consume_groups表
  22. * @property int $reward_group_id 奖励组ID,外键关联kku_game_reward_groups表
  23. * @property int $max_single_buy 单次最大购买数量(0表示无限制)
  24. * @property bool $is_active 是否激活(0:否, 1:是)
  25. * @property int $sort_order 排序权重
  26. * @property string $image 商品图片
  27. * @property string $start_time 上架时间
  28. * @property string $end_time 下架时间
  29. * @property \Carbon\Carbon $created_at 创建时间
  30. * @property \Carbon\Carbon $updated_at 更新时间
  31. * field end
  32. */
  33. class ShopItem extends ModelCore
  34. {
  35. /**
  36. * 与模型关联的表名
  37. *
  38. * @var string
  39. */
  40. protected $table = 'shop_items';
  41. /**
  42. * 可批量赋值的属性
  43. *
  44. * @var array
  45. */
  46. protected $fillable = [
  47. 'name',
  48. 'description',
  49. 'category_id',
  50. 'category_name',
  51. 'consume_group_id',
  52. 'reward_group_id',
  53. 'max_single_buy',
  54. 'is_active',
  55. 'sort_order',
  56. 'image',
  57. 'start_time',
  58. 'end_time',
  59. ];
  60. /**
  61. * 应该被转换为日期的属性
  62. *
  63. * @var array
  64. */
  65. protected $dates = [
  66. 'start_time',
  67. 'end_time',
  68. 'created_at',
  69. 'updated_at',
  70. ];
  71. /**
  72. * 应该被转换为原生类型的属性
  73. *
  74. * @var array
  75. */
  76. protected $casts = [
  77. 'is_active' => 'boolean',
  78. ];
  79. /**
  80. * 获取关联的分类
  81. *
  82. * @return BelongsTo
  83. */
  84. public function category(): BelongsTo
  85. {
  86. return $this->belongsTo(ShopCategory::class, 'category_id');
  87. }
  88. /**
  89. * 获取关联的消耗组
  90. *
  91. * @return BelongsTo
  92. */
  93. public function consumeGroup(): BelongsTo
  94. {
  95. return $this->belongsTo(\App\Module\Game\Models\GameConsumeGroup::class, 'consume_group_id');
  96. }
  97. /**
  98. * 获取关联的奖励组
  99. *
  100. * @return BelongsTo
  101. */
  102. public function rewardGroup(): BelongsTo
  103. {
  104. return $this->belongsTo(\App\Module\Game\Models\GameRewardGroup::class, 'reward_group_id');
  105. }
  106. /**
  107. * 获取用户已购买数量
  108. *
  109. * @param int $userId 用户ID
  110. * @return int 已购买数量
  111. */
  112. public function getUserBoughtCount(int $userId): int
  113. {
  114. return ShopPurchaseLog::where('user_id', $userId)
  115. ->where('shop_item_id', $this->id)
  116. ->sum('quantity');
  117. }
  118. /**
  119. * 获取该商品关联的促销活动
  120. *
  121. * @return BelongsToMany
  122. */
  123. public function promotions(): BelongsToMany
  124. {
  125. return $this->belongsToMany(
  126. ShopPromotion::class,
  127. 'shop_promotion_items',
  128. 'shop_item_id',
  129. 'promotion_id'
  130. )->withPivot('custom_discount_value')
  131. ->withTimestamps();
  132. }
  133. /**
  134. * 获取商品的促销关联记录
  135. *
  136. * @return HasMany
  137. */
  138. public function promotionItems(): HasMany
  139. {
  140. return $this->hasMany(ShopPromotionItem::class, 'shop_item_id');
  141. }
  142. /**
  143. * 获取当前有效的促销活动
  144. *
  145. * @return ShopPromotion|null
  146. */
  147. public function getActivePromotion(): ?ShopPromotion
  148. {
  149. $now = now();
  150. return $this->promotions()
  151. ->where('is_active', true)
  152. ->where(function ($query) use ($now) {
  153. $query->whereNull('start_time')
  154. ->orWhere('start_time', '<=', $now);
  155. })
  156. ->where(function ($query) use ($now) {
  157. $query->whereNull('end_time')
  158. ->orWhere('end_time', '>=', $now);
  159. })
  160. ->orderBy('sort_order')
  161. ->first();
  162. }
  163. /**
  164. * 获取折扣后的价格
  165. * 注意:由于商品不再有固定价格,此方法需要根据消耗组重新实现
  166. *
  167. * @return int
  168. */
  169. public function getDiscountedPrice(): int
  170. {
  171. // TODO: 根据消耗组重新实现价格计算逻辑
  172. $promotion = $this->getActivePromotion();
  173. if (!$promotion) {
  174. return 0; // 暂时返回0,需要根据消耗组计算
  175. }
  176. $promotionItem = $this->promotionItems()
  177. ->where('promotion_id', $promotion->id)
  178. ->first();
  179. $customDiscountValue = $promotionItem ? $promotionItem->custom_discount_value : null;
  180. return $promotion->calculateDiscountedPrice(0, $customDiscountValue); // 暂时传入0
  181. }
  182. /**
  183. * 记录购买记录
  184. * 注意:由于商品结构变更,此方法需要根据新的消耗组和奖励组重新实现
  185. *
  186. * @param int $userId 用户ID
  187. * @param int $quantity 购买数量
  188. * @param int $totalPrice 总价
  189. * @return ShopPurchaseLog 购买记录
  190. */
  191. public function recordPurchase(int $userId, int $quantity, int $totalPrice): ShopPurchaseLog
  192. {
  193. $log = new ShopPurchaseLog([
  194. 'user_id' => $userId,
  195. 'shop_item_id' => $this->id,
  196. 'item_id' => null, // 不再关联单一物品
  197. 'quantity' => $quantity,
  198. 'price' => 0, // 不再有固定价格
  199. 'total_price' => $totalPrice,
  200. 'currency_id' => null, // 不再有单一货币类型
  201. 'purchase_time' => now(),
  202. 'ip_address' => request()->ip(),
  203. 'device_info' => request()->userAgent(),
  204. ]);
  205. $log->save();
  206. return $log;
  207. }
  208. /**
  209. * 获取关联的限购配置
  210. *
  211. * @return HasMany
  212. */
  213. public function purchaseLimits(): HasMany
  214. {
  215. return $this->hasMany(ShopPurchaseLimit::class, 'shop_item_id');
  216. }
  217. /**
  218. * 获取激活的限购配置
  219. *
  220. * @return HasMany
  221. */
  222. public function activePurchaseLimits(): HasMany
  223. {
  224. return $this->purchaseLimits()->where('is_active', true)->orderBy('sort_order');
  225. }
  226. /**
  227. * 检查用户是否可以购买指定数量(包含所有限购规则)
  228. *
  229. * @param int $userId 用户ID
  230. * @param int $quantity 购买数量
  231. * @return array [是否可购买, 错误消息, 剩余可购买数量]
  232. */
  233. public function canUserPurchaseWithLimits(int $userId, int $quantity): array
  234. {
  235. // 检查单次购买限制
  236. if ($this->max_single_buy > 0 && $quantity > $this->max_single_buy) {
  237. return [false, "单次购买数量不能超过{$this->max_single_buy}个", $this->max_single_buy];
  238. }
  239. // 检查所有激活的限购配置
  240. $activeLimits = $this->activePurchaseLimits;
  241. $minRemainingQuantity = PHP_INT_MAX;
  242. foreach ($activeLimits as $limit) {
  243. list($canPurchase, $errorMessage, $remainingQuantity) = $limit->canUserPurchase($userId, $quantity);
  244. if (!$canPurchase) {
  245. return [false, $errorMessage, $remainingQuantity];
  246. }
  247. $minRemainingQuantity = min($minRemainingQuantity, $remainingQuantity);
  248. }
  249. $finalRemainingQuantity = $minRemainingQuantity === PHP_INT_MAX ? $quantity : $minRemainingQuantity;
  250. return [true, '', $finalRemainingQuantity];
  251. }
  252. /**
  253. * 更新所有相关的限购计数
  254. *
  255. * @param int $userId 用户ID
  256. * @param int $quantity 购买数量
  257. * @return bool
  258. */
  259. public function updatePurchaseLimitCounters(int $userId, int $quantity): bool
  260. {
  261. $activeLimits = $this->activePurchaseLimits;
  262. foreach ($activeLimits as $limit) {
  263. $limit->incrementUserPurchaseCount($userId, $quantity);
  264. }
  265. return true;
  266. }
  267. }