ShopItem.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 \App\Module\Shop\Casts\ShopDisplayAttributesCast $display_attributes 展示属性,以JSON格式存储键值对,用于界面展示和描述的属性
  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. 'display_attributes',
  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. 'display_attributes' => \App\Module\Shop\Casts\ShopDisplayAttributesCast::class,
  79. ];
  80. /**
  81. * 获取关联的分类
  82. *
  83. * @return BelongsTo
  84. */
  85. public function category(): BelongsTo
  86. {
  87. return $this->belongsTo(ShopCategory::class, 'category_id');
  88. }
  89. /**
  90. * 获取关联的消耗组
  91. *
  92. * @return BelongsTo
  93. */
  94. public function consumeGroup(): BelongsTo
  95. {
  96. return $this->belongsTo(\App\Module\Game\Models\GameConsumeGroup::class, 'consume_group_id');
  97. }
  98. /**
  99. * 获取关联的奖励组
  100. *
  101. * @return BelongsTo
  102. */
  103. public function rewardGroup(): BelongsTo
  104. {
  105. return $this->belongsTo(\App\Module\Game\Models\GameRewardGroup::class, 'reward_group_id');
  106. }
  107. /**
  108. * 获取用户已购买数量
  109. *
  110. * @param int $userId 用户ID
  111. * @return int 已购买数量
  112. */
  113. public function getUserBoughtCount(int $userId): int
  114. {
  115. return ShopPurchaseLog::where('user_id', $userId)
  116. ->where('shop_item_id', $this->id)
  117. ->sum('quantity');
  118. }
  119. /**
  120. * 获取该商品关联的促销活动
  121. *
  122. * @return BelongsToMany
  123. */
  124. public function promotions(): BelongsToMany
  125. {
  126. return $this->belongsToMany(
  127. ShopPromotion::class,
  128. 'shop_promotion_items',
  129. 'shop_item_id',
  130. 'promotion_id'
  131. )->withPivot('custom_discount_value')
  132. ->withTimestamps();
  133. }
  134. /**
  135. * 获取商品的促销关联记录
  136. *
  137. * @return HasMany
  138. */
  139. public function promotionItems(): HasMany
  140. {
  141. return $this->hasMany(ShopPromotionItem::class, 'shop_item_id');
  142. }
  143. /**
  144. * 获取当前有效的促销活动
  145. *
  146. * @return ShopPromotion|null
  147. */
  148. public function getActivePromotion(): ?ShopPromotion
  149. {
  150. $now = now();
  151. return $this->promotions()
  152. ->where('is_active', true)
  153. ->where(function ($query) use ($now) {
  154. $query->whereNull('start_time')
  155. ->orWhere('start_time', '<=', $now);
  156. })
  157. ->where(function ($query) use ($now) {
  158. $query->whereNull('end_time')
  159. ->orWhere('end_time', '>=', $now);
  160. })
  161. ->orderBy('sort_order')
  162. ->first();
  163. }
  164. /**
  165. * 获取折扣后的价格
  166. * 注意:由于商品不再有固定价格,此方法需要根据消耗组重新实现
  167. *
  168. * @return int
  169. */
  170. public function getDiscountedPrice(): int
  171. {
  172. // TODO: 根据消耗组重新实现价格计算逻辑
  173. $promotion = $this->getActivePromotion();
  174. if (!$promotion) {
  175. return 0; // 暂时返回0,需要根据消耗组计算
  176. }
  177. $promotionItem = $this->promotionItems()
  178. ->where('promotion_id', $promotion->id)
  179. ->first();
  180. $customDiscountValue = $promotionItem ? $promotionItem->custom_discount_value : null;
  181. return $promotion->calculateDiscountedPrice(0, $customDiscountValue); // 暂时传入0
  182. }
  183. /**
  184. * 记录购买记录
  185. * 注意:由于商品结构变更,此方法需要根据新的消耗组和奖励组重新实现
  186. *
  187. * @param int $userId 用户ID
  188. * @param int $quantity 购买数量
  189. * @param int $totalPrice 总价
  190. * @return ShopPurchaseLog 购买记录
  191. */
  192. public function recordPurchase(int $userId, int $quantity, int $totalPrice): ShopPurchaseLog
  193. {
  194. $log = new ShopPurchaseLog([
  195. 'user_id' => $userId,
  196. 'shop_item_id' => $this->id,
  197. 'item_id' => null, // 不再关联单一物品
  198. 'quantity' => $quantity,
  199. 'price' => 0, // 不再有固定价格
  200. 'total_price' => $totalPrice,
  201. 'currency_id' => null, // 不再有单一货币类型
  202. 'purchase_time' => now(),
  203. 'ip_address' => request()->ip(),
  204. 'device_info' => request()->userAgent(),
  205. ]);
  206. $log->save();
  207. return $log;
  208. }
  209. /**
  210. * 获取关联的限购配置
  211. *
  212. * @return HasMany
  213. */
  214. public function purchaseLimits(): HasMany
  215. {
  216. return $this->hasMany(ShopPurchaseLimit::class, 'shop_item_id');
  217. }
  218. /**
  219. * 获取激活的限购配置
  220. *
  221. * @return HasMany
  222. */
  223. public function activePurchaseLimits(): HasMany
  224. {
  225. return $this->purchaseLimits()->where('is_active', true)->orderBy('sort_order');
  226. }
  227. /**
  228. * 检查用户是否可以购买指定数量(包含所有限购规则)
  229. *
  230. * @param int $userId 用户ID
  231. * @param int $quantity 购买数量
  232. * @return array [是否可购买, 错误消息, 剩余可购买数量]
  233. */
  234. public function canUserPurchaseWithLimits(int $userId, int $quantity): array
  235. {
  236. // 检查单次购买限制
  237. if ($this->max_single_buy > 0 && $quantity > $this->max_single_buy) {
  238. return [false, "单次购买数量不能超过{$this->max_single_buy}个", $this->max_single_buy];
  239. }
  240. // 检查所有激活的限购配置
  241. $activeLimits = $this->activePurchaseLimits;
  242. $minRemainingQuantity = PHP_INT_MAX;
  243. foreach ($activeLimits as $limit) {
  244. list($canPurchase, $errorMessage, $remainingQuantity) = $limit->canUserPurchase($userId, $quantity);
  245. if (!$canPurchase) {
  246. return [false, $errorMessage, $remainingQuantity];
  247. }
  248. $minRemainingQuantity = min($minRemainingQuantity, $remainingQuantity);
  249. }
  250. $finalRemainingQuantity = $minRemainingQuantity === PHP_INT_MAX ? $quantity : $minRemainingQuantity;
  251. return [true, '', $finalRemainingQuantity];
  252. }
  253. /**
  254. * 更新所有相关的限购计数
  255. *
  256. * @param int $userId 用户ID
  257. * @param int $quantity 购买数量
  258. * @return bool
  259. */
  260. public function updatePurchaseLimitCounters(int $userId, int $quantity): bool
  261. {
  262. $activeLimits = $this->activePurchaseLimits;
  263. foreach ($activeLimits as $limit) {
  264. $limit->incrementUserPurchaseCount($userId, $quantity);
  265. }
  266. return true;
  267. }
  268. }