You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

469 lines
18 KiB

<?php
namespace App\Service\v3\Implementations;
use App\Model\v3\Coupon;
use App\Model\v3\CouponRec;
use App\Model\v3\CouponRecType;
use App\Model\v3\GoodsActivity;
use App\Service\CommonService;
use App\Service\v3\Interfaces\CouponRecServiceInterface;
use App\Service\v3\Interfaces\CouponServiceInterface;
use App\Service\v3\Interfaces\ShopCartServiceInterface;
use Hyperf\DbConnection\Db;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Di\Annotation\Inject;
use App\Constants\v3\SsdbKeys;
use App\TaskWorker\SSDBTask;
class CouponRecService implements CouponRecServiceInterface
{
/**
* @Inject
* @var ShopCartServiceInterface
*/
protected $shopCartService;
/**
* @Inject
* @var CouponServiceInterface
*/
protected $couponService;
/**
* @Inject
* @var HelperService
*/
protected $helperService;
public function do()
{
// TODO: Implement do() method.
}
public function check()
{
// TODO: Implement check() method.
}
public function undo()
{
// TODO: Implement undo() method.
}
/**
* 获取当前订单可使用的优惠券
* @param $totalAmount
* @param $userId
* @param $marketId
* @param $type
* @param $storeTypeIds
* @return array
*/
public function allForOrderOlAvailable($totalAmount, $userId, $marketId, $type, $storeTypeIds = [])
{
// 用户今日使用过的优惠券
$redis = ApplicationContext::getContainer()->get(Redis::class);
$couponTodayUsedIds = $redis->sMembers('coupon_'.date('Ymd').'_used_'.$userId);
$currentTime = time();
$builder = Db::table('lanzu_coupon_receive as receive')
->join('lanzu_coupon as coupon', 'coupon.id', '=', 'receive.coupon_id', 'inner');
if (is_array($couponTodayUsedIds)&&!empty($couponTodayUsedIds)) {
$builder->whereNotIn('coupon.id', $couponTodayUsedIds);
}
foreach ($storeTypeIds as $key => &$item) {
$item = (string)$item;
}
if (!empty($storeTypeIds)) {
$builder->whereJsonContains('coupon.category_ids', $storeTypeIds);
}
$builder->where(['receive.user_id' => $userId])
->whereIn('receive.status', [0,1])
->where('receive.number_remain', '>', 0)
->whereIn('coupon.type', [1,$type])
->where('coupon.full_amount', '<=', $totalAmount)
->where('coupon.usable_start_time', '<=', $currentTime)
->where('coupon.usable_end_time', '>=', $currentTime)
->where('coupon.usable_number', '<=', Db::raw('receive.number_remain'));
if ($marketId) {
$builder->whereJsonContains('coupon.market_ids', [(string)$marketId]);
}
return $builder->orderByRaw('coupon.discounts DESC, coupon.full_amount DESC')
->get()
->toArray();
}
/**
* 用户优惠券列表
* @param $userId
* @param $type
* @param int $page
* @param int $pagesize
* @return array
*/
public function getListByUser($userId,$type,$page = 1,$pagesize = 5)
{
//查询优惠券
$builder = CouponRec::query()->join('lanzu_coupon', 'lanzu_coupon.id', '=', 'lanzu_coupon_receive.coupon_id')->with('coupon')
->where([
['lanzu_coupon_receive.user_id' ,'=', $userId],
]);
/**
* $type unused 未使用 used 已使用 expired 已失效
*/
switch ($type){
case 'unused':
$builder = $builder->where([
['lanzu_coupon.usable_end_time' ,'>', time()],
['lanzu_coupon_receive.number_remain' ,'>', 0]
])
->whereIn('lanzu_coupon_receive.status',[0,1]);
break;
case 'used':
$builder = $builder->whereIn('lanzu_coupon_receive.status',[1,2]);
break;
case 'expired':
$builder = $builder->where(function ($query) {
$query->where('lanzu_coupon.usable_end_time', '<', time());
});
break;
}
$builder = $builder->select(
'lanzu_coupon_receive.*'
)
->orderBy('lanzu_coupon.weigh','desc');
$paginate = $builder->paginate($pagesize);
$couponList = $paginate->toArray();
return ['has_more_pages' => $paginate->hasMorePages(), 'list' => $couponList['data']];
}
/**
* 获取用户当前线上订单可以使用的优惠券
* 1、所有未过期额优惠券,并且满足订单的金额要求
* 2、筛选出其中当日使用过的优惠券
* 3、筛选出其中活动商品不可用的优惠券(订单中有活动商品且活动商品不可使用优惠券)
* 4、筛选出其中活动商品可用但商品的活动类型不符合优惠券活动使用类型的要求(订单中有活动商品可以用优惠券,但是活动类型type和优惠券中的available活动类型不可用)
* @param $userId
* @param $marketId
* @param $shopcartIds
* @return array
*/
public function allForOnlineOrderAvailable($userId, $marketId, $shopcartIds = [])
{
// 获取购物车数据
$carts = $this->shopCartService->allForUser($userId, $marketId, $shopcartIds);
$totalAmount = $carts['total'];
// 获取购物车中商品和店铺的类别
$storeCategoryIds = []; // 商户类型
$goodsCategoryIds = []; // 商品类型
$hasActivityGoodsCannotUse = false; // 购物车中是否有不可使用优惠券的活动商品
$goodsActivityTypes = []; // 活动商品的类型集合
foreach ($carts['store_lists'] as $key => &$cart) {
// 商户类型
if (isset($cart['store']['category_id']) && $cart['store']['category_id']) {
array_push($storeCategoryIds, $cart['store']['category_id']);
}
if (isset($cart['shopping_cart']) && is_array($cart['shopping_cart'])) {
foreach ($cart['shopping_cart'] as $key2 => &$goods) {
// 商品类型
if (isset($goods['goods']['category_id']) && $goods['goods']['category_id']) {
array_push($goodsCategoryIds, $goods['goods']['category_id']);
}
// 活动商品不可使用优惠券的情况
if ($goods['activity_type'] == 2 && $goods['goods']['can_use_coupon'] != 1) {
$hasActivityGoodsCannotUse = true;
}
// 活动商品类型集合
if ($goods['activity_type'] == 2) {
array_merge($goodsActivityTypes, [$goods['goods']['type']]);
}
}
}
}
$categoryIds = array_merge($storeCategoryIds, $goodsCategoryIds);
$coupon = ApplicationContext::getContainer()->get(Coupon::class);
$couponRec = ApplicationContext::getContainer()->get(CouponRec::class);
$couponTable = $coupon->getTable();
$receiveTable = $couponRec->getTable();
$currentTime = time();
// 获取用户已领取的优惠券,同时是在使用期内的优惠券
$builder = $couponRec->with('coupon')
->select("{$receiveTable}.*")
->join($couponTable, "{$couponTable}.id", "=", "{$receiveTable}.coupon_id")
->where("{$receiveTable}.user_id", $userId)
->where("{$receiveTable}.number_remain", ">", 0)
->whereIn("{$receiveTable}.status", [0,1])
->where("{$couponTable}.usable_start_time", "<=", $currentTime)
->where("{$couponTable}.usable_end_time", ">=", $currentTime);
if (!empty($marketId)) { # 市场限制
$builder->where(function ($query) use ($marketId, $couponTable) {
return $query->whereJsonContains("{$couponTable}.market_ids", [(string)$marketId])
->orWhereJsonLength("{$couponTable}.market_ids", '=', 0);
});
}
if (!empty($categoryIds)) { # 分类限制
$builder->where(function ($query) use ($categoryIds, $couponTable) {
return $query->whereJsonContains("{$couponTable}.category_ids", $categoryIds)
->orWhereJsonLength("{$couponTable}.category_ids", '=', 0);
});
}
$couponReceives = $builder->orderByRaw("{$couponTable}.usable_end_time ASC")->get();
$available = [];
$notAvailable = [];
// 有活动商品不可用优惠券,全部都不可用了
if ($hasActivityGoodsCannotUse) {
$notAvailable = $couponReceives;
foreach ($notAvailable as $key => &$item) {
$item['not_available_reason'] = '活动不可用';
}
$couponReceives = [];
}
// 循环摘出失效不可用的优惠券
foreach ($couponReceives as $key => &$item) {
// 不满订单金额
if ($item['coupon']['full_amount'] > $totalAmount) {
$item['not_available_reason'] = '差'.bcsub($item['coupon']['full_amount'], $totalAmount, 2).'元';
array_push($notAvailable, $item);
continue;
}
// 今日已使用
$todayUsedCouponIds = $this->couponService->allTodayCouponUsed($userId);
if (in_array($item['coupon']['id'], $todayUsedCouponIds)) {
$item['not_available_reason'] = '今日已使用';
array_push($notAvailable, $item);
continue;
}
// 活动商品可用,校验对应的优惠券可使用活动类型
$intersects = array_intersect($goodsActivityTypes, (array)$item['activity_available']); # 所有活动商品的活动类型和优惠券的可用活动类型求交集
$canUseByTypes = array_diff($goodsActivityTypes, $intersects); # 所有活动商品的活动类型和上述交集求差集,如果为空则是可以用的,否则说明前者中有后者不能用的活动类型
if (!empty($canUseByTypes)) {
$item['not_available_reason'] = '活动不可用';
array_push($notAvailable, $item);
continue;
}
$item['not_available_reason'] = '';
array_push($available, $item);
}
return ['available' => $available, 'not_available' => $notAvailable];
}
/**
* @param $userId
* @return mixed
*/
public function statistics($userId)
{
//未使用
$res['unused'] = 0;
//已使用
$res['used'] = 0;
//已过期
$res['expired'] = 0;
$coupons = CouponRec::query()
->with('coupon')
->where('user_id',$userId)
->get();
foreach ($coupons as $coupon){
if($coupon->coupon->usable_end_time < time()){
$res['expired'] += $coupon->number_remain;
}else{
$res['unused'] += $coupon->number_remain;
}
$res['used'] += $coupon->used_num;
}
return $res;
}
public function getAvailableList($userId,$receiveType)
{
/* 优惠券活动标志 2 */
$ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
$couponActivity = $ssdb->exec('hgetall', SsdbKeys::COUPON_REBATE_ACTIVITY);
$activityType = $couponActivity === false ? 0 : $couponActivity['activity'];
$result = [
'active_type' => 1,
'not_receive' => [],
'jump_data' => [
'src' => "/zh_cjdianc/pages/couponrebate/index?activity_type=".$activityType,
'src2' => "/zh_cjdianc/pages/couponrebate/index?activity_type=".$activityType,
'share_bg' => env('OSS_IMG_HOST').'/static/img/coupon_share.png',
'receive_bg' => env('OSS_IMG_HOST').'/static/img/coupon_bg.png',
'coupons' => []
]
];
$nowTime = time();
$c_ids = [];
// 渠道开启,查询该渠道可以领取的优惠券ID
// 渠道未开启,查询所有优惠券
if (env('SUB_CHANNEL') == 1) {
$c_ids = CouponRecType::join('lanzu_coupon','lanzu_coupon.id','lanzu_coupon_receive_type.coupon_id')
->where([
['lanzu_coupon_receive_type.receive_type','=',$receiveType],
['lanzu_coupon.end_time','>',$nowTime],
['lanzu_coupon.start_time','<=',$nowTime],
['lanzu_coupon.status','=',1]
])
->pluck('lanzu_coupon.id');
} else {
$c_ids = Coupon::where([
['end_time','>',$nowTime],
['start_time','<=',$nowTime],
['status','=',1]
])->pluck('id');
}
$couponReceive = CouponRec::where('user_id',$userId);
// 渠道开启,查询该用户在此渠道领过的优惠券ID
if (env('SUB_CHANNEL') == 1) {
$couponReceive->where('receive_type', $receiveType);
}
$cr_ids = $couponReceive->pluck('coupon_id');
// 可领取的券ID
$c_ids = $c_ids->toArray();
// 已经领取的券ID
$cr_ids = $cr_ids->toArray();
// 当前用户可领的优惠券ID
$couponIds = array_diff($c_ids, $cr_ids);
// 转发型优惠券
$couponReceiveIds = ($couponActivity === false || $this->helperService->nonempty($couponActivity['forward']) )? [] : explode(',',$couponActivity['forward']);
// 所有优惠券
$couponIds = array_merge($couponIds,$couponReceiveIds);
$whereC = [
['lanzu_coupon.end_time','>',$nowTime],
['lanzu_coupon.start_time','<=',$nowTime],
['lanzu_coupon.status','=',1]
];
// 查询领取型1 和 转发型2
$whereActiveType = [1,2];
if (env('SUB_CHANNEL') == 1) {
array_push($whereC, ['type.receive_type','=', $receiveType]);
}
$coupons = Coupon::join('lanzu_coupon_receive_type as type', 'lanzu_coupon.id', '=', 'type.coupon_id')
->whereIn('lanzu_coupon.id', $couponIds)
->whereIn('lanzu_coupon.active_type', $whereActiveType)
->where($whereC)
->whereRaw('lanzu_coupon.inventory_use < lanzu_coupon.inventory and lanzu_coupon.inventory-lanzu_coupon.inventory_use >= type.one_receive_number')
->select('lanzu_coupon.*','type.one_receive_number')
->orderBy('lanzu_coupon.weigh','desc')
->get();
foreach ($coupons as $k => &$v){
if($v->active_type == 1 && count($result['not_receive']) < 4){
$result['not_receive'][] = $v;
}else if($v->active_type == 2 && in_array($v->id,$couponReceiveIds)){
$result['jump_data']['coupons'][] = $v->id;
}
if($v->discount_type == 2){
$v->discounts = floatval($v->discounts);
}
}
$result['active_type'] = count($result['jump_data']['coupons']) > 0 ? 2 : $result['active_type'] ;
return $result;
}
/**
* @param $userId
* @param $receiveType
* @param $ids
*/
public function receive($userId, $receiveType, $ids)
{
$ids = explode(',', $ids);
$now = time();
$success = [];
$fail = [];
// if ($this->empty($userId) || $this->empty($receiveType) || $this->empty($ids)) {
// return [
// 'success' => $success,
// 'fail' => $fail,
// ];
// }
Db::transaction(function () use ($ids,$receiveType,$userId,&$success,&$fail,$now) {
//读写锁,完全控制,性能底
$cps = Coupon::whereIn('id', $ids)->lockForUpdate()->get();
//写锁,可能在高并发下,读取到脏数据,写入可能有超发情况
//$cps = Coupon::whereIn('id', $ids)->sharedLock()->get();
foreach ($cps as $key => $cp) {
$where = [
'coupon_id' => $cp->id,
];
if (env('SUB_CHANNEL') == 1) {
$where['receive_type'] = $receiveType;
}
$oneReceiveNumber = CouponRecType::where($where)->value('one_receive_number');
$cr = new CouponRec;
$cr->user_id = $userId;
$cr->coupon_id = $cp->id;
$cr->order_main_id = 0;
$cr->receive_time = $now;
$cr->number = $oneReceiveNumber;
$cr->number_remain = $oneReceiveNumber;
$cr->status = 0;
$cr->update_time = $now;
$cr->receive_type = $receiveType;
//如果优惠卷库存小于等于已领取的数量, 则返回领取失败的优惠券
if ($cp->inventory<=$cp->inventory_use||$cp->inventory<($cp->inventory_use+$cr->number)){
$fail[] = $cp;
}else{
$cp->inventory_use += $cr->number;//记录已领取的数量
if ($cr->save()&&$cp->save()) {
$success[] = $cp;
} else {
$fail[] = $cp;
}
}
}
});
return [
'success' => $success,
'fail' => $fail,
];
}
}