// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\model; use app\api\service\User as UserService; use app\common\enum\order\PayStatus as PayStatusEnum; use app\common\exception\BaseException; use app\common\model\UserCoupon as UserCouponModel; use app\common\enum\coupon\ExpireType as ExpireTypeEnum; use app\common\enum\coupon\ApplyRange as ApplyRangeEnum; use app\common\library\helper; use app\api\model\Coupon; use app\common\model\CouponGood as CouponGoodModel; use app\common\model\UserCouponGoods as UserCouponGoodsModel; use app\api\model\User as UserModel; use app\api\model\Order as OrderModel; use think\facade\Db; /** * 用户优惠券模型 * Class UserCoupon * @package app\api\model */ class UserCoupon extends UserCouponModel { public function getUserCouponRdsKey($user_id){ $key = 'receive:send_type:20:user_id:'.$user_id; return $key; } /** * 获取用户优惠券列表 * @param int $userId * @param array $param * @return \think\Paginator * @throws \think\db\exception\DbException */ public function getList(int $userId, array $param) { $filter = $this->getFilter($param); return $this->where($filter) ->where('user_id', '=', $userId) ->order(['coupon_type'=>'asc','reduce_price'=>'desc','discount'=>'asc','expire_time'=>'asc']) ->paginate(30); } /** * 检索查询条件 * @param array $param * @return array|mixed */ private function getFilter(array $param = []) { // 设置默认查询参数 $params = $this->setQueryDefaultValue($param, [ 'dataType' => 'all', // all:全部 isUsable:可用的 isExpire:已过期 isUse:已使用 'amount' => null, // 订单消费金额 ]); // 检索列表类型 $filter = []; // 可用的优惠券 if ($params['dataType'] === 'isUsable') { $filter[] = ['is_use', '=', 0]; $filter[] = ['start_time', '<=', date('Y-m-d H:i:s')]; $filter[] = ['expire_time', '>', date('Y-m-d H:i:s')]; } // 已过期的优惠券 if ($params['dataType'] === 'isExpire') { $filter[] = ['is_use', '=', 0]; $filter[] = ['expire_time', '<', date('Y-m-d H:i:s')]; } // 已使用的优惠券 if ($params['dataType'] === 'isUse') { $filter[] = ['is_use', '=', 1]; } // 订单消费金额 $params['amount'] > 0 && $filter[] = ['min_price', '<=', $params['amount']]; return $filter; } /** * 获取用户优惠券总数量(可用) * @param int $userId * @return int */ public function getCount(int $userId) { return $this->where('user_id', '=', $userId) ->where('is_use', '=', 0) ->where('start_time', '<=', date('Y-m-d H:i:s')) ->where('expire_time', '>', date('Y-m-d H:i:s')) ->count(); } /** * 获取用户优惠券ID集 * @param $userId * @return array */ public function getUserCouponIds(int $userId) { return $this->where('user_id', '=', $userId)->column('coupon_id'); } //获取指定用户指定优惠券 public function getUserCouponByCouponId(int $userId,array $couponIdArray){ return $this->where('user_id', '=', $userId)->where('coupon_id','in',$couponIdArray)->select(); } /** * 获取用户优惠券ID集 * @param $userId * @return array */ public function getTodayUserCouponIds(int $userId) { $today = strtotime(date("Y-m-d"),time()); $today_end = $today + 86400; return $this->where('user_id', '=', $userId)->where('create_time','>',$today)->where('create_time','<',$today_end)->column('coupon_id'); } //查某优惠券用户领了几张 public function getUserCouponCount(int $userId,int $coupon_id){ return $this->where('user_id', '=', $userId)->where('coupon_id',$coupon_id)->count(); } /** * 领取优惠券 * @param int $couponId 优惠券ID * @return bool * @throws BaseException */ public function receive(int $couponId,bool $exchange = false,$needUserCouponId =false) { // 当前用户信息 $userInfo = UserService::getCurrentLoginUser(true); // 获取优惠券信息 $coupon = Coupon::where("coupon_id",$couponId)->lock('for update')->find(); // dd($coupon); // $coupon = Coupon::detail($couponId); // 验证优惠券是否可领取 if (!$this->checkReceive($userInfo, $coupon,$exchange)) { return false; } // 添加领取记录 return $this->add($userInfo, $coupon,$needUserCouponId); } /** * 领取优惠券 * @param int $couponId 优惠券ID * @return bool * @throws BaseException */ public function userreceive(int $couponId,int $user_id,bool $exchange = false) { // 用户信息 $userInfo = UserModel::where('user_id',$user_id)->find(); // 获取优惠券信息 $coupon = Coupon::where("coupon_id",$couponId)->lock('for update')->find(); // $coupon = Coupon::detail($couponId); // 验证优惠券是否可领取 if (!$this->checkReceive($userInfo, $coupon,$exchange)) { log_record("receive fail:coupon_id".$couponId.",error:".$this->error,'error'); return false; } log_record("userreceive:coupon_id",'error'); // 添加领取记录 return $this->add($userInfo, $coupon); } /** * 领取优惠券 * @param $coupon 优惠券信息 * @param $userInfo 用户信息 * @return bool * @throws BaseException */ public function receiveLogin($coupon, $userInfo) { // 当前用户信息 // $userInfo = UserService::getCurrentLoginUser(true); // 获取优惠券信息 // $coupon = Coupon::detail($couponId); // 验证优惠券是否可领取 if (!$this->checkReceive($userInfo, $coupon)) { return false; } // 添加领取记录 return $this->add($userInfo, $coupon); } /** * 添加领取记录 * @param $user * @param Coupon $coupon * @param bool $needUserCouponId * @return bool */ private function add($user, Coupon $coupon,$needUserCouponId =false) { // 计算有效期 if ($coupon['expire_type'] == 10) { if($coupon['expire_day']>1){ $d = $coupon['expire_day'] -1; $expire_time = date('Y-m-d',strtotime('+'.$d.' day')). ' 23:59:59' ; }else{ $expire_time = date("Y-m-d",time()).' 23:59:59'; } } else { $expire_time = $coupon['expire_time']; } $startTime = $coupon['start_time']; $endTime = $coupon['end_time']; // 整理领取记录 $data = [ 'coupon_id' => $coupon['coupon_id'], 'name' => $coupon['name'], 'coupon_type' => $coupon['coupon_type'], 'reduce_price' => $coupon['reduce_price'], 'discount' => $coupon['discount'], 'min_price' => $coupon['min_price'], 'expire_type' => $coupon['expire_type'], 'expire_day' => $coupon['expire_day'], 'start_time' => $startTime, 'end_time' => $endTime, 'apply_range' => $coupon['apply_range'], 'apply_range_config' => $coupon['apply_range_config'], 'user_id' => $user['user_id'], 'store_id' => self::$storeId?:10001, 'subtitle'=>$coupon['subtitle'], 'describe'=>$coupon['describe'], 'expire_time'=>$expire_time, 'discount_type'=>$coupon['discount_type'], 'max_discount_price'=>$coupon['max_discount_price'], 'overlay_discount'=>implode(',', $coupon['overlay_discount']), ]; $coupon_id = $coupon['coupon_id']; // 事务处理 return $this->transaction(function () use ($data,$coupon_id,$needUserCouponId) { // 添加领取记录 $coupon = Coupon::where("coupon_id",$coupon_id)->find(); $status = $this->save($data); log_record("save data:".json_encode($data)); if ($status) { // 更新优惠券领取数量 $coupon->setIncReceiveNum(); } $goodList = CouponGoodModel::where('coupon_id', $coupon_id)->select(); $now = Date("Y-m-d H:i:s",time()); foreach($goodList as $row){ $gdata['user_coupon_id'] = $this->user_coupon_id; $gdata['goods_id'] = $row->goods_id; $gdata['image_id'] = $row->image_id; $gdata['goods_sku_id'] = $row->goods_sku_id; $gdata['goods_sku_no'] = $row->goods_sku_no; $gdata['goods_name'] = $row->goods_name; $gdata['goods_props'] = isset($row->goods_props)?json_encode($row->goods_props):''; $gdata['goods_price'] = $row->goods_price; $gdata['goods_num'] = $row->goods_num; $gdata['total_price'] = $row->total_price; $gdata['is_except'] = $row->is_except; $gdata['create_time'] = $now; $gdata['update_time'] = $now; $ugood = new UserCouponGoodsModel; $ugood->save($gdata); } log_record("transaction finish data:".$coupon_id); if ($needUserCouponId === true){ return $this->user_coupon_id; } return $status; }); //return false; } /** * 验证优惠券是否可领取 * @param mixed $userInfo 当前登录用户信息 * @param Coupon $coupon 优惠券详情 * @return bool */ private function checkReceive($userInfo, Coupon $coupon,bool $exchange = false) { if (!$coupon) { $this->error = '优惠券不存在'; return false; } //生日礼券每年只能领取一张 if ($coupon['coupon_type'] == Coupon::MEMBER_BIRTH_COUPON){ //检查是否用户生日月份 $birthMonth = $userInfo['birthday']?substr($userInfo['birthday'],5,2):0; if (intval($birthMonth) != intval(date('m'))){ $this->error ='请在生日当月领取使用~'; return false; } if (UserCoupon::where(['user_id'=>$userInfo['user_id'],'coupon_id'=>$coupon['coupon_id']])->whereYear('create_time')->find()) { $this->error ='今年已经领过了哟'; return false; } } //会员专享券每月只能领取一张 if ($coupon['coupon_type'] == Coupon::MEMBER_COUPON){ if (UserCoupon::where(['user_id'=>$userInfo['user_id'],'coupon_id'=>$coupon['coupon_id']])->whereMonth('create_time')->find()) { $this->error ='这个月领过了哟'; return false; } } if($coupon['limit_total_type']==10&&$coupon['avaiable_num']<=0){ $this->error ='领完啦,下次再来'; return false; } if(!$exchange){ if(Date("Y-m-d H:i:s",time())>$coupon['end_time']){ $this->error = '优惠券已失效!'; return false; } if(Date("Y-m-d H:i:s",time())<$coupon['start_time']){ $this->error = '优惠券已失效!!'; return false; } if($coupon['status']==0||$coupon['is_delete']==1||$coupon['audit_status']!=10){ $this->error = '优惠券已失效'; return false; } //属于限时券 if($coupon['is_limit_hour']==1){ $coupon['start_hour'] = str_pad($coupon['start_hour'],2,"0",STR_PAD_LEFT); $coupon['end_hour'] = str_pad($coupon['end_hour'],2,"0",STR_PAD_LEFT); $today_start = Date("Y-m-d",time())." ".$coupon['start_hour'].":00:00"; $today_end = Date("Y-m-d",time())." ".$coupon['end_hour'].":00:00"; $now = Date("Y-m-d H:i:s",time()); if(!($now>$today_start&&$now<$today_end)){ $this->error = '限时券不在领取的时间段'; return false; } } }else{ if(Date("Y-m-d H:i:s",time())>$coupon['end_time']){ $this->error = '兑换码已失效!'; return false; } if(Date("Y-m-d H:i:s",time())<$coupon['start_time']){ $this->error = '兑换码已失效!!'; return false; } if($coupon['status']==0||$coupon['is_delete']==1||$coupon['audit_status']!=10){ $this->error = '兑换码已失效'; return false; } } // if (!$coupon->checkReceive()) { // $this->error = $coupon->getError(); // return false; // } // 验证是否已领取 if($coupon['limit_receive_type']==10){ $userCouponIds = $this->getUserCouponIds($userInfo['user_id']); if (in_array($coupon['coupon_id'], $userCouponIds)) { if($coupon->limit_receive_cnt==1){ $this->error = '您已经领过了噢~'; return false; } $count = $this->getUserCouponCount($userInfo['user_id'],$coupon['coupon_id']); if($count>=$coupon->limit_receive_cnt){ $this->error = '您已经领过了噢~'; return false; } } } if($coupon['limit_receive_type']==20){ $userCouponIds = $this->getTodayUserCouponIds($userInfo['user_id']); $ac = array_count_values($userCouponIds); if(isset($ac[$coupon['coupon_id']])&&$ac[$coupon['coupon_id']]>=$coupon['limit_receive_cnt']){ $this->error = '优惠券领取已达到当天限额'; return false; } } return true; } /** * 订单结算可用优惠券列表 * @param int $userId 用户id * @param float $goodsList 订单商品列表 * @param array $orderGoodsIds 订单商品ID集合 * @return array|mixed * @throws \think\db\exception\DbException */ public static function getUserCouponList(int $userId, $goodsList, array $orderGoodsIds) { // 获取用户可用的优惠券列表 $list = (new static)->getList($userId, ['dataType' => 'isUsable']); $data = []; foreach ($list as $coupon) { // 有效期范围内 if ($coupon['start_time'] > date('Y-m-d H:i:s')) continue; $isOrderCount = OrderModel::where(['pay_status'=>PayStatusEnum::SUCCESS,'user_id'=>$userId])->count(); //如果是首单折扣券,并且当前用户已经下过单了 if($coupon['coupon_type']==50 && $coupon['discount_type']==1 && $isOrderCount>0){ continue; } $exceptGoodsId = helper::getArrayColumn($coupon['userCouponGoodsExcept'],'goods_id'); // $excludedGoodsIds = array_intersect($exceptGoodsId, $orderGoodsIds); // if(count($excludedGoodsIds)>0) continue; //zq220128此代码为了解决购物车中只要有一款商品可以用券就显示此劵,但是会导致不能用券的商品也分摊了优惠券金额,看后面优化 $diffGoodsIds = array_diff($orderGoodsIds,$exceptGoodsId); if (count($diffGoodsIds) == 0)continue; $amount = 0;//计算可用商品优惠券的金额 $newGoods = [];//计算可用商品优惠券的商品ID集合 foreach ($goodsList as $good){ if(in_array($good['goods_id'],$diffGoodsIds)){ if($good['is_activity_discount']==0 || stripos($good['activity_discount_overlay'],'1') !== false){ $newGoods[] = $good['goods_id']; $amount += ($good['total_price']-$good['activity_discount_total_price']??0); } $amount -= $good['member_total_money']??0;//有会员优惠就减会员优惠 } } if($amount<=0) continue;//如果没有满减金额就取消 //满减金额不达标 if($coupon['min_price']>$amount) continue; //end----- $key = $coupon['user_coupon_id']; $data[$key] = [ 'user_coupon_id' => $coupon['user_coupon_id'], 'name' => $coupon['name'], 'subtitle' => $coupon['subtitle'], 'describe' => $coupon['describe'], 'coupon_type' => $coupon['coupon_type'], 'reduce_price' => $coupon['reduce_price'], 'discount' => $coupon['discount'], 'min_price' => $coupon['min_price'], 'expire_type' => $coupon['expire_type'], 'start_time' => $coupon['start_time'], 'end_time' => $coupon['end_time'], 'expire_time' => $coupon['expire_time'], 'apply_range' => $coupon['apply_range'], 'apply_range_config' => $coupon['apply_range_config'], 'except_goods_id' => $exceptGoodsId, 'max_discount_price' => $coupon['max_discount_price'], 'first_order_discount' => $coupon['first_order_discount'], 'overlay_discount' => $coupon['overlay_discount'], 'discount_type' => $coupon['discount_type'], 'new_good_ids' => $newGoods, ]; // 计算打折金额 if ($coupon['coupon_type'] == 50) { $reducePrice = helper::bcmul($amount, 1-($coupon['discount'] / 100)); //如果设置了最大的优惠金额并且当前优惠的金额大于设置的金额 if($coupon['max_discount_price']>0 && $coupon['max_discount_price']<$reducePrice){ $reducePrice = $coupon['max_discount_price']; } $data[$key]['reduced_price'] = $reducePrice; } else $data[$key]['reduced_price'] = $coupon['reduce_price']; } // 根据折扣金额排序并返回 return array_sort($data, 'reduced_price', true); } /** * 判断当前优惠券是否满足订单使用条件 * @param $couponList * @param array $orderGoodsIds 订单商品ID集 * @return mixed */ public static function couponListApplyRange(array $couponList, array $orderGoodsIds) { // 名词解释(is_apply):允许用于抵扣当前订单 foreach ($couponList as &$item) { if ($item['apply_range'] == ApplyRangeEnum::ALL) { // 1. 全部商品 $item['is_apply'] = true; } elseif ($item['apply_range'] == ApplyRangeEnum::SOME) { // 2. 指定商品, 判断订单商品是否存在可用 $applyGoodsIds = array_intersect($item['apply_range_config']['applyGoodsIds'], $orderGoodsIds); $item['is_apply'] = !empty($applyGoodsIds); } elseif ($item['apply_range'] == ApplyRangeEnum::EXCLUDE) { // 2. 排除商品, 判断订单商品是否全部都在排除行列 $excludedGoodsIds = array_intersect($item['apply_range_config']['excludedGoodsIds'], $orderGoodsIds); $item['is_apply'] = count($excludedGoodsIds) != count($orderGoodsIds); } !$item['is_apply'] && $item['not_apply_info'] = '该优惠券不支持当前商品'; } return $couponList; } /** * 获取用户可用优惠券列表 * @param int $userId * @param array $param * @return \think\Paginator * @throws \think\db\exception\DbException */ public function getUserCouponLists(int $userId,$sum_total_price) { $params['dataType'] = 'isUsable';// $filter = $this->getFilter($params); $list = $this->with(['userCouponGoodsExcept']) ->where($filter) ->where('user_id', '=', $userId) ->where('min_price', '<=', $sum_total_price) ->order('reduce_price','desc') ->select(); return $list; } /** * 获取今天是否领过该券 * @param $userId * @param $couponId * @return UserCoupon|array|\think\Model|null * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getUserCouponId($userId,$couponId){ return self::where('user_id',$userId)->where('coupon_id',$couponId) ->field('user_coupon_id') ->whereYear('create_time','this year') ->find(); //->column('user_coupon_id'); } public static function showDetail($userCouponId){ return self::where('user_coupon_id',$userCouponId) ->field('user_coupon_id,coupon_id,name,reduce_price,min_price,expire_time')->find(); } }