UserCoupon.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2021 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\api\model;
  13. use app\api\service\User as UserService;
  14. use app\common\enum\order\PayStatus as PayStatusEnum;
  15. use app\common\exception\BaseException;
  16. use app\common\model\UserCoupon as UserCouponModel;
  17. use app\common\enum\coupon\ExpireType as ExpireTypeEnum;
  18. use app\common\enum\coupon\ApplyRange as ApplyRangeEnum;
  19. use app\common\library\helper;
  20. use app\api\model\Coupon;
  21. use app\common\model\CouponGood as CouponGoodModel;
  22. use app\common\model\UserCouponGoods as UserCouponGoodsModel;
  23. use app\api\model\User as UserModel;
  24. use app\api\model\Order as OrderModel;
  25. use think\facade\Db;
  26. /**
  27. * 用户优惠券模型
  28. * Class UserCoupon
  29. * @package app\api\model
  30. */
  31. class UserCoupon extends UserCouponModel
  32. {
  33. public function getUserCouponRdsKey($user_id){
  34. $key = 'receive:send_type:20:user_id:'.$user_id;
  35. return $key;
  36. }
  37. /**
  38. * 获取用户优惠券列表
  39. * @param int $userId
  40. * @param array $param
  41. * @return \think\Paginator
  42. * @throws \think\db\exception\DbException
  43. */
  44. public function getList(int $userId, array $param)
  45. {
  46. $filter = $this->getFilter($param);
  47. return $this->where($filter)
  48. ->where('user_id', '=', $userId)
  49. ->order(['coupon_type'=>'asc','reduce_price'=>'desc','discount'=>'asc','expire_time'=>'asc'])
  50. ->paginate(30);
  51. }
  52. /**
  53. * 检索查询条件
  54. * @param array $param
  55. * @return array|mixed
  56. */
  57. private function getFilter(array $param = [])
  58. {
  59. // 设置默认查询参数
  60. $params = $this->setQueryDefaultValue($param, [
  61. 'dataType' => 'all', // all:全部 isUsable:可用的 isExpire:已过期 isUse:已使用
  62. 'amount' => null, // 订单消费金额
  63. ]);
  64. // 检索列表类型
  65. $filter = [];
  66. // 可用的优惠券
  67. if ($params['dataType'] === 'isUsable') {
  68. $filter[] = ['is_use', '=', 0];
  69. $filter[] = ['start_time', '<=', date('Y-m-d H:i:s')];
  70. $filter[] = ['expire_time', '>', date('Y-m-d H:i:s')];
  71. }
  72. // 已过期的优惠券
  73. if ($params['dataType'] === 'isExpire') {
  74. $filter[] = ['is_use', '=', 0];
  75. $filter[] = ['expire_time', '<', date('Y-m-d H:i:s')];
  76. }
  77. // 已使用的优惠券
  78. if ($params['dataType'] === 'isUse') {
  79. $filter[] = ['is_use', '=', 1];
  80. }
  81. // 订单消费金额
  82. $params['amount'] > 0 && $filter[] = ['min_price', '<=', $params['amount']];
  83. return $filter;
  84. }
  85. /**
  86. * 获取用户优惠券总数量(可用)
  87. * @param int $userId
  88. * @return int
  89. */
  90. public function getCount(int $userId)
  91. {
  92. return $this->where('user_id', '=', $userId)
  93. ->where('is_use', '=', 0)
  94. ->where('start_time', '<=', date('Y-m-d H:i:s'))
  95. ->where('expire_time', '>', date('Y-m-d H:i:s'))
  96. ->count();
  97. }
  98. /**
  99. * 获取用户优惠券ID集
  100. * @param $userId
  101. * @return array
  102. */
  103. public function getUserCouponIds(int $userId)
  104. {
  105. return $this->where('user_id', '=', $userId)->column('coupon_id');
  106. }
  107. //获取指定用户指定优惠券
  108. public function getUserCouponByCouponId(int $userId,array $couponIdArray){
  109. return $this->where('user_id', '=', $userId)->where('coupon_id','in',$couponIdArray)->select();
  110. }
  111. /**
  112. * 获取用户优惠券ID集
  113. * @param $userId
  114. * @return array
  115. */
  116. public function getTodayUserCouponIds(int $userId)
  117. {
  118. $today = strtotime(date("Y-m-d"),time());
  119. $today_end = $today + 86400;
  120. return $this->where('user_id', '=', $userId)->where('create_time','>',$today)->where('create_time','<',$today_end)->column('coupon_id');
  121. }
  122. //查某优惠券用户领了几张
  123. public function getUserCouponCount(int $userId,int $coupon_id){
  124. return $this->where('user_id', '=', $userId)->where('coupon_id',$coupon_id)->count();
  125. }
  126. /**
  127. * 领取优惠券
  128. * @param int $couponId 优惠券ID
  129. * @return bool
  130. * @throws BaseException
  131. */
  132. public function receive(int $couponId,bool $exchange = false,$needUserCouponId =false)
  133. {
  134. // 当前用户信息
  135. $userInfo = UserService::getCurrentLoginUser(true);
  136. // 获取优惠券信息
  137. $coupon = Coupon::where("coupon_id",$couponId)->lock('for update')->find();
  138. // dd($coupon);
  139. // $coupon = Coupon::detail($couponId);
  140. // 验证优惠券是否可领取
  141. if (!$this->checkReceive($userInfo, $coupon,$exchange)) {
  142. return false;
  143. }
  144. // 添加领取记录
  145. return $this->add($userInfo, $coupon,$needUserCouponId);
  146. }
  147. /**
  148. * 领取优惠券
  149. * @param int $couponId 优惠券ID
  150. * @return bool
  151. * @throws BaseException
  152. */
  153. public function userreceive(int $couponId,int $user_id,bool $exchange = false)
  154. {
  155. // 用户信息
  156. $userInfo = UserModel::where('user_id',$user_id)->find();
  157. // 获取优惠券信息
  158. $coupon = Coupon::where("coupon_id",$couponId)->lock('for update')->find();
  159. // $coupon = Coupon::detail($couponId);
  160. // 验证优惠券是否可领取
  161. if (!$this->checkReceive($userInfo, $coupon,$exchange)) {
  162. log_record("receive fail:coupon_id".$couponId.",error:".$this->error,'error');
  163. return false;
  164. }
  165. log_record("userreceive:coupon_id",'error');
  166. // 添加领取记录
  167. return $this->add($userInfo, $coupon);
  168. }
  169. /**
  170. * 领取优惠券
  171. * @param $coupon 优惠券信息
  172. * @param $userInfo 用户信息
  173. * @return bool
  174. * @throws BaseException
  175. */
  176. public function receiveLogin($coupon, $userInfo)
  177. {
  178. // 当前用户信息
  179. // $userInfo = UserService::getCurrentLoginUser(true);
  180. // 获取优惠券信息
  181. // $coupon = Coupon::detail($couponId);
  182. // 验证优惠券是否可领取
  183. if (!$this->checkReceive($userInfo, $coupon)) {
  184. return false;
  185. }
  186. // 添加领取记录
  187. return $this->add($userInfo, $coupon);
  188. }
  189. /**
  190. * 添加领取记录
  191. * @param $user
  192. * @param Coupon $coupon
  193. * @param bool $needUserCouponId
  194. * @return bool
  195. */
  196. private function add($user, Coupon $coupon,$needUserCouponId =false)
  197. {
  198. // 计算有效期
  199. if ($coupon['expire_type'] == 10) {
  200. if($coupon['expire_day']>1){
  201. $d = $coupon['expire_day'] -1;
  202. $expire_time = date('Y-m-d',strtotime('+'.$d.' day')). ' 23:59:59' ;
  203. }else{
  204. $expire_time = date("Y-m-d",time()).' 23:59:59';
  205. }
  206. } else {
  207. $expire_time = $coupon['expire_time'];
  208. }
  209. $startTime = $coupon['start_time'];
  210. $endTime = $coupon['end_time'];
  211. // 整理领取记录
  212. $data = [
  213. 'coupon_id' => $coupon['coupon_id'],
  214. 'name' => $coupon['name'],
  215. 'coupon_type' => $coupon['coupon_type'],
  216. 'reduce_price' => $coupon['reduce_price'],
  217. 'discount' => $coupon['discount'],
  218. 'min_price' => $coupon['min_price'],
  219. 'expire_type' => $coupon['expire_type'],
  220. 'expire_day' => $coupon['expire_day'],
  221. 'start_time' => $startTime,
  222. 'end_time' => $endTime,
  223. 'apply_range' => $coupon['apply_range'],
  224. 'apply_range_config' => $coupon['apply_range_config'],
  225. 'user_id' => $user['user_id'],
  226. 'store_id' => self::$storeId?:10001,
  227. 'subtitle'=>$coupon['subtitle'],
  228. 'describe'=>$coupon['describe'],
  229. 'expire_time'=>$expire_time,
  230. 'discount_type'=>$coupon['discount_type'],
  231. 'max_discount_price'=>$coupon['max_discount_price'],
  232. 'overlay_discount'=>implode(',', $coupon['overlay_discount']),
  233. ];
  234. $coupon_id = $coupon['coupon_id'];
  235. // 事务处理
  236. return $this->transaction(function () use ($data,$coupon_id,$needUserCouponId) {
  237. // 添加领取记录
  238. $coupon = Coupon::where("coupon_id",$coupon_id)->find();
  239. $status = $this->save($data);
  240. log_record("save data:".json_encode($data));
  241. if ($status) {
  242. // 更新优惠券领取数量
  243. $coupon->setIncReceiveNum();
  244. }
  245. $goodList = CouponGoodModel::where('coupon_id',
  246. $coupon_id)->select();
  247. $now = Date("Y-m-d H:i:s",time());
  248. foreach($goodList as $row){
  249. $gdata['user_coupon_id'] = $this->user_coupon_id;
  250. $gdata['goods_id'] = $row->goods_id;
  251. $gdata['image_id'] = $row->image_id;
  252. $gdata['goods_sku_id'] = $row->goods_sku_id;
  253. $gdata['goods_sku_no'] = $row->goods_sku_no;
  254. $gdata['goods_name'] = $row->goods_name;
  255. $gdata['goods_props'] = isset($row->goods_props)?json_encode($row->goods_props):'';
  256. $gdata['goods_price'] = $row->goods_price;
  257. $gdata['goods_num'] = $row->goods_num;
  258. $gdata['total_price'] = $row->total_price;
  259. $gdata['is_except'] = $row->is_except;
  260. $gdata['create_time'] = $now;
  261. $gdata['update_time'] = $now;
  262. $ugood = new UserCouponGoodsModel;
  263. $ugood->save($gdata);
  264. }
  265. log_record("transaction finish data:".$coupon_id);
  266. if ($needUserCouponId === true){
  267. return $this->user_coupon_id;
  268. }
  269. return $status;
  270. });
  271. //return false;
  272. }
  273. /**
  274. * 验证优惠券是否可领取
  275. * @param mixed $userInfo 当前登录用户信息
  276. * @param Coupon $coupon 优惠券详情
  277. * @return bool
  278. */
  279. private function checkReceive($userInfo, Coupon $coupon,bool $exchange = false)
  280. {
  281. if (!$coupon) {
  282. $this->error = '优惠券不存在';
  283. return false;
  284. }
  285. //生日礼券每年只能领取一张
  286. if ($coupon['coupon_type'] == Coupon::MEMBER_BIRTH_COUPON){
  287. //检查是否用户生日月份
  288. $birthMonth = $userInfo['birthday']?substr($userInfo['birthday'],5,2):0;
  289. if (intval($birthMonth) != intval(date('m'))){
  290. $this->error ='请在生日当月领取使用~';
  291. return false;
  292. }
  293. if (UserCoupon::where(['user_id'=>$userInfo['user_id'],'coupon_id'=>$coupon['coupon_id']])->whereYear('create_time')->find())
  294. {
  295. $this->error ='今年已经领过了哟';
  296. return false;
  297. }
  298. }
  299. //会员专享券每月只能领取一张
  300. if ($coupon['coupon_type'] == Coupon::MEMBER_COUPON){
  301. if (UserCoupon::where(['user_id'=>$userInfo['user_id'],'coupon_id'=>$coupon['coupon_id']])->whereMonth('create_time')->find())
  302. {
  303. $this->error ='这个月领过了哟';
  304. return false;
  305. }
  306. }
  307. if($coupon['limit_total_type']==10&&$coupon['avaiable_num']<=0){
  308. $this->error ='领完啦,下次再来';
  309. return false;
  310. }
  311. if(!$exchange){
  312. if(Date("Y-m-d H:i:s",time())>$coupon['end_time']){
  313. $this->error = '优惠券已失效!';
  314. return false;
  315. }
  316. if(Date("Y-m-d H:i:s",time())<$coupon['start_time']){
  317. $this->error = '优惠券已失效!!';
  318. return false;
  319. }
  320. if($coupon['status']==0||$coupon['is_delete']==1||$coupon['audit_status']!=10){
  321. $this->error = '优惠券已失效';
  322. return false;
  323. }
  324. //属于限时券
  325. if($coupon['is_limit_hour']==1){
  326. $coupon['start_hour'] = str_pad($coupon['start_hour'],2,"0",STR_PAD_LEFT);
  327. $coupon['end_hour'] = str_pad($coupon['end_hour'],2,"0",STR_PAD_LEFT);
  328. $today_start = Date("Y-m-d",time())." ".$coupon['start_hour'].":00:00";
  329. $today_end = Date("Y-m-d",time())." ".$coupon['end_hour'].":00:00";
  330. $now = Date("Y-m-d H:i:s",time());
  331. if(!($now>$today_start&&$now<$today_end)){
  332. $this->error = '限时券不在领取的时间段';
  333. return false;
  334. }
  335. }
  336. }else{
  337. if(Date("Y-m-d H:i:s",time())>$coupon['end_time']){
  338. $this->error = '兑换码已失效!';
  339. return false;
  340. }
  341. if(Date("Y-m-d H:i:s",time())<$coupon['start_time']){
  342. $this->error = '兑换码已失效!!';
  343. return false;
  344. }
  345. if($coupon['status']==0||$coupon['is_delete']==1||$coupon['audit_status']!=10){
  346. $this->error = '兑换码已失效';
  347. return false;
  348. }
  349. }
  350. // if (!$coupon->checkReceive()) {
  351. // $this->error = $coupon->getError();
  352. // return false;
  353. // }
  354. // 验证是否已领取
  355. if($coupon['limit_receive_type']==10){
  356. $userCouponIds = $this->getUserCouponIds($userInfo['user_id']);
  357. if (in_array($coupon['coupon_id'], $userCouponIds)) {
  358. if($coupon->limit_receive_cnt==1){
  359. $this->error = '您已经领过了噢~';
  360. return false;
  361. }
  362. $count = $this->getUserCouponCount($userInfo['user_id'],$coupon['coupon_id']);
  363. if($count>=$coupon->limit_receive_cnt){
  364. $this->error = '您已经领过了噢~';
  365. return false;
  366. }
  367. }
  368. }
  369. if($coupon['limit_receive_type']==20){
  370. $userCouponIds = $this->getTodayUserCouponIds($userInfo['user_id']);
  371. $ac = array_count_values($userCouponIds);
  372. if(isset($ac[$coupon['coupon_id']])&&$ac[$coupon['coupon_id']]>=$coupon['limit_receive_cnt']){
  373. $this->error = '优惠券领取已达到当天限额';
  374. return false;
  375. }
  376. }
  377. return true;
  378. }
  379. /**
  380. * 订单结算可用优惠券列表
  381. * @param int $userId 用户id
  382. * @param float $goodsList 订单商品列表
  383. * @param array $orderGoodsIds 订单商品ID集合
  384. * @return array|mixed
  385. * @throws \think\db\exception\DbException
  386. */
  387. public static function getUserCouponList(int $userId, $goodsList, array $orderGoodsIds)
  388. {
  389. // 获取用户可用的优惠券列表
  390. $list = (new static)->getList($userId, ['dataType' => 'isUsable']);
  391. $data = [];
  392. foreach ($list as $coupon) {
  393. // 有效期范围内
  394. if ($coupon['start_time'] > date('Y-m-d H:i:s')) continue;
  395. $isOrderCount = OrderModel::where(['pay_status'=>PayStatusEnum::SUCCESS,'user_id'=>$userId])->count();
  396. //如果是首单折扣券,并且当前用户已经下过单了
  397. if($coupon['coupon_type']==50 && $coupon['discount_type']==1 && $isOrderCount>0){
  398. continue;
  399. }
  400. $exceptGoodsId = helper::getArrayColumn($coupon['userCouponGoodsExcept'],'goods_id');
  401. // $excludedGoodsIds = array_intersect($exceptGoodsId, $orderGoodsIds);
  402. // if(count($excludedGoodsIds)>0) continue;
  403. //zq220128此代码为了解决购物车中只要有一款商品可以用券就显示此劵,但是会导致不能用券的商品也分摊了优惠券金额,看后面优化
  404. $diffGoodsIds = array_diff($orderGoodsIds,$exceptGoodsId);
  405. if (count($diffGoodsIds) == 0)continue;
  406. $amount = 0;//计算可用商品优惠券的金额
  407. $newGoods = [];//计算可用商品优惠券的商品ID集合
  408. foreach ($goodsList as $good){
  409. if(in_array($good['goods_id'],$diffGoodsIds)){
  410. if($good['is_activity_discount']==0 || stripos($good['activity_discount_overlay'],'1') !== false){
  411. $newGoods[] = $good['goods_id'];
  412. $amount += ($good['total_price']-$good['activity_discount_total_price']??0);
  413. }
  414. $amount -= $good['member_total_money']??0;//有会员优惠就减会员优惠
  415. }
  416. }
  417. if($amount<=0) continue;//如果没有满减金额就取消
  418. //满减金额不达标
  419. if($coupon['min_price']>$amount) continue;
  420. //end-----
  421. $key = $coupon['user_coupon_id'];
  422. $data[$key] = [
  423. 'user_coupon_id' => $coupon['user_coupon_id'],
  424. 'name' => $coupon['name'],
  425. 'subtitle' => $coupon['subtitle'],
  426. 'describe' => $coupon['describe'],
  427. 'coupon_type' => $coupon['coupon_type'],
  428. 'reduce_price' => $coupon['reduce_price'],
  429. 'discount' => $coupon['discount'],
  430. 'min_price' => $coupon['min_price'],
  431. 'expire_type' => $coupon['expire_type'],
  432. 'start_time' => $coupon['start_time'],
  433. 'end_time' => $coupon['end_time'],
  434. 'expire_time' => $coupon['expire_time'],
  435. 'apply_range' => $coupon['apply_range'],
  436. 'apply_range_config' => $coupon['apply_range_config'],
  437. 'except_goods_id' => $exceptGoodsId,
  438. 'max_discount_price' => $coupon['max_discount_price'],
  439. 'first_order_discount' => $coupon['first_order_discount'],
  440. 'overlay_discount' => $coupon['overlay_discount'],
  441. 'discount_type' => $coupon['discount_type'],
  442. 'new_good_ids' => $newGoods,
  443. ];
  444. // 计算打折金额
  445. if ($coupon['coupon_type'] == 50) {
  446. $reducePrice = helper::bcmul($amount, 1-($coupon['discount'] / 100));
  447. //如果设置了最大的优惠金额并且当前优惠的金额大于设置的金额
  448. if($coupon['max_discount_price']>0 && $coupon['max_discount_price']<$reducePrice){
  449. $reducePrice = $coupon['max_discount_price'];
  450. }
  451. $data[$key]['reduced_price'] = $reducePrice;
  452. } else
  453. $data[$key]['reduced_price'] = $coupon['reduce_price'];
  454. }
  455. // 根据折扣金额排序并返回
  456. return array_sort($data, 'reduced_price', true);
  457. }
  458. /**
  459. * 判断当前优惠券是否满足订单使用条件
  460. * @param $couponList
  461. * @param array $orderGoodsIds 订单商品ID集
  462. * @return mixed
  463. */
  464. public static function couponListApplyRange(array $couponList, array $orderGoodsIds)
  465. {
  466. // 名词解释(is_apply):允许用于抵扣当前订单
  467. foreach ($couponList as &$item) {
  468. if ($item['apply_range'] == ApplyRangeEnum::ALL) {
  469. // 1. 全部商品
  470. $item['is_apply'] = true;
  471. } elseif ($item['apply_range'] == ApplyRangeEnum::SOME) {
  472. // 2. 指定商品, 判断订单商品是否存在可用
  473. $applyGoodsIds = array_intersect($item['apply_range_config']['applyGoodsIds'], $orderGoodsIds);
  474. $item['is_apply'] = !empty($applyGoodsIds);
  475. } elseif ($item['apply_range'] == ApplyRangeEnum::EXCLUDE) {
  476. // 2. 排除商品, 判断订单商品是否全部都在排除行列
  477. $excludedGoodsIds = array_intersect($item['apply_range_config']['excludedGoodsIds'], $orderGoodsIds);
  478. $item['is_apply'] = count($excludedGoodsIds) != count($orderGoodsIds);
  479. }
  480. !$item['is_apply'] && $item['not_apply_info'] = '该优惠券不支持当前商品';
  481. }
  482. return $couponList;
  483. }
  484. /**
  485. * 获取用户可用优惠券列表
  486. * @param int $userId
  487. * @param array $param
  488. * @return \think\Paginator
  489. * @throws \think\db\exception\DbException
  490. */
  491. public function getUserCouponLists(int $userId,$sum_total_price)
  492. {
  493. $params['dataType'] = 'isUsable';//
  494. $filter = $this->getFilter($params);
  495. $list = $this->with(['userCouponGoodsExcept'])
  496. ->where($filter)
  497. ->where('user_id', '=', $userId)
  498. ->where('min_price', '<=', $sum_total_price)
  499. ->order('reduce_price','desc')
  500. ->select();
  501. return $list;
  502. }
  503. /**
  504. * 获取今天是否领过该券
  505. * @param $userId
  506. * @param $couponId
  507. * @return UserCoupon|array|\think\Model|null
  508. * @throws \think\db\exception\DataNotFoundException
  509. * @throws \think\db\exception\DbException
  510. * @throws \think\db\exception\ModelNotFoundException
  511. */
  512. public function getUserCouponId($userId,$couponId){
  513. return self::where('user_id',$userId)->where('coupon_id',$couponId)
  514. ->field('user_coupon_id')
  515. ->whereYear('create_time','this year')
  516. ->find();
  517. //->column('user_coupon_id');
  518. }
  519. public static function showDetail($userCouponId){
  520. return self::where('user_coupon_id',$userCouponId)
  521. ->field('user_coupon_id,coupon_id,name,reduce_price,min_price,expire_time')->find();
  522. }
  523. }