init
- 框架初始化 - 安装插件 - 修复PHP8.4报错
This commit is contained in:
379
addons/shopro/library/activity/Activity.php
Normal file
379
addons/shopro/library/activity/Activity.php
Normal file
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity;
|
||||
|
||||
use addons\shopro\facade\ActivityRedis;
|
||||
use addons\shopro\library\activity\contract\ActivityInterface;
|
||||
use addons\shopro\library\activity\contract\ActivityGetterInterface;
|
||||
|
||||
class Activity
|
||||
{
|
||||
/**
|
||||
* 活动model
|
||||
*/
|
||||
public $model = null;
|
||||
public $redis = null;
|
||||
|
||||
protected $type = null;
|
||||
protected $rules = null;
|
||||
|
||||
protected $hasRedis = null;
|
||||
|
||||
protected $getters = [];
|
||||
|
||||
public function __construct($model_name)
|
||||
{
|
||||
$this->hasRedis = has_redis();
|
||||
$this->model = new $model_name;
|
||||
$this->redis = ActivityRedis::instance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加活动
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function save($params)
|
||||
{
|
||||
$this->rules = $params['rules'];
|
||||
$this->type = $params['type'];
|
||||
$params['classify'] = $this->model->getClassify($this->type); // 设置 classify
|
||||
$params['prehead_time'] = in_array($params['classify'], ['promo', 'app']) ? '' : ($params['prehead_time'] ?? ''); // 触发触发器,promo 不能设置 prehead_time
|
||||
// 检测活动之间的冲突
|
||||
$this->checkActivity($params);
|
||||
|
||||
// 保存活动
|
||||
$this->model->allowField(true)->save($params);
|
||||
|
||||
// 保存活动其他数据
|
||||
$this->saveOther($params);
|
||||
|
||||
if ($this->hasRedis) {
|
||||
$this->redis->setActivity($this->model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新活动
|
||||
*
|
||||
* @param \think\Model $activity
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function update($activity, $params)
|
||||
{
|
||||
$this->model = $activity;
|
||||
|
||||
$this->rules = $params['rules'];
|
||||
$this->type = $activity->type;
|
||||
$params['type'] = $activity->type; // 活动类型不可编辑,赋值活动本身的 type
|
||||
$params['classify'] = $this->model->getClassify($this->type); // 设置 classify
|
||||
$params['prehead_time'] = in_array($params['classify'], ['promo', 'app']) ? '' : ($params['prehead_time'] ?? ''); // 触发触发器,promo 不能设置 prehead_time
|
||||
|
||||
if ($activity->status == 'ended') {
|
||||
error_stop('活动已结束');
|
||||
}
|
||||
|
||||
// 检测活动之间的冲突
|
||||
$params = $this->checkActivity($params, $this->model->id);
|
||||
|
||||
$activities = $activity->classifies()['activity'];
|
||||
$activities = array_keys($activities);
|
||||
if ($activity->status == 'ing') {
|
||||
if (in_array($activity->type, $activities)) {
|
||||
// 活动正在进行中,只能改结束时间
|
||||
$params = [
|
||||
'title' => $params['title'],
|
||||
'end_time' => $params['end_time'],
|
||||
'goods_list' => $params['goods_list'],
|
||||
'richtext_id' => $params['richtext_id'],
|
||||
'richtext_title' => $params['richtext_title'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 保存活动
|
||||
$this->model->allowField(true)->save($params);
|
||||
|
||||
// 保存活动其他数据
|
||||
$this->saveOther($params);
|
||||
|
||||
if ($this->hasRedis) {
|
||||
$this->redis->setActivity($this->model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除活动
|
||||
*
|
||||
* @param \think\Model $activity
|
||||
* @return void
|
||||
*/
|
||||
public function delete($activity)
|
||||
{
|
||||
if ($this->hasRedis) {
|
||||
$this->redis->delActivity($activity);
|
||||
}
|
||||
|
||||
return $activity->delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 活动规格相关数据展示
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public function showSkuPrice($type, $skuPrice)
|
||||
{
|
||||
$skuPrice = $this->provider($type)->showSkuPrice($skuPrice);
|
||||
|
||||
return $skuPrice;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 活动规则相关信息
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public function rulesInfo($type, $rules)
|
||||
{
|
||||
$this->rules = $rules;
|
||||
$this->type = $type;
|
||||
|
||||
$activity = $this->provider()->rulesInfo($type, $rules);
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验活动特有的数据
|
||||
*
|
||||
* @param array $params
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function checkActivity($params, $activity_id = 0, $type = null)
|
||||
{
|
||||
return $this->provider($type)->check($params, $activity_id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存活动特有的数据
|
||||
*
|
||||
* @param array $params
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function saveOther($params, $type = null)
|
||||
{
|
||||
return $this->provider($type)->save($this->model, $params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化促销标签
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function formatRuleTags($rules, $type = null)
|
||||
{
|
||||
return $this->provider($type)->formatTags($rules, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化促销标签
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function formatRuleTexts($rules, $type = null)
|
||||
{
|
||||
return $this->provider($type)->formatTexts($rules, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用活动覆盖商品数据
|
||||
*
|
||||
* @param \think\Model|array $goods
|
||||
* @return void
|
||||
*/
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
$skuPrices = $this->provider($activity['type'])->recoverSkuPrices($goods, $activity);
|
||||
return $skuPrices;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 活动购买检测(仅处理活动,不处理促销)
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buyCheck($buyInfo, $activity)
|
||||
{
|
||||
if ($activity) {
|
||||
return $this->provider($activity['type'])->buyCheck($buyInfo, $activity);
|
||||
}
|
||||
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 活动购买检测(仅处理活动,不处理促销)
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buy($buyInfo, $activity)
|
||||
{
|
||||
if ($activity) {
|
||||
return $this->provider($activity['type'])->buy($buyInfo, $activity);
|
||||
}
|
||||
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 购买成功
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @return array
|
||||
*/
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
if ($order->activity_type) {
|
||||
$this->provider($order->activity_type)->buyOk($order, $user);
|
||||
}
|
||||
|
||||
if ($order->promo_types) {
|
||||
$promoTypes = explode(',', $order->promo_types);
|
||||
foreach ($promoTypes as $promo_type) {
|
||||
$this->provider($promo_type)->buyOk($order, $user);
|
||||
}
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 购买失败(释放库存,剪掉销量,移除参团数据)
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param string $type 失败类型:invalid=订单取消,关闭;refund=退款
|
||||
* @return array
|
||||
*/
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
if ($order->activity_type) {
|
||||
$this->provider($order->activity_type)->buyFail($order, $type);
|
||||
}
|
||||
|
||||
if ($order->promo_types) {
|
||||
$promoTypes = explode(',', $order->promo_types);
|
||||
foreach ($promoTypes as $promo_type) {
|
||||
$this->provider($promo_type)->buyFail($order, $type);
|
||||
}
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取促销优惠信息
|
||||
*
|
||||
* @param array $promo
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function getPromoInfo($promo, ?array $data = [])
|
||||
{
|
||||
return $this->provider($promo['type'])->getPromoInfo($promo, $data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 活动提供器
|
||||
*
|
||||
* @param string $type
|
||||
* @return ActivityInterface
|
||||
*/
|
||||
public function provider($type = null)
|
||||
{
|
||||
$type = $type ?: $this->type;
|
||||
$class = "\\addons\\shopro\\library\\activity\\provider\\" . \think\helper\Str::studly($type);
|
||||
if (class_exists($class)) {
|
||||
return new $class($this);
|
||||
}
|
||||
|
||||
error_stop('活动类型不支持');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动提供器
|
||||
*
|
||||
* @param string $getter
|
||||
* @return ActivityGetterInterface
|
||||
*/
|
||||
public function getter($getter = null)
|
||||
{
|
||||
$getter = $getter ? $getter : $this->defaultGetter();
|
||||
|
||||
if (isset($this->getters[$getter])) {
|
||||
return $this->getters[$getter];
|
||||
}
|
||||
|
||||
$class = "\\addons\\shopro\\library\\activity\\getter\\" . \think\helper\Str::studly($getter);
|
||||
if (class_exists($class)) {
|
||||
return $this->getters[$getter] = new $class($this);
|
||||
}
|
||||
|
||||
error_stop('活动类型不支持');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取默认获取器
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function defaultGetter()
|
||||
{
|
||||
return $this->hasRedis ? 'redis' : 'db';
|
||||
}
|
||||
|
||||
|
||||
public function __call($funcname, $arguments)
|
||||
{
|
||||
return $this->getter()->{$funcname}(...$arguments);
|
||||
}
|
||||
}
|
||||
186
addons/shopro/library/activity/ActivityRedis.php
Normal file
186
addons/shopro/library/activity/ActivityRedis.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity;
|
||||
|
||||
use addons\shopro\facade\Redis;
|
||||
use app\admin\model\shopro\activity\Activity as ActivityModel;
|
||||
use addons\shopro\library\activity\traits\ActivityRedis as ActivityRedisTrait;
|
||||
|
||||
class ActivityRedis
|
||||
{
|
||||
use ActivityRedisTrait;
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将活动设置到 redis 中
|
||||
*
|
||||
* @param mixed $activity
|
||||
* @param array $goodsList
|
||||
* @return void
|
||||
*/
|
||||
public function setActivity($activity)
|
||||
{
|
||||
$activity = ActivityModel::with('activity_sku_prices')->where('id', $activity['id'])->find();
|
||||
|
||||
// hash 键值
|
||||
$keyActivity = $this->keyActivity($activity->id, $activity->type);
|
||||
|
||||
// 删除旧的可变数据,需要排除销量 key
|
||||
if (Redis::EXISTS($keyActivity)) {
|
||||
// 如果 hashKey 存在,删除规格
|
||||
$activityCache = $this->getActivityByKey($keyActivity);
|
||||
|
||||
foreach ($activityCache as $field => $value) {
|
||||
// 是商品规格,并且不是销量
|
||||
if (strpos($field, $this->hashGoodsPrefix) !== false && strpos($field, '-sale') === false) {
|
||||
// 商品规格信息,删掉
|
||||
Redis::HDEL($keyActivity, $field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Redis::HMSET(
|
||||
$keyActivity,
|
||||
[
|
||||
'id' => $activity['id'],
|
||||
'title' => $activity['title'],
|
||||
'type' => $activity['type'],
|
||||
'type_text' => $activity['type_text'],
|
||||
'classify' => $activity['classify'],
|
||||
'goods_ids' => $activity['goods_ids'],
|
||||
'richtext_id' => $activity['richtext_id'],
|
||||
'richtext_title' => $activity['richtext_title'],
|
||||
'prehead_time' => strtotime($activity['prehead_time']),
|
||||
'start_time' => strtotime($activity['start_time']),
|
||||
'end_time' => strtotime($activity['end_time']),
|
||||
'rules' => is_array($activity['rules']) ? json_encode($activity['rules']) : $activity['rules'],
|
||||
]
|
||||
);
|
||||
|
||||
// 将活动规格保存 redis
|
||||
foreach ($activity['activity_sku_prices'] as $goods) {
|
||||
unset($goods['sales']); // 规格销量单独字段保存 goods-id-id-sale key
|
||||
$keyActivityGoods = $this->keyActivityGoods($goods['goods_id'], $goods['goods_sku_price_id']);
|
||||
// 获取当前规格的销量,修改库存的时候,需要把 stock 加上这部分销量
|
||||
$cacheSale = Redis::HGET($keyActivity, $keyActivityGoods . '-sale');
|
||||
$goods['stock'] = $goods['stock'] + $cacheSale;
|
||||
Redis::HSET($keyActivity, $keyActivityGoods, json_encode($goods));
|
||||
}
|
||||
|
||||
// 将 hash 键值存入 有序集合,score 为 id
|
||||
Redis::ZADD($this->zsetKey, strtotime($activity['start_time']), $keyActivity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 删除活动缓存
|
||||
*
|
||||
* @param object $activity
|
||||
* @return void
|
||||
*/
|
||||
public function delActivity($activity)
|
||||
{
|
||||
// hash 键值
|
||||
$keyActivity = $this->keyActivity($activity->id, $activity->type);
|
||||
|
||||
// 删除 hash
|
||||
Redis::DEL($keyActivity);
|
||||
|
||||
// 删除集合
|
||||
Redis::ZREM($this->zsetKey, $keyActivity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据活动类型,获取所有活动(前端:秒杀商品列表,拼团商品列表)
|
||||
*
|
||||
* @param array $activityTypes 要查询的活动类型
|
||||
* @param array|string $status 要查询的活动的状态
|
||||
* @param string $format_type // 格式化类型,默认clear,清理多余的字段,比如拼团的 团信息 normal=格式化拼团,秒杀等|promo=格式化满减,满折,赠送
|
||||
* @return array
|
||||
*/
|
||||
public function getActivityList($activityTypes = [], $status = 'all', $format_type = 'normal')
|
||||
{
|
||||
// 获取对应的活动类型的集合
|
||||
$keysActivity = $this->getKeysActivityByTypes($activityTypes, $status);
|
||||
|
||||
$activityList = [];
|
||||
foreach ($keysActivity as $keyActivity) {
|
||||
// 格式化活动
|
||||
$activity = $this->formatActivityByKey($keyActivity, $format_type);
|
||||
if ($activity) {
|
||||
$activityList[] = $activity;
|
||||
}
|
||||
}
|
||||
|
||||
return $activityList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询商品列表,详情时,获取这个商品对应的秒杀拼团等活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @param Array $activityTypes
|
||||
* @param array|string $status 要查询的活动的状态
|
||||
* @param string $format_type
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivitys($goods_id, $activityTypes = [], $status = 'all', $format_type = 'goods')
|
||||
{
|
||||
// 获取商品第一条活动的 hash key
|
||||
$keysActivity = $this->getkeysActivityByGoods($goods_id, $activityTypes, $status);
|
||||
|
||||
// 如果存在活动
|
||||
foreach ($keysActivity as $keyActivity) {
|
||||
// 格式化活动
|
||||
$activity = $this->formatActivityByKey($keyActivity, $format_type, ['goods_id' => $goods_id]);
|
||||
if ($activity) {
|
||||
$activityList[] = $activity;
|
||||
}
|
||||
}
|
||||
|
||||
return $activityList ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是商品的特定的活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @param integer $activity_id
|
||||
* @param array|string $status 要查询的活动的状态
|
||||
* @param string $format_type
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivityByActivity($goods_id, $activity_id, $status = 'all', $format_type = 'goods')
|
||||
{
|
||||
// 获取商品第一条活动的 hash key
|
||||
$keyActivity = $this->getKeyActivityById($activity_id);
|
||||
if (!$keyActivity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$activity = $this->formatActivityByKey($keyActivity, $format_type, ['goods_id' => $goods_id]);
|
||||
if ($activity) {
|
||||
// 判断商品
|
||||
$goods_ids = array_values(array_filter(explode(',', $activity['goods_ids'])));
|
||||
if (!in_array($goods_id, $goods_ids) && !empty($goods_ids)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 判断状态
|
||||
$status = is_array($status) ? $status : [$status];
|
||||
if (!in_array('all', $status)) {
|
||||
if (!in_array($activity['status'], $status)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $activity ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\contract;
|
||||
|
||||
interface ActivityGetterInterface
|
||||
{
|
||||
/**
|
||||
* 获取所有给定类型给定状态的活动
|
||||
*
|
||||
* @param array $activityTypes
|
||||
* @return array
|
||||
*/
|
||||
public function getActivities($activityTypes, $status = 'all');
|
||||
|
||||
|
||||
/**
|
||||
* 获取时间区间内的所有给定类型的活动
|
||||
*
|
||||
* @param array $range
|
||||
* @param array $activityTypes
|
||||
* @param string $range_type overlap=只要区间有重叠的就算|contain=包含,必须在这个区间之内的
|
||||
* @return array
|
||||
*/
|
||||
public function getActivitiesByRange($range, $activityTypes = [], $range_type = 'overlap');
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivitys($goods_id);
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的营销
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsPromos($goods_id);
|
||||
|
||||
|
||||
|
||||
}
|
||||
141
addons/shopro/library/activity/contract/ActivityInterface.php
Normal file
141
addons/shopro/library/activity/contract/ActivityInterface.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\contract;
|
||||
|
||||
interface ActivityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* 活动规则表单验证
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function validate(array $data);
|
||||
|
||||
/**
|
||||
* 检查要添加的活动状态
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function check(array $params);
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动相关的信息
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public function rulesInfo($type, $rules);
|
||||
|
||||
|
||||
/**
|
||||
* 保存当前活动的专属数据
|
||||
*
|
||||
* @param \think\Model $activity
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function save(\think\Model $activity, ?array $params = []);
|
||||
|
||||
/**
|
||||
* 展示当前活动规格的专属数据
|
||||
*
|
||||
* @param \think\Model $activity
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function showSkuPrice(\think\Model $skuPrice);
|
||||
|
||||
/**
|
||||
* 格式化活动标签(满减,满折等)
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function formatTags(array $rules, $type);
|
||||
|
||||
|
||||
/**
|
||||
* 格式化活动标签单个(满减,满折等)
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function formatTag(array $discountData);
|
||||
|
||||
|
||||
/**
|
||||
* 格式化活动规则,完整版(满减,满折等)
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function formatTexts(array $rules, $type);
|
||||
|
||||
|
||||
/**
|
||||
* 覆盖商品活动数据
|
||||
*
|
||||
* @param \think\Model|array $goods
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function recoverSkuPrices(array $goods, ?array $activity);
|
||||
|
||||
|
||||
/**
|
||||
* 购买活动处理
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buyCheck($buyInfo, $activity);
|
||||
|
||||
|
||||
/**
|
||||
* 购买活动
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buy($buyInfo, $activity);
|
||||
|
||||
|
||||
/**
|
||||
* 购买活动成功
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @return array
|
||||
*/
|
||||
public function buyOk($order, $user);
|
||||
|
||||
|
||||
/**
|
||||
* 购买活动失败
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function buyFail($order, $type);
|
||||
|
||||
|
||||
/**
|
||||
* 活动信息
|
||||
*
|
||||
* @param array $promo
|
||||
* @param array $data 附加数据
|
||||
* @return array
|
||||
*/
|
||||
public function getPromoInfo(array $promo, ?array $data = []);
|
||||
}
|
||||
20
addons/shopro/library/activity/getter/Base.php
Normal file
20
addons/shopro/library/activity/getter/Base.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\getter;
|
||||
|
||||
use addons\shopro\library\activity\contract\ActivityGetterInterface;
|
||||
use addons\shopro\library\activity\Activity as ActivityManager;
|
||||
|
||||
abstract class Base implements ActivityGetterInterface
|
||||
{
|
||||
protected $manager = null;
|
||||
protected $model = null;
|
||||
|
||||
public function __construct(ActivityManager $activityManager)
|
||||
{
|
||||
$this->manager = $activityManager;
|
||||
|
||||
$this->model = $activityManager->model;
|
||||
$this->redis = $activityManager->redis;
|
||||
}
|
||||
}
|
||||
138
addons/shopro/library/activity/getter/Db.php
Normal file
138
addons/shopro/library/activity/getter/Db.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\getter;
|
||||
|
||||
class Db extends Base
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取所有给定类型给定状态的活动
|
||||
*
|
||||
* @param array $activityTypes
|
||||
* @return array
|
||||
*/
|
||||
public function getActivities($activityTypes, $status = 'all')
|
||||
{
|
||||
$status = is_array($status) ? $status : [$status];
|
||||
|
||||
$activities = $this->model->where('type', 'in', $activityTypes);
|
||||
|
||||
if (!in_array('all', $status)) {
|
||||
$activities = $activities->statusComb($status);
|
||||
}
|
||||
|
||||
$activities = $activities->select();
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定时间区间内的活动
|
||||
*
|
||||
* @param array $range
|
||||
* @param array $activityTypes
|
||||
* @param string $range_type overlap=只要区间有重叠的就算|contain=包含,必须在这个区间之内的
|
||||
* @return array
|
||||
*/
|
||||
public function getActivitiesByRange($range, $activityTypes = [], $range_type = 'overlap')
|
||||
{
|
||||
$activities = $this->model->where('type', 'in', $activityTypes);
|
||||
|
||||
if ($range_type == 'overlap') {
|
||||
$activities = $activities->where('prehead_time', '<=', $range[1])->where('end_time', '>=', $range[0]);
|
||||
} elseif ($range_type == 'contain') {
|
||||
$activities = $activities->where('prehead_time', '>=', $range[0])->where('end_time', '<=', $range[1]);
|
||||
}
|
||||
|
||||
$activities = $activities->select();
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivitys($goods_id)
|
||||
{
|
||||
$classify = $this->model->classifies();
|
||||
$activityTypes = array_keys($classify['activity']);
|
||||
|
||||
$activities = $this->model->show()->where('find_in_set(:id,goods_ids)', ['id' => $goods_id])
|
||||
->with(['activity_sku_prices' => function ($query) use ($goods_id) {
|
||||
$query->where('goods_id', $goods_id)
|
||||
->where(
|
||||
'status',
|
||||
'up'
|
||||
);
|
||||
}])
|
||||
->where('type', 'in', $activityTypes)
|
||||
->order('start_time', 'asc') // 优先查询最先开始的活动(允许商品同时存在多个活动中, 只要开始结束时间不重合)
|
||||
->select();
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的营销
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsPromos($goods_id)
|
||||
{
|
||||
$classify = $this->model->classifies();
|
||||
$activityTypes = array_keys($classify['promo']);
|
||||
|
||||
$promos = $this->model->show()
|
||||
->where(function ($query) use ($goods_id) {
|
||||
// goods_ids 里面有当前商品,或者 goods_ids 为null(所有商品都参与)
|
||||
$query->where('find_in_set('. $goods_id .',goods_ids)')
|
||||
->whereOr('goods_ids', null)
|
||||
->whereOr('goods_ids', '');
|
||||
})
|
||||
->where('type', 'in', $activityTypes)
|
||||
->order('start_time', 'asc') // 优先查询最先开始的活动(允许商品同时存在多个活动中, 只要开始结束时间不重合)
|
||||
->select();
|
||||
|
||||
return $promos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 活动 id 获取指定活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @param integer $activity_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivityByActivity($goods_id, $activity_id)
|
||||
{
|
||||
$classify = $this->model->classifies();
|
||||
$activityTypes = array_keys($classify['activity']);
|
||||
|
||||
$activity = $this->model->where('id', $activity_id)->find();
|
||||
|
||||
if ($activity) {
|
||||
$goods_ids = array_values(array_filter(explode(',', $activity->goods_ids)));
|
||||
if (!in_array($goods_id, $goods_ids) && !empty($goods_ids)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (in_array($activity['type'], $activityTypes)) {
|
||||
// 活动规格
|
||||
$activity->activity_sku_prices = $activity->activity_sku_prices;
|
||||
}
|
||||
|
||||
$activity = $activity->toArray();
|
||||
}
|
||||
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
124
addons/shopro/library/activity/getter/Redis.php
Normal file
124
addons/shopro/library/activity/getter/Redis.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\getter;
|
||||
|
||||
class Redis extends Base
|
||||
{
|
||||
public $redis;
|
||||
|
||||
/**
|
||||
* 获取所有给定类型给定状态的活动
|
||||
*
|
||||
* @param array $activityTypes
|
||||
* @return array
|
||||
*/
|
||||
public function getActivities($activityTypes, $status = 'all')
|
||||
{
|
||||
$activities = $this->redis->getActivityList($activityTypes, $status, 'clear');
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定时间区间内的活动
|
||||
*
|
||||
* @param array $range
|
||||
* @param array $activityTypes
|
||||
* @param string $range_type overlap=只要区间有重叠的就算|contain=包含,必须在这个区间之内的
|
||||
* @return array
|
||||
*/
|
||||
public function getActivitiesByRange($range, $activityTypes = [], $range_type = 'overlap')
|
||||
{
|
||||
$activities = $this->redis->getActivityList($activityTypes);
|
||||
|
||||
$newActivities = [];
|
||||
foreach ($activities as $key => $activity) {
|
||||
if ($this->rangeCompare($range, [$activity['prehead_time'], $activity['end_time']], $range_type)) {
|
||||
$newActivities[] = $activity;
|
||||
}
|
||||
}
|
||||
|
||||
return $newActivities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivitys($goods_id)
|
||||
{
|
||||
$classify = $this->model->classifies();
|
||||
$activityTypes = array_keys($classify['activity']);
|
||||
|
||||
$activities = $this->redis->getGoodsActivitys($goods_id, $activityTypes, ['prehead', 'ing']);
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品的所有正在进行,或正在预售的营销
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsPromos($goods_id)
|
||||
{
|
||||
$classify = $this->model->classifies();
|
||||
$activityTypes = array_keys($classify['promo']);
|
||||
|
||||
$activities = $this->redis->getGoodsActivitys($goods_id, $activityTypes, ['prehead', 'ing'], 'promo');
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过 活动 id 获取指定活动
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @param integer $activity_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGoodsActivityByActivity($goods_id, $activity_id)
|
||||
{
|
||||
$activities = $this->redis->getGoodsActivityByActivity($goods_id, $activity_id);
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 比较时间区间
|
||||
*
|
||||
* @param array $range
|
||||
* @param array $activityRange
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
private function rangeCompare($range, $activityRange, $type = 'overlap')
|
||||
{
|
||||
if ($type == 'overlap') {
|
||||
if ($range[1] >= $activityRange[0] && $range[0] <= $activityRange[1]) { // 时间相等也算没有交集
|
||||
// 两个时间区间有交集
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} elseif ($type == 'contain') {
|
||||
if ($range[0] <= $activityRange[0] && $range[1] >= $activityRange[1]) { // 时间相等算包含
|
||||
// activityRange 是 range 的子集
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
235
addons/shopro/library/activity/provider/Base.php
Normal file
235
addons/shopro/library/activity/provider/Base.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
use addons\shopro\library\activity\Activity as ActivityManager;
|
||||
use addons\shopro\library\activity\contract\ActivityInterface;
|
||||
use addons\shopro\library\activity\traits\CheckActivity;
|
||||
use app\admin\model\shopro\activity\SkuPrice as ActivitySkuPriceModel;
|
||||
|
||||
abstract class Base implements ActivityInterface
|
||||
{
|
||||
use CheckActivity;
|
||||
|
||||
/**
|
||||
* ActivityManager
|
||||
*
|
||||
* @var ActivityManager
|
||||
*/
|
||||
protected $manager = null;
|
||||
|
||||
protected $rules = [];
|
||||
|
||||
|
||||
protected $message = [];
|
||||
|
||||
|
||||
protected $default = [];
|
||||
|
||||
|
||||
public function __construct(ActivityManager $activityManager)
|
||||
{
|
||||
$this->manager = $activityManager;
|
||||
}
|
||||
|
||||
|
||||
public function validate($data)
|
||||
{
|
||||
$data = array_merge($this->default, $data);
|
||||
|
||||
$validate = (new \think\Validate)->message($this->message)->rule($this->rules);
|
||||
if (!$validate->check($data)) {
|
||||
error_stop($validate->getError());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function check($params)
|
||||
{
|
||||
if ((isset($params['start_time']) && $params['start_time'] > $params['end_time']) || $params['end_time'] < date('Y-m-d H:i:s')) {
|
||||
error_stop('请设置正确的活动时间');
|
||||
}
|
||||
if (isset($params['prehead_time']) && $params['prehead_time'] > $params['start_time']) {
|
||||
error_stop('预热时间必须小于活动开始时间');
|
||||
}
|
||||
|
||||
$rules = $this->validate($params['rules']);
|
||||
|
||||
// 存在折扣,将折扣按照从小到大排序
|
||||
if (isset($rules['discounts']) && $rules['discounts']) {
|
||||
// 处理展示优惠,full 从小到大
|
||||
$discounts = $rules['discounts'];
|
||||
|
||||
$discountsKeys = array_column($discounts, null, 'full');
|
||||
ksort($discountsKeys);
|
||||
$rules['discounts'] = array_values($discountsKeys); // 优惠按照 full 从小到大排序
|
||||
}
|
||||
|
||||
$params['rules'] = $rules;
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 附加活动信息
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public function rulesInfo($type, $rules)
|
||||
{
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function save($activity, $params = [])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function showSkuPrice($skuPrice)
|
||||
{
|
||||
return $skuPrice;
|
||||
}
|
||||
|
||||
|
||||
public function formatTags($rules, $type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function formatTag($discountData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function formatTexts($rules, $type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
return $goods['sku_prices'];
|
||||
}
|
||||
|
||||
|
||||
public function buyCheck($buyInfo, $activity)
|
||||
{
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
public function buy($buyInfo, $activity)
|
||||
{
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
public function getPromoInfo($promo, ?array $data = [])
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected function promoGoodsData($promo)
|
||||
{
|
||||
$promo_goods_amount = '0'; // 该活动中商品的总价
|
||||
$promo_goods_num = '0'; // 该活动商品总件数
|
||||
$goodsIds = []; // 该活动中所有的商品 id
|
||||
$promo_dispatch_amount = '0'; // 该活动中总运费
|
||||
|
||||
// 活动中的商品总金额,总件数,所有商品 id
|
||||
foreach ($promo['goods'] as $buyInfo) {
|
||||
$promo_goods_amount = bcadd($promo_goods_amount, (string)$buyInfo['goods_amount'], 2);
|
||||
$promo_goods_num = bcadd($promo_goods_num, (string)$buyInfo['goods_num'], 2);
|
||||
$goodsIds[] = $buyInfo['goods_id'];
|
||||
|
||||
$promo_dispatch_amount = bcadd($promo_dispatch_amount, (string)$buyInfo['dispatch_amount'], 2);
|
||||
}
|
||||
|
||||
return compact(
|
||||
"promo_goods_amount",
|
||||
"promo_goods_num",
|
||||
"promo_dispatch_amount",
|
||||
"goodsIds"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加,编辑活动规格,type = stock 只编辑库存
|
||||
*
|
||||
* @param array $goodsList 商品列表
|
||||
* @param int $activity_id 活动 id
|
||||
* @param string $type type = all 全部编辑,type = stock 只编辑库存
|
||||
* @return void
|
||||
*/
|
||||
protected function saveSkuPrice($goodsList, $activity, \Closure $extCb = null)
|
||||
{
|
||||
//如果是编辑 先下架所有的规格产品,防止丢失历史销量数据;
|
||||
|
||||
$type = 'all';
|
||||
if (request()->isPut() && $activity->status == 'ing') {
|
||||
// 修改并且是进行中的活动,只能改库存
|
||||
$type = 'stock';
|
||||
}
|
||||
|
||||
ActivitySkuPriceModel::where('activity_id', $activity->id)->update(['status' => 'down']);
|
||||
|
||||
foreach ($goodsList as $key => $goods) {
|
||||
$actSkuPrice[$key] = $goods['activity_sku_prices'];
|
||||
|
||||
foreach ($actSkuPrice[$key] as $ke => $skuPrice) {
|
||||
if ($type == 'all') {
|
||||
$current = $skuPrice;
|
||||
// 处理 ext 回调
|
||||
if ($extCb) {
|
||||
$current = $extCb($current);
|
||||
}
|
||||
} else {
|
||||
$current = [
|
||||
'id' => $skuPrice['id'],
|
||||
'stock' => $skuPrice['stock'],
|
||||
'status' => $skuPrice['status'] // 这个要去掉,不能改参与状态
|
||||
];
|
||||
}
|
||||
|
||||
if ($current['id'] == 0) {
|
||||
unset($current['id']);
|
||||
}
|
||||
unset($current['sales']);
|
||||
$current['activity_id'] = $activity->id;
|
||||
$current['goods_id'] = $goods['id'];
|
||||
|
||||
$actSkuPriceModel = new ActivitySkuPriceModel();
|
||||
if (isset($current['id'])) {
|
||||
// type == 'edit'
|
||||
$actSkuPriceModel = $actSkuPriceModel->find($current['id']);
|
||||
}
|
||||
|
||||
if ($actSkuPriceModel) {
|
||||
unset($current['createtime'], $current['updatetime']);
|
||||
$actSkuPriceModel->allowField(true)->save($current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
148
addons/shopro/library/activity/provider/FreeShipping.php
Normal file
148
addons/shopro/library/activity/provider/FreeShipping.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
/**
|
||||
* 满额包邮
|
||||
*/
|
||||
class FreeShipping extends Base
|
||||
{
|
||||
protected $rules = [
|
||||
"type" => "require",
|
||||
"full_num" => "require|float"
|
||||
];
|
||||
|
||||
|
||||
|
||||
protected $message = [
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"type" => "money", // money=满足金额|num=满足件数
|
||||
"province_except" => '', // 不包邮的省份
|
||||
"city_except" => '', // 不包邮的城市
|
||||
"district_except" => '', // 不包邮的地区
|
||||
"district_text" => [], // 中文
|
||||
"full_num" => 0
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function formatTags($rules, $type)
|
||||
{
|
||||
$full_num = $rules['full_num'] ?? ($rules['discounts'][0]['full_num'] ?? 0); // activity_order 存的格式不一样是在 discount 里面包裹着
|
||||
$tags[] = $this->formatTag([
|
||||
'type' => $rules['type'],
|
||||
'full_num' => $full_num,
|
||||
]);
|
||||
|
||||
return array_values(array_filter($tags));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化 discount 折扣为具体优惠标签
|
||||
*
|
||||
* @param array $discountData
|
||||
* @return string
|
||||
*/
|
||||
public function formatTag($discountData)
|
||||
{
|
||||
$tag = '满' . $discountData['full_num'] . ($discountData['type'] == 'money' ? '元' : '件') . '包邮';
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化 discount 折扣为具体优惠详情
|
||||
*/
|
||||
public function formatTexts($rules, $type)
|
||||
{
|
||||
$text = '满' . $rules['full_num'] . ($rules['type'] == 'money' ? '元' : '件') . '即可包邮';
|
||||
if (isset($rules['district_text']) && $rules['district_text']) {
|
||||
$district = '';
|
||||
if (isset($rules['district_text']['province']) && $rules['district_text']['province']) {
|
||||
$district .= join(',', $rules['district_text']['province']) . ',';
|
||||
}
|
||||
if (isset($rules['district_text']['city']) && $rules['district_text']['city']) {
|
||||
$district .= join(',', $rules['district_text']['city']) . ',';
|
||||
}
|
||||
if (isset($rules['district_text']['district']) && $rules['district_text']['district']) {
|
||||
$district .= join(',', $rules['district_text']['district']) . ',';
|
||||
}
|
||||
|
||||
if ($district) {
|
||||
$text .= " (不支持包邮地区:" . rtrim($district, ',') . ")";
|
||||
}
|
||||
}
|
||||
|
||||
$texts[] = $text;
|
||||
|
||||
return array_values(array_filter($texts));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getPromoInfo($promo, $data = [])
|
||||
{
|
||||
extract($this->promoGoodsData($promo));
|
||||
$rules = $promo['rules'];
|
||||
$userAddress = $data['userAddress'] ?? null;
|
||||
|
||||
// 是按金额,还是按件数比较
|
||||
$compareif = $rules['type'] == 'num' ? 'promo_goods_num' : 'promo_goods_amount';
|
||||
|
||||
// 判断除外的地区
|
||||
$district_except = isset($rules['district_except']) && $rules['district_except'] ? explode(',', $rules['district_except']) : [];
|
||||
$city_except = isset($rules['city_except']) && $rules['city_except'] ? explode(',', $rules['city_except']) : [];
|
||||
$province_except = isset($rules['province_except']) && $rules['province_except'] ? explode(',', $rules['province_except']) : [];
|
||||
if ($userAddress) {
|
||||
if (
|
||||
in_array($userAddress['district_id'], $district_except)
|
||||
|| in_array($userAddress['city_id'], $city_except)
|
||||
|| in_array($userAddress['province_id'], $province_except)
|
||||
) {
|
||||
// 收货地址在非包邮地区,则继续循环下个活动
|
||||
return null;
|
||||
}
|
||||
} else if ($district_except || $city_except || $province_except) {
|
||||
// 没有选择收货地址,并且活动中包含地区限制,不计算活动
|
||||
return null;
|
||||
}
|
||||
|
||||
if (${$compareif} < $rules['full_num']) {
|
||||
// 不满足条件,接着循环下个规则
|
||||
return null;
|
||||
}
|
||||
|
||||
// 记录活动信息
|
||||
$promo_discount_info = [
|
||||
'activity_id' => $promo['id'], // 活动id
|
||||
'activity_title' => $promo['title'], // 活动标题
|
||||
'activity_type' => $promo['type'], // 活动类型
|
||||
'activity_type_text' => $promo['type_text'], // 活动类型中文
|
||||
'promo_discount_money' => 0, // 这里无法知道真实运费减免,会在 orderCreate 后续计算完包邮优惠之后,改为真实减免的运费
|
||||
'promo_goods_amount' => $promo_goods_amount, // 当前活动商品总金额
|
||||
'rule_type' => $rules['type'], // 满多少元|还是满多少件
|
||||
'discount_rule' => [
|
||||
'full_num' => $rules['full_num']
|
||||
], // 满足的那条规则
|
||||
'goods_ids' => $goodsIds // 这个活动包含的这次购买的商品
|
||||
];
|
||||
|
||||
return $promo_discount_info;
|
||||
}
|
||||
}
|
||||
132
addons/shopro/library/activity/provider/FullDiscount.php
Normal file
132
addons/shopro/library/activity/provider/FullDiscount.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
/**
|
||||
* 满额折扣
|
||||
*/
|
||||
class FullDiscount extends Base
|
||||
{
|
||||
protected $rules = [
|
||||
"type" => "require",
|
||||
"discounts" => "require|array"
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
"discounts.require" => '请填写优惠规则',
|
||||
"discounts.array" => '请填写优惠规则',
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"type" => "money", // money=满足金额|num=满足件数
|
||||
"discounts" => []
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function formatTags($rules, $type)
|
||||
{
|
||||
$tags = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$tags[] = $this->formatTag([
|
||||
'type' => $rules['type'],
|
||||
'full' => $discount['full'],
|
||||
'discount' => $discount['discount']
|
||||
]);
|
||||
}
|
||||
|
||||
return array_values(array_filter($tags));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化 discount 折扣为具体优惠标签
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $discountData
|
||||
* @return string
|
||||
*/
|
||||
public function formatTag($discountData)
|
||||
{
|
||||
$tag = '满' . $discountData['full'] . ($discountData['type'] == 'money' ? '元' : '件');
|
||||
$tag .= $discountData['discount'] . '折';
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function formatTexts($rules, $type)
|
||||
{
|
||||
$texts = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$text = '满' . $discount['full'] . ($rules['type'] == 'money' ? '元' : '件');
|
||||
$text .= ',商品总价打' . $discount['discount'] . '折';
|
||||
|
||||
$texts[] = $text;
|
||||
}
|
||||
|
||||
return array_values(array_filter($texts));
|
||||
}
|
||||
|
||||
|
||||
public function getPromoInfo($promo, $data = [])
|
||||
{
|
||||
extract($this->promoGoodsData($promo));
|
||||
$rules = $promo['rules'];
|
||||
|
||||
// 是按金额,还是按件数比较
|
||||
$compareif = $rules['type'] == 'num' ? 'promo_goods_num' : 'promo_goods_amount';
|
||||
|
||||
// 将规则按照从大到校排列,优先比较是否满足最大规则
|
||||
$rulesDiscounts = isset($rules['discounts']) && $rules['discounts'] ? array_reverse($rules['discounts']) : []; // 数组反转
|
||||
|
||||
// 满减, 满折多个规则从大到小匹配最优惠
|
||||
foreach ($rulesDiscounts as $d) {
|
||||
if (${$compareif} < $d['full']) {
|
||||
// 不满足条件,接着循环下个规则
|
||||
continue;
|
||||
}
|
||||
|
||||
$dis = bcdiv($d['discount'], '10', 3); // 保留三位小数,转化折扣
|
||||
$dis = $dis > 1 ? 1 : ($dis < 0 ? 0 : $dis); // 定义边界 0 - 1
|
||||
$promo_dis = 1 - $dis;
|
||||
|
||||
$current_promo_discount_money = bcmul($promo_goods_amount, (string)$promo_dis, 3);
|
||||
$current_promo_discount_money = number_format((float)$current_promo_discount_money, 2, '.', ''); // 计算折扣金额,四舍五入
|
||||
|
||||
// 记录该活动的一些统计信息
|
||||
$promo_discount_info = [
|
||||
'activity_id' => $promo['id'], // 活动id
|
||||
'activity_title' => $promo['title'], // 活动标题
|
||||
'activity_type' => $promo['type'], // 活动类型
|
||||
'activity_type_text' => $promo['type_text'], // 活动类型中文
|
||||
'promo_discount_money' => $current_promo_discount_money, // 优惠金额
|
||||
'promo_goods_amount' => $promo_goods_amount, // 当前活动商品总金额
|
||||
'rule_type' => $rules['type'], // 满多少元|还是满多少件
|
||||
'discount_rule' => $d, // 满足的那条规则
|
||||
'goods_ids' => $goodsIds // 这个活动包含的这次购买的商品
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $promo_discount_info ?? null;
|
||||
}
|
||||
}
|
||||
248
addons/shopro/library/activity/provider/FullGift.php
Normal file
248
addons/shopro/library/activity/provider/FullGift.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
use addons\shopro\library\activity\traits\GiveGift;
|
||||
use app\admin\model\shopro\Coupon;
|
||||
use app\admin\model\shopro\order\Order;
|
||||
|
||||
/**
|
||||
* 满额赠送
|
||||
*/
|
||||
class FullGift extends Base
|
||||
{
|
||||
use GiveGift;
|
||||
|
||||
protected $rules = [
|
||||
"limit_num" => "number|egt:0",
|
||||
"type" => "require",
|
||||
"event" => "require",
|
||||
"discounts" => "require|array"
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
"discounts.require" => '请填写优惠规则',
|
||||
"discounts.array" => '请填写优惠规则',
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"limit_num" => 0, // 每人可参与次数 0:不限制
|
||||
"type" => "money", // money=满足金额|num=满足件数
|
||||
"event" => "confirm", // 赠送时机 paid=支付完成|confirm=确认收货(必须全部确认收货才可以)|finish=订单完成(评价完成)
|
||||
"discounts" => [] //{"full":"100",
|
||||
// "types":"coupon=优惠券|score=积分|money=余额|goods=商品",
|
||||
// "coupon_ids":"赠优惠券时存在",
|
||||
// "total":"赠送优惠券总金额",
|
||||
// "score":"积分",
|
||||
// "money":"余额",
|
||||
// "goods_ids":"商品时存在",
|
||||
// "gift_num":"礼品份数"}
|
||||
];
|
||||
|
||||
protected $giftType = [
|
||||
'money' => '余额',
|
||||
'score' => '积分',
|
||||
'coupon' => '优惠券',
|
||||
'goods' => '商品',
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 验证赠送规则字段
|
||||
$this->checkGiftDiscount($params['rules']['discounts']);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 赠送的优惠券列表
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public function rulesInfo($type, $rules)
|
||||
{
|
||||
$discounts = $rules['discounts'];
|
||||
foreach ($discounts as &$discount) {
|
||||
$discount['coupon_list'] = [];
|
||||
if (in_array('coupon', $discount['types']) && isset($discount['coupon_ids']) && $discount['coupon_ids']) {
|
||||
$discount['coupon_list'] = Coupon::statusHidden()->whereIn('id', $discount['coupon_ids'])->select();
|
||||
}
|
||||
}
|
||||
$rules['discounts'] = $discounts;
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
public function formatTags($rules, $type)
|
||||
{
|
||||
$tags = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$tags[] = $this->formatTag([
|
||||
'type' => $rules['type'],
|
||||
'simple' => $rules['simple'] ?? false, // 简单信息展示
|
||||
'full' => $discount['full'],
|
||||
'discount' => $discount
|
||||
]);
|
||||
}
|
||||
|
||||
return array_values(array_filter($tags));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化 discount 折扣为具体优惠标签
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $discountData
|
||||
* @return string
|
||||
*/
|
||||
public function formatTag($discountData)
|
||||
{
|
||||
$discount = $discountData['discount'];
|
||||
$gift_type_text = '';
|
||||
foreach ($discount['types'] as $type) {
|
||||
$gift_type_text = $gift_type_text . (isset($this->giftType[$type]) ? ',' . $this->giftType[$type] : '');
|
||||
}
|
||||
|
||||
$tag = '满' . $discountData['full'] . ($discountData['type'] == 'money' ? '元' : '件');
|
||||
$tag .= '赠送' . ($discountData['simple'] ? '礼品' : trim($gift_type_text, ','));
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
public function formatTexts($rules, $type)
|
||||
{
|
||||
$texts = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$text = '消费满' . $discount['full'] . ($rules['type'] == 'money' ? '元' : '件');
|
||||
$text .= '';
|
||||
foreach ($discount['types'] as $type) {
|
||||
$text .= ',';
|
||||
if ($type == 'money') {
|
||||
$text .= '赠送' . $discount['money'] . '元余额 ';
|
||||
} elseif ($type == 'score') {
|
||||
$text .= '赠送' . $discount['score'] . '积分 ';
|
||||
} elseif ($type == 'coupon') {
|
||||
$text .= '赠送价值' . $discount['total'] . '元优惠券 ';
|
||||
}
|
||||
}
|
||||
|
||||
$text .= ' (条件:活动礼品共 ' . $discount['gift_num'] . ' 份';
|
||||
if ($rules['limit_num'] > 0) {
|
||||
$text .= ',每人仅限参与 ' . $rules['limit_num'] . ' 次';
|
||||
}
|
||||
$text .= ')';
|
||||
$texts[] = $text;
|
||||
}
|
||||
|
||||
return array_values(array_filter($texts));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function getPromoInfo($promo, $data = [])
|
||||
{
|
||||
extract($this->promoGoodsData($promo));
|
||||
$rules = $promo['rules'];
|
||||
|
||||
// 是按金额,还是按件数比较
|
||||
$compareif = $rules['type'] == 'num' ? 'promo_goods_num' : 'promo_goods_amount';
|
||||
|
||||
// 将规则按照从大到校排列,优先比较是否满足最大规则
|
||||
$rulesDiscounts = isset($rules['discounts']) && $rules['discounts'] ? array_reverse($rules['discounts']) : []; // 数组反转
|
||||
|
||||
// 满减, 满折多个规则从大到小匹配最优惠
|
||||
foreach ($rulesDiscounts as $d) {
|
||||
unset($d['coupon_list']); // 移除规则里面的 coupon_list
|
||||
if (${$compareif} < $d['full']) {
|
||||
// 不满足条件,接着循环下个规则
|
||||
continue;
|
||||
}
|
||||
|
||||
// 记录该活动的一些统计信息
|
||||
$promo_discount_info = [
|
||||
'activity_id' => $promo['id'], // 活动id
|
||||
'activity_title' => $promo['title'], // 活动标题
|
||||
'activity_type' => $promo['type'], // 活动类型
|
||||
'activity_type_text' => $promo['type_text'], // 活动类型中文
|
||||
'promo_discount_money' => 0, // 优惠金额 (赠送,不优惠)
|
||||
'promo_goods_amount' => $promo_goods_amount, // 当前活动商品总金额
|
||||
'rule_type' => $rules['type'], // 满多少元|还是满多少件
|
||||
'discount_rule' => $d, // 满足的那条规则
|
||||
"limit_num" => $rules['limit_num'], // 每个人可参与次数
|
||||
"event" => $rules['event'], // 赠送时机
|
||||
'goods_ids' => $goodsIds // 这个活动包含的这次购买的商品
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $promo_discount_info ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功(货到付款下单),添加礼品记录
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @return void
|
||||
*/
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
// 满赠送
|
||||
$ext = $order->ext;
|
||||
$promoInfos = $ext['promo_infos'];
|
||||
|
||||
foreach ($promoInfos as $info) {
|
||||
if ($info['activity_type'] == 'full_gift') {
|
||||
// 满赠,开始赠送
|
||||
$this->addGiftsLog($order, $user, $info);
|
||||
}
|
||||
}
|
||||
|
||||
$event = $order->status == Order::STATUS_PENDING ? 'pending' : 'paid'; // 货到付款不是真的付款,不能发放礼品 event 改为 pending
|
||||
|
||||
// 检测并赠送礼品
|
||||
$this->checkAndGift($order, $user, $promoInfos, $event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 促销购买失败(退款)
|
||||
*
|
||||
* @param \think\Model $order
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
if ($type == 'refund') {
|
||||
// 退款,将礼品标记为已退款,如果已经送出去的不扣除
|
||||
$this->checkAndFailGift($order, '订单全额退款');
|
||||
} else if ($type == 'invalid') {
|
||||
if ($order->pay_mode == 'offline') {
|
||||
// 只有线下付款取消时才需要标记礼品赠送失败
|
||||
$this->checkAndFailGift($order, '货到付款订单取消');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
addons/shopro/library/activity/provider/FullReduce.php
Normal file
128
addons/shopro/library/activity/provider/FullReduce.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
/**
|
||||
* 满额立减
|
||||
*/
|
||||
class FullReduce extends Base
|
||||
{
|
||||
protected $rules = [
|
||||
"type" => "require",
|
||||
"discounts" => "require|array"
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
"discounts.require" => '请填写优惠规则',
|
||||
"discounts.array" => '请填写优惠规则',
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"type" => "money", // money=满足金额|num=满足件数
|
||||
"discounts" => []
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function formatTags($rules, $type)
|
||||
{
|
||||
$tags = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$tags[] = self::formatTag([
|
||||
'type' => $rules['type'],
|
||||
'full' => $discount['full'],
|
||||
'discount' => $discount['discount']
|
||||
]);
|
||||
}
|
||||
|
||||
return array_values(array_filter($tags));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 discount 折扣为具体优惠标签
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $discountData
|
||||
* @return string
|
||||
*/
|
||||
public function formatTag($discountData)
|
||||
{
|
||||
$tag = '满' . $discountData['full'] . ($discountData['type'] == 'money' ? '元' : '件');
|
||||
$tag .= '减' . $discountData['discount'];
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function formatTexts($rules, $type)
|
||||
{
|
||||
$texts = [];
|
||||
$discounts = $rules['discounts'] ?? [];
|
||||
|
||||
foreach ($discounts as $discount) {
|
||||
$text = '满' . $discount['full'] . ($rules['type'] == 'money' ? '元' : '件');
|
||||
$text .= '减' . $discount['discount'] . '元';
|
||||
|
||||
$texts[] = $text;
|
||||
}
|
||||
|
||||
return array_values(array_filter($texts));
|
||||
}
|
||||
|
||||
|
||||
public function getPromoInfo($promo, $data = [])
|
||||
{
|
||||
extract($this->promoGoodsData($promo));
|
||||
$rules = $promo['rules'];
|
||||
|
||||
// 是按金额,还是按件数比较
|
||||
$compareif = $rules['type'] == 'num' ? 'promo_goods_num' : 'promo_goods_amount';
|
||||
|
||||
// 将规则按照从大到校排列,优先比较是否满足最大规则
|
||||
$rulesDiscounts = isset($rules['discounts']) && $rules['discounts'] ? array_reverse($rules['discounts']) : []; // 数组反转
|
||||
|
||||
// 满减, 满折多个规则从大到小匹配最优惠
|
||||
foreach ($rulesDiscounts as $d) {
|
||||
if (${$compareif} < $d['full']) {
|
||||
// 不满足条件,接着循环下个规则
|
||||
continue;
|
||||
}
|
||||
|
||||
// 满足优惠
|
||||
$current_promo_discount_money = (isset($d['discount']) && $d['discount']) ? $d['discount'] : 0;
|
||||
$current_promo_discount_money = number_format((float)$current_promo_discount_money, 2, '.', ''); // 格式化金额,四舍五入
|
||||
|
||||
// 记录该活动的一些统计信息
|
||||
$promo_discount_info = [
|
||||
'activity_id' => $promo['id'], // 活动id
|
||||
'activity_title' => $promo['title'], // 活动标题
|
||||
'activity_type' => $promo['type'], // 活动类型
|
||||
'activity_type_text' => $promo['type_text'], // 活动类型中文
|
||||
'promo_discount_money' => $current_promo_discount_money, // 优惠金额
|
||||
'promo_goods_amount' => $promo_goods_amount, // 当前活动商品总金额
|
||||
'rule_type' => $rules['type'], // 满多少元|还是满多少件
|
||||
'discount_rule' => $d, // 满足的那条规则
|
||||
'goods_ids' => $goodsIds // 这个活动包含的这次购买的商品
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $promo_discount_info ?? null;
|
||||
}
|
||||
}
|
||||
275
addons/shopro/library/activity/provider/Groupon.php
Normal file
275
addons/shopro/library/activity/provider/Groupon.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
use addons\shopro\library\activity\traits\Groupon as GrouponTrait;
|
||||
use addons\shopro\service\StockSale;
|
||||
use addons\shopro\exception\ShoproException;
|
||||
|
||||
/**
|
||||
* 普通拼团
|
||||
*/
|
||||
class Groupon extends Base
|
||||
{
|
||||
use GrouponTrait;
|
||||
|
||||
protected $rules = [
|
||||
"is_commission" => "require|boolean",
|
||||
"is_free_shipping" => "require|boolean",
|
||||
"sales_show_type" => "require",
|
||||
"team_num" => "require|number",
|
||||
"is_alone" => "require|boolean",
|
||||
"is_fictitious" => "require|boolean",
|
||||
"fictitious_time" => "requireIf:is_fictitious,1|float|egt:0",
|
||||
"is_team_card" => "require|boolean",
|
||||
"is_leader_discount" => "require|boolean",
|
||||
"valid_time" => "require|float|egt:0",
|
||||
"limit_num" => "number|egt:0",
|
||||
"refund_type" => "require", // 退款方式 back=原路退回|money=退回到余额
|
||||
"order_auto_close" => "float|egt:0",
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'team_num.require' => '请填写成团人数',
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"is_commission" => 0, // 是否参与分销
|
||||
"is_free_shipping" => 0, // 是否包邮
|
||||
"sales_show_type" => "real", // real=真实活动销量|goods=商品总销量(包含虚拟销量)
|
||||
"team_num" => 2, // 成团人数,最少两人
|
||||
"is_alone" => 0, // 是否允许单独购买
|
||||
"is_fictitious" => 0, // 是否允许虚拟成团
|
||||
"fictitious_num" => 0, // 最多虚拟人数 0:不允许虚拟 '' 不限制
|
||||
"fictitious_time" => 0, // 开团多长时间自动虚拟成团
|
||||
"is_team_card" => 0, // 参团卡显示
|
||||
"is_leader_discount" => 0, // 团长优惠
|
||||
"valid_time" => 0, // 组团有效时间, 0:一直有效
|
||||
"limit_num" => 0, // 每人限购数量 0:不限购
|
||||
"refund_type" => "back", // 退款方式 back=原路退回|money=退回到余额
|
||||
"order_auto_close" => 0, // 订单自动关闭时间,如果为 0 将使用系统级订单自动关闭时间
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 验证添加的活动商品是否至少设置了一个活动规格
|
||||
$this->checkActivitySkuPrice($params['goods_list']);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function save($activity, $params = [])
|
||||
{
|
||||
$goodsList = $params['goods_list'];
|
||||
|
||||
$this->saveSkuPrice($goodsList, $activity, function ($skuPrice) use ($activity) {
|
||||
// 处理 团长优惠
|
||||
$rules = $activity->rules;
|
||||
$is_leader_discount = $rules['is_leader_discount'] ?? 0;
|
||||
$leader_price = $skuPrice['price'];
|
||||
if ($is_leader_discount && isset($skuPrice['leader_price'])) {
|
||||
$leader_price = $skuPrice['leader_price'];
|
||||
}
|
||||
$ext = [
|
||||
'is_leader_discount' => $is_leader_discount,
|
||||
'leader_price' => number_format(floatval($leader_price), 2, '.', '')
|
||||
];
|
||||
unset($skuPrice['leader_price']);
|
||||
$skuPrice['ext'] = $ext;
|
||||
|
||||
return $skuPrice;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function showSkuPrice($skuPrice)
|
||||
{
|
||||
$ext = $skuPrice['ext'] ?? [];
|
||||
|
||||
$skuPrice['leader_price'] = $ext['leader_price'] ?? $skuPrice['price'];
|
||||
|
||||
return $skuPrice;
|
||||
}
|
||||
|
||||
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
$activitySkuPrices = $activity['activity_sku_prices'];
|
||||
$skuPrices = $goods->sku_prices;
|
||||
|
||||
foreach ($skuPrices as $key => &$skuPrice) {
|
||||
$stock = $skuPrice->stock; // 下面要用
|
||||
$skuPrice->stock = 0;
|
||||
$skuPrice->sales = 0;
|
||||
foreach ($activitySkuPrices as $activitySkuPrice) {
|
||||
if ($skuPrice->id == $activitySkuPrice['goods_sku_price_id']) {
|
||||
// 采用活动的 规格内容
|
||||
$is_leader_discount = $activitySkuPrice['ext']['is_leader_discount'];
|
||||
$leader_price = $activitySkuPrice['ext']['leader_price'];
|
||||
$skuPrice->old_price = $skuPrice->price; // 保存原始普通商品规格的价格(计算活动的优惠)
|
||||
$skuPrice->stock = ($activitySkuPrice['stock'] > $stock) ? $stock : $activitySkuPrice['stock']; // 活动库存不能超过商品库存
|
||||
$skuPrice->sales = $activitySkuPrice['sales'];
|
||||
$skuPrice->groupon_price = $activitySkuPrice['price']; // 不覆盖原来规格价格,用作单独购买,将活动的价格设置为新的拼团价格
|
||||
$skuPrice->is_leader_discount = $is_leader_discount; // 是否团长优惠
|
||||
$skuPrice->leader_price = $leader_price; // 团长优惠价格
|
||||
$skuPrice->status = $activitySkuPrice['status']; // 采用活动的上下架
|
||||
$skuPrice->ext = $activitySkuPrice['ext']; // 活动规格 ext, order_item 保存备用
|
||||
$skuPrice->min_price = $activitySkuPrice['price']; // 当前活动规格最小价格,这里是拼团价
|
||||
$skuPrice->max_price = $activitySkuPrice['price']; // 用作计算活动中最大价格
|
||||
|
||||
// 记录相关活动类型
|
||||
$skuPrice->activity_type = $activity['type'];
|
||||
$skuPrice->activity_id = $activity['id'];
|
||||
// 下单的时候需要存活动 的 sku_price_id)
|
||||
$skuPrice->item_goods_sku_price = $activitySkuPrice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $skuPrices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 这里要使用 shoproException 抛出异常
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buyCheck($buyInfo, $activity)
|
||||
{
|
||||
$buy_type = request()->param('buy_type', 'groupon');
|
||||
$groupon_id = request()->param('groupon_id', 0);
|
||||
// 拼团
|
||||
$rules = $activity['rules'];
|
||||
$is_alone = $rules['is_alone'] ?? 1;
|
||||
|
||||
$currentSkuPrice = $buyInfo['current_sku_price'];
|
||||
$is_leader_discount = $currentSkuPrice['is_leader_discount'];
|
||||
|
||||
// 成团人数
|
||||
$num = $rules['team_num'] ?? 1;
|
||||
// 额外需要的库存
|
||||
$need_add_num = 0;
|
||||
|
||||
// 要单独购买
|
||||
if ($buy_type == 'alone') {
|
||||
// 不允许单独购买
|
||||
if (!$is_alone) {
|
||||
throw new ShoproException('该商品不允许单独购买');
|
||||
}
|
||||
} else {
|
||||
// 拼团,临时将拼团价设置为商品价格
|
||||
if (!$groupon_id && $is_leader_discount) {
|
||||
// 开新团,并且有团长优惠,使用优惠价格
|
||||
$buyInfo['current_sku_price']['price'] = $currentSkuPrice['leader_price'];
|
||||
} else {
|
||||
// 参与团,或者没有团长优惠
|
||||
$buyInfo['current_sku_price']['price'] = $currentSkuPrice['groupon_price'];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是开新团
|
||||
if (!$groupon_id && $buy_type == 'groupon') {
|
||||
// 开团需要的最小库存
|
||||
$need_add_num = ($num - 1);
|
||||
}
|
||||
|
||||
// 当前库存,小于要购买的数量
|
||||
$need_num = $buyInfo['goods_num'] + ($need_add_num ?? 0);
|
||||
if ($currentSkuPrice['stock'] < $need_num) {
|
||||
if ($need_add_num && $is_alone && !$groupon_id && $buy_type == 'groupon') {
|
||||
throw new ShoproException('商品库存不足以开团,请选择单独购买');
|
||||
} else if ($buy_type == 'alone') {
|
||||
throw new ShoproException('商品库存不足');
|
||||
} else {
|
||||
throw new ShoproException('该商品不允商品库存不足以开团许单独购买');
|
||||
}
|
||||
}
|
||||
|
||||
$buyInfo['is_commission'] = $rules['is_commission'] ?? 0; // 是否参与分销
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buy($buyInfo, $activity)
|
||||
{
|
||||
$user = auth_user();
|
||||
$buy_type = request()->param('buy_type', 'groupon');
|
||||
$groupon_id = request()->param('groupon_id', 0);
|
||||
|
||||
// 参与现有团
|
||||
if ($buy_type != 'alone' && $groupon_id) {
|
||||
// 检测并获取要参与的团
|
||||
$activityGroupon = $this->checkAndGetJoinGroupon($buyInfo, $user, $groupon_id);
|
||||
}
|
||||
|
||||
// 判断 并 增加 redis 销量
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheForwardSale($buyInfo);
|
||||
|
||||
// (开新团不判断)参与旧团 增加预拼团人数,上面加入团的时候已经判断过一次了,所以这里 99.99% 会加入成功的
|
||||
if (isset($activityGroupon) && $activityGroupon) {
|
||||
// 增加拼团预成员人数
|
||||
$goods = $buyInfo['goods'];
|
||||
$activity = $goods['activity'];
|
||||
$this->grouponCacheForwardNum($activityGroupon, $activity, $user);
|
||||
}
|
||||
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
$this->joinGroupon($order, $user, function ($activityRules, $itemExt) {
|
||||
$team_num = $activityRules['team_num'] ?? 1;
|
||||
|
||||
return compact('team_num');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 拼团购买失败
|
||||
*
|
||||
* @param \think\Model $order
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
if ($type == 'invalid') {
|
||||
if ($order->pay_mode == 'offline') {
|
||||
// 肯定是已经货到付款的订单取消订单,这时候已经添加了参团记录
|
||||
$this->refundGrouponLog($order);
|
||||
} else {
|
||||
// 订单失效,扣除预拼团人数(只处理正在进行中的团)
|
||||
$this->grouponCacheBackNum($order, $type);
|
||||
}
|
||||
} else {
|
||||
// type = refund 退款订单将参团标记为已退款
|
||||
$this->refundGrouponLog($order);
|
||||
}
|
||||
|
||||
// 判断扣除预销量 (活动信息还在 redis)
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheBackSale($order);
|
||||
}
|
||||
}
|
||||
320
addons/shopro/library/activity/provider/GrouponLadder.php
Normal file
320
addons/shopro/library/activity/provider/GrouponLadder.php
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
use addons\shopro\library\activity\traits\Groupon as GrouponTrait;
|
||||
use addons\shopro\service\StockSale;
|
||||
use addons\shopro\exception\ShoproException;
|
||||
|
||||
/**
|
||||
* 阶梯拼团
|
||||
*/
|
||||
class GrouponLadder extends Base
|
||||
{
|
||||
use GrouponTrait;
|
||||
|
||||
protected $rules = [
|
||||
"is_commission" => "require|boolean",
|
||||
"is_free_shipping" => "require|boolean",
|
||||
"sales_show_type" => "require",
|
||||
"ladders" => "require|array",
|
||||
"is_alone" => "require|boolean",
|
||||
"is_fictitious" => "require|boolean",
|
||||
"fictitious_time" => "requireIf:is_fictitious,1|float|egt:0",
|
||||
"is_team_card" => "require|boolean",
|
||||
"is_leader_discount" => "require|boolean",
|
||||
"valid_time" => "require|float|egt:0",
|
||||
"limit_num" => "number|egt:0",
|
||||
"refund_type" => "require", // 退款方式 back=原路退回|money=退回到余额
|
||||
"order_auto_close" => "float|egt:0",
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'ladders.require' => '请填写拼团阶梯',
|
||||
'ladders.array' => '请填写拼团阶梯',
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"is_commission" => 0, // 是否参与分销
|
||||
"is_free_shipping" => 0, // 是否包邮
|
||||
"sales_show_type" => "real", // real=真实活动销量|goods=商品总销量(包含虚拟销量)
|
||||
"ladders" => [], // {ladder_one:2,ladder_two:2,ladder_three:3}
|
||||
"is_alone" => 0, // 是否允许单独购买
|
||||
"is_fictitious" => 0, // 是否允许虚拟成团
|
||||
"fictitious_num" => 0, // 最多虚拟人数 0:不允许虚拟 '' 不限制
|
||||
"fictitious_time" => 0, // 开团多长时间自动虚拟成团
|
||||
"is_team_card" => 0, // 参团卡显示
|
||||
"is_leader_discount" => 0, // 团长优惠
|
||||
"valid_time" => 0, // 组团有效时间, 0:一直有效
|
||||
"limit_num" => 0, // 每人限购数量 0:不限购
|
||||
"refund_type" => "back", // 退款方式 back=原路退回|money=退回到余额
|
||||
"order_auto_close" => 0, // 订单自动关闭时间,如果为 0 将使用系统级订单自动关闭时间
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 验证添加的活动商品是否至少设置了一个活动规格
|
||||
$this->checkActivitySkuPrice($params['goods_list']);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function save($activity, $params = [])
|
||||
{
|
||||
$goodsList = $params['goods_list'];
|
||||
|
||||
$this->saveSkuPrice($goodsList, $activity, function ($skuPrice) use ($activity) {
|
||||
// 处理 阶梯价格,团长优惠
|
||||
$rules = $activity->rules;
|
||||
$is_leader_discount = $rules['is_leader_discount'] ?? 0;
|
||||
$ladders = $rules['ladders'] ?? 0;
|
||||
|
||||
$ext = [
|
||||
'is_leader_discount' => $is_leader_discount,
|
||||
'ladders' => []
|
||||
];
|
||||
foreach ($ladders as $ladder_level => $ladder) {
|
||||
$ladder_price = isset($skuPrice[$ladder_level]) ? number_format(floatval($skuPrice[$ladder_level]), 2, '.', '') : 0;
|
||||
$leader_ladder_price = (isset($skuPrice[$ladder_level . '_leader']) && $skuPrice[$ladder_level . '_leader'] > 0) ? number_format(floatval($skuPrice[$ladder_level . '_leader']), 2, '.', '') : $ladder_price; // 默认当前阶梯参团价
|
||||
|
||||
$current = [
|
||||
'ladder_level' => $ladder_level,
|
||||
'ladder' => $ladder,
|
||||
'ladder_price' => $ladder_price,
|
||||
'leader_ladder_price' => $leader_ladder_price
|
||||
];
|
||||
unset($skuPrice[$ladder_level], $skuPrice[$ladder_level . '_leader']);
|
||||
$ext['ladders'][] = $current;
|
||||
}
|
||||
|
||||
$skuPrice['ext'] = $ext;
|
||||
return $skuPrice;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function showSkuPrice($skuPrice)
|
||||
{
|
||||
$ext = $skuPrice['ext'] ?? [];
|
||||
$ladders = $ext['ladders'] ?? [];
|
||||
|
||||
if ($ladders) {
|
||||
foreach ($ladders as $ladder) {
|
||||
$ladder_level = $ladder['ladder_level'];
|
||||
$skuPrice[$ladder_level] = $ladder['ladder_price'];
|
||||
$skuPrice[$ladder_level . '_leader'] = $ladder['leader_ladder_price'];
|
||||
}
|
||||
} else {
|
||||
// 全部初始化为 0
|
||||
$skuPrice['ladder_one'] = 0;
|
||||
$skuPrice['ladder_two'] = 0;
|
||||
$skuPrice['ladder_three'] = 0;
|
||||
$skuPrice['ladder_one_leader'] = 0;
|
||||
$skuPrice['ladder_two_leader'] = 0;
|
||||
$skuPrice['ladder_three_leader'] = 0;
|
||||
}
|
||||
|
||||
return $skuPrice;
|
||||
}
|
||||
|
||||
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
$groupon_num = request()->param('groupon_num', 0); // 是否传了开团人数(这里不再使用阶梯,前端没反)
|
||||
$activitySkuPrices = $activity['activity_sku_prices'];
|
||||
$skuPrices = $goods->sku_prices;
|
||||
|
||||
foreach ($skuPrices as $key => &$skuPrice) {
|
||||
$stock = $skuPrice->stock; // 下面要用
|
||||
$skuPrice->stock = 0;
|
||||
$skuPrice->sales = 0;
|
||||
foreach ($activitySkuPrices as $activitySkuPrice) {
|
||||
if ($skuPrice['id'] == $activitySkuPrice['goods_sku_price_id']) {
|
||||
// 采用活动的 规格内容
|
||||
$is_leader_discount = $activitySkuPrice['ext']['is_leader_discount'];
|
||||
$ladders = $activitySkuPrice['ext']['ladders'];
|
||||
$skuPrice->old_price = $skuPrice->price; // 保存原始普通商品规格的价格(计算活动的优惠)
|
||||
$skuPrice->stock = ($activitySkuPrice['stock'] > $stock) ? $stock : $activitySkuPrice['stock']; // 活动库存不能超过商品库存
|
||||
$skuPrice->sales = $activitySkuPrice['sales'];
|
||||
$skuPrice->is_leader_discount = $is_leader_discount; // 是否团长优惠
|
||||
$skuPrice->ladders = $ladders; // 阶梯价格,包含团长优惠
|
||||
$skuPrice->status = $activitySkuPrice['status']; // 采用活动的上下架
|
||||
$skuPrice->ext = $activitySkuPrice['ext']; // 活动规格 ext, order_item 保存备用
|
||||
$skuPrice->min_price = min(array_column($ladders, 'ladder_price')); // 当前活动规格最小价格,这里是阶梯最低拼团价(不要团长价)
|
||||
$skuPrice->max_price = max(array_column($ladders, 'ladder_price')); // 当前活动规格最大价格,这里是阶梯最低拼团价(不要团长价)
|
||||
|
||||
$ladders = array_column($ladders, null, 'ladder');
|
||||
$currentLadder = $ladders[$groupon_num] ?? current($ladders);
|
||||
$skuPrice->ladder_price = $currentLadder['ladder_price']; // 当前阶梯价格(默认是 ladder_one)
|
||||
$skuPrice->leader_ladder_price = $currentLadder['leader_ladder_price']; // 当前阶梯团长价(默认是 ladder_one)
|
||||
$skuPrice->price = $is_leader_discount ? $skuPrice->leader_ladder_price : $skuPrice->ladder_price; // 默认是计算好的价格,团长价或者普通价
|
||||
|
||||
// 记录相关活动类型
|
||||
$skuPrice->activity_type = $activity['type'];
|
||||
$skuPrice->activity_id = $activity['id'];
|
||||
// 下单的时候需要存活动 的 sku_price_id)
|
||||
$skuPrice->item_goods_sku_price = $activitySkuPrice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $skuPrices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 这里要使用 shoproException 抛出异常
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buyCheck($buyInfo, $activity)
|
||||
{
|
||||
$buy_type = request()->param('buy_type', 'groupon');
|
||||
$groupon_id = request()->param('groupon_id', 0);
|
||||
$groupon_num = request()->param('groupon_num', 0);
|
||||
// 拼团
|
||||
$rules = $activity['rules'];
|
||||
$is_alone = $rules['is_alone'] ?? 1;
|
||||
|
||||
$currentSkuPrice = $buyInfo['current_sku_price'];
|
||||
$is_leader_discount = $currentSkuPrice['is_leader_discount']; // 是否团长优惠
|
||||
$ladders = $currentSkuPrice['ladders']; // 阶梯数据
|
||||
$ladders = array_column($ladders, null, 'ladder');
|
||||
$currentLadder = $ladders[$groupon_num] ?? current($ladders); // 当前阶梯的 价格数据
|
||||
|
||||
// 开新团,并且没有找到要参与的阶梯数据
|
||||
if (!$groupon_id && (!$currentLadder || $currentLadder['ladder'] <= 1)) {
|
||||
throw new ShoproException('请选择正确的开团阶梯');
|
||||
}
|
||||
|
||||
$buyInfo['ladder'] = $currentLadder; // 存储当前购买的拼团阶梯 ladder
|
||||
|
||||
// 额外需要的库存
|
||||
$need_add_num = 0;
|
||||
|
||||
// 要单独购买
|
||||
if ($buy_type == 'alone') {
|
||||
// 不允许单独购买
|
||||
if (!$is_alone) {
|
||||
throw new ShoproException('该商品不允许单独购买');
|
||||
}
|
||||
} else {
|
||||
// 拼团,临时将拼团价设置为商品价格
|
||||
if (!$groupon_id && $is_leader_discount) {
|
||||
// 开新团,并且有团长优惠,使用优惠价格
|
||||
$buyInfo['current_sku_price']['price'] = $currentLadder['leader_ladder_price'];
|
||||
} else {
|
||||
// 参与团,或者没有团长优惠
|
||||
$buyInfo['current_sku_price']['price'] = $currentSkuPrice['ladder_price'];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是开新团
|
||||
if (!$groupon_id && $buy_type == 'groupon') {
|
||||
// 成团人数
|
||||
$num = $currentLadder['ladder'] ?? 1;
|
||||
|
||||
// 开团需要的最小库存
|
||||
$need_add_num = ($num - 1);
|
||||
}
|
||||
|
||||
// 当前库存,小于要购买的数量
|
||||
$need_num = $buyInfo['goods_num'] + ($need_add_num ?? 0);
|
||||
if ($currentSkuPrice['stock'] < $need_num) {
|
||||
if ($need_add_num && $is_alone && !$groupon_id && $buy_type == 'groupon') {
|
||||
throw new ShoproException('商品库存不足以开团,请选择单独购买');
|
||||
} else if ($buy_type == 'alone') {
|
||||
throw new ShoproException('商品库存不足');
|
||||
} else {
|
||||
throw new ShoproException('商品库存不足以开团');
|
||||
}
|
||||
}
|
||||
|
||||
$buyInfo['is_commission'] = $rules['is_commission'] ?? 0; // 是否参与分销
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buy($buyInfo, $activity)
|
||||
{
|
||||
$user = auth_user();
|
||||
$buy_type = request()->param('buy_type', 'groupon');
|
||||
$groupon_id = request()->param('groupon_id', 0);
|
||||
|
||||
// 参与现有团
|
||||
if ($buy_type != 'alone' && $groupon_id) {
|
||||
// 检测并获取要参与的团
|
||||
$activityGroupon = $this->checkAndGetJoinGroupon($buyInfo, $user, $groupon_id);
|
||||
}
|
||||
|
||||
// 判断 并 增加 redis 销量
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheForwardSale($buyInfo);
|
||||
|
||||
// (开新团不判断)参与旧团 增加预拼团人数,上面加入团的时候已经判断过一次了,所以这里 99.99% 会加入成功的
|
||||
if (isset($activityGroupon) && $activityGroupon) {
|
||||
// 增加拼团预成员人数
|
||||
$goods = $buyInfo['goods'];
|
||||
$activity = $goods['activity'];
|
||||
$this->grouponCacheForwardNum($activityGroupon, $activity, $user);
|
||||
}
|
||||
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
$this->joinGroupon($order, $user, function ($activityRules, $itemExt) {
|
||||
// 处理拼团特殊的数据
|
||||
$ladder = $itemExt['ladder'];
|
||||
$team_num = $ladder['ladder'];
|
||||
|
||||
return compact('team_num');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼团购买失败
|
||||
*
|
||||
* @param \think\Model $order
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
if ($type == 'invalid') {
|
||||
if ($order->pay_mode == 'offline') {
|
||||
// 肯定是已经货到付款的订单取消订单,这时候已经添加了参团记录
|
||||
$this->refundGrouponLog($order);
|
||||
} else {
|
||||
// 订单失效,扣除预拼团人数(只处理正在进行中的团)
|
||||
$this->grouponCacheBackNum($order, $type);
|
||||
}
|
||||
} else {
|
||||
// type = refund 退款订单将参团标记为已退款
|
||||
$this->refundGrouponLog($order);
|
||||
}
|
||||
|
||||
// 判断扣除预销量 (活动信息还在 redis)
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheBackSale($order);
|
||||
}
|
||||
}
|
||||
113
addons/shopro/library/activity/provider/GrouponLucky.php
Normal file
113
addons/shopro/library/activity/provider/GrouponLucky.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
/**
|
||||
* 幸运拼团
|
||||
*/
|
||||
class GrouponLucky extends Base
|
||||
{
|
||||
protected $rules = [
|
||||
// "is_commission" => "require|bool",
|
||||
// "is_free_shipping" => "require|bool",
|
||||
// "sales_show_type" => "require",
|
||||
// "team_num" => "require|number",
|
||||
// "lucky_num" => "require|number",
|
||||
// "is_fictitious" => "require|bool",
|
||||
// "fictitious_num" => "number|gt:0",
|
||||
// "fictitious_time" => "require|float|egt:0",
|
||||
// "part_gift" => "require|array",
|
||||
// "is_team_card" => "require|bool",
|
||||
// "is_leader_discount" => "require|bool",
|
||||
// "valid_time" => "require|float|gt:0",
|
||||
// "limit_num" => "number|gt:0",
|
||||
// "limit_team_buy" => "number|gt:0",
|
||||
// "refund_type" => "back", // 退款方式 back=原路退回|money=退回到余额
|
||||
// "order_auto_close" => "float|gt:0",
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
// 'team_num.require' => '请填写成团人数',
|
||||
// 'is_alone.require' => '请选择单独购买',
|
||||
// 'stock.gt' => '请填写补货数量'
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"is_commission" => 0, // 是否参与分销
|
||||
"is_free_shipping" => 0, // 是否包邮
|
||||
"sales_show_type" => "real", // real=真实活动销量|goods=商品总销量(包含虚拟销量)
|
||||
"team_num" => 2, // 成团人数,最少两人
|
||||
"lucky_num" => 1, // 拼中人数,最少一人
|
||||
"is_fictitious" => 0, // 是否允许虚拟成团
|
||||
"fictitious_num" => 0, // 最多虚拟人数
|
||||
"fictitious_time" => 0, // 开团多长时间自动虚拟成团
|
||||
"part_gift" => [], // {"types": "coupon=优惠券|score=积分|money=余额","coupon_ids":"赠优惠券时存在","total":"赠送优惠券总金额","score":"积分","money":"余额"}
|
||||
"is_team_card" => 0, // 参团卡显示
|
||||
"is_leader_discount" => 0, // 团长优惠
|
||||
"valid_time" => 0, // 组团有效时间
|
||||
"limit_num" => 0, // 每人限购数量
|
||||
"limit_team_buy" => 0, // 每人每团可参与次数
|
||||
"refund_type" => "back", // 退款方式 back=原路退回|money=退回到余额
|
||||
"order_auto_close" => 0, // 订单自动关闭时间
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 验证添加的活动商品是否至少设置了一个活动规格
|
||||
$this->checkActivitySkuPrice($params['goods_list']);
|
||||
|
||||
// 验证赠送规则字段
|
||||
$this->checkLuckyPartGift($params['rules']['part_gift']);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function save($activity, $params = [])
|
||||
{
|
||||
$goodsList = $params['goods_list'];
|
||||
|
||||
$this->saveSkuPrice($goodsList, $activity->id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
$activitySkuPrices = $activity['activity_sku_prices'];
|
||||
$skuPrices = $goods->sku_prices;
|
||||
|
||||
foreach ($skuPrices as $key => &$skuPrice) {
|
||||
$stock = $skuPrice->stock; // 下面要用
|
||||
$skuPrice->stock = 0;
|
||||
$skuPrice->sales = 0;
|
||||
foreach ($activitySkuPrices as $activitySkuPrice) {
|
||||
if ($skuPrice['id'] == $activitySkuPrice['goods_sku_price_id']) {
|
||||
// 采用活动的 规格内容
|
||||
$skuPrice->stock = ($activitySkuPrice['stock'] > $stock) ? $stock : $activitySkuPrice['stock']; // 活动库存不能超过商品库存
|
||||
$skuPrice->sales = $activitySkuPrice['sales'];
|
||||
$skuPrice->groupon_price = $activitySkuPrice['price']; // 不覆盖原来规格价格,用作单独购买,将活动的价格设置为新的拼团价格
|
||||
$skuPrice->status = $activitySkuPrice['status']; // 采用活动的上下架
|
||||
|
||||
// 记录相关活动类型
|
||||
$skuPrice->activity_type = $activity['type'];
|
||||
$skuPrice->activity_id = $activity['id'];
|
||||
// 下单的时候需要存活动 的 sku_price_id)
|
||||
$skuPrice->item_goods_sku_price = $activitySkuPrice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $skuPrice;
|
||||
}
|
||||
}
|
||||
151
addons/shopro/library/activity/provider/Seckill.php
Normal file
151
addons/shopro/library/activity/provider/Seckill.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
use addons\shopro\service\StockSale;
|
||||
use addons\shopro\exception\ShoproException;
|
||||
|
||||
/**
|
||||
* 秒杀
|
||||
*/
|
||||
class Seckill extends Base
|
||||
{
|
||||
protected $rules = [
|
||||
"is_commission" => "require|boolean",
|
||||
"is_free_shipping" => "require|boolean",
|
||||
"sales_show_type" => "require",
|
||||
"limit_num" => "number|egt:0",
|
||||
"order_auto_close" => "float|egt:0",
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"is_commission" => 0, // 是否参与分销
|
||||
"is_free_shipping" => 0, // 是否包邮
|
||||
"sales_show_type" => "real", // real=真实活动销量|goods=商品总销量(包含虚拟销量)
|
||||
"limit_num" => 0, // 每人限购数量 0:不限购
|
||||
"order_auto_close" => 0, // 订单自动关闭时间,如果为 0 将使用系统级订单自动关闭时间
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 验证添加的活动商品是否至少设置了一个活动规格
|
||||
$this->checkActivitySkuPrice($params['goods_list']);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, $params['goods_list'], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
public function save($activity, $params = [])
|
||||
{
|
||||
$goodsList = $params['goods_list'];
|
||||
|
||||
$this->saveSkuPrice($goodsList, $activity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function recoverSkuPrices($goods, $activity)
|
||||
{
|
||||
$activitySkuPrices = $activity['activity_sku_prices'];
|
||||
$skuPrices = $goods->sku_prices;
|
||||
|
||||
foreach ($skuPrices as $key => &$skuPrice) {
|
||||
$stock = $skuPrice->stock; // 下面要用
|
||||
$skuPrice->stock = 0;
|
||||
$skuPrice->sales = 0;
|
||||
foreach ($activitySkuPrices as $activitySkuPrice) {
|
||||
if ($skuPrice['id'] == $activitySkuPrice['goods_sku_price_id']) {
|
||||
// 采用活动的 规格内容
|
||||
$skuPrice->old_price = $skuPrice->price; // 保存原始普通商品规格的价格(计算活动的优惠)
|
||||
$skuPrice->stock = ($activitySkuPrice['stock'] > $stock) ? $stock : $activitySkuPrice['stock']; // 活动库存不能超过商品库存
|
||||
$skuPrice->sales = $activitySkuPrice['sales'];
|
||||
$skuPrice->price = $activitySkuPrice['price'];
|
||||
$skuPrice->status = $activitySkuPrice['status']; // 采用活动的上下架
|
||||
$skuPrice->min_price = $activitySkuPrice['price']; // 当前活动规格最小价格,这里是秒杀价
|
||||
$skuPrice->max_price = $activitySkuPrice['price']; // 用作计算活动中最大价格
|
||||
|
||||
// 记录相关活动类型
|
||||
$skuPrice->activity_type = $activity['type'];
|
||||
$skuPrice->activity_id = $activity['id'];
|
||||
// 下单的时候需要存活动 的 sku_price_id)
|
||||
$skuPrice->item_goods_sku_price = $activitySkuPrice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $skuPrices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 这里要使用 shoproException 抛出异常
|
||||
*
|
||||
* @param array $buyInfo
|
||||
* @param array $activity
|
||||
* @return array
|
||||
*/
|
||||
public function buyCheck($buyInfo, $activity)
|
||||
{
|
||||
// 秒杀
|
||||
$rules = $activity['rules'];
|
||||
|
||||
$currentSkuPrice = $buyInfo['current_sku_price'];
|
||||
|
||||
// 当前库存,小于要购买的数量
|
||||
$need_num = $buyInfo['goods_num'] + ($need_add_num ?? 0);
|
||||
if ($currentSkuPrice['stock'] < $need_num) {
|
||||
throw new ShoproException('商品库存不足');
|
||||
}
|
||||
|
||||
$buyInfo['is_commission'] = $rules['is_commission'] ?? 0; // 是否参与分销
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buy($buyInfo, $activity)
|
||||
{
|
||||
$user = auth_user();
|
||||
|
||||
// 判断 并 增加 redis 销量
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheForwardSale($buyInfo);
|
||||
|
||||
return $buyInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buyOk($order, $user)
|
||||
{
|
||||
// 不需要处理
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 购买失败
|
||||
*
|
||||
* @param array $order
|
||||
* @return void
|
||||
*/
|
||||
public function buyFail($order, $type)
|
||||
{
|
||||
// 判断扣除预销量 (活动信息还在 redis)
|
||||
$stockSale = new StockSale();
|
||||
$stockSale->cacheBackSale($order);
|
||||
}
|
||||
}
|
||||
51
addons/shopro/library/activity/provider/Signin.php
Normal file
51
addons/shopro/library/activity/provider/Signin.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\provider;
|
||||
|
||||
/**
|
||||
* 签到
|
||||
*/
|
||||
class Signin extends Base
|
||||
{
|
||||
|
||||
protected $rules = [
|
||||
"everyday" => "require",
|
||||
"is_inc" => "require|boolean",
|
||||
"inc_num" => "require",
|
||||
"until_day" => "require|egt:0",
|
||||
"discounts" => "array",
|
||||
"is_replenish" => "require|boolean",
|
||||
"replenish_days" => "require|gt:0",
|
||||
"replenish_limit" => "require|egt:0",
|
||||
"replenish_num" => "require|gt:0"
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
];
|
||||
|
||||
|
||||
protected $default = [
|
||||
"everyday" => 0, // 每日签到固定积分
|
||||
"is_inc" => 0, // 是否递增签到
|
||||
"inc_num" => 0, // 递增奖励
|
||||
"until_day" => 0, // 递增持续天数
|
||||
"discounts" => [], // 连续签到奖励 {full:5, value:10} // 可以为空
|
||||
"is_replenish" => 0, // 是否开启补签
|
||||
"replenish_days" => 1, // 可补签天数,最小 1
|
||||
"replenish_limit" => 0, // 补签时间限制,0 不限制
|
||||
"replenish_num" => 1, // 补签所消耗积分
|
||||
];
|
||||
|
||||
|
||||
public function check($params, $activity_id = 0)
|
||||
{
|
||||
// 数据验证
|
||||
$params = parent::check($params);
|
||||
|
||||
// 检测活动之间是否存在冲突
|
||||
$this->checkActivityConflict($params, [], $activity_id);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
576
addons/shopro/library/activity/traits/ActivityRedis.php
Normal file
576
addons/shopro/library/activity/traits/ActivityRedis.php
Normal file
@@ -0,0 +1,576 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\traits;
|
||||
|
||||
use addons\shopro\facade\Redis;
|
||||
use app\admin\model\shopro\activity\Activity;
|
||||
use think\helper\Str;
|
||||
|
||||
/**
|
||||
* 获取活动 redis 基础方法
|
||||
*/
|
||||
trait ActivityRedis
|
||||
{
|
||||
|
||||
protected $zsetKey = 'zset-activity'; // 活动集合 key
|
||||
protected $hashPrefix = 'hash-activity:'; // 活动前缀
|
||||
protected $hashGoodsPrefix = 'goods-'; // 活动中商品的前缀
|
||||
protected $hashGrouponPrefix = 'groupon-';
|
||||
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 获取活动相关信息 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
/**
|
||||
* 获取活动完整信息
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function getActivity($id, $type)
|
||||
{
|
||||
$keyActivity = $this->keyActivity($id, $type);
|
||||
|
||||
return $this->getActivityByKey($keyActivity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过活动的键值,获取活动完整信息
|
||||
*
|
||||
* @param string $activityHashKey
|
||||
* @return array
|
||||
*/
|
||||
public function getActivityByKey($keyActivity)
|
||||
{
|
||||
// 取出整条 hash 记录
|
||||
$activity = Redis::HGETALL($keyActivity);
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除活动
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function delActivity($id, $type)
|
||||
{
|
||||
$keyActivity = $this->keyActivity($id, $type);
|
||||
|
||||
$this->delActivityByKey($keyActivity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过 key 删除活动
|
||||
*
|
||||
* @param string $keyActivity
|
||||
* @return void
|
||||
*/
|
||||
public function delActivityByKey($keyActivity)
|
||||
{
|
||||
// 删除 hash
|
||||
Redis::DEL($keyActivity);
|
||||
|
||||
// 删除集合
|
||||
Redis::ZREM($this->zsetKey, $keyActivity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动的状态
|
||||
*
|
||||
* @param string $keyActivity
|
||||
* @return string
|
||||
*/
|
||||
public function getActivityStatusByKey($keyActivity)
|
||||
{
|
||||
$prehead_time = Redis::HGET($keyActivity, 'prehead_time'); // 预热时间
|
||||
$start_time = Redis::HGET($keyActivity, 'start_time'); // 开始时间
|
||||
$end_time = Redis::HGET($keyActivity, 'end_time'); // 结束时间
|
||||
|
||||
// 获取活动状态
|
||||
$status = Activity::getStatusCode($prehead_time, $start_time, $end_time);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 获取活动相关信息 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
|
||||
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 操作活动 hash ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
|
||||
/**
|
||||
* 计算每个规格的真实库存、销量
|
||||
*
|
||||
* @param array $goods
|
||||
* @param string $keyActivity
|
||||
* @return array
|
||||
*/
|
||||
private function calcGoods($goods, $keyActivity)
|
||||
{
|
||||
// 销量 key
|
||||
$keyActivityGoods = $this->keyActivityGoods($goods['goods_id'], $goods['goods_sku_price_id'], true);
|
||||
|
||||
// 缓存中的销量
|
||||
$cacheSale = Redis::HGET($keyActivity, $keyActivityGoods);
|
||||
|
||||
$stock = $goods['stock'] - $cacheSale;
|
||||
$goods['stock'] = $stock > 0 ? $stock : 0;
|
||||
$goods['sales'] = $cacheSale;
|
||||
|
||||
return $goods;
|
||||
}
|
||||
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 操作活动 hash ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 格式化活动内容 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
|
||||
/**
|
||||
* 格式化活动
|
||||
*
|
||||
* @param string array $keyActivity 活动 key 或者活动完整信息
|
||||
* @param string $type 格式化方式
|
||||
* @param array $data 额外参数
|
||||
* @return array
|
||||
*/
|
||||
public function formatActivityByKey($keyActivity, $type = 'normal', $data = [])
|
||||
{
|
||||
$activity = $this->{'formatActivity' . Str::studly($type)}($keyActivity, $data);
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 正常模式,只移除销量, 团信息,保留全部商品规格数据
|
||||
*
|
||||
* @param string|array $originalActivity
|
||||
* @param array $data 额外数据,商品 id
|
||||
* @return array|null
|
||||
*/
|
||||
public function formatActivityNormal($originalActivity, $data = [])
|
||||
{
|
||||
if (is_string($originalActivity)) {
|
||||
// 传入的是活动的key
|
||||
$keyActivity = $originalActivity;
|
||||
$originalActivity = $this->getActivityByKey($originalActivity);
|
||||
} else {
|
||||
$keyActivity = $this->keyActivity($originalActivity['id'], $originalActivity['type']);
|
||||
}
|
||||
|
||||
$activity = [];
|
||||
|
||||
foreach ($originalActivity as $key => $value) {
|
||||
// 包含 -sale 全部跳过
|
||||
if (strpos($key, '-sale') !== false) {
|
||||
continue;
|
||||
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
|
||||
// 拼团的参团人数,团用户,移除
|
||||
continue;
|
||||
} else if ($key == 'rules') {
|
||||
$activity[$key] = json_decode($value, true);
|
||||
} else {
|
||||
// 普通键值
|
||||
$activity[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity) {
|
||||
// 处理活动状态
|
||||
$activity['status'] = Activity::getStatusCode($activity['prehead_time'], $activity['start_time'], $activity['end_time']);
|
||||
$activity['status_text'] = Activity::getStatusText($activity['status']);
|
||||
}
|
||||
|
||||
return $activity ?: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 简洁模式,只保留活动表基本信息
|
||||
*
|
||||
* @param string $originalActivity
|
||||
* @param array $data 额外数据,商品 id
|
||||
* @return array|null
|
||||
*/
|
||||
private function formatActivityClear($originalActivity, $data = [])
|
||||
{
|
||||
if (is_string($originalActivity)) {
|
||||
// 传入的是活动的key
|
||||
$keyActivity = $originalActivity;
|
||||
$originalActivity = $this->getActivityByKey($originalActivity);
|
||||
} else {
|
||||
$keyActivity = $this->keyActivity($originalActivity['id'], $originalActivity['type']);
|
||||
}
|
||||
|
||||
$activity = [];
|
||||
|
||||
foreach ($originalActivity as $key => $value) {
|
||||
// 包含 -sale 全部跳过
|
||||
if (strpos($key, $this->hashGoodsPrefix) !== false) {
|
||||
continue;
|
||||
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
|
||||
// 拼团的参团人数,团用户,移除
|
||||
continue;
|
||||
} else if ($key == 'rules') {
|
||||
$activity[$key] = json_decode($value, true);
|
||||
} else {
|
||||
// 普通键值
|
||||
$activity[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity) {
|
||||
// 处理活动状态
|
||||
$activity['status'] = Activity::getStatusCode($activity['prehead_time'], $activity['start_time'], $activity['end_time']);
|
||||
$activity['status_text'] = Activity::getStatusText($activity['status']);
|
||||
}
|
||||
|
||||
return $activity ?: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取并按照商品展示格式化活动数据
|
||||
*
|
||||
* @param string $originalActivity hash key
|
||||
* @param array $data 额外数据,商品 id
|
||||
* @return array|null
|
||||
*/
|
||||
private function formatActivityGoods($originalActivity, $data = [])
|
||||
{
|
||||
$goods_id = $data['goods_id'] ?? 0;
|
||||
|
||||
if (is_string($originalActivity)) {
|
||||
// 传入的是活动的key
|
||||
$keyActivity = $originalActivity;
|
||||
$originalActivity = $this->getActivityByKey($originalActivity);
|
||||
} else {
|
||||
$keyActivity = $this->keyActivity($originalActivity['id'], $originalActivity['type']);
|
||||
}
|
||||
|
||||
$activity = [];
|
||||
|
||||
// 商品前缀
|
||||
$goodsPrefix = $this->hashGoodsPrefix . ($goods_id ? $goods_id . '-' : '');
|
||||
|
||||
foreach ($originalActivity as $key => $value) {
|
||||
// 包含 -sale 全部跳过
|
||||
if (strpos($key, '-sale') !== false) {
|
||||
continue;
|
||||
} else if (strpos($key, $goodsPrefix) !== false) {
|
||||
// 商品规格信息,或者特定商品规格信息
|
||||
$goods = json_decode($value, true);
|
||||
|
||||
// 计算销量库存数据
|
||||
$goods = $this->calcGoods($goods, $keyActivity);
|
||||
|
||||
// 商品规格项
|
||||
$activity['activity_sku_prices'][] = $goods;
|
||||
} else if ($goods_id && strpos($key, $this->hashGoodsPrefix) !== false) {
|
||||
// 需要特定商品时,移除别的非当前商品的数据
|
||||
continue;
|
||||
} else if (strpos($key, $this->hashGrouponPrefix) !== false) {
|
||||
// 拼团的参团人数,团用户,移除
|
||||
continue;
|
||||
} else if ($key == 'rules') {
|
||||
$activity[$key] = json_decode($value, true);
|
||||
} else {
|
||||
// 普通键值
|
||||
$activity[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity) {
|
||||
// 处理活动状态
|
||||
$activity['status'] = Activity::getStatusCode($activity['prehead_time'], $activity['start_time'], $activity['end_time']);
|
||||
$activity['status_text'] = Activity::getStatusText($activity['status']);
|
||||
}
|
||||
|
||||
return $activity ?: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取并按照折扣格式展示格式化活动数据
|
||||
*
|
||||
* @param string $originalActivity hash key
|
||||
* @param array $data 额外数据
|
||||
* @return array|null
|
||||
*/
|
||||
public function formatActivityPromo($originalActivity, $data = [])
|
||||
{
|
||||
if (is_string($originalActivity)) {
|
||||
// 传入的是活动的key
|
||||
$keyActivity = $originalActivity;
|
||||
$originalActivity = $this->getActivityByKey($originalActivity);
|
||||
} else {
|
||||
$keyActivity = $this->keyActivity($originalActivity['id'], $originalActivity['type']);
|
||||
}
|
||||
|
||||
$activity = [];
|
||||
foreach ($originalActivity as $key => $value) {
|
||||
if ($key == 'rules') {
|
||||
$rules = json_decode($value, true);
|
||||
$activity[$key] = $rules;
|
||||
} else {
|
||||
// 普通键值
|
||||
$activity[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity) {
|
||||
// 处理活动状态
|
||||
$activity['status'] = Activity::getStatusCode($activity['prehead_time'], $activity['start_time'], $activity['end_time']);
|
||||
$activity['status_text'] = Activity::getStatusText($activity['status']);
|
||||
}
|
||||
|
||||
return $activity ?: null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 格式化活动内容 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 获取活动的 keys 数组 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有活动的 keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getKeysActivity()
|
||||
{
|
||||
// 获取活动集合
|
||||
$keysActivity = Redis::ZRANGE($this->zsetKey, 0, 999999999);
|
||||
|
||||
return $keysActivity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过活动 id 获取活动的 key(不知道活动类型,只知道 id 的时候用)
|
||||
*
|
||||
* @param integer $id
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyActivityById($id)
|
||||
{
|
||||
$keysActivity = $this->getKeysActivity();
|
||||
|
||||
foreach ($keysActivity as $keyActivity) {
|
||||
$suffix = ':' . $id;
|
||||
// 判断是否是要找的活动id, 截取 hashKey 后面几位,是否为当前要查找的活动 id
|
||||
if (substr($keyActivity, (strlen($keyActivity) - strlen($suffix))) == $suffix) {
|
||||
$currentKeyActivity = $keyActivity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $currentKeyActivity ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过活动 id 和 活动 type 获取 活动 key
|
||||
*
|
||||
* @param integer $activity_id
|
||||
* @param string $activity_type
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyActivityByIdType($activity_id, $activity_type)
|
||||
{
|
||||
$keyActivity = $this->keyActivity($activity_id, $activity_type);
|
||||
return $keyActivity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取对应活动类型的 活动 keys
|
||||
*
|
||||
* @param array $activityTypes
|
||||
* @param array|string $status 要查询的活动的状态
|
||||
* @return array
|
||||
*/
|
||||
public function getKeysActivityByTypes($activityTypes, $status = 'all')
|
||||
{
|
||||
$status = is_array($status) ? $status : [$status];
|
||||
|
||||
$activityTypes = is_array($activityTypes) ? $activityTypes : [$activityTypes];
|
||||
$activityTypes = array_values(array_filter(array_unique($activityTypes))); // 过滤空值
|
||||
|
||||
$keysActivity = $this->getKeysActivity();
|
||||
|
||||
// 获取对应的活动类型的集合
|
||||
$keysActivityTypes = [];
|
||||
foreach ($keysActivity as $keyActivity) {
|
||||
// 循环要查找的活动类型数组
|
||||
foreach ($activityTypes as $type) {
|
||||
$prefix = $this->hashPrefix . $type . ':';
|
||||
if (strpos($keyActivity, $prefix) === 0) { // 是要查找的类型
|
||||
$keysActivityTypes[] = $keyActivity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断活动状态
|
||||
if (!in_array('all', $status)) {
|
||||
foreach ($keysActivityTypes as $key => $keyActivity) {
|
||||
$activity_status = $this->getActivityStatusByKey($keyActivity);
|
||||
if (!in_array($activity_status, $status)) {
|
||||
unset($keysActivityTypes[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($keysActivityTypes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通过商品获取该商品参与的活动的hash key
|
||||
*
|
||||
* @param integer $goods_id
|
||||
* @param Array $activityType
|
||||
* @param array|string $status 要查询的活动的状态
|
||||
* @return array
|
||||
*/
|
||||
private function getkeysActivityByGoods($goods_id, $activityType = [], $status = 'all')
|
||||
{
|
||||
// 获取对应类型的活动集合
|
||||
$keysActivity = $this->getKeysActivityByTypes($activityType, $status);
|
||||
|
||||
$keysActivityGoods = [];
|
||||
foreach ($keysActivity as $keyActivity) {
|
||||
// 判断这条活动是否包含该商品
|
||||
$goods_ids = array_filter(explode(',', Redis::HGET($keyActivity, 'goods_ids')));
|
||||
if (!in_array($goods_id, $goods_ids) && !empty($goods_ids)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keysActivityGoods[] = $keyActivity;
|
||||
}
|
||||
|
||||
return $keysActivityGoods;
|
||||
}
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 获取活动的 keys 数组 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 获取活动相关 key ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
|
||||
/**
|
||||
* 获取活动 hash 的 key
|
||||
*
|
||||
* @param integer $activity_id 活动 id
|
||||
* @param string $activity_type 活动类型
|
||||
* @return string
|
||||
*/
|
||||
private function keyActivity($activity_id, $activity_type)
|
||||
{
|
||||
// 示例 hash-activity:groupon:25
|
||||
return $this->hashPrefix . $activity_type . ':' . $activity_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动 hash 中 商品相关的 key (is_sale 对应的活动商品的销量)
|
||||
*
|
||||
* @param integer $goods_id 商品
|
||||
* @param integer $sku_price_id 规格
|
||||
* @param boolean $is_sale 对应的活动商品的销量
|
||||
* @return string
|
||||
*/
|
||||
private function keyActivityGoods($goods_id, $sku_price_id, $is_sale = false)
|
||||
{
|
||||
// 示例 商品规格:goods-25-30 or 商品规格销量:goods-25-30-sale
|
||||
return $this->hashGoodsPrefix . $goods_id . '-' . $sku_price_id . ($is_sale ? '-sale' : '');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动中 拼团 的团数据的 key
|
||||
*
|
||||
* @param integer $groupon_id
|
||||
* @param integer $goods_id
|
||||
* @param string $type 空=团 key|num=团人数|users=团用户
|
||||
* @return string
|
||||
*/
|
||||
private function keyActivityGroupon($groupon_id, $goods_id, $type = '')
|
||||
{
|
||||
return $this->hashGrouponPrefix . $groupon_id . '-' . $goods_id . ($type ? '-' . $type : '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取活动相关的所有 key
|
||||
*
|
||||
* @param array $detail 商品相关数据
|
||||
* @param array $activity 活动相关数据
|
||||
* @return array
|
||||
*/
|
||||
public function keysActivity($detail, $activity)
|
||||
{
|
||||
// 获取 hash key
|
||||
$keyActivity = $this->keyActivity($activity['activity_id'], $activity['activity_type']);
|
||||
|
||||
$keyGoodsSkuPrice = '';
|
||||
$keySale = '';
|
||||
if (isset($detail['goods_sku_price_id']) && $detail['goods_sku_price_id']) {
|
||||
// 获取 hash 表中商品 sku 的 key
|
||||
$keyGoodsSkuPrice = $this->keyActivityGoods($detail['goods_id'], $detail['goods_sku_price_id']);
|
||||
// 获取 hash 表中商品 sku 的 销量的 key
|
||||
$keySale = $this->keyActivityGoods($detail['goods_id'], $detail['goods_sku_price_id'], true);
|
||||
}
|
||||
|
||||
// 需要拼团的字段
|
||||
$keyGroupon = '';
|
||||
$keyGrouponNum = '';
|
||||
$keyGrouponUserlist = '';
|
||||
if (isset($detail['groupon_id']) && $detail['groupon_id']) {
|
||||
// 获取 hash 表中团 key
|
||||
$keyGroupon = $this->keyActivityGroupon($detail['groupon_id'], $detail['goods_id']);
|
||||
// 获取 hash 表中团当前人数 key
|
||||
$keyGrouponNum = $this->keyActivityGroupon($detail['groupon_id'], $detail['goods_id'], 'num');
|
||||
// 获取 hash 表中团当前人员列表 key
|
||||
$keyGrouponUserlist = $this->keyActivityGroupon($detail['groupon_id'], $detail['goods_id'], 'users');
|
||||
}
|
||||
|
||||
return compact('keyActivity', 'keyGoodsSkuPrice', 'keySale', 'keyGroupon', 'keyGrouponNum', 'keyGrouponUserlist');
|
||||
}
|
||||
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 获取活动相关 key ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
}
|
||||
160
addons/shopro/library/activity/traits/CheckActivity.php
Normal file
160
addons/shopro/library/activity/traits/CheckActivity.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\traits;
|
||||
|
||||
trait CheckActivity
|
||||
{
|
||||
|
||||
/**
|
||||
* 检测活动商品必须至少设置一个规格为活动规格
|
||||
*
|
||||
* @param array $goodsList
|
||||
* @return void
|
||||
*/
|
||||
public function checkActivitySkuPrice($goodsList)
|
||||
{
|
||||
foreach ($goodsList as $key => $goods) {
|
||||
$activitySkuPrice = $goods['activity_sku_prices'] ?? [];
|
||||
if (!$activitySkuPrice) {
|
||||
error_stop('请至少将商品一个规格设置为活动规格');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检测满赠规则设置
|
||||
*
|
||||
* @param array $discounts
|
||||
* @return void
|
||||
*/
|
||||
public function checkGiftDiscount($discounts)
|
||||
{
|
||||
foreach ($discounts as $discount) {
|
||||
$types = $discount['types'];
|
||||
|
||||
if (in_array('coupon', $types) && (!isset($discount['coupon_ids']) || empty($discount['coupon_ids']))) {
|
||||
// 验证优惠券
|
||||
error_stop('请选择要赠送的优惠券');
|
||||
}
|
||||
if (in_array('money', $types) && (!isset($discount['money']) || empty($discount['money']))) {
|
||||
// 赠送余额
|
||||
error_stop('请填写要赠送的余额');
|
||||
}
|
||||
if (in_array('score', $types) && (!isset($discount['score']) || empty($discount['score']))) {
|
||||
// 赠送积分
|
||||
error_stop('请填写要赠送的积分');
|
||||
}
|
||||
|
||||
if (in_array('goods', $types) && (!isset($discount['goods_ids']) || empty($discount['goods_ids']))) {
|
||||
// 赠送优惠券
|
||||
error_stop('请选择要赠送的商品');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 幸运拼团参与奖校验
|
||||
*
|
||||
* @param array $part_gift
|
||||
* @return void
|
||||
*/
|
||||
public function checkLuckyPartGift($part_gift)
|
||||
{
|
||||
$types = $part_gift['types'];
|
||||
|
||||
if (in_array('coupon', $types) && (!isset($part_gift['coupon_ids']) || empty($part_gift['coupon_ids']))) {
|
||||
// 验证优惠券
|
||||
error_stop('请选择要赠送的优惠券');
|
||||
}
|
||||
if (in_array('money', $types) && (!isset($discount['money']) || empty($discount['money']))) {
|
||||
// 赠送余额
|
||||
error_stop('请填写要赠送的余额');
|
||||
}
|
||||
if (in_array('score', $types) && (!isset($discount['score']) || empty($discount['score']))) {
|
||||
// 赠送积分
|
||||
error_stop('请填写要赠送的积分');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检测活动商品是否重合
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkActivityConflict($params, $goodsList = [], $activity_id = 0)
|
||||
{
|
||||
if ($params['classify'] == 'activity') {
|
||||
// 活动可以共存,不检测冲突
|
||||
return true;
|
||||
}
|
||||
$start_time = strtotime($params['start_time']);
|
||||
$end_time = strtotime($params['end_time']);
|
||||
$prehead_time = isset($params['prehead_time']) && $params['prehead_time'] ? strtotime($params['prehead_time']) : $start_time;
|
||||
$goodsIds = array_column($goodsList, 'id'); // 获取活动提交过来的所有商品的 id
|
||||
$goodsList = array_column($goodsList, null, 'id');
|
||||
|
||||
// 获取所有时间有交叉的活动
|
||||
$activities = $this->getActivities($params['type'], [$prehead_time, $end_time]);
|
||||
|
||||
foreach ($activities as $key => $activity) {
|
||||
if ($activity_id && $activity_id == $activity['id']) {
|
||||
// 编辑的时候,把自己排除在外
|
||||
continue;
|
||||
}
|
||||
|
||||
$intersect = []; // 两个活动重合的商品Ids
|
||||
if ($goodsIds) {
|
||||
$activityGoodsIds = array_filter(explode(',', $activity['goods_ids']));
|
||||
// 不是全部商品,并且不重合
|
||||
if ($activityGoodsIds && !$intersect = array_intersect($activityGoodsIds, $goodsIds)) {
|
||||
// 商品不重合,继续验证下个活动
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$goods_names = '';
|
||||
foreach ($intersect as $id) {
|
||||
if (isset($goodsList[$id]) && isset($goodsList[$id]['title'])) {
|
||||
$goods_names .= $goodsList[$id]['title'] . ',';
|
||||
}
|
||||
}
|
||||
|
||||
if ($goods_names) {
|
||||
$goods_names = mb_strlen($goods_names) > 40 ? mb_substr($goods_names, 0, 37) . '...' : $goods_names;
|
||||
}
|
||||
|
||||
if (!$goodsIds && !$intersect) {
|
||||
// 没有商品
|
||||
$msg = '活动时间与 “' . $activity['type_text'] . ' 活动的 ' . $activity['title'] . '” 冲突';
|
||||
} else {
|
||||
$msg = ((count($intersect) > 1 || !$goodsIds) ? '部分商品' : '该商品') . ($goods_names ? ' ' . $goods_names . ' ' : '') . '已在 “' . $activity['title'] . '” 活动中设置';
|
||||
}
|
||||
|
||||
error_stop($msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有活动
|
||||
*
|
||||
* @param string $current_activity_type
|
||||
* @param array $range 要查询的时间区间
|
||||
* @return array
|
||||
*/
|
||||
private function getActivities($current_activity_type, $range) {
|
||||
// 获取当前活动的互斥活动
|
||||
$activityTypes = $this->manager->model->getMutexActivityTypes($current_activity_type);
|
||||
|
||||
$activities = $this->manager->getActivitiesByRange($range, $activityTypes);
|
||||
|
||||
return $activities;
|
||||
}
|
||||
}
|
||||
269
addons/shopro/library/activity/traits/GiveGift.php
Normal file
269
addons/shopro/library/activity/traits/GiveGift.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\traits;
|
||||
|
||||
use think\Db;
|
||||
use addons\shopro\traits\CouponSend;
|
||||
use app\admin\model\shopro\activity\GiftLog;
|
||||
use addons\shopro\service\Wallet as WalletService;
|
||||
|
||||
/**
|
||||
* 赠送赠品 (full_gift, groupon_lucky 幸运拼团未拼中)
|
||||
*/
|
||||
trait GiveGift
|
||||
{
|
||||
use CouponSend;
|
||||
|
||||
/**
|
||||
* 按照规则添加赠送日志
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @param array $info {"full":"100","types":"coupon=优惠券|score=积分|money=余额|goods=商品","coupon_ids":"赠优惠券时存在","total":"赠送优惠券总金额","score":"积分","money":"余额","goods_ids":"商品时存在",gift_num:"礼品份数"}
|
||||
* @return void
|
||||
*/
|
||||
public function addGiftsLog($order, $user, $info)
|
||||
{
|
||||
$rules = $info['discount_rule'];
|
||||
|
||||
Db::transaction(function () use ($order, $user, $info, $rules) {
|
||||
$types = $rules['types'];
|
||||
|
||||
foreach ($types as $type) {
|
||||
extract($this->getTypeGift($rules, $type));
|
||||
|
||||
$giftLog = new GiftLog();
|
||||
$giftLog->activity_id = $info['activity_id'];
|
||||
$giftLog->order_id = $order->id;
|
||||
$giftLog->user_id = $user->id;
|
||||
$giftLog->type = $type;
|
||||
$giftLog->gift = $gift;
|
||||
$giftLog->value = $value;
|
||||
$giftLog->rules = $rules;
|
||||
$giftLog->status = 'waiting';
|
||||
$giftLog->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 标记礼品为赠送失败
|
||||
*/
|
||||
public function checkAndFailGift($order, $fail_msg, $errors = null)
|
||||
{
|
||||
// 找到所有没有赠送的礼品,设置为 fail,fail_msg 订单退款
|
||||
$giftLogs = GiftLog::waiting()->where('order_id', $order->id)->lock(true)->select();
|
||||
foreach ($giftLogs as $giftLog) {
|
||||
$giftLog->status = 'fail';
|
||||
$giftLog->fail_msg = $fail_msg;
|
||||
$giftLog->errors = $errors;
|
||||
$giftLog->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查并赠送礼品
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @param array $promoInfos
|
||||
* @param string $event
|
||||
* @return void
|
||||
*/
|
||||
public function checkAndGift($order, $user, $promoInfos, $event)
|
||||
{
|
||||
foreach ($promoInfos as $info) {
|
||||
if ($info['activity_type'] == 'full_gift') {
|
||||
$this->checkPromoAndGift($order, $user, $info, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查并赠送礼品
|
||||
*
|
||||
* @param array|object $order
|
||||
* @param array|object $user
|
||||
* @param array $infos
|
||||
* @param string $event
|
||||
* @return void
|
||||
*/
|
||||
public function checkPromoAndGift($order, $user, $info, $event)
|
||||
{
|
||||
if ($info['event'] == $event) {
|
||||
// 判断领取次数
|
||||
$rules = $info['discount_rule'];
|
||||
$gift_num = $rules['gift_num']; // 礼品数量
|
||||
|
||||
// 查询已发放数量
|
||||
$send_num = GiftLog::where('activity_id', $info['activity_id'])->opered()->group('order_id')->count();
|
||||
|
||||
$giftLogs = GiftLog::waiting()
|
||||
->where('activity_id', $info['activity_id'])
|
||||
->where('order_id', $order->id)
|
||||
->select();
|
||||
|
||||
if ($send_num >= $gift_num) {
|
||||
// 礼品已经发放完毕
|
||||
foreach ($giftLogs as $log) {
|
||||
$log->status = 'fail';
|
||||
$log->fail_msg = '礼品已经发完了';
|
||||
$log->save();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查询当前用户已领取数量 (只算赠送成功的)
|
||||
$user_send_num = GiftLog::where('user_id', $order->user_id)->where('activity_id', $info['activity_id'])->finish()->group('order_id')->count();
|
||||
if ($info['limit_num'] > 0 && $user_send_num >= $info['limit_num']) {
|
||||
// 已经领取过了
|
||||
foreach ($giftLogs as $log) {
|
||||
$log->status = 'fail';
|
||||
$log->fail_msg = '已经领取过了,每人最多领取 ' . $info['limit_num'] . ' 份';
|
||||
$log->save();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 赠送礼品
|
||||
foreach ($giftLogs as $giftLog) {
|
||||
$this->{'gift' . $giftLog->type}($user, $giftLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 赠送优惠券
|
||||
*
|
||||
* @param array|object $user
|
||||
* @param array|object $giftLog
|
||||
* @return void
|
||||
*/
|
||||
public function giftCoupon($user, $giftLog)
|
||||
{
|
||||
$couponIds = explode(',', $giftLog->gift);
|
||||
|
||||
$result = $this->giveCoupons($user, $couponIds);
|
||||
|
||||
$giftLog->status = 'finish';
|
||||
if ($result['errors']) {
|
||||
$giftLog->status = 'fail';
|
||||
$giftLog->fail_msg = $result['success'] ? '优惠券部分发放成功' : '优惠券发放失败';
|
||||
$giftLog->errors = $result['errors'];
|
||||
}
|
||||
|
||||
$giftLog->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 赠送积分
|
||||
*
|
||||
* @param array|object $user
|
||||
* @param array|object $giftLog
|
||||
* @return void
|
||||
*/
|
||||
public function giftScore($user, $giftLog)
|
||||
{
|
||||
$score = $giftLog->gift;
|
||||
|
||||
// 增加用户积分
|
||||
WalletService::change($user, 'score', $score, 'activity_gift', [
|
||||
'activity_id' => $giftLog->activity_id,
|
||||
'order_id' => $giftLog->order_id,
|
||||
'user_id' => $giftLog->user_id,
|
||||
'type' => $giftLog->type,
|
||||
'gift' => $giftLog->gift,
|
||||
'value' => $giftLog->value,
|
||||
]);
|
||||
|
||||
$giftLog->status = 'finish';
|
||||
$giftLog->save();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 赠送余额
|
||||
*
|
||||
* @param array|object $user
|
||||
* @param array|object $giftLog
|
||||
* @return void
|
||||
*/
|
||||
public function giftMoney($user, $giftLog)
|
||||
{
|
||||
$money = $giftLog->gift;
|
||||
|
||||
// 增加用户余额
|
||||
WalletService::change($user, 'money', $money, 'activity_gift', [
|
||||
'activity_id' => $giftLog->activity_id,
|
||||
'order_id' => $giftLog->order_id,
|
||||
'user_id' => $giftLog->user_id,
|
||||
'type' => $giftLog->type,
|
||||
'gift' => $giftLog->gift,
|
||||
'value' => $giftLog->value,
|
||||
]);
|
||||
|
||||
$giftLog->status = 'finish';
|
||||
$giftLog->save();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 赠送商品(暂不开发)
|
||||
*
|
||||
* @param array|object $user
|
||||
* @param array|object $giftLog
|
||||
* @return void
|
||||
*/
|
||||
public function giftGoods($user, $giftLog)
|
||||
{
|
||||
$goodsIds = explode(',', $giftLog->gift);
|
||||
|
||||
// 赠送商品,暂不开发
|
||||
$giftLog->status = 'finish';
|
||||
$giftLog->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取赠送的 gift 和价值
|
||||
*
|
||||
* @param array $rules
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
private function getTypeGift($rules, $type)
|
||||
{
|
||||
$gift = null;
|
||||
switch ($type) {
|
||||
case 'coupon':
|
||||
$gift = $rules['coupon_ids'];
|
||||
$value = $rules['total'];
|
||||
break;
|
||||
case 'score':
|
||||
$gift = $rules['score'];
|
||||
$value = $rules['score'];
|
||||
break;
|
||||
case 'money':
|
||||
$gift = $rules['money'];
|
||||
$value = $rules['money'];
|
||||
break;
|
||||
case 'goods':
|
||||
$gift = $rules['goods_ids'];
|
||||
$value = $rules['goods_ids'];
|
||||
break;
|
||||
}
|
||||
|
||||
return compact('gift', 'value');
|
||||
}
|
||||
}
|
||||
527
addons/shopro/library/activity/traits/Groupon.php
Normal file
527
addons/shopro/library/activity/traits/Groupon.php
Normal file
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\activity\traits;
|
||||
|
||||
use addons\shopro\facade\Redis;
|
||||
use addons\shopro\facade\ActivityRedis;
|
||||
use app\admin\model\shopro\activity\Activity;
|
||||
use app\admin\model\shopro\activity\Groupon as ActivityGroupon;
|
||||
use app\admin\model\shopro\activity\GrouponLog;
|
||||
use app\admin\model\shopro\order\Order;
|
||||
use app\admin\model\shopro\order\OrderItem;
|
||||
use app\admin\model\shopro\data\FakeUser;
|
||||
use addons\shopro\service\order\OrderRefund;
|
||||
use addons\shopro\service\order\OrderOper;
|
||||
|
||||
/**
|
||||
* 拼团 (普通拼团,阶梯拼团,幸运拼团)
|
||||
*/
|
||||
trait Groupon
|
||||
{
|
||||
/**
|
||||
* *、redis 没有存团完整信息,只存了团当前人数,团成员(当前人数,团成员均没有存虚拟用户)
|
||||
* *、redis userList 没有存这个人的购买状态
|
||||
* *、团 解散,成团,(因为直接修改了数据库,参团判断,先判断的数据库后判断的 redis)
|
||||
* *、虚拟成团时将虚拟人数存入 redis userList 中,因为团中有虚拟人时,redis 实际人数 和 团需要人数 都没有计算虚拟人,导致团可以超员
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 判断加入旧拼团
|
||||
*/
|
||||
protected function checkAndGetJoinGroupon($buyInfo, $user, $groupon_id)
|
||||
{
|
||||
$goods = $buyInfo['goods'];
|
||||
$activity = $goods['activity'];
|
||||
|
||||
// 获取团信息
|
||||
$activityGroupon = ActivityGroupon::where('id', $groupon_id)->find();
|
||||
if (!$activityGroupon) {
|
||||
error_stop('要参与的团不存在');
|
||||
}
|
||||
// 判断团所属活动是否正常
|
||||
if ($activityGroupon->activity_id != $activity['id']) { // 修复,后台手动将活动删除,然后又立即给这个商品创建新的拼团活动,导致参与新活动的旧团错乱问题
|
||||
error_stop('要参与的活动已结束');
|
||||
}
|
||||
if ($activityGroupon['status'] != 'ing') {
|
||||
error_stop('要参与的团已成团,请选择其它团或自己开团');
|
||||
}
|
||||
|
||||
if ($activityGroupon['current_num'] >= $activityGroupon['num']) {
|
||||
error_stop('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
if (!has_redis()) {
|
||||
// 没有 redis 直接判断数据库团信息,因为 current_num 支付成功才会累加,故无法保证超员,
|
||||
$isJoin = GrouponLog::where('user_id', $user['id'])->where('groupon_id', $activityGroupon->id)->where('is_fictitious', 0)->count();
|
||||
if ($isJoin) {
|
||||
error_stop('您已参与该团,请不要重复参团');
|
||||
}
|
||||
|
||||
// 该团可加入
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
$keys = ActivityRedis::keysActivity([
|
||||
'groupon_id' => $activityGroupon['id'],
|
||||
'goods_id' => $activityGroupon['goods_id'],
|
||||
], [
|
||||
'activity_id' => $activity['id'],
|
||||
'activity_type' => $activity['type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
$current_num = Redis::HGET($keyActivity, $keyGrouponNum);
|
||||
if ($current_num >= $activityGroupon['num']) {
|
||||
error_stop('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
// 将用户加入拼团缓存,用来判断同一个人在一个团,多次下单,订单失效时删除缓存
|
||||
$userList = Redis::HGET($keyActivity, $keyGrouponUserlist);
|
||||
$userList = json_decode($userList, true);
|
||||
$userIds = array_column($userList, 'user_id');
|
||||
if (in_array($user['id'], $userIds)) {
|
||||
error_stop('您已参与该团,请不要重复参团');
|
||||
}
|
||||
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 增加拼团预成员人数
|
||||
*/
|
||||
protected function grouponCacheForwardNum($activityGroupon, $activity, $user, $payed = 'nopay')
|
||||
{
|
||||
if (!has_redis()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$keys = ActivityRedis::keysActivity([
|
||||
'groupon_id' => $activityGroupon['id'],
|
||||
'goods_id' => $activityGroupon['goods_id'],
|
||||
], [
|
||||
'activity_id' => $activity['id'],
|
||||
'activity_type' => $activity['type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
// 当前团人数 grouponNumKey 如果不存在,自动创建
|
||||
$current_num = Redis::HINCRBY($keyActivity, $keyGrouponNum, 1);
|
||||
|
||||
if ($current_num > $activityGroupon['num']) {
|
||||
// 再把刚加上的减回来
|
||||
$current_num = Redis::HINCRBY($keyActivity, $keyGrouponNum, -1);
|
||||
|
||||
error_stop('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
// 将用户加入拼团缓存,用来判断同一个人在一个团,多次下单,取消失效订单时删除缓存
|
||||
$userList = Redis::HGET($keyActivity, $keyGrouponUserlist);
|
||||
$userList = json_decode($userList, true);
|
||||
$userList = $userList ?: [];
|
||||
$userList[] = [
|
||||
'user_id' => $user['id'],
|
||||
// 'status' => $payed // 太复杂,先不做
|
||||
];
|
||||
Redis::HSET($keyActivity, $keyGrouponUserlist, json_encode($userList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 拼团团成员预成员退回
|
||||
protected function grouponCacheBackNum($order, $type)
|
||||
{
|
||||
if (!has_redis()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 查询拼团商品
|
||||
$item = OrderItem::where('order_id', $order['id'])->find(); // 拼团订单只有一个商品
|
||||
|
||||
// 扩展字段
|
||||
$order_ext = $order['ext'];
|
||||
// 团 id
|
||||
$groupon_id = $order_ext['groupon_id'] ?? 0;
|
||||
|
||||
if (!$groupon_id) {
|
||||
return true; // 商品独立购买,未参团,或者开新团
|
||||
}
|
||||
|
||||
// 查询拼团,必须是拼团中才处理(已结束的(完成或者解散的没意义了)),redis 中没有存 团信息和状态
|
||||
$groupon = ActivityGroupon::ing()->lock(true)->find($groupon_id);
|
||||
if (!$groupon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if ($type == 'refund') { // 退款这里不删除拼团记录,当成正常团成员处理
|
||||
// // 退款,真实删除拼团记录,并减少参团人数
|
||||
// $this->delGrouponLog($order, $groupon);
|
||||
// }
|
||||
|
||||
$keys = ActivityRedis::keysActivity([
|
||||
'groupon_id' => $groupon_id,
|
||||
'goods_id' => $item['goods_id'],
|
||||
'goods_sku_price_id' => $item['goods_sku_price_id'],
|
||||
], [
|
||||
'activity_id' => $item['activity_id'],
|
||||
'activity_type' => $item['activity_type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
if (!Redis::EXISTS($keyActivity)) {
|
||||
// redis 不存在,可能活动已删除,不处理
|
||||
return true;
|
||||
}
|
||||
|
||||
// 扣除预参团成员
|
||||
if (Redis::HEXISTS($keyActivity, $keyGrouponNum)) {
|
||||
$groupon_num = Redis::HINCRBY($keyActivity, $keyGrouponNum, -1);
|
||||
}
|
||||
|
||||
$userList = Redis::HGET($keyActivity, $keyGrouponUserlist);
|
||||
$userList = json_decode($userList, true);
|
||||
$userList = $userList ?: [];
|
||||
foreach ($userList as $key => $user) {
|
||||
if ($user['user_id'] == $item['user_id']) {
|
||||
unset($userList[$key]);
|
||||
}
|
||||
}
|
||||
$userList = array_values($userList);
|
||||
Redis::HSET($keyActivity, $keyGrouponUserlist, json_encode($userList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功真实加入团
|
||||
*/
|
||||
protected function joinGroupon($order, $user, \Closure $grouponCb = null)
|
||||
{
|
||||
$items = $order->items;
|
||||
$item = $items[0]; // 拼团只能单独购买
|
||||
|
||||
// 扩展字段
|
||||
$order_ext = $order['ext'];
|
||||
// 团 id
|
||||
$groupon_id = $order_ext['groupon_id'] ?? 0;
|
||||
$buy_type = $order_ext['buy_type'] ?? 'groupon';
|
||||
|
||||
// 单独购买,不加入团
|
||||
if ($buy_type == 'alone') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($groupon_id) {
|
||||
// 加入旧团,查询团
|
||||
$activityGroupon = ActivityGroupon::find($groupon_id);
|
||||
} else {
|
||||
// 加入新团,创建团
|
||||
$activityGroupon = $this->joinNewGroupon($order, $user, $item, $grouponCb);
|
||||
}
|
||||
// 添加参团记录
|
||||
$activityGrouponLog = $this->addGrouponLog($order, $user, $item, $activityGroupon);
|
||||
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功开启新拼团
|
||||
*/
|
||||
protected function joinNewGroupon($order, $user, $item, \Closure $grouponCb = null)
|
||||
{
|
||||
// 获取活动
|
||||
$activity = Activity::where('id', $item['activity_id'])->find();
|
||||
$rules = $activity['rules'];
|
||||
|
||||
// 小于 0 不限结束时间单位小时
|
||||
$expire_time = 0;
|
||||
if (isset($rules['valid_time']) && $rules['valid_time'] > 0) {
|
||||
// 转为 秒
|
||||
$expire_time = $rules['valid_time'] * 3600;
|
||||
}
|
||||
|
||||
// 小于 0 不限结束时间单位小时
|
||||
$fictitious_time = 0;
|
||||
if (isset($rules['is_fictitious']) && $rules['is_fictitious'] && isset($rules['fictitious_time']) && $rules['fictitious_time'] > 0) {
|
||||
// 转为 秒
|
||||
$fictitious_time = $rules['fictitious_time'] * 3600;
|
||||
}
|
||||
|
||||
if ($grouponCb) {
|
||||
// team_num
|
||||
extract($grouponCb($rules, $item['ext']));
|
||||
}
|
||||
|
||||
// 开团
|
||||
$activityGroupon = new ActivityGroupon();
|
||||
$activityGroupon->user_id = $user['id'];
|
||||
$activityGroupon->goods_id = $item['goods_id'];
|
||||
$activityGroupon->activity_id = $item['activity_id'];
|
||||
$activityGroupon->num = $team_num ?? 1; // 避免活动找不到
|
||||
$activityGroupon->current_num = 0; // 真实团成员等支付完成之后再增加
|
||||
$activityGroupon->status = 'ing';
|
||||
$activityGroupon->expire_time = $expire_time > 0 ? (time() + $expire_time) : 0;
|
||||
$activityGroupon->save();
|
||||
|
||||
// 记录团 id
|
||||
$order->ext = array_merge($order->ext, ['groupon_id' => $activityGroupon->id]);
|
||||
$order->save();
|
||||
|
||||
// 将团信息存入缓存,增加缓存中当前团人数
|
||||
$this->grouponCacheForwardNum($activityGroupon, $activity, $user, 'payed');
|
||||
|
||||
if ($expire_time > 0) {
|
||||
// 增加自动关闭拼团队列(如果有虚拟成团,会判断虚拟成团)
|
||||
\think\Queue::later($expire_time, '\addons\shopro\job\GrouponAutoOper@expire', [
|
||||
'activity' => $activity,
|
||||
'activity_groupon_id' => $activityGroupon->id
|
||||
], 'shopro');
|
||||
}
|
||||
|
||||
if ($fictitious_time > 0) {
|
||||
// 自动虚拟成团时间(提前自动虚拟成团,让虚拟成团更加真实一点,避免在团结束那一刻突然成团了)应小于自动过期时间
|
||||
\think\Queue::later($fictitious_time, '\addons\shopro\job\GrouponAutoOper@fictitious', [
|
||||
'activity' => $activity,
|
||||
'activity_groupon_id' => $activityGroupon->id
|
||||
], 'shopro');
|
||||
}
|
||||
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 增加团成员记录
|
||||
*/
|
||||
protected function addGrouponLog($order, $user, $item, $activityGroupon)
|
||||
{
|
||||
if (!$activityGroupon) {
|
||||
\think\Log::error('groupon-notfund: order_id: ' . $order['id']);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 增加团成员数量
|
||||
$activityGroupon->setInc('current_num', 1);
|
||||
|
||||
// 增加参团记录
|
||||
$activityGrouponLog = new GrouponLog();
|
||||
$activityGrouponLog->user_id = $user['id'];
|
||||
$activityGrouponLog->nickname = $user['nickname'];
|
||||
$activityGrouponLog->avatar = $user['avatar'];
|
||||
$activityGrouponLog->groupon_id = $activityGroupon['id'] ?? 0;
|
||||
$activityGrouponLog->goods_id = $item['goods_id'];
|
||||
$activityGrouponLog->goods_sku_price_id = $item['goods_sku_price_id'];
|
||||
$activityGrouponLog->activity_id = $item['activity_id'];
|
||||
$activityGrouponLog->is_leader = ($activityGroupon['user_id'] == $user['id']) ? 1 : 0;
|
||||
$activityGrouponLog->is_fictitious = 0;
|
||||
$activityGrouponLog->order_id = $order['id'];
|
||||
$activityGrouponLog->save();
|
||||
|
||||
return $activityGrouponLog;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 【此方法即将废除,加入团之后,不删除参团记录】,删除团成员记录(退款:已经真实加入团了,这里扣除)()
|
||||
*/
|
||||
protected function delGrouponLog($order, $groupon)
|
||||
{
|
||||
$activityGrouponLog = GrouponLog::where('user_id', $order->user_id)
|
||||
->where('groupon_id', $groupon->id)
|
||||
->where('order_id', $order->id)
|
||||
->find();
|
||||
|
||||
if ($activityGrouponLog) {
|
||||
$activityGrouponLog->delete();
|
||||
|
||||
// 扣除参团人数
|
||||
$groupon->setDec('current_num', 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单退款时标记拼团记录为已退款(主动退款和拼团失败退款)
|
||||
*
|
||||
* @param \think\Model $order
|
||||
* @return void
|
||||
*/
|
||||
protected function refundGrouponLog($order)
|
||||
{
|
||||
$order_ext = $order['ext'];
|
||||
$groupon_id = $order_ext['groupon_id'] ?? 0;
|
||||
if (!$groupon_id) {
|
||||
return true; // 商品独立购买,未参团,或者开新团
|
||||
}
|
||||
|
||||
$activityGrouponLog = GrouponLog::where('user_id', $order->user_id)
|
||||
->where('groupon_id', $groupon_id)
|
||||
->where('order_id', $order->id)
|
||||
->find();
|
||||
|
||||
if ($activityGrouponLog) {
|
||||
// 修改 logs 为已退款
|
||||
$activityGrouponLog->is_refund = 1;
|
||||
$activityGrouponLog->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 虚拟成团,增加虚拟成员,并判断是否完成,然后将团状态改为,虚拟成团成功
|
||||
protected function finishFictitiousGroupon($activity, $activityGroupon, $invalid = true, $num = 0, $users = [])
|
||||
{
|
||||
// 拼团剩余人数
|
||||
$surplus_num = $activityGroupon['num'] - $activityGroupon['current_num'];
|
||||
|
||||
// 团已经满员
|
||||
if ($surplus_num <= 0) {
|
||||
if ($activityGroupon['status'] == 'ing') {
|
||||
// 已满员但还是进行中状态,检测并完成团,起到纠正作用
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 本次虚拟人数, 如果传入 num 则使用 num 和 surplus_num 中最小值, 如果没有传入,默认剩余人数全部虚拟
|
||||
$fictitious_num = $num ? ($num > $surplus_num ? $surplus_num : $num) : $surplus_num;
|
||||
|
||||
$fakeUsers = FakeUser::orderRaw('rand()')->limit($fictitious_num)->select();
|
||||
|
||||
if (count($fakeUsers) < $fictitious_num && $num == 0) {
|
||||
if ($invalid) {
|
||||
// 虚拟用户不足,并且是自动虚拟成团进程,自动解散团
|
||||
return $this->invalidRefundGroupon($activityGroupon);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 增加团人数
|
||||
$activityGroupon->setInc('current_num', $fictitious_num);
|
||||
|
||||
if (has_redis()) {
|
||||
// redis 参数
|
||||
$keys = ActivityRedis::keysActivity([
|
||||
'groupon_id' => $activityGroupon['id'],
|
||||
'goods_id' => $activityGroupon['goods_id'],
|
||||
], [
|
||||
'activity_id' => $activity['id'],
|
||||
'activity_type' => $activity['type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
Redis::HINCRBY($keyActivity, $keyGrouponNum, $fictitious_num); // 增加 redis 参团人数
|
||||
|
||||
// 将用户加入拼团缓存,用来判断同一个人在一个团,多次下单,取消失效订单时删除缓存
|
||||
$userList = Redis::HGET($keyActivity, $keyGrouponUserlist);
|
||||
$userList = json_decode($userList, true);
|
||||
$userList = $userList ?: [];
|
||||
for ($i =0; $i < $fictitious_num; $i++) {
|
||||
$userList[] = [
|
||||
'user_id' => 'fictitiou_' . time() . mt_rand(1000, 9999),
|
||||
];
|
||||
}
|
||||
Redis::HSET($keyActivity, $keyGrouponUserlist, json_encode($userList));
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $fictitious_num; $i++) {
|
||||
// 先用传过来的
|
||||
$avatar = isset($users[$i]['avatar']) ? $users[$i]['avatar'] : '';
|
||||
$nickname = isset($users[$i]['nickname']) ? $users[$i]['nickname'] : '';
|
||||
|
||||
// 如果没有,用查的虚拟的
|
||||
$avatar = $avatar ?: $fakeUsers[$i]['avatar'];
|
||||
$nickname = $nickname ?: $fakeUsers[$i]['nickname'];
|
||||
|
||||
// 增加参团记录
|
||||
$activityGrouponLog = new GrouponLog();
|
||||
$activityGrouponLog->user_id = 0;
|
||||
$activityGrouponLog->nickname = $nickname;
|
||||
$activityGrouponLog->avatar = $avatar;
|
||||
$activityGrouponLog->groupon_id = $activityGroupon['id'] ?? 0;
|
||||
$activityGrouponLog->goods_id = $activityGroupon['goods_id'];
|
||||
$activityGrouponLog->goods_sku_price_id = 0; // 没有订单,所以也就没有 goods_sku_price_id
|
||||
$activityGrouponLog->activity_id = $activityGroupon['activity_id'];
|
||||
$activityGrouponLog->is_leader = 0; // 不是团长
|
||||
$activityGrouponLog->is_fictitious = 1; // 虚拟用户
|
||||
$activityGrouponLog->order_id = 0; // 虚拟成员没有订单
|
||||
$activityGrouponLog->save();
|
||||
}
|
||||
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 团过期退款,或者后台手动解散退款
|
||||
*/
|
||||
protected function invalidRefundGroupon($activityGroupon, $user = null)
|
||||
{
|
||||
$activityGroupon->status = 'invalid'; // 拼团失败
|
||||
$activityGroupon->save();
|
||||
|
||||
// 查询参团真人
|
||||
$logs = GrouponLog::with(['order'])->where('groupon_id', $activityGroupon['id'])->where('is_fictitious', 0)->select();
|
||||
|
||||
foreach ($logs as $key => $log) {
|
||||
$order = $log->order;
|
||||
if ($order && in_array($order->status, [Order::STATUS_PAID, Order::STATUS_COMPLETED])) {
|
||||
$refundNum = OrderItem::where('order_id', $order->id)->where('refund_status', '<>', OrderItem::REFUND_STATUS_NOREFUND)->count();
|
||||
if (!$refundNum) {
|
||||
// 无条件全额退款
|
||||
$refund = new OrderRefund($order);
|
||||
$refund->fullRefund($user, [
|
||||
'remark' => '拼团失败退款'
|
||||
]);
|
||||
}
|
||||
} else if ($order && $order->isOffline($order)) {
|
||||
$orderOper = new OrderOper();
|
||||
$orderOper->cancel($order, null, 'system', '拼团失败,系统自动取消订单');
|
||||
}
|
||||
}
|
||||
|
||||
// 触发拼团失败行为
|
||||
$data = ['groupon' => $activityGroupon];
|
||||
\think\Hook::listen('activity_groupon_fail', $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查团状态
|
||||
*/
|
||||
protected function checkGrouponStatus($activityGroupon)
|
||||
{
|
||||
if (!$activityGroupon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 重新获取团信息
|
||||
$activityGroupon = ActivityGroupon::where('id', $activityGroupon['id'])->find();
|
||||
if ($activityGroupon['current_num'] >= $activityGroupon['num'] && !in_array($activityGroupon['status'], ['finish', 'finish_fictitious'])) {
|
||||
// 查询是否有虚拟团成员
|
||||
$fictitiousCount = GrouponLog::where('groupon_id', $activityGroupon['id'])->where('is_fictitious', 1)->count();
|
||||
|
||||
// 将团设置为已完成
|
||||
$activityGroupon->status = $fictitiousCount ? 'finish_fictitious' : 'finish';
|
||||
$activityGroupon->finish_time = time();
|
||||
$activityGroupon->save();
|
||||
|
||||
// 触发成团行为
|
||||
$data = ['groupon' => $activityGroupon];
|
||||
\think\Hook::listen('activity_groupon_finish', $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user