chore: 添加静态资源文件和版本配置文件

添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材
添加系统版本配置文件(.version)和常量定义文件(.constant)
更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件
添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
2026-03-06 20:14:23 +08:00
commit c9a98f342c
4485 changed files with 490986 additions and 0 deletions

View File

@@ -0,0 +1,392 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity;
use app\dao\activity\StoreActivityDao;
use app\services\activity\seckill\StoreSeckillServices;
use app\services\activity\seckill\StoreSeckillTimeServices;
use app\services\BaseServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrResultServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\store\SystemStoreServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
/**
* 活动
* Class StoreActivityServices
* @package app\services\activity
* @mixin StoreActivityDao
*/
class StoreActivityServices extends BaseServices
{
/**
* @var string[]
*/
public array $typeName = [
1 => '秒杀',
];
/**
* @var StoreActivityDao
*/
#[Inject]
protected StoreActivityDao $dao;
/**
* 活动列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit, ['seckill' => function ($query) {
$query->field('id,activity_id');
}]);
$count = $this->dao->count($where);
/** @var StoreSeckillTimeServices $seckillTimeServices */
$seckillTimeServices = app()->make(StoreSeckillTimeServices::class);
$timeList = $seckillTimeServices->time_list();
if ($timeList) {
$timeList = array_combine(array_column($timeList, 'id'), $timeList);
}
$ids = [];
foreach ($list as &$item) {
$item['product_count'] = count($item['seckill']);
$item['time_id'] = is_string($item['time_id']) ? explode(',', $item['time_id']) : $item['time_id'];
$item['time_list'] = [];
foreach ($item['time_id'] as $time_id) {
if (isset($timeList[$time_id])) $item['time_list'][] = $timeList[$time_id];
}
if ($item['status']) {
if ($item['start_day'] > time()) {
$item['start_name'] = '未开始';
} else if (bcadd((string)$item['end_day'], '86400') < time()) {
$item['start_name'] = '已结束';
$ids[] = $item['id'];
$item['status'] = 0;
} else if (bcadd((string)$item['end_day'], '86400') > time() && $item['start_day'] < time()) {
$item['start_name'] = '进行中';
}
} else {
$item['start_name'] = '已结束';
}
switch ($item['type']) {
case 1://秒杀
$item['start_time'] = $item['start_time'] ? substr_replace($item['start_time'], ':', 2, 0) : '';
$item['end_time'] = $item['start_time'] ? substr_replace($item['end_time'], ':', 2, 0) : '';
break;
}
$item['start_day'] = date('Y-m-d', $item['start_day']);
$item['end_day'] = date('Y-m-d', $item['end_day']);
// $item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
if ($where['type'] == 1) {
$ids = array_unique($ids);
$this->dao->update([['id', 'in', $ids]], ['status' => 0]);
foreach($ids as $id) {
/** @var StoreSeckillServices $storeSeckillServices */
$storeSeckillServices = app()->make(StoreSeckillServices::class);
$seckillIds = $storeSeckillServices->getActivitySeckillIds(['activity_id' => $id]);
if (!empty($seckillIds)) {
foreach ($seckillIds as $seckillId) {
$storeSeckillServices->update($seckillId, ['status' => 0]);
//修改状态同步缓存
$this->cacheSaveValue($seckillId, 'status', 0);
}
}
}
}
return compact('list', 'count');
}
/**
* 获取一条活动信息
* @param int $id
* @param array $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id, array $field = ['*'])
{
$info = $this->dao->get($id, $field);
if (!$info) {
throw new AdminException('数据不存在');
}
$info = $info->toArray();
return $info;
}
/**
* 获取一条秒杀活动
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSeckillInfo(int $id)
{
$info = $this->getInfo($id);
/** @var StoreSeckillServices $seckillServices */
$seckillServices = app()->make(StoreSeckillServices::class);
$seckill = $seckillServices->getList(['activity_id' => $id, 'is_del' => 0], 0, 0, ['attrValue']);
$info['section_data'] = [date('Y-m-d', $info['start_day']), date('Y-m-d', $info['end_day'])];
$productList = [];
if ($seckill) {
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$productList = $productServices->searchList(['id' => array_column($seckill, 'product_id'), 'is_del' => 0, 'is_verify' => 1]);
$productList = $productList['list'] ?? [];
$seckill = array_combine(array_column($seckill, 'product_id'), $seckill);
//放入秒杀商品价格
foreach ($productList as &$product) {
$seckillInfo = $seckill[$product['id']] ?? [];
$attrValue = $product['attrValue'] ?? [];
if ($seckillInfo && $attrValue) {
$product['status'] = $seckillInfo['status'];
$seckillAttrValue = $seckillInfo['attrValue'] ?? [];
if ($seckillAttrValue) {
$seckillAttrValue = array_combine(array_column($seckillAttrValue, 'suk'), $seckillAttrValue);
foreach ($attrValue as &$value) {
$value['status'] = 0;
if (isset($seckillAttrValue[$value['suk']])) {
$value['status'] = 1;
}
$value['quota'] = $seckillAttrValue[$value['suk']]['quota'] ?? 0;
$value['quota_show'] = $seckillAttrValue[$value['suk']]['quota_show'] ?? 0;
$value['price'] = $seckillAttrValue[$value['suk']]['price'] ?? 0;
$value['cost'] = $seckillAttrValue[$value['suk']]['cost'] ?? 0;
$value['ot_price'] = $seckillAttrValue[$value['suk']]['ot_price'] ?? 0;
}
$product['attrValue'] = $attrValue;
}
}
}
}
$info['productList'] = $productList;
//适用门店
$info['stores'] = [];
if (isset($info['applicable_type']) && ($info['applicable_type'] == 1 || ($info['applicable_type'] == 2 && isset($info['applicable_store_id']) && $info['applicable_store_id']))) {//查询门店信息
$where = ['is_del' => 0];
if ($info['applicable_type'] == 2) {
$store_ids = is_array($info['applicable_store_id']) ? $info['applicable_store_id'] : explode(',', $info['applicable_store_id']);
$where['id'] = $store_ids;
}
$field = ['id', 'name', 'phone', 'address', 'detailed_address', 'image', 'is_show', 'day_time', 'day_start', 'day_end'];
/** @var SystemStoreServices $storeServices */
$storeServices = app()->make(SystemStoreServices::class);
$storeData = $storeServices->getStoreList($where, $field, '', '', 0, ['categoryName']);
$info['stores'] = $storeData['list'] ?? [];
}
return $info;
}
/**
* 保存活动信息
* @param int $id
* @param array $data
* @param array $expend
* @param int $type
* @return bool
*/
public function saveData(int $id, array $data, array $expend, int $type = 1)
{
if (!$data || !$expend) {
throw new AdminException('缺少活动数据');
}
$section_data = $data['section_data'];
$data['start_day'] = strtotime($section_data[0]);
$data['end_day'] = strtotime($section_data[1]);
unset($data['section_data']);
$this->transaction(function () use ($id, $type, $data, $section_data, $expend) {
if ($id) {
$this->getInfo($id);
$this->dao->update($id, $data);
$this->clearAcivityAttr($id, array_column($data['seckill_ids'], 'id'), $type);
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
$id = (int)$res->id;
}
//处理活动商品
switch ($type) {
case 1:
$data['section_data'] = $section_data;
$this->saveActivitySeckill($id, $expend, $data);
break;
}
});
return true;
}
/**
* 保存秒杀商品
* @param int $id
* @param array $data
* @param array $activity_data
* @return bool
*/
public function saveActivitySeckill(int $id, array $data, array $activity_data)
{
$productIds = array_column($data, 'id');
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$productList = $productServices->searchList(['id' => $productIds, 'is_del' => 0, 'is_verify' => 1]);
$productList = $productList['list'] ?? [];
//放入秒杀商品价格
/** @var StoreSeckillServices $seckillServices */
$seckillServices = app()->make(StoreSeckillServices::class);
$data = array_combine($productIds, $data);
foreach ($productList as &$product) {
$attrInfo = $productServices->getProductRules((int)$product['id'], 0);
$seckillData = [];
$seckillData['activity_id'] = $id;
$seckillData['product_id'] = $product['id'] ?? 0;
$seckillData['title'] = $product['store_name'] ?? '';
$seckillData['info'] = $product['store_info'] ?? '';
$seckillData['unit_name'] = $product['unit_name'] ?? '';
$seckillData['section_time'] = $activity_data['section_data'];
$seckillData['images'] = $product['slider_image'] ?? '';
$seckillData['description'] = $product['description'] ?? '';
$seckillData['status'] = $data[$product['id']]['status'] ?? 1;
$seckillData['time_id'] = $activity_data['time_id'] ?? [];
$seckillData['num'] = $activity_data['num'] ?? 0;
$seckillData['once_num'] = $activity_data['once_num'] ?? 0;
$seckillData['delivery_type'] = $product['delivery_type'] ?? '';
$seckillData['temp_id'] = $product['temp_id'];//运费设置
$seckillData['freight'] = $product['freight'];//运费设置
$seckillData['postage'] = $product['postage'];//邮费
$seckillData['custom_form'] = $product['custom_form'];//自定义表单
$seckillData['system_form_id'] = $product['system_form_id'];//系统表单ID
$seckillData['product_type'] = $product['product_type'];//商品类型
$seckillData['applicable_type'] = $activity_data['applicable_type'];//适用门店类型
$seckillData['applicable_store_id'] = $activity_data['applicable_store_id'];//适用门店IDS
$seckillData['is_commission'] = $activity_data['is_commission'];//适用门店IDS
$seckillData['items'] = $attrInfo['items'];
$attrs = $attrInfo['attrs'] ?? [];
if ($attrs) {
$seckillAttrValue = $data[$product['id']]['attrValue'] ?? [];
if (!$seckillAttrValue) {
throw new AdminException('请选择商品规格');
}
foreach ($seckillAttrValue as $sattr) {
if (!isset($sattr['status']) || !$sattr['status']) {//不参与的规格不验证
continue;
}
if (!isset($sattr['price']) || !$sattr['price']) {
throw new AdminException('请填写商品(' . $product['store_name'] . ')活动价');
}
// if (!isset($sattr['quota']) || !$sattr['quota']) {
// throw new AdminException('请填写商品('.$product['store_name'] .')限量');
// }
}
$seckillAttrValue = array_combine(array_column($seckillAttrValue, 'suk'), $seckillAttrValue);
foreach ($attrs as $attr) {
$sku = implode(',', $attr['detail']);
if (!isset($seckillAttrValue[$sku])) {
throw new AdminException('请重新选择商品规格');
}
if (!isset($seckillAttrValue[$sku]['status']) || !$seckillAttrValue[$sku]['status']) {
continue;
}
if (($seckillAttrValue[$sku]['quota'] ?? 0) > $attr['stock']) {
throw new AdminException('限量超过了商品库存');
}
$attr['quota'] = $attr['quota_show'] = $seckillAttrValue[$sku]['quota'] ?? 0;
$attr['price'] = $seckillAttrValue[$sku]['price'] ?? 0;
$attr['cost'] = $seckillAttrValue[$sku]['cost'] ?? 0;
$attr['ot_price'] = $seckillAttrValue[$sku]['ot_price'] ?? 0;
$seckillData['attrs'][] = $attr;
}
}
$seckillId = $seckillServices->value(['activity_id' => $id, 'product_id' => $seckillData['product_id']], 'id') ?? 0;
$seckillServices->saveData($seckillId, $seckillData);
}
return true;
}
/**
* 清空活动商品数据
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function clearAcivityAttr(int $id, array $productIds = [], int $type = 1)
{
/** @var StoreSeckillServices $seckillServices */
$seckillServices = app()->make(StoreSeckillServices::class);
$seckill = $seckillServices->getList(['activity_id' => $id, 'is_del' => 0]);
$seckillIds = [];
foreach ($seckill as $item) {
if (!in_array($item['product_id'], $productIds)) {
$seckillIds[] = $item['id'];
}
}
if (count($seckillIds)) {
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$where = ['product_id' => $seckillIds, 'type' => $type];
$storeProductAttrResultServices->delete($where);
$storeDescriptionServices->delete($where);
$storeProductAttrServices->delete($where);
$storeProductAttrValueServices->delete($where);
$seckillServices->delete(['activity_id' => $id, 'is_del' => 0]);
$seckillServices->cacheDelByIds($seckillIds);
$seckillServices->cacheTag()->clear();
}
CacheService::redisHandler('product_attr')->clear();
return true;
}
/**
* 判断时间段是否存在秒杀活动
* @param array $where
* @param int $type
* @return bool
* @throws \think\db\exception\DbException
*/
public function existenceActivity(array $where, int $type = 1): bool
{
$where['is_del'] = 0;
if (!isset($where['type'])) $where['type'] = $type;
return $this->dao->checkActivity($where);
}
}

View File

@@ -0,0 +1,287 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\activityBackground;
use app\dao\activity\promotions\StorePromotionsDao;
use app\services\activity\promotions\StorePromotionsAuxiliaryServices;
use app\services\BaseServices;
use app\services\product\brand\StoreBrandServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreProductRelationServices;
use app\services\product\product\StoreProductServices;
use crmeb\exceptions\AdminException;
use \crmeb\traits\OptionTrait;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
/**
* 活动背景图
* Class ActivityBackgroundServices
* @package app\services\activity\activityBackground
* @mixin StorePromotionsDao
*/
class ActivityBackgroundServices extends BaseServices
{
use OptionTrait;
/**
* 活动类型
* @var string[]
*/
protected array $promotionsType = [
1 => '限时折扣',
2 => '第N件N折',
3 => '满减满折',
4 => '满送',
5 => '活动背景图',
6 => '活动背景',
];
/**
* 参与活动商品类型
* @var string[]
*/
protected array $productPartakeType = [
1 => '全部商品',
2 => '指定商品参与',
3 => '指定商品不参与',
4 => '指定品牌参与',
5 => '指定商品标签参与',
];
/**
* @var StorePromotionsDao
*/
#[Inject]
protected StorePromotionsDao $dao;
/**
* 后台获取活动背景图列表
* @param array $where
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = 0;
if ($list) {
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
/** @var StoreProductRelationServices $storeProductRelationServices */
$storeProductRelationServices = app()->make(StoreProductRelationServices::class);
/** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
$promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
foreach ($list as &$item) {
if ($item['status']) {
if ($item['start_time'] > time()) {
$item['start_status'] = 0;
$item['start_name'] = '未开始';
} else if ($item['stop_time'] < time()) {
$item['start_name'] = '已结束';
$item['start_status'] = -1;
} else if ($item['stop_time'] > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
$item['start_status'] = 1;
}
} else {
$item['start_status'] = -1;
$item['start_name'] = '已结束';
}
$item['start_time'] = $item['start_time'] ? date('Y-m-d H:i:s', $item['start_time']) : '';
$item['stop_time'] = $item['stop_time'] ? date('Y-m-d H:i:s', $item['stop_time']) : '';
$promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($item['id']);
switch ($item['product_partake_type']) {
case 1://所有商品
$item['product_count'] = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'is_verify' => 1]);
break;
case 2://选中商品参与
$product_ids = $promotionsAuxiliaryData;
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
case 3:
$item['product_count'] = 0;
break;
case 4://品牌
$product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 2, 'relation_id' => $promotionsAuxiliaryData]) : [];
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
case 5://商品标签
$product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 3, 'relation_id' => $promotionsAuxiliaryData]) : [];
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
}
}
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取活动背景图想你去那个
* @param int $id
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id, ['*'], ['products' => function ($query) {
$query->field('promotions_id,product_id,unique')->with(['productInfo']);
}, 'brands' => function ($query) {
$query->field('promotions_id,brand_id')->with(['brandInfo' => function ($q) {
$q->field('id,brand_name');
}]);
}, 'productLabels' => function ($query) {
$query->field('promotions_id,store_label_id')->with(['productLabelInfo' => function ($q) {
$q->field('id,label_name');
}]);
}]);
if (!$info) {
throw new AdminException('数据不存在');
}
$info = $info->toArray();
$info['start_time'] = $info['start_time'] ? date('Y-m-d H:i:s', $info['start_time']) : '';
$info['stop_time'] = $info['stop_time'] ? date('Y-m-d H:i:s', $info['stop_time']) : '';
$products = [];
if (isset($info['products']) && $info['products']) {
foreach ($info['products'] as $item) {
$product = is_object($item) ? $item->toArray() : $item;
$product = array_merge($product, $product['productInfo'] ?? []);
unset($product['productInfo']);
$products[] = $product;
}
if ($products) {
$cateIds = implode(',', array_column($products, 'cate_id'));
/** @var StoreProductCategoryServices $categoryService */
$categoryService = app()->make(StoreProductCategoryServices::class);
$cateList = $categoryService->getCateParentAndChildName($cateIds);
foreach ($products as $key => &$item) {
$item['cate_name'] = '';
if (isset($item['cate_id']) && $item['cate_id']) {
$cate_ids = explode(',', $item['cate_id']);
$cate_name = $categoryService->getCateName($cate_ids, $cateList);
if ($cate_name) {
$item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
}
}
}
}
}
$info['products'] = $products;
$info['brand_id'] = isset($info['brands']) && $info['brands'] ? array_column($info['brands'], 'brand_id') : [];
$info['store_label_id'] = [];
if (isset($info['productLabels']) && $info['productLabels']) {
foreach ($info['productLabels'] as $label) {
if (isset($label['productLabelInfo']) && $label['productLabelInfo']) {
$info['store_label_id'][] = $label['productLabelInfo'];
}
}
}
unset($info['brands'], $info['productLabels']);
return $info;
}
/**
* 保存活动背景图
* @param int $id
* @param array $data
* @return bool
*/
public function saveData(int $id, array $data)
{
if (!$data['section_time'] || count($data['section_time']) != 2) {
throw new AdminException('请选择活动时间');
}
[$start_time, $end_time] = $data['section_time'];
if (strtotime($end_time) < time()) {
throw new AdminException('活动结束时间不能小于当前时间');
}
if ($id) {
$info = $this->dao->get((int)$id);
if (!$info) {
throw new AdminException('数据不存在');
}
}
$data['start_time'] = strtotime($start_time);
$data['stop_time'] = strtotime($end_time);
$promotionsAuxiliaryData = [];
switch ($data['product_partake_type']) {
case 1://全部
$data['product_id'] = [];
break;
case 2://指定ID参与
case 3://指定ID不参与
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$count = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $data['product_id']]);
$productCount = count(array_unique($data['product_id']));
if ($count != $productCount) {
throw new AdminException('选择商品中有已下架或移入回收站');
}
$promotionsAuxiliaryData['product_id'] = $data['product_id'];
break;
case 4://指定品牌
$data['brand_id'] = array_unique($data['brand_id']);
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
$brandCount = $storeBrandServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $data['brand_id']]);
if (count($data['brand_id']) != $brandCount) {
throw new AdminException('选择商品品牌中有已下架或删除的');
}
$promotionsAuxiliaryData['brand_id'] = $data['brand_id'];
break;
case 5://指定商品标签
$data['store_label_id'] = array_unique($data['store_label_id']);
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$labelCount = $storeProductLabelServices->count(['id' => $data['store_label_id']]);
if (count($data['store_label_id']) != $labelCount) {
throw new ValidateException('选择商品标签中有已下架或删除的');
}
$promotionsAuxiliaryData['store_label_id'] = $data['store_label_id'];
break;
default:
throw new ValidateException('暂不支持该类型商品');
break;
}
unset($data['section_time'], $data['promotions']);
$this->transaction(function () use ($id, $data, $promotionsAuxiliaryData) {
$time = time();
$data['update_time'] = $time;
if ($id) {
$this->dao->update($id, $data);
} else {
$data['add_time'] = $time;
$res = $this->dao->save($data);
$id = $res->id;
}
/** @var StorePromotionsAuxiliaryServices $storePromotionsAuxiliaryServices */
$storePromotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
//保存关联数据
$storePromotionsAuxiliaryServices->savePromotionsRelation((int)$id, (int)$data['product_partake_type'], $promotionsAuxiliaryData, [], [], false);
});
return true;
}
}

View File

@@ -0,0 +1,284 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\activityFrame;
use app\dao\activity\promotions\StorePromotionsDao;
use app\services\activity\promotions\StorePromotionsAuxiliaryServices;
use app\services\BaseServices;
use app\services\product\brand\StoreBrandServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreProductRelationServices;
use app\services\product\product\StoreProductServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
use think\exception\ValidateException;
use \crmeb\traits\OptionTrait;
/**
* 活动边框
* Class ActivityFrameServices
* @package app\services\activity\activityFrame
* @mixin StorePromotionsDao
*/
class ActivityFrameServices extends BaseServices
{
use OptionTrait;
/**
* 活动类型
* @var string[]
*/
protected array $promotionsType = [
1 => '限时折扣',
2 => '第N件N折',
3 => '满减满折',
4 => '满送',
5 => '活动边框',
6 => '活动背景',
];
/**
* 参与活动商品类型
* @var string[]
*/
protected array $productPartakeType = [
1 => '全部商品',
2 => '指定商品参与',
3 => '指定商品不参与',
4 => '指定品牌参与',
5 => '指定商品标签参与',
];
/**
* @var StorePromotionsDao
*/
#[Inject]
protected StorePromotionsDao $dao;
/**
* 后台获取活动边框列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = 0;
if ($list) {
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
/** @var StoreProductRelationServices $storeProductRelationServices */
$storeProductRelationServices = app()->make(StoreProductRelationServices::class);
/** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
$promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
foreach ($list as &$item) {
if ($item['status']) {
if ($item['start_time'] > time()) {
$item['start_status'] = 0;
$item['start_name'] = '未开始';
} else if ($item['stop_time'] < time()) {
$item['start_name'] = '已结束';
$item['start_status'] = -1;
} else if ($item['stop_time'] > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
$item['start_status'] = 1;
}
} else {
$item['start_status'] = -1;
$item['start_name'] = '已结束';
}
$item['start_time'] = $item['start_time'] ? date('Y-m-d H:i:s', $item['start_time']) : '';
$item['stop_time'] = $item['stop_time'] ? date('Y-m-d H:i:s', $item['stop_time']) : '';
$promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($item['id']);
switch ($item['product_partake_type']) {
case 1://所有商品
$item['product_count'] = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'is_verify' => 1]);
break;
case 2://选中商品参与
$product_ids = $promotionsAuxiliaryData;
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
case 3:
$item['product_count'] = 0;
break;
case 4://品牌
$product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 2, 'relation_id' => $promotionsAuxiliaryData]) : [];
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
case 5://商品标签
$product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 3, 'relation_id' => $promotionsAuxiliaryData]) : [];
$item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
break;
}
}
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取活动边框想你去那个
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id, ['*'], ['products' => function ($query) {
$query->field('promotions_id,product_id,unique')->with(['productInfo']);
}, 'brands' => function ($query) {
$query->field('promotions_id,brand_id')->with(['brandInfo' => function ($q) {
$q->field('id,brand_name');
}]);
}, 'productLabels' => function ($query) {
$query->field('promotions_id,store_label_id')->with(['productLabelInfo' => function ($q) {
$q->field('id,label_name');
}]);
}]);
if (!$info) {
throw new AdminException('数据不存在');
}
$info = $info->toArray();
$info['start_time'] = $info['start_time'] ? date('Y-m-d H:i:s', $info['start_time']) : '';
$info['stop_time'] = $info['stop_time'] ? date('Y-m-d H:i:s', $info['stop_time']) : '';
$products = [];
if (isset($info['products']) && $info['products']) {
foreach ($info['products'] as $item) {
$product = is_object($item) ? $item->toArray() : $item;
$product = array_merge($product, $product['productInfo'] ?? []);
unset($product['productInfo']);
$products[] = $product;
}
if ($products) {
$cateIds = implode(',', array_column($products, 'cate_id'));
/** @var StoreProductCategoryServices $categoryService */
$categoryService = app()->make(StoreProductCategoryServices::class);
$cateList = $categoryService->getCateParentAndChildName($cateIds);
foreach ($products as $key => &$item) {
$item['cate_name'] = '';
if (isset($item['cate_id']) && $item['cate_id']) {
$cate_ids = explode(',', $item['cate_id']);
$cate_name = $categoryService->getCateName($cate_ids, $cateList);
if ($cate_name) {
$item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
}
}
}
}
}
$info['products'] = $products;
$info['brand_id'] = isset($info['brands']) && $info['brands'] ? array_column($info['brands'], 'brand_id') : [];
$info['store_label_id'] = [];
if (isset($info['productLabels']) && $info['productLabels']) {
foreach ($info['productLabels'] as $label) {
if (isset($label['productLabelInfo']) && $label['productLabelInfo']) {
$info['store_label_id'][] = $label['productLabelInfo'];
}
}
}
unset($info['brands'], $info['productLabels']);
return $info;
}
/**
* 保存活动边框
* @param int $id
* @param array $data
* @return bool
*/
public function saveData(int $id, array $data)
{
if (!$data['section_time'] || count($data['section_time']) != 2) {
throw new AdminException('请选择活动时间');
}
[$start_time, $end_time] = $data['section_time'];
if (strtotime($end_time) < time()) {
throw new AdminException('活动结束时间不能小于当前时间');
}
if ($id) {
$info = $this->dao->get((int)$id);
if (!$info) {
throw new AdminException('数据不存在');
}
}
$data['start_time'] = strtotime($start_time);
$data['stop_time'] = strtotime($end_time);
$promotionsAuxiliaryData = [];
switch ($data['product_partake_type']) {
case 1://全部
$data['product_id'] = [];
break;
case 2://指定ID参与
case 3://指定ID不参与
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$count = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $data['product_id']]);
$productCount = count(array_unique($data['product_id']));
if ($count != $productCount) {
throw new AdminException('选择商品中有已下架或移入回收站');
}
$promotionsAuxiliaryData['product_id'] = $data['product_id'];
break;
case 4://指定品牌
$data['brand_id'] = array_unique($data['brand_id']);
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
$brandCount = $storeBrandServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $data['brand_id']]);
if (count(array_unique($data['brand_id'])) != $brandCount) {
throw new AdminException('选择商品品牌中有已下架或删除的');
}
$promotionsAuxiliaryData['brand_id'] = $data['brand_id'];
break;
case 5://指定商品标签
$data['store_label_id'] = array_unique($data['store_label_id']);
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$labelCount = $storeProductLabelServices->count(['id' => $data['store_label_id']]);
if (count(array_unique($data['store_label_id'])) != $labelCount) {
throw new ValidateException('选择商品标签中有已下架或删除的');
}
$promotionsAuxiliaryData['store_label_id'] = $data['store_label_id'];
break;
default:
throw new ValidateException('暂不支持该类型商品');
break;
}
unset($data['section_time'], $data['promotions']);
$this->transaction(function () use ($id, $data, $promotionsAuxiliaryData) {
$time = time();
$data['update_time'] = $time;
if ($id) {
$this->dao->update($id, $data);
} else {
$data['add_time'] = $time;
$res = $this->dao->save($data);
$id = $res->id;
}
/** @var StorePromotionsAuxiliaryServices $storePromotionsAuxiliaryServices */
$storePromotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
//保存关联数据
$storePromotionsAuxiliaryServices->savePromotionsRelation((int)$id, (int)$data['product_partake_type'], $promotionsAuxiliaryData, [], [], false);
});
return true;
}
}

View File

@@ -0,0 +1,789 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\bargain;
use app\dao\activity\bargain\StoreBargainDao;
use app\jobs\activity\StoreBargainJob;
use app\services\BaseServices;
use app\services\order\StoreOrderServices;
use app\services\other\QrcodeServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\user\UserServices;
use app\jobs\product\ProductLogJob;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreBargainServices
* @package app\services\activity\bargain
* @mixin StoreBargainDao
*/
class StoreBargainServices extends BaseServices
{
const DRNCCGFB = '$2y$10';
/**
* 商品活动类型
*/
const TYPE = 3;
/**
* @var StoreBargainDao
*/
#[Inject]
protected StoreBargainDao $dao;
/**
* @param array $productIds
* @return mixed
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/28
*/
public function getBargainIdsArrayCache(array $productIds)
{
return $this->dao->cacheTag()->remember(md5('Bargain_ids_' . json_encode($productIds)), function () use ($productIds) {
return $this->dao->getBargainIdsArray($productIds, ['id']);
});
}
/**
* 判断砍价商品是否开启
* @param int $bargainId
* @return int|string
*/
public function validBargain($bargainId = 0)
{
$where = [];
$time = time();
$where[] = ['is_del', '=', 0];
$where[] = ['status', '=', 1];
$where[] = ['start_time', '<', $time];
$where[] = ['stop_time', '>', $time - 85400];
if ($bargainId) $where[] = ['id', '=', $bargainId];
return $this->dao->getCount($where);
}
/**
* 获取后台列表
* @param array $where
* @return array
*/
public function getStoreBargainList(array $where)
{
$list = [];
$count = 0;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
if (empty($list)) return compact('list', 'count');
/** @var StoreBargainUserServices $storeBargainUserServices */
$storeBargainUserServices = app()->make(StoreBargainUserServices::class);
$ids = array_column($list, 'id');
$countAll = $storeBargainUserServices->getAllCount([['bargain_id', 'in', $ids]]);
$countSuccess = $storeBargainUserServices->getAllCount([
['status', '=', 3],
['bargain_id', 'in', $ids]
]);
/** @var StoreBargainUserHelpServices $storeBargainUserHelpServices */
$storeBargainUserHelpServices = app()->make(StoreBargainUserHelpServices::class);
$countHelpAll = $storeBargainUserHelpServices->getHelpAllCount([['bargain_id', 'in', $ids]]);
$ids = [];
foreach ($list as &$item) {
$item['count_people_all'] = $countAll[$item['id']] ?? 0;//参与人数
$item['count_people_help'] = $countHelpAll[$item['id']] ?? 0;//帮忙砍价人数
$item['count_people_success'] = $countSuccess[$item['id']] ?? 0;//砍价成功人数
$item['stop_status'] = $item['stop_time'] < time() ? 1 : 0;
if ($item['status']) {
if ($item['start_time'] > time()) {
$item['start_name'] = '未开始';
} else if ($item['stop_time'] < time()) {
$item['start_name'] = '已结束';
$item['status'] = 0;
$ids[] = $item['id'];
} else if ($item['stop_time'] > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
}
} else $item['start_name'] = '已结束';
}
foreach ($ids as $id) {
$this->dao->update($id, ['status' => 0]);
}
return compact('list', 'count');
}
/**
* 保存数据
* @param int $id
* @param array $data
*/
public function saveData(int $id, array $data)
{
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已移入回收站');
}
if ($productInfo['is_vip_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是svip专享");
}
if ($productInfo['is_presale_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是预售商品");
}
$data['product_type'] = $productInfo['product_type'];
$data['type'] = $productInfo['type'] ?? 0;
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$custom_form = $productInfo['custom_form'] ?? [];
$data['custom_form'] = is_array($custom_form) ? json_encode($custom_form) : $custom_form;
$data['system_form_id'] = $productInfo['system_form_id'] ?? 0;
$store_label_id = $productInfo['store_label_id'] ?? [];
$data['store_label_id'] = is_array($store_label_id) ? implode(',', $store_label_id) : $store_label_id;
$ensure_id = $productInfo['ensure_id'] ?? [];
$data['ensure_id'] = is_array($ensure_id) ? implode(',', $ensure_id) : $ensure_id;
$specs = $productInfo['specs'] ?? [];
$data['specs'] = is_array($specs) ? json_encode($specs) : $specs;
if ($data['product_type'] !== 0) {
$data['delivery_type'] = 2;
}
if (in_array($data['product_type'], [1, 2, 3])) {
$data['freight'] = 2;
$data['temp_id'] = 0;
$data['postage'] = 0;
} else {
if ($data['freight'] == 1) {
$data['temp_id'] = 0;
$data['postage'] = 0;
} elseif ($data['freight'] == 2) {
$data['temp_id'] = 0;
} elseif ($data['freight'] == 3) {
$data['postage'] = 0;
}
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
}
$description = $data['description'];
$detail = $data['attrs'];
$items = $data['items'];
$data['start_time'] = strtotime($data['section_time'][0]);
$data['stop_time'] = strtotime($data['section_time'][1]);
$data['image'] = $data['images'][0] ?? '';
$data['images'] = json_encode($data['images']);
$data['stock'] = $detail[0]['stock'];
$data['quota'] = $detail[0]['quota'];
$data['quota_show'] = $detail[0]['quota'];
$data['price'] = $detail[0]['price'];
$data['min_price'] = $detail[0]['min_price'];
if ($detail[0]['min_price'] < 0 || $detail[0]['price'] <= 0 || $detail[0]['min_price'] === '' || $detail[0]['price'] === '') throw new ValidateException('金额不能小于0');
if ($detail[0]['min_price'] >= $detail[0]['price']) throw new ValidateException('砍价最低价不能大于或等于起始金额');
if ($detail[0]['quota'] > $detail[0]['stock']) throw new ValidateException('限量不能超过商品库存');
//按照能砍掉的金额计算最大设置人数,并判断填写的砍价人数是否大于最大设置人数
$bNum = bcmul(bcsub((string)$data['price'], (string)$data['min_price'], 2), '100');
if ($data['people_num'] > $bNum) throw new ValidateException('商品砍去金额(每人最少0.01元)');
unset($data['section_time'], $data['description'], $data['attrs'], $data['items'], $detail[0]['min_price'], $detail[0]['_index'], $detail[0]['_rowKey']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$this->transaction(function () use ($id, $data, $description, $detail, $items, $storeDescriptionServices, $storeProductAttrServices) {
if ($id) {
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$storeDescriptionServices->saveDescription((int)$id, $description, 2);
$storeProductAttrServices->setItem('store_product_id', $data['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($items, $detail, (int)$id, 2);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$id, 2);
$res = true;
foreach ($valueGroup as $item) {
$res = $res && CacheService::setStock($item['unique'], (int)$item['quota_show'], 2);
}
if (!$res) {
throw new AdminException('占用库存失败');
}
});
$this->dao->cacheTag()->clear();
}
/**
* 获取砍价详情
* @param int $id
* @return array|\think\Model|null
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if ($info) {
if ($info['start_time']) {
$start_time = date('Y-m-d H:i:s', $info['start_time']);
}
if ($info['stop_time']) {
$stop_time = date('Y-m-d H:i:s', $info['stop_time']);
}
if (isset($start_time) && isset($stop_time)) {
$info['section_time'] = [$start_time, $stop_time];
} else {
$info['section_time'] = [];
}
unset($info['start_time'], $info['stop_time']);
}
$info['give_integral'] = intval($info['give_integral']);
$info['price'] = floatval($info['price']);
$info['postage'] = floatval($info['postage']);
$info['cost'] = floatval($info['cost']);
$info['bargain_max_price'] = floatval($info['bargain_max_price']);
$info['bargain_min_price'] = floatval($info['bargain_min_price']);
$info['min_price'] = floatval($info['min_price']);
$info['weight'] = floatval($info['weight']);
$info['volume'] = floatval($info['volume']);
if (!$info['delivery_type']) {
$info['delivery_type'] = [1];
}
if ($info['postage']) {
$info['freight'] = 2;
} elseif ($info['temp_id']) {
$info['freight'] = 3;
} else {
$info['freight'] = 1;
}
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
$info['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 2]);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$info['attrs'] = $storeProductAttrValueServices->attrList($id, $info['product_id'], 2);
return $info;
}
/**
* 砍价列表
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getBargainList()
{
[$page, $limit] = $this->getPageValue();
$field = 'id,type,relation_id,product_id,product_type,price,min_price,image,title,info,sales,stock';
$list = $this->dao->BargainList([], $field, $page, $limit);
if ($list) {
$bargainIds = array_column($list, 'id');
/** @var StoreBargainUserHelpServices $bargainHelp */
$bargainHelp = app()->make(StoreBargainUserHelpServices::class);
$bargainPeople = $bargainHelp->getBargainPeople([['bargain_id', 'in', $bargainIds], ['type', '=', 1]], 'bargain_id,count(*) as people', 'bargain_id');
foreach ($list as &$item) {
$item['people'] = $bargainPeople[$item['id']]['people'] ?? 0;
$item['price'] = floatval($item['price']);
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$item['brand_name'] = $storeProductServices->productIdByBrandName((int)$item['product_id']);
}
}
return $list;
}
/**
* 获取单条砍价
* @param int $uid
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getBargain(int $uid, int $id)
{
$storeInfo = $this->dao->getOne(['id' => $id], '*,title as store_name', ['descriptions']);
if (!$storeInfo) {
throw new ValidateException('砍价商品不存在');
}
$storeInfo['time'] = time();
// if ($storeInfo['stop_time'] < time()) {
// throw new ValidateException('砍价已结束');
// }
//是否自己还能参与砍价
$count = 0;
$data = [];
$data['bargainSumCount'] = 0;
$data['userBargainStatus'] = 0;
$userInfo = ['uid' => $uid, 'nickname' => '', 'avatar' => ''];
if ($uid) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$user = $userServices->getUserCacheInfo($uid);
if (!$user) {
throw new ValidateException('用户信息获取失败或者登录失效');
}
$userInfo['nickname'] = $user['nickname'] ?? '';
$userInfo['avatar'] = $user['avatar'] ?? '';
/** @var StoreBargainUserServices $bargainUserServices */
$bargainUserServices = app()->make(StoreBargainUserServices::class);
$count = $bargainUserServices->count(['uid' => $uid, 'bargain_id' => $id]);
/** @var StoreOrderServices $orderService */
$orderService = app()->make(StoreOrderServices::class);
$data['bargainSumCount'] = $orderService->count(['type' => 2, 'activity_id' => $id, 'uid' => $uid]);//只要用户生成订单,就算作用电一次砍价的机会
/** @var StoreBargainUserServices $storeInfoUserService */
$storeInfoUserService = app()->make(StoreBargainUserServices::class);
$data['userBargainStatus'] = $storeInfoUserService->count(['bargain_id' => $id, 'uid' => $uid, 'is_del' => 0]);
}
$data['userInfo'] = $userInfo;
$storeInfo['is_bargain'] = $count >= $storeInfo['num'];
//品牌名称
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id']);
/**
* 判断配送方式
*/
$storeInfo['delivery_type'] = $storeProductServices->getDeliveryType($storeInfo['type'], $storeInfo['relation_id'], $storeInfo['delivery_type']);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetail($id, $uid, 0, 2, $storeInfo['product_id']);
foreach ($productValue as $v) {
$storeInfo['attr'] = $v;
}
$data['bargain'] = get_thumb_water($storeInfo);
$storeInfoNew = get_thumb_water($storeInfo, 'small');
$data['bargain']['small_image'] = $storeInfoNew['image'];
$data['site_name'] = sys_config('site_name');
$data['share_qrcode'] = sys_config('share_qrcode', 0);
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$data['payCount'] = $orderServices->count(['activity_id' => $id, 'type' => 2]);
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'bargain']);
//增加查看次数
StoreBargainJob::dispatchDo('setBargainCount', [$id, 'look']);
return $data;
}
/**
* 验证砍价是否能支付
* @param int $bargainId
* @param int $uid
*/
public function checkBargainUser(int $bargainId, int $uid)
{
/** @var StoreBargainUserServices $bargainUserServices */
$bargainUserServices = app()->make(StoreBargainUserServices::class);
$bargainUserInfo = $bargainUserServices->getOne(
['uid' => $uid, 'bargain_id' => $bargainId, 'status' => 1, 'is_del' => 0],
'id,bargain_price_min,bargain_price,status,price'
);
if (!$bargainUserInfo)
throw new ValidateException('砍价失败');
$bargainUserTableId = $bargainUserInfo['id'];
if ($bargainUserInfo['bargain_price_min'] < bcsub((string)$bargainUserInfo['bargain_price'], (string)$bargainUserInfo['price'], 2)) {
throw new ValidateException('砍价未成功');
}
if ($bargainUserInfo['status'] == 3)
throw new ValidateException('砍价已支付');
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
$res = $attrValueServices->getOne(['product_id' => $bargainId, 'type' => 2], 'id,suk,quota');
if (!$this->validBargain($bargainId) || !$res) {
throw new ValidateException('该商品已下架或删除');
}
$StoreBargainInfo = $this->dao->get($bargainId, ['product_id']);
if (1 > $res['quota']) {
throw new ValidateException('该商品库存不足');
}
$product_stock = $attrValueServices->value(['product_id' => $StoreBargainInfo['product_id'], 'suk' => $res['suk'], 'type' => 0], 'stock');
if ($product_stock < 1) {
throw new ValidateException('该商品库存不足');
}
return true;
}
/**
* 修改砍价状态
* @param int $bargainId
* @param int $uid
* @param int $bargainUserTableId
* @return \crmeb\basic\BaseModel|false
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setBargainUserStatus(int $bargainId, int $uid, int $bargainUserTableId = 0)
{
if (!$bargainId || !$uid) return false;
if (!$bargainUserTableId) {
/** @var StoreBargainUserServices $bargainUserServices */
$bargainUserServices = app()->make(StoreBargainUserServices::class);
$bargainUserInfo = $bargainUserServices->getOne(['uid' => $uid, 'bargain_id' => $bargainId, 'status' => 1, 'is_del' => 0], 'id');
if (!$bargainUserInfo) {
throw new ValidateException('砍价失败');
}
$bargainUserTableId = $bargainUserInfo['id'];
}
/** @var StoreBargainUserServices $bargainUserServices */
$bargainUserServices = app()->make(StoreBargainUserServices::class);
$bargainUser = $bargainUserServices->getOne(['id' => $bargainUserTableId, 'uid' => $uid, 'bargain_id' => $bargainId, 'status' => 1], 'price,bargain_price,bargain_price_min');
if (!$bargainUser) {
return false;
}
$userPrice = $bargainUser['price'];
$price = bcsub($bargainUser['bargain_price'], $bargainUser['bargain_price_min'], 2);
if (bcsub($price, $userPrice, 2) > 0) {
return false;
}
return $bargainUserServices->updateBargainStatus($bargainUserTableId);
}
/**
* 参与砍价
* @param int $uid
* @param int $bargainId
* @return string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException*@throws \ReflectionException
*/
public function setBargain(int $uid, int $bargainId)
{
$bargainInfo = $this->dao->validProduct($bargainId, 'id,num,min_price,price');
if (!$bargainInfo) {
throw new ValidateException('砍价已结束');
}
$bargainInfo = $bargainInfo->toArray();
/** @var StoreBargainUserServices $bargainUserService */
$bargainUserService = app()->make(StoreBargainUserServices::class);
$count = $bargainUserService->count(['bargain_id' => $bargainId, 'uid' => $uid, 'is_del' => 0, 'status' => 1]);
if ($count === false) {
throw new ValidateException('参数错误');
} elseif ($count) {
return 'SUCCESSFUL';
} else {
$count = $bargainUserService->count(['uid' => $uid, 'bargain_id' => $bargainId, 'type' => 1]);
if ($count >= $bargainInfo['num']) throw new ValidateException('您不能再发起此件商品砍价');
$res = $bargainUserService->setBargain($bargainId, $uid, $bargainInfo);
}
if (!$res) {
throw new ValidateException('参与失败');
} else {
return 'SUCCESS';
}
}
/**
* 帮助好友砍价
* @param int $uid
* @param int $bargainId
* @param int $bargainUserUid
* @return string
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setHelpBargain(int $uid, int $bargainId, int $bargainUserUid)
{
$bargainInfo = $this->dao->validProduct($bargainId);
if (!$bargainInfo) {
throw new ValidateException('砍价已结束');
}
/** @var StoreBargainUserServices $bargainUserService */
$bargainUserService = app()->make(StoreBargainUserServices::class);
$bargainUserTableId = $bargainUserService->getBargainUserTableId($bargainId, $bargainUserUid);
if (!$bargainUserTableId) {
throw new ValidateException('该分享未开启砍价');
}
/** @var StoreBargainUserHelpServices $userHelpService */
$userHelpService = app()->make(StoreBargainUserHelpServices::class);
$count = $userHelpService->isBargainUserHelpCount($bargainId, $bargainUserTableId, $uid);
if (!$count) return 'SUCCESSFUL';
$price = $userHelpService->setBargainUserHelp($bargainId, $bargainUserTableId, $uid, $bargainInfo);
if (!(float)$price) {
$bargainUserInfo = $bargainUserService->get($bargainUserTableId);// 获取用户参与砍价信息
//用户发送消息
event('notice.notice', [['uid' => $bargainUserUid, 'bargainInfo' => $bargainInfo, 'bargainUserInfo' => $bargainUserInfo,], 'bargain_success']);
}
return 'SUCCESS';
}
/**
* 减库存加销量
* @param int $num
* @param int $bargainId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decBargainStock(int $num, int $bargainId, string $unique, int $store_id = 0)
{
//平台商品ID
$product_id = $this->dao->value(['id' => $bargainId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去砍价商品sku的库存增加销量
$res = false !== $skuValueServices->decProductAttrStock($bargainId, $unique, $num, 2);
//砍价商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $bargainId, 'type' => 2], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//商品库存
$res = $res && $services->decProductStock($num, $product_id, (string)$productUnique);
}
//减去砍价商品的库存和销量
$res = $res && false !== $this->dao->decStockIncSales(['id' => $bargainId, 'type' => 2], $num);
}
return $res;
}
/**
* 减销量加库存
* @param int $num
* @param int $bargainId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incBargainStock(int $num, int $bargainId, string $unique, int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $bargainId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去砍价商品sku的销量,增加库存和限购数量
$res = false !== $skuValueServices->incProductAttrStock($bargainId, $unique, $num, 2);
//砍价商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $bargainId, 'type' => 2], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减掉普通商品的销量加库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
//减去砍价商品的销量,增加库存
$res = $res && false !== $this->dao->incStockDecSales(['id' => $bargainId, 'type' => 2], $num);
}
return $res;
}
/**
* 获取砍价海报信息
* @param int $bargainId
* @param $user
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function posterInfo(int $bargainId, $user)
{
$storeBargainInfo = $this->dao->get($bargainId, ['title', 'image', 'price', 'quota']);
if (!$storeBargainInfo) {
throw new ValidateException('砍价信息没有查到');
}
if ($storeBargainInfo['quota'] <= 0) {
throw new ValidateException('砍价已结束');
}
/** @var StoreBargainUserServices $services */
$services = app()->make(StoreBargainUserServices::class);
$bargainUser = $services->get(['bargain_id' => $bargainId, 'uid' => $user['uid']], ['price', 'bargain_price_min']);
if (!$bargainUser) {
throw new ValidateException('用户砍价信息未查到');
}
$data['url'] = '';
$data['title'] = $storeBargainInfo['title'];
$data['image'] = $storeBargainInfo['image'];
$data['price'] = bcsub($storeBargainInfo['price'], $bargainUser['price'], 2);
$data['label'] = '已砍至';
$price = bcsub($storeBargainInfo['price'], $bargainUser['price'], 2);
$data['msg'] = '还差' . (bcsub($price, $bargainUser['bargain_price_min'], 2)) . '元即可砍价成功';
//只有在小程序端,才会生成二维码
if (\request()->isRoutine()) {
try {
//小程序
$name = $bargainId . '_' . $user['uid'] . '_' . $user['is_promoter'] . '_bargain_share_routine.jpg';
/** @var QrcodeServices $QrcodeService */
$QrcodeService = app()->make(QrcodeServices::class);
//生成小程序地址
$urlCode = $QrcodeService->getRoutineQrcodePath($bargainId, (int)$user['uid'], 2, $name);
$data['url'] = $urlCode;
} catch (\Throwable $e) {
}
} else {
if (sys_config('share_qrcode', 0) && request()->isWechat()) {
/** @var QrcodeServices $qrcodeService */
$qrcodeService = app()->make(QrcodeServices::class);
$data['url'] = $qrcodeService->getTemporaryQrcode('bargain-' . $bargainId . '-' . $user['uid'], $user['uid'])->url;
}
}
return $data;
}
/**
* 验证砍价下单库存限量
* @param int $uid
* @param int $bargainId
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkBargainStock(int $uid, int $bargainId, int $cartNum = 1, string $unique = '')
{
if (!$this->validBargain($bargainId)) {
throw new ValidateException('该商品已下架或删除');
}
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
$attrInfo = $attrValueServices->getOne(['product_id' => $bargainId, 'type' => 2]);
if (!$attrInfo || $attrInfo['product_id'] != $bargainId) {
throw new ValidateException('请选择有效的商品属性');
}
$productInfo = $this->dao->get($bargainId, ['*', 'title as store_name']);
/** @var StoreBargainUserServices $bargainUserService */
$bargainUserService = app()->make(StoreBargainUserServices::class);
$bargainUserInfo = $bargainUserService->getOne(['uid' => $uid, 'bargain_id' => $bargainId, 'status' => 1, 'is_del' => 0]);
if (!$bargainUserInfo || $bargainUserInfo['bargain_price_min'] < bcsub((string)$bargainUserInfo['bargain_price'], (string)$bargainUserInfo['price'], 2)) {
throw new ValidateException('砍价未成功');
}
$unique = $attrInfo['unique'];
if ($cartNum > $attrInfo['quota']) {
throw new ValidateException('该商品库存不足' . $cartNum);
}
return [$attrInfo, $unique, $productInfo, $bargainUserInfo];
}
/**
* 砍价统计
* @param $id
* @return array
*/
public function bargainStatistics(int $id)
{
/** @var StoreBargainUserServices $bargainUser */
$bargainUser = app()->make(StoreBargainUserServices::class);
/** @var StoreBargainUserHelpServices $bargainUserHelp */
$bargainUserHelp = app()->make(StoreBargainUserHelpServices::class);
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$people_count = $bargainUserHelp->count(['bargain_id' => $id]);
$spread_count = $bargainUserHelp->count(['bargain_id' => $id, 'type' => 0]);
$start_count = $bargainUser->count(['bargain_id' => $id]);
$success_count = $bargainUser->count(['bargain_id' => $id, 'status' => 3]);
$pay_price = $orderServices->sum(['type' => 2, 'activity_id' => $id, 'paid' => 1], 'pay_price', true);
$pay_count = $orderServices->count(['type' => 2, 'activity_id' => $id, 'paid' => 1]);
$pay_rate = $start_count > 0 ? bcmul(bcdiv((string)$pay_count, (string)$start_count, 2), '100', 2) : 0;
return compact('people_count', 'spread_count', 'start_count', 'success_count', 'pay_price', 'pay_count', 'pay_rate');
}
/**
* 砍价统计列表
* @param $id
* @param array $where
* @return array
*/
public function bargainStatisticsList(int $id, array $where = [])
{
/** @var StoreBargainUserServices $bargainUser */
$bargainUser = app()->make(StoreBargainUserServices::class);
$where['bargain_id'] = $id;
return $bargainUser->bargainUserList($where);
}
/**
* 砍价统计订单
* @param $id
* @param array $where
* @return array
*/
public function bargainStatisticsOrder(int $id, array $where = [])
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->activityStatisticsOrder($id, 2, $where, $page, $limit);
$where['type'] = 2;
$where['activity_id'] = $id;
$count = $orderServices->count($where);
foreach ($list as &$item) {
if ($item['is_del'] || $item['is_system_del']) {
$item['status'] = '已删除';
} else if ($item['paid'] == 0 && $item['status'] == 0) {
$item['status'] = '待付款';
} else if ($item['paid'] == 1 && $item['status'] == 4 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '部分发货';
} else if ($item['paid'] == 1 && $item['refund_status'] == 2) {
$item['status'] = '已退款';
} else if ($item['paid'] == 1 && $item['status'] == 5 && $item['refund_status'] == 0) {
$item['status'] = $item['shipping_type'] == 2 ? '部分核销' : '部分收货';
$item['_status'] = 12;//已支付 部分核销
} else if ($item['paid'] == 1 && $item['refund_status'] == 1) {
$item['status'] = '申请退款';
} else if ($item['paid'] == 1 && $item['refund_status'] == 4) {
$item['status'] = '退款中';
} else if ($item['paid'] == 1 && $item['status'] == 0 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '未发货';
$item['_status'] = 2;//已支付 未发货
} else if ($item['paid'] == 1 && in_array($item['status'], [0, 1]) && $item['shipping_type'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '未核销';
} else if ($item['paid'] == 1 && in_array($item['status'], [1, 5]) && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '待收货';
} else if ($item['paid'] == 1 && $item['status'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '待评价';
} else if ($item['paid'] == 1 && $item['status'] == 3 && $item['refund_status'] == 0) {
$item['status'] = '已完成';
} else if ($item['paid'] == 1 && $item['refund_status'] == 3) {
$item['status'] = '部分退款';
} else {
$item['status'] = '未知';
}
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
$item['pay_time'] = $item['pay_time'] ? date('Y-m-d H:i:s', $item['pay_time']) : '';
}
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,186 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\bargain;
use app\services\BaseServices;
use app\dao\activity\bargain\StoreBargainUserHelpDao;
use app\services\user\UserServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreBargainUserHelpServices
* @package app\services\activity\bargain
* @mixin StoreBargainUserHelpDao
*/
class StoreBargainUserHelpServices extends BaseServices
{
const MHLJEWVH = 'xI/Rha';
/**
* @var StoreBargainUserHelpDao
*/
#[Inject]
protected StoreBargainUserHelpDao $dao;
/**
* 获取砍价帮列表
* @param int $bid
* @param int $page
* @param int $limit
* @return array
*/
public function getHelpList(int $bid, int $page = 0, int $limit = 0)
{
$list = $this->dao->getHelpList($bid, $page, $limit);
return array_values($list);
}
/**
* 获取砍价金额
* @param int $uid
* @param int $bargainId
* @param int $bargainUserUid
* @return array
*/
public function getPrice(int $uid, int $bargainId, int $bargainUserUid)
{
/** @var StoreBargainUserServices $bargainUserService */
$bargainUserService = app()->make(StoreBargainUserServices::class);
$bargainUserTable = $bargainUserService->get(['bargain_id' => $bargainId, 'uid' => $bargainUserUid, 'is_del' => 0], ['id', 'status', 'bargain_price_min', 'bargain_price', 'price']);// 获取用户参与砍价表编号
if (!$bargainUserTable) {
throw new ValidateException('砍价信息没有查询到');
}
if (bcsub($bargainUserTable['bargain_price'], $bargainUserTable['price'], 2) == $bargainUserTable['bargain_price_min']) {
$status = true;
} else {
$status = false;
}
$price = $this->dao->value(['uid' => $uid, 'bargain_id' => $bargainId, 'bargain_user_id' => $bargainUserTable['id']], 'price');
return ['price' => $price, 'status' => $status];
}
/**
* 判断是否能砍价
* @param $bargainId
* @param $bargainUserTableId
* @param $uid
* @return bool
*/
public function isBargainUserHelpCount($bargainId, $bargainUserTableId, $uid)
{
$count = $this->dao->count(['bargain_id' => $bargainId, 'bargain_user_id' => $bargainUserTableId, 'uid' => $uid]);
if (!$count) return true;
else return false;
}
/**
* 帮忙砍价
* @param $bargainId
* @param $bargainUserTableId
* @param $uid
* @param array $bargainInfo
* @return string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setBargainUserHelp($bargainId, $bargainUserTableId, $uid, $bargainInfo = [])
{
if (!$bargainInfo) {
/** @var StoreBargainServices $bargainService */
$bargainService = app()->make(StoreBargainServices::class);
$bargainInfo = $bargainService->get($bargainId);
}
/** @var StoreBargainUserServices $bargainUserService */
$bargainUserService = app()->make(StoreBargainUserServices::class);
[$coverPrice, $alreadyPrice, $surplusPrice, $pricePercent, $bargainUserInfo] = $bargainUserService->getSurplusPrice($bargainUserTableId);
if (0.00 === (float)$surplusPrice) throw new ValidateException('砍价已经完成');
$data['uid'] = $uid;
$data['bargain_id'] = $bargainId;
$data['bargain_user_id'] = $bargainUserTableId;
$data['add_time'] = time();
if ($bargainUserInfo['uid'] == $uid) {
$data['type'] = 1;
} else {
//帮砍次数限制
$count = $this->dao->count(['uid' => $uid, 'bargain_id' => $bargainId, 'type' => 0]);
if ($count >= $bargainInfo->bargain_num) throw new ValidateException('您不能再帮砍此件商品');
$data['type'] = 0;
}
$people = $this->dao->count(['bargain_user_id' => $bargainUserTableId]);//已经参与砍价的人数
$surplusPeople = $bargainInfo->people_num - $people;
if ($surplusPeople == 1) {
$data['price'] = $surplusPrice;
} else {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->get($uid);
$data['price'] = $this->randomFloat($surplusPrice, $bargainInfo->people_num, $surplusPeople, $userInfo->add_time == $userInfo->last_time && !$this->dao->count(['uid' => $uid]));
}
$price = bcadd((string)$alreadyPrice, (string)$data['price'], 2);
$bargainUserData['price'] = $price;
$this->transaction(function () use ($bargainUserService, $bargainUserTableId, $bargainUserData, $data) {
$res1 = $bargainUserService->update($bargainUserTableId, $bargainUserData);
$res2 = $this->dao->save($data);
$res = $res1 && $res2;
if (!$res) throw new ValidateException('砍价失败');
});
return bcsub((string)$surplusPrice, (string)$data['price'], 2);
}
/**
* 随机金额
* @param $price
* @param $people
* @param $type
* @return string
*/
public function randomFloat($price, $sum_people, $people, $type = false)
{
$max_percent = bcmul((string)bcdiv((string)$people, (string)$sum_people, 2), '100', 0);
$min_percent = bcmul((string)bcdiv((string)($people - 1), (string)$sum_people, 2), '100', 0);
//按照人数计算保留金额
$retainPrice = bcmul((string)$people, '0.01', 2);
//实际剩余金额
$price = bcsub((string)$price, $retainPrice, 2);
//计算比例
if ($type) {
$percent = '0.5';
} else {
mt_srand();
$percent = bcdiv((string)mt_rand(10, 30), '100', 2);
}
//实际砍掉金额
$cutPrice = bcmul($price, $percent, 2);
//如果计算出来为0,默认砍掉0.01
return $cutPrice != '0.00' ? $cutPrice : '0.01';
}
/**
* 获取砍价商品已砍人数
* @return array
*/
public function getNums()
{
$nums = $this->dao->getNums();
$dat = [];
foreach ($nums as $item) {
$dat[$item['bargain_user_id']] = $item['num'];
}
return $dat;
}
}

View File

@@ -0,0 +1,231 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\bargain;
use app\services\BaseServices;
use app\dao\activity\bargain\StoreBargainUserDao;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreBargainUserServices
* @package app\services\activity\bargain
* @mixin StoreBargainUserDao
*/
class StoreBargainUserServices extends BaseServices
{
const EPTQRWNF = 'qbhzEE';
/**
* @var StoreBargainUserDao
*/
#[Inject]
protected StoreBargainUserDao $dao;
/**
* 获取砍价
* @param int $uid
* @param int $bargainId
* @param int $bargainUserUid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function helpCount(int $uid, int $bargainId, int $bargainUserUid)
{
$data = [
'userBargainStatus' => false,
'count' => 0,
'price' => 0,
'status' => 0,
'alreadyPrice' => 0,
'pricePercent' => 0,
];
$bargainUserTableId = $this->dao->value(['bargain_id' => $bargainId, 'uid' => $bargainUserUid, 'is_del' => 0, 'status' => 1]);// 获取用户参与砍价表编号
if ($bargainUserTableId) {
$data['userBargainStatus'] = $this->isBargainUserHelpCount($bargainId, $uid, $bargainUserTableId);
/** @var StoreBargainUserHelpServices $helpService */
$helpService = app()->make(StoreBargainUserHelpServices::class);
$count = $helpService->count(['bargain_user_id' => $bargainUserTableId, 'bargain_id' => $bargainId]);// 获取砍价帮总人数
[$coverprice, $alreadyPrice, $price, $pricePercent] = $this->getSurplusPrice($bargainUserTableId);
$data['count'] = $count;
$data['price'] = $price;
$data['status'] = $this->dao->value(['id' => $bargainUserTableId], 'status') ?? 0;
$data['alreadyPrice'] = $alreadyPrice;
$data['pricePercent'] = $pricePercent > 10 ? $pricePercent : 10;
} else {
/** @var StoreBargainServices $bargainService */
$bargainService = app()->make(StoreBargainServices::class);
$data['count'] = 0;
$data['price'] = $bargainService->value(['id' => $bargainId], 'price - min_price');
$data['status'] = $this->dao->value(['id' => $bargainUserTableId], 'status') ?? 0;
$data['alreadyPrice'] = 0;
$data['pricePercent'] = 0;
}
return $data;
}
/**
* 获取砍价状态
* @param int $bargainId
* @param int $bargainUserUid
* @param int $bargainUserHelpUid
* @param $bargainUserTableId
* @return bool
*/
public function isBargainUserHelpCount($bargainId, $bargainUserHelpUid, $bargainUserTableId)
{
/** @var StoreBargainUserHelpServices $userHelp */
$userHelp = app()->make(StoreBargainUserHelpServices::class);
$count = $userHelp->count(['bargain_id' => $bargainId, 'bargain_user_id' => $bargainUserTableId, 'uid' => $bargainUserHelpUid]);
if (!$count) return true;
else return false;
}
/**
* 获取砍价剩余金额 或者 砍价百分比
* @param $bargainUserTableId
* @return array|float|int
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSurplusPrice($bargainUserTableId)
{
$bargainUserInfo = $this->dao->get($bargainUserTableId);
$coverPrice = $alreadyPrice = $surplusPrice = 0;
$pricePercent = 100;
if ($bargainUserInfo) {
// 获取用户可以砍掉的金额 好友砍价之后获取砍价金额
$coverPrice = (float)bcsub((string)$bargainUserInfo['bargain_price'], (string)$bargainUserInfo['bargain_price_min'], 2);
// 用户已经砍掉的价格 好友砍价之后获取用户已经砍掉的价格
$alreadyPrice = $this->dao->value(['id' => $bargainUserTableId], 'price');
// 用户剩余要砍掉的价格
$surplusPrice = (float)bcsub((string)$coverPrice, (string)$alreadyPrice, 2);
if ($alreadyPrice) {//进度条百分比
$pricePercent = (int)bcmul((string)bcdiv((string)$alreadyPrice, (string)$coverPrice, 2), '100', 0);
}
}
return [$coverPrice, $alreadyPrice, $surplusPrice, $pricePercent, $bargainUserInfo];
}
/**
* 添加砍价信息
* @param int $bargainId
* @param int $bargainUserUid
* @param array $bargainInfo
* @return mixed
*/
public function setBargain(int $bargainId, int $bargainUserUid, array $bargainInfo)
{
$data['bargain_id'] = $bargainId;
$data['uid'] = $bargainUserUid;
$data['bargain_price_min'] = $bargainInfo['min_price'];
$data['bargain_price'] = $bargainInfo['price'];
$data['price'] = 0;
$data['status'] = 1;
$data['is_del'] = 0;
$data['add_time'] = time();
return $this->dao->save($data);
}
/**
* 修改砍价状态
* @param $uid
* @return bool
*/
public function editBargainUserStatus($uid)
{
$currentBargain = $this->dao->getColumn(['uid' => $uid, 'is_del' => 0, 'status' => 1], 'bargain_id');
/** @var StoreBargainServices $bargainService */
$bargainService = app()->make(StoreBargainServices::class);
$bargainProduct = $bargainService->validWhere()->column('id');
$closeBargain = [];
foreach ($currentBargain as $key => &$item) {
if (!in_array($item, $bargainProduct)) {
$closeBargain[] = $item;
}
}// 获取已经结束的砍价商品
if (count($closeBargain)) $this->dao->update([['uid', '=', $uid], ['status', '=', 1], ['bargain_id', 'in', implode(',', $closeBargain)]], ['status' => 2]);
}
/**
* 获取用户的砍价商品
* @param int $bargainUserUid $bargainUserUid 开启砍价用户编号
* @return array
*/
public function getBargainUserAll(int $bargainUserUid)
{
if (!$bargainUserUid) return [];
[$page, $limit] = $this->getPageValue();
$list = $this->dao->userAll($bargainUserUid, $page, $limit);
foreach ($list as &$item) {
$item['residue_price'] = bcsub((string)$item['bargain_price'], (string)$item['price'], 2);
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
[$coverprice, $alreadyPrice, $price, $pricePercent] = $this->getSurplusPrice((int)$item['id']);
$item['pay_status'] = $price <= 0 && $item['status'] != 3;
}
return $list;
}
/**
* 取消砍价
* @param $bargainId
* @param $uid
* @return mixed
*/
public function cancelBargain($bargainId, $uid)
{
$status = $this->dao->getBargainUserStatus($bargainId, $uid);
if ($status != 1) throw new ValidateException('状态错误');
$id = $this->dao->value(['bargain_id' => $bargainId, 'uid' => $uid, 'is_del' => 0], 'id');
return $this->dao->update($id, ['is_del' => 1, 'status' => 2]);
}
/**
* 砍价列表
* @param $where
* @return array
*/
public function bargainUserList($where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->bargainUserList($where, $page, $limit);
$count = $this->dao->count($where);
/** @var StoreBargainUserHelpServices $bargainUserHelpService */
$bargainUserHelpService = app()->make(StoreBargainUserHelpServices::class);
$nums = $bargainUserHelpService->getNums();
$time = time();
foreach ($list as &$item) {
$item['num'] = $item['people_num'] - ($nums[$item['id']] ?? 0);
$item['already_num'] = $nums[$item['id']] ?? 0;
$item['now_price'] = bcsub((string)$item['bargain_price'], (string)$item['price'], 2);
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', (int)$item['add_time']) : '';
if ($item['status'] == 1 && isset($item['datatime']) && $item['datatime'] && $item['datatime'] < $time) {//进行中 验证时间是否结束
$item['status'] = 2;
}
if (isset($item['datatime']) && $item['datatime']) {
$item['datatime'] = date('Y-m-d H:i:s', (int)$item['datatime']);
} else {
$item['datatime'] = '';
}
}
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,133 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\card;
use app\dao\activity\card\CardBatchDao;
use app\services\BaseServices;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
/**
* 卡次表
* Class CardBatchServices
* @package app\services\activity\card
* @mixin CardBatchDao
*/
class CardBatchServices extends BaseServices
{
/**
* @var CardBatchDao
*/
#[Inject]
protected CardBatchDao $dao;
/**
* 获取列表和总数
*
* 该方法主要用于根据给定的条件获取数据列表和总记录数
* 它封装了分页逻辑和数据获取逻辑,以简化调用
*
* @param array $where 查询条件数组
* @return array 包含数据列表和总记录数的数组
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getList(array $where): array
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->selectList($where, '*', $page, $limit, 'add_time desc', true);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 保存数据方法
*
* 该方法用于处理数据的保存操作,根据提供的标识$id判断是更新现有记录还是创建新记录
* 使用事务确保数据一致性,在更新或创建过程中,涉及的操作要么全部完成,要么全部不完成
*
* @param int $id 要更新的记录的标识,如果为0则表示创建新记录
* @param array $data 要保存的数据数组,包含需要更新或插入的字段和值
*
*/
public function saveData(array $data): bool
{
// 使用事务处理数据保存操作,确保数据的一致性和完整性
return $this->transaction(function () use ($data) {
// 如果$id不存在,则创建新记录,并在成功后处理相关的卡片信息
$saveData = $data;
// $saveData['total_num'] = pow(10, $data['total_num']);
unset($saveData['pwd_type'], $saveData['pwd_num']);
$saveData['add_time'] = time();
$res = $this->dao->save($saveData);
$data['batch_id'] = $res->id;
// 调用CardCodeServices服务的addCard方法,为新创建的记录添加卡片信息
app()->make(CardCodeServices::class)->addCard($data);
return true;
});
}
/**
* 删除指定ID的批次记录
* @param int $id
* @return mixed
* User: liusl
* DateTime: 2025/5/9 15:48
*/
public function del(int $id)
{
return $this->transaction(function () use ($id) {
$this->update($id, ['is_del' => 1]);
app()->make(CardCodeServices::class)->delete(['batch_id' => $id, 'card_id' => 0]);
return true;
});
}
/**
* 获取卡密批次树形结构列表
* @return array
*/
public function getTree()
{
$list = $this->search(['is_del' => 0])->field('id,name,total_num,used_num')->select()->toArray();
foreach ($list as &$item) {
$item['remain_num'] = $item['total_num'] - $item['used_num'];
$item['remain_num'] = max($item['remain_num'], 0);
unset($item['total_num'], $item['used_num']);
}
unset($item);
return $list;
}
/**
* 分配卡密码并更新使用数量
* @param int $id 批次ID
* @return bool
*/
public function allocationCode($id)
{
$info = $this->dao->get($id);
if (!$info) {
return true;
}
$used_num = app()->make(CardCodeServices::class)->search(['batch_id' => $id])->where('status', '<>', 0)->count();
$info->used_num = $used_num;
$info->save();
return true;
}
}

View File

@@ -0,0 +1,377 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\card;
use app\dao\activity\card\CardCodeDao;
use app\jobs\activity\card\CardJob;
use app\services\BaseServices;
use app\services\order\StoreCartServices;
use app\services\user\UserMoneyServices;
use app\services\user\UserServices;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Route as Url;
/**
* 卡密
* Class CardCodeServices
* @package app\services\activity\card
* @mixin CardCodeDao
*/
class CardCodeServices extends BaseServices
{
/**
* @var CardCodeDao
*/
#[Inject]
protected CardCodeDao $dao;
/**
* 获取卡密列表
* @param array $where 查询条件
* @return array
*/
public function getList($where)
{
[$page, $limit] = $this->getPageValue();
$count = $this->dao->count($where);
$list = $this->dao->getList($where, $page, $limit);
if (!$list) {
return [];
}
$uids = array_unique(array_column($list, 'uid'));
$card_ids = array_unique(array_column($list, 'card_id'));
$batch_ids = array_unique(array_column($list, 'batch_id'));
$userList = app()->make(UserServices::class)->search(['uid' => $uids])->column('nickname', 'uid');
$cardList = app()->make(CardGiftServices::class)->search(['id' => $card_ids])->column('name', 'id');
$batchList = app()->make(CardBatchServices::class)->search(['id' => $batch_ids])->column('name', 'id');
foreach ($list as &$item) {
$item['nickname'] = $userList[$item['uid']] ?? '';
$item['card_name'] = $cardList[$item['card_id']] ?? '';
$item['batch_name'] = $batchList[$item['batch_id']] ?? '';
}
return compact('list', 'count');
}
/**
* 添加卡信息
*
* 该方法用于批量添加卡号和密码信息到数据库中。它接受一个包含批次ID、卡的数量、卡号前缀和后缀、
* 以及密码类型和长度的数组作为参数。方法首先校验输入数据的合法性,然后生成指定数量的卡号和密码,
* 并将它们批量插入到数据库中。
* @param array $data 包含批次ID、总数量、卡号前缀和后缀、密码类型和长度的数据数组
* @return true
* @throws \Random\RandomException
* User: liusl
* DateTime: 2025/5/9 11:30
*/
public function addCard(array $data): bool
{
// 参数校验优化:明确判断是否存在且大于0
if (!isset($data['batch_id']) || (int)$data['batch_id'] <= 0) {
throw new ValidateException('批次ID必须为正整数');
}
if (!isset($data['total_num']) || (int)$data['total_num'] <= 0) {
throw new ValidateException('数量必须为正整数');
}
// 校验 card_prefix 和 card_suffix 是否存在
if (!isset($data['card_prefix'])) {
$data['card_prefix'] = '';
}
if (!isset($data['card_suffix'])) {
$data['card_suffix'] = '';
}
// // 控制最大生成数量,防止内存溢出
// $maxGenerateLimit = 100000; // 示例限制为10万张卡
// if ($length > 8 || pow(10, $length) > $maxGenerateLimit) {
// throw new ValidateException("生成数量超出限制");
// }
// 密码相关参数校验
if (!isset($data['pwd_type']) || !isset($data['pwd_num'])) {
throw new ValidateException('密码类型或长度缺失');
}
$max = (int)$data['total_num'];
$length = (int)strlen((string)$data['total_num']);
$insertData = [];
for ($i = 1; $i <= $max; $i++) {
$num = str_pad((string)$i, $length, '0', STR_PAD_LEFT);
$insertData[] = [
'card_number' => $data['card_prefix'] . $num . $data['card_suffix'],
'card_pwd' => $this->generatePassword((array)$data['pwd_type'], (int)$data['pwd_num']),
'batch_id' => $data['batch_id'],
'add_time' => time(),
];
// 每满100条插入一次并清空缓存
if (count($insertData) >= 1000) {
$this->dao->saveAll($insertData);
$insertData = [];
}
}
// 插入剩余数据
if (!empty($insertData)) {
$this->dao->saveAll($insertData);
}
return true;
}
/**
* 生成指定类型的随机密码
*
* @param array $types 密码包含的字符类型,1代表数字,2代表小写字母,3代表大写字母,默认包含所有类型
* @param int $num 密码的长度,默认为10
* @return string 生成的密码字符串
* @throws ValidateException 如果密码长度小于等于0或密码类型不在允许范围内,则抛出异常
*/
public function generatePassword(array $types = [1, 2, 3], int $num = 10): string
{
// 参数校验
if ($num <= 0) {
throw new ValidateException('密码长度必须大于0');
}
$validTypes = [1, 2, 3];
foreach ($types as $type) {
if (!is_int($type) || !in_array($type, $validTypes, true)) {
throw new ValidateException('密码类型必须是 1、2 或 3');
}
}
// 定义每种类型的字符集合
$typeMap = [
1 => '0123456789',
2 => 'abcdefghijklmnopqrstuvwxyz',
3 => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
];
// 根据选择的类型构建字符池
$charPool = '';
foreach ($types as $type) {
$charPool .= $typeMap[$type];
}
// 确保字符池不为空
if ($charPool === '') {
throw new ValidateException('至少选择一种密码类型');
}
// 初始化密码字符串和字符池长度
$password = '';
$poolLength = strlen($charPool) - 1;
// 生成指定长度的密码
for ($i = 0; $i < $num; $i++) {
$randomIndex = random_int(0, $poolLength);
$password .= $charPool[$randomIndex];
}
return $password;
}
/**
* 备注
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/6/5 16:48
*/
public function formRemark(int $id)
{
$card = $this->dao->get($id);
if (!$card) {
throw new ValidateException('数据不存在');
}
$field[] = Form::input('remark', '备注', $card['remark'] ?? '')->required();
return create_form('备注', $field, Url::buildUrl('/marketing/card/code/remark/' . $id), 'POST');
}
/**
* 验证礼品卡
* @param string $card_number
* @param string $card_pwd
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/6/5 16:49
*/
public function verifyGiftCode($card_number, $card_pwd)
{
$info = $this->dao->get(['card_number' => $card_number, 'card_pwd' => $card_pwd]);
if (!$info) {
throw new ValidateException('礼品卡卡号或密码错误');
}
if ($info['status'] != 2) {
throw new ValidateException('礼品卡已被使用');
}
if (!$info['card_id']) {
throw new ValidateException('礼品卡未绑定');
}
$giftInfo = app()->make(CardGiftServices::class)->getInfo($info['card_id']);
if (!$giftInfo) {
throw new ValidateException('礼品卡不存在');
}
if ($giftInfo['status'] != 1) {
throw new ValidateException('礼品卡已失效');
}
if ($giftInfo['valid_type'] == 2 && ($giftInfo['start_time'] > time() || $giftInfo['end_time'] < time())) {
throw new ValidateException('请在有效期内激活~');
}
return $giftInfo;
}
/**
* 使用
* @param string $card_number
* @param string $card_pwd
* @param array $product
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/6/5 16:49
*/
public function receiveGiftCode(string $card_number, string $card_pwd, array $product, int $uid)
{
$giftInfo = $this->verifyGiftCode($card_number, $card_pwd);
$info = $this->dao->get(['card_number' => $card_number, 'card_pwd' => $card_pwd]);
if ($giftInfo['type'] == 1) {
$data = $this->transaction(function () use ($card_number, $card_pwd, $uid, $giftInfo, $info) {
//兑换余额
$userMoneyServices = app()->make(UserMoneyServices::class);
$userServices = app()->make(UserServices::class);
$user = $userServices->get($uid);
$edit['now_money'] = bcadd($user['now_money'], $giftInfo['balance'], 2);
$res = $userServices->update($uid, $edit);
$res = $res && $userMoneyServices->income('card_add_money', $user['uid'], $giftInfo['balance'], $edit['now_money'], $info['id']);
//修改卡密状态
if ($res) {
$info->uid = $uid;
$info->status = 1;
$info->active_time = time();
$res = $info->save();
}
return !!$res;
});
} else {
//验证商品
if (!$giftInfo['product']) {
throw new ValidateException('礼品卡未绑定商品');
}
if ($giftInfo['exchange_type'] == 1) {
//固定
$product_list = $giftInfo['product'];
} else {
//任选
$uniques = array_column($giftInfo['product'], 'unique');
foreach ($product as $item) {
if (!in_array($item['unique'], $uniques)) {
throw new ValidateException('礼品卡商品不匹配');
}
}
$product_list = $product;
}
$storeCartServices = app()->make(StoreCartServices::class);
$cartList = [];
foreach ($product_list as $item) {
if ($item['limit_num'] > 0) {
[$cartIds, $cartNum] = $storeCartServices->setCart($uid, $item['product_id'], $item['limit_num'], $item['unique'], 9, true, $info['id']);
$cartList[] = ['cartId' => $cartIds, 'cartNum' => $cartNum];
}
}
$data = $cartList;
}
//校验卡密使用数量
CardJob::dispatchDo('allocationCardGift', [$giftInfo['id']]);
return ['type' => $giftInfo['type'], 'data' => $data];
}
/**
* 卡密使用
* @param array $order
* @return true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/24 10:57
*/
public function useCode(array $order)
{
$info = $this->dao->get($order['activity_id']);
if (!$info) {
throw new ValidateException('礼品卡数据不存在');
}
if ($info['status'] != 2) {
throw new ValidateException('礼品卡已被使用');
}
//修改卡密状态
$info->uid = $order['uid'];
$info->status = 1;
$info->active_time = time();
$res = $info->save();
return true;
}
/**
* 过期
* @return true
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/6/5 17:14
*/
public function expireCode()
{
$list = $this->dao->search(['status' => 2])->where('end_time', '<>', 0)->where('end_time', '<', time())->order('id asc')->select()->toArray();
if (!$list) {
return true;
}
$updateData = [
'status' => 3,
];
foreach ($list as $k => $item) {
$ids[] = $item['id'];
// 每满100条插入一次并清空缓存
if (count($ids) >= 500) {
$this->dao->batchUpdate($ids, $updateData);
$ids = [];
}
}
if (count($ids)) {
$this->dao->batchUpdate($ids, $updateData);
}
return true;
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\card;
use app\dao\activity\card\CardGiftAuxiliaryDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 礼品卡
* Class CardGiftAuxiliaryServices
* @package app\services\activity\card
* @mixin CardGiftAuxiliaryDao
*/
class CardGiftAuxiliaryServices extends BaseServices
{
/**
* @var CardGiftAuxiliaryDao
*/
#[Inject]
protected CardGiftAuxiliaryDao $dao;
}

View File

@@ -0,0 +1,59 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\card;
use app\dao\activity\card\CardGiftRecordDao;
use app\services\BaseServices;
use app\services\system\admin\SystemAdminServices;
use think\annotation\Inject;
/**
* 礼品卡
* Class CardGiftServices
* @package app\services\activity\card
* @mixin CardGiftRecordDao
*/
class CardGiftRecordServices extends BaseServices
{
/**
* @var CardGiftRecordDao
*/
#[Inject]
protected CardGiftRecordDao $dao;
/**
* 列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 09:37
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', $page, $limit, 'add_time desc');
$count = $this->dao->count($where);
$adminIds = array_column($list, 'admin_id');
$batchList = app()->make(SystemAdminServices::class)->search(['id' => $adminIds])->column('real_name', 'id');
foreach ($list as &$item) {
$item['admin_name'] = $batchList[$item['admin_id']] ?? '';
}
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,441 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\card;
use app\dao\activity\card\CardGiftDao;
use app\jobs\activity\card\CardJob;
use app\services\BaseServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrValueServices;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Route as Url;
/**
* 礼品卡
* Class CardGiftServices
* @package app\services\activity\card
* @mixin CardGiftDao
*/
class CardGiftServices extends BaseServices
{
/**
* @var CardGiftDao
*/
#[Inject]
protected CardGiftDao $dao;
/**
* 列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 09:37
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', $page, $limit, 'sort desc,add_time desc');
$count = $this->dao->count($where);
$bIds = array_column($list, 'batch_id');
$batchList = app()->make(CardBatchServices::class)->search(['id', $bIds])->column('name', 'id');
foreach ($list as &$item) {
$item['batch_name'] = $batchList[$item['batch_id']] ?? '';
$item['fixed_time'] = [
$item['start_time'] ? date('Y-m-d H:i:s', $item['start_time']) : '',
$item['end_time'] ? date('Y-m-d H:i:s', $item['end_time']) : '',
];
}
return compact('list', 'count');
}
/**
* 添加
* @param array $data
* @param int $id
* @return true
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 09:46
*/
public function saveData(array $data, int $id = 0, int $admin_id = 0)
{
if ($id && !$info = $this->dao->get($id)) {
throw new ValidateException('礼品卡不存在');
}
//验证卡密未使用的数量是否超过礼品卡填写的总量
if (!$id) {
$codeCount = app()->make(CardCodeServices::class)->count(['batch_id' => $data['batch_id'], 'status' => 0, 'card_id' => 0]);
if ($codeCount < $data['total_num']) {
throw new ValidateException('卡密未使用的数量不能超过礼品卡填写的总量');
}
}
if ($data['valid_type'] == 2) {
$data['start_time'] = isset($data['fixed_time'][0]) ? strtotime($data['fixed_time'][0]) : 0;
$data['end_time'] = isset($data['fixed_time'][1]) ? strtotime($data['fixed_time'][1]) : 0;
}
unset($data['fixed_time']);
$this->transaction(function () use ($id, $data, $admin_id) {
$product = $data['product'];
$description = $data['description'];
unset($data['product'], $data['description']);
$data['add_time'] = time();
if ($id) {
$this->dao->update($id, $data);
} else {
$res = $this->dao->save($data);
$id = $res->id;
//修改卡密
$this->saveCode($id, $data, $admin_id);
}
app()->make(StoreDescriptionServices::class)->saveDescription($id, $description, 5);
//保存礼品卡商品
$this->saveProduct($id, $product);
return true;
});
$batch_id = $id ? $info['batch_id'] : $data['batch_id'];
//校验卡密使用数量
CardJob::dispatchDo('allocationCode', [$batch_id]);
return true;
}
/**
* 修改卡密
* @param int $id
* @param array $data
* @return true
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 09:46
*/
public function saveCode(int $id, array $data, int $admin_id = 0)
{
$cardCodeServices = app()->make(CardCodeServices::class);
$list = $cardCodeServices->search(['batch_id' => $data['batch_id'], 'status' => 0, 'card_id' => 0])->order('id asc')->limit($data['total_num'])->select()->toArray();
if (!$list) {
throw new ValidateException('没有可操作的卡密');
}
$ids = [];
$updateData = [
'type' => $data['type'],
'end_time' => $data['end_time'] ?? 0,
'card_id' => $id,
'status' => 2,
];
$str = '';
$i = $list[0]['id'];
$status = true;
foreach ($list as $k => $item) {
if ($k == 0) {
$str .= $item['card_number'] . '--';
} elseif ($k == count($list) - 1) {
//获取列表最后一条数据
$str .= $item['card_number'];
} else {
if ($item['id'] != $i) {
if ($status) {
$str .= $item['card_number'] . ',';
$status = false;
} else {
$str .= $item['card_number'] . '--';
$status = true;
}
}
}
$i = $item['id'] + 1;
$ids[] = $item['id'];
// 每满100条插入一次并清空缓存
if (count($ids) >= 500) {
$cardCodeServices->batchUpdate($ids, $updateData);
$ids = [];
}
}
if (count($ids)) {
$cardCodeServices->batchUpdate($ids, $updateData);
}
app()->make(CardGiftRecordServices::class)->save([
'gift_id' => $id,
'record' => $str,
'num' => count($list),
'pm' => 1,
'add_time' => time(),
'admin_id' => $admin_id
]);
return true;
}
/**
* 新增商品
* @param int $id
* @param array $productData
* @return true
* User: liusl
* DateTime: 2025/5/12 09:45
*/
public function saveProduct(int $id, array $productData)
{
$auxiliary = app()->make(CardGiftAuxiliaryServices::class);
$auxiliary->delete(['gift_id' => $id]);
$saveData = [];
foreach ($productData as $product) {
$saveData[] = [
'gift_id' => $id,
'product_id' => $product['product_id'] ?? 0,
'limit_num' => $product['limit_num'] ?? 0,
'unique' => $product['unique'] ?? '',
];
}
if (count($saveData)) {
$auxiliary->saveAll($saveData);
}
return true;
}
/**
* 增加卡密表单
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 15:41
*/
public function otherForm(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('礼品卡不存在');
}
$info = $info->toArray();
$batchInfo = app()->make(CardBatchServices::class)->get($info['batch_id']);
$remain_num = $batchInfo['total_num'] - $batchInfo['used_num'];
$remain_num = max($remain_num, 0);
$batchName = $batchInfo['name'] . "({$remain_num})";
$field[] = Form::input('batch_name', '卡号批次', $batchName)->disabled(true);
$field[] = Form::radio('type', '修改数量', $formData['status'] ?? 1)->options([
['label' => '增加', 'value' => 1],
['label' => '减少', 'value' => 2],
]);
$field[] = Form::number('number', '数量', 0)->min(0);
return create_form('修改数量', $field, Url::buildUrl('/marketing/card/gift/update_other/' . $id), 'POST');
}
/**
* 增加卡密提交
* @param int $id
* @param int $type
* @param int $number
* @return true
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 15:42
*/
public function updateOther(int $id, int $type, int $number, int $admin_id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('礼品卡不存在');
}
if ($type == 1) {
$total_num = $info->total_num + $number;
//增加
$count = app()->make(CardCodeServices::class)->count(['batch_id' => $info['batch_id'], 'status' => 0, 'card_id' => 0]);
if ($count < $number) {
throw new ValidateException('卡密不足');
}
$this->saveCode($id, ['total_num' => $number, 'batch_id' => $info['batch_id'], 'type' => $info['type']], $admin_id);
} else {
$total_num = $info->total_num - $number;
//减少
$count = app()->make(CardCodeServices::class)->count(['batch_id' => $info['batch_id'], 'status' => 2, 'card_id' => $id]);
if ($count < $number) {
throw new ValidateException('未卡密不足,无法减少,未使用卡密:' . $count);
}
$cardCodeServices = app()->make(CardCodeServices::class);
$list = $cardCodeServices->search(['batch_id' => $info['batch_id'], 'status' => 2, 'card_id' => $id])->order('id asc')->limit($number)->select()->toArray();
if (!$list) {
throw new ValidateException('没有可操作的卡密');
}
$ids = [];
$updateData = [
'type' => 0,
'card_id' => 0,
'end_time' => 0,
'status' => 0,
];
$str = '';
$i = $list[0]['id'];
$status = true;
foreach ($list as $k => $item) {
//检查 id 是否间断, 如果有给$str追加
if ($k == 0) {
$str .= $item['card_number'] . '--';
} elseif ($k == count($list) - 1) {
//获取列表最后一条数据
$str .= $item['card_number'];
} else {
if ($item['id'] != $i) {
if ($status) {
$str .= $item['card_number'] . ',';
$status = false;
} else {
$str .= $item['card_number'] . '--';
$status = true;
}
}
}
$i = $item['id'] + 1;
$ids[] = $item['id'];
// 每满100条插入一次并清空缓存
if (count($ids) >= 500) {
$cardCodeServices->batchUpdate($ids, $updateData);
$ids = [];
}
}
if (count($ids)) {
$cardCodeServices->batchUpdate($ids, $updateData);
}
app()->make(CardGiftRecordServices::class)->save([
'gift_id' => $id,
'record' => $str,
'num' => count($list),
'pm' => 2,
'add_time' => time(),
'admin_id' => $admin_id
]);
}
$info->total_num = $total_num;
$info->save();
//校验卡密使用数量
CardJob::dispatchDo('allocationCode', [$info['batch_id']]);
return true;
}
/**
* 详情
* @param $id
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 15:42
*/
public function getInfo($id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('礼品卡不存在');
}
$info = $info->toArray();
$auxiliaryList = app()->make(CardGiftAuxiliaryServices::class)->search(['gift_id' => $id])->select()->toArray();
$info['product'] = [];
$info['description'] = app()->make(StoreDescriptionServices::class)->getDescription(['product_id' => $id, 'type' => 5]);
$info['fixed_time'] = [
$info['start_time'] ? date('Y-m-d H:i:s', $info['start_time']) : '',
$info['end_time'] ? date('Y-m-d H:i:s', $info['end_time']) : '',
];
if (!$auxiliaryList) {
return $info;
}
$productIds = array_unique(array_column($auxiliaryList, 'product_id'));
$uniques = array_unique(array_column($auxiliaryList, 'unique'));
$productList = app()->make(StoreProductServices::class)->search(['id' => $productIds])->column('store_name', 'id');
$uniqueList = app()->make(StoreProductAttrValueServices::class)->search(['unique' => $uniques])->column('unique,suk,stock,price,image', 'unique');
foreach ($auxiliaryList as &$item) {
$item['store_name'] = $productList[$item['product_id']] ?? '';
$item['suk'] = $uniqueList[$item['unique']]['suk'] ?? '';
$item['stock'] = $uniqueList[$item['unique']]['stock'] ?? '';
$item['price'] = $uniqueList[$item['unique']]['price'] ?? '';
$item['image'] = $uniqueList[$item['unique']]['image'] ?? '';
}
$info['product'] = $auxiliaryList;
return $info;
}
/**
* 删除
* @param $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/5/12 09:45
*/
public function delete($id)
{
//删除礼品卡,
//删除礼品卡商品
//删除未使用的礼品卡卡密
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('礼品卡不存在');
}
return $this->transaction(function () use ($id, $info) {
$res = $this->dao->update($id, ['is_del' => 1]);
$res = $res && app()->make(CardGiftAuxiliaryServices::class)->delete(['gift_id' => $id]);
$res = $res && app()->make(CardCodeServices::class)->update(['card_id' => $id, 'status' => 2], ['type' => 0, 'status' => 0, 'card_id' => 0]);
return !!$res;
});
}
/**
* 获取卡礼品树形结构列表
* @return array
*/
public function getTree()
{
return $this->search(['is_del' => 0])->field('id,name')->order('sort desc,add_time desc')->select()->toArray();
}
/**
* 使用数量矫正
* @return
* User: liusl
* DateTime: 2025/5/24 10:47
*/
public function allocationCardGift($id)
{
$info = $this->dao->get($id);
$used_num = app()->make(CardCodeServices::class)->search(['card_id' => $id, 'status' => 1])->count();
$info->used_num = $used_num;
$info->save();
return true;
}
}

View File

@@ -0,0 +1,703 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\combination;
use app\services\BaseServices;
use app\dao\activity\combination\StoreCombinationDao;
use app\services\diy\DiyServices;
use app\services\order\StoreOrderServices;
use app\services\product\ensure\StoreProductEnsureServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\user\UserRelationServices;
use app\services\product\product\StoreProductReplyServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\jobs\product\ProductLogJob;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\services\SystemConfigService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 拼团商品
* Class StoreCombinationServices
* @package app\services\activity\combination
* @mixin StoreCombinationDao
*/
class StoreCombinationServices extends BaseServices
{
const THODLCEG = 'ykGUKB';
/**
* 商品活动类型
*/
const TYPE = 2;
/**
* @var StoreCombinationDao
*/
#[Inject]
protected StoreCombinationDao $dao;
/**
* 获取指定条件下的条数
* @param array $where
*/
public function getCount(array $where)
{
$this->dao->count($where);
}
/**
* @param array $productIds
* @return mixed
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/28
*/
public function getPinkIdsArrayCache(array $productIds)
{
return $this->dao->cacheTag()->remember(md5('pink_ids_' . json_encode($productIds)), function () use ($productIds) {
return $this->dao->getPinkIdsArray($productIds, ['id']);
});
}
/**
* 获取是否有拼团商品
* */
public function validCombination()
{
return $this->dao->count([
'is_del' => 0,
'is_show' => 1,
'pinkIngTime' => true
]);
}
/**
* 拼团商品添加
* @param int $id
* @param array $data
*/
public function saveData(int $id, array $data)
{
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已移入回收站');
}
if ($productInfo['is_vip_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是svip专享");
}
if ($productInfo['is_presale_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是预售商品");
}
$data['product_type'] = $productInfo['product_type'];
$data['type'] = $productInfo['type'] ?? 0;
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$custom_form = $productInfo['custom_form'] ?? [];
$data['custom_form'] = is_array($custom_form) ? json_encode($custom_form) : $custom_form;
$data['system_form_id'] = $productInfo['system_form_id'] ?? 0;
$store_label_id = $productInfo['store_label_id'] ?? [];
$data['store_label_id'] = is_array($store_label_id) ? implode(',', $store_label_id) : $store_label_id;
$ensure_id = $productInfo['ensure_id'] ?? [];
$data['ensure_id'] = is_array($ensure_id) ? implode(',', $ensure_id) : $ensure_id;
$specs = $productInfo['specs'] ?? [];
$data['specs'] = is_array($specs) ? json_encode($specs) : $specs;
if ($data['product_type'] !== 0) {
$data['delivery_type'] = 2;
}
if (in_array($data['product_type'], [1, 2, 3])) {
$data['freight'] = 2;
$data['temp_id'] = 0;
$data['postage'] = 0;
} else {
if ($data['freight'] == 1) {
$data['temp_id'] = 0;
$data['postage'] = 0;
} elseif ($data['freight'] == 2) {
$data['temp_id'] = 0;
} elseif ($data['freight'] == 3) {
$data['postage'] = 0;
}
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
}
$description = $data['description'];
$detail = $data['attrs'];
$items = $data['items'];
$data['start_time'] = strtotime($data['section_time'][0]);
$data['stop_time'] = strtotime($data['section_time'][1]);
if ($data['stop_time'] < strtotime(date('Y-m-d', time()))) throw new AdminException('结束时间不能小于今天');
$data['image'] = $data['images'][0] ?? '';
$data['images'] = json_encode($data['images']);
$data['price'] = min(array_column($detail, 'price'));
$data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
if ($data['quota'] > $productInfo['stock']) {
throw new ValidateException('限量不能超过商品库存');
}
$data['stock'] = array_sum(array_column($detail, 'stock'));
unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$this->transaction(function () use ($id, $data, $description, $detail, $items, $storeDescriptionServices, $storeProductAttrServices) {
if ($id) {
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$storeDescriptionServices->saveDescription((int)$id, $description, 3);
$storeProductAttrServices->setItem('store_product_id', $data['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($items, $detail, (int)$id, 3);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$id, 3);
$res = true;
foreach ($valueGroup as $item) {
$res = $res && CacheService::setStock($item['unique'], (int)$item['quota_show'], 3);
}
if (!$res) {
throw new AdminException('占用库存失败');
}
});
$this->dao->cacheTag()->clear();
}
/**
* 拼团列表
* @param array $where
* @return array
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
/** @var StorePinkServices $storePinkServices */
$storePinkServices = app()->make(StorePinkServices::class);
$countAll = $storePinkServices->getPinkCount([]);
$countTeam = $storePinkServices->getPinkCount(['k_id' => 0, 'status' => 2]);
$countPeople = $storePinkServices->getPinkCount(['k_id' => 0]);
$ids = [];
foreach ($list as &$item) {
$item['count_people'] = $countPeople[$item['id']] ?? 0;//拼团数量
$item['count_people_all'] = $countAll[$item['id']] ?? 0;//参与人数
$item['count_people_pink'] = $countTeam[$item['id']] ?? 0;//成团数量
$item['stop_status'] = $item['stop_time'] < time() ? 1 : 0;
if ($item['is_show']) {
if ($item['start_time'] > time()) {
$item['start_name'] = '未开始';
} else if ($item['stop_time'] < time()) {
$item['start_name'] = '已结束';
$item['is_show'] = 0;
$ids[] = $item['id'];
} else if ($item['stop_time'] > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
}
} else {
$item['start_name'] = '已结束';
}
}
foreach ($ids as $id) {
$this->dao->update($id, ['is_show' => 0]);
app()->make(StorePinkServices::class)->update(['cid' => $id, 'status' => 1], ['stop_time' => time()]);
}
return compact('list', 'count');
}
/**
* 获取详情
* @param int $id
* @return array|\think\Model|null
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('查看的商品不存在!');
}
if ($info->is_del) {
throw new ValidateException('您查看的团团商品已被删除!');
}
if ($info['start_time'])
$start_time = date('Y-m-d H:i:s', $info['start_time']);
if ($info['stop_time'])
$stop_time = date('Y-m-d H:i:s', $info['stop_time']);
if (isset($start_time) && isset($stop_time))
$info['section_time'] = [$start_time, $stop_time];
else
$info['section_time'] = [];
unset($info['start_time'], $info['stop_time']);
$info['price'] = floatval($info['price']);
$info['postage'] = floatval($info['postage']);
$info['weight'] = floatval($info['weight']);
$info['volume'] = floatval($info['volume']);
if (!$info['delivery_type']) {
$info['delivery_type'] = [1];
}
if ($info['postage']) {
$info['freight'] = 2;
} elseif ($info['temp_id']) {
$info['freight'] = 3;
} else {
$info['freight'] = 1;
}
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
$info['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 3]);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$info['attrs'] = $storeProductAttrValueServices->attrList($id, $info['product_id'], 3);
return $info;
}
/**
* 根据id获取拼团数据列表
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCombinationList()
{
[$page, $limit] = $this->getPageValue();
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$field = 'id,title,image,price,product_id,people,quota,quota_show,stock';
$list = $this->dao->combinationList(['is_del' => 0, 'is_show' => 1, 'pinkIngTime' => true, 'storeProductId' => true], $field, $page, $limit);
foreach ($list as &$item) {
$item['image'] = set_file_url($item['image']);
$item['price'] = floatval($item['price']);
$item['product_price'] = floatval($item['product_price']);
$item['pink_count'] = max((int)bcsub((string)$item['quota_show'], (string)$item['quota'], 0), 0);
$item['brand_name'] = $storeProductServices->productIdByBrandName((int)$item['product_id']);
}
return $list;
}
/**
* 拼团商品详情
* @param int $uid
* @param int $id
* @return array
* @throws \Throwable
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function combinationDetail(int $uid, int $id)
{
$storeInfo = $this->dao->cacheTag()->remember('' . $id, function () use ($id) {
$storeInfo = $this->dao->getOne(['id' => $id], '*,title as store_name', ['descriptions', 'total']);
if (!$storeInfo) {
throw new ValidateException('商品不存在');
} else {
$storeInfo = $storeInfo->toArray();
}
return $storeInfo;
}, 600);
/** @var DiyServices $diyServices */
$diyServices = app()->make(DiyServices::class);
$infoDiy = $diyServices->getProductDetailDiy();
//diy控制参数
if (!isset($infoDiy['showService']) || in_array(3, $infoDiy['showService'])) {
$storeInfo['specs'] = [];
}
$configData = SystemConfigService::more(['site_url', 'routine_contact_type', 'site_name', 'share_qrcode', 'store_self_mention', 'store_func_status', 'product_poster_title']);
$siteUrl = $configData['site_url'] ?? '';
$storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['sale_stock'] = 0;
if ($storeInfo['stock'] > 0) $storeInfo['sale_stock'] = 1;
//品牌名称
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getCacheProductInfo((int)$storeInfo['product_id']);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id'], $productInfo);
$delivery_type = $storeInfo['delivery_type'] ?? $productInfo['delivery_type'];
$storeInfo['delivery_type'] = is_string($delivery_type) ? explode(',', $delivery_type) : $delivery_type;
$storeInfo['product_show'] = $productInfo['is_show']; // 商品是否上架
/**
* 判断配送方式
*/
$storeInfo['delivery_type'] = $storeProductServices->getDeliveryType($storeInfo['type'], $storeInfo['relation_id'], $storeInfo['delivery_type']);
$storeInfo['store_label'] = $storeInfo['ensure'] = [];
if ($storeInfo['store_label_id']) {
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
if ($storeInfo['ensure_id'] && isset($infoDiy['showService']) && in_array(2, $infoDiy['showService'])) {
/** @var StoreProductEnsureServices $storeProductEnsureServices */
$storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
$storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
}
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$storeInfo['userCollect'] = $userRelationServices->isProductRelationCache(['uid' => $uid, 'relation_id' => $storeInfo['product_id'], 'type' => 'collect', 'category' => UserRelationServices::CATEGORY_PRODUCT]);
$storeInfo['userLike'] = 0;
$storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
$data['storeInfo'] = $storeInfo;
/** @var StorePinkServices $pinkService */
$pinkService = app()->make(StorePinkServices::class);
[$pink, $pindAll] = $pinkService->getPinkList($id, true, 1);//拼团进行中列表
$data['pink_ok_list'] = $pinkService->getPinkOkList($uid);
$data['pink_ok_sum'] = $pinkService->getPinkOkSumTotalNum();
$data['pink'] = $pink;
$data['pindAll'] = $pindAll;
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$data['buy_num'] = $storeOrderServices->getBuyCount($uid, 3, $id);
$data['reply'] = [];
$data['replyChance'] = $data['replyCount'] = 0;
if (isset($infoDiy['showReply']) && $infoDiy['showReply']) {
/** @var StoreProductReplyServices $storeProductReplyService */
$storeProductReplyService = app()->make(StoreProductReplyServices::class);
$reply = $storeProductReplyService->getRecProductReplyCache($storeInfo['product_id'], (int)($infoDiy['replyNum'] ?? 1));
$data['reply'] = $reply ? get_thumb_water($reply, 'small', ['pics']) : [];
[$replyCount, $goodReply, $replyChance] = $storeProductReplyService->getProductReplyData((int)$storeInfo['product_id']);
$data['replyChance'] = $replyChance;
$data['replyCount'] = $replyCount;
}
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetailCache($id, $uid, 0, 3, $storeInfo['product_id'], $productInfo);
$data['productAttr'] = $productAttr;
$data['productValue'] = $productValue;
$data['routine_contact_type'] = sys_config('routine_contact_type', 0);
$data['store_func_status'] = (int)($configData['store_func_status'] ?? 1);//门店是否开启
$data['store_self_mention'] = $data['store_func_status'] ? (int)($configData['store_self_mention'] ?? 0) : 0;//门店自提是否开启
$data['site_name'] = sys_config('site_name');
$data['share_qrcode'] = sys_config('share_qrcode', 0);
$data['product_poster_title'] = $configData['product_poster_title'] ?? '';
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'combination']);
return $data;
}
/**
* 修改销量和库存
* @param int $num
* @param int $CombinationId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decCombinationStock(int $num, int $CombinationId, string $unique, int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $CombinationId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去拼团商品的sku库存增加销量
$res = false !== $skuValueServices->decProductAttrStock($CombinationId, $unique, $num, 3);
//拼团商品sku
$sku = $skuValueServices->value(['product_id' => $CombinationId, 'unique' => $unique, 'type' => 3], 'suk');
//平台普通商品sku unique
$productUnique = $skuValueServices->value(['suk' => $sku, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//商品库存
$res = $res && $services->decProductStock($num, $product_id, (string)$productUnique);
}
$res = $res && false !== $this->dao->decStockIncSales(['id' => $CombinationId, 'type' => 3], $num);
}
return $res;
}
/**
* 加库存减销量
* @param int $num
* @param int $CombinationId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incCombinationStock(int $num, int $CombinationId, string $unique, int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $CombinationId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//增加拼团商品的sku库存,减去销量
$res = false !== $skuValueServices->incProductAttrStock($CombinationId, $unique, $num, 3);
//拼团商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $CombinationId, 'type' => 3], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//增加普通商品库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
//增加拼团库存
$res = $res && false !== $this->dao->incStockDecSales(['id' => $CombinationId, 'type' => 3], $num);
}
return $res;
}
/**
* 获取一条拼团数据
* @param $id
* @return mixed
*/
public function getCombinationOne($id, $field = '*')
{
return $this->dao->validProduct($id, $field);
}
/**
* 获取拼团详情
* @param int $uid
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getPinkInfo(int $uid, int $id)
{
$is_ok = 0;//判断拼团是否完成
$userBool = 0;//判断当前用户是否在团内 0未在 1在
$pinkBool = 0;//判断拼团是否成功 0未在 1在
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$user = $userServices->getUserCacheInfo($uid);
if (!$user) {
throw new ValidateException('用户不存在');
}
/** @var StorePinkServices $pinkService */
$pinkService = app()->make(StorePinkServices::class);
$pink = $pinkService->getPinkUserOne($id);
if (!$pink) throw new ValidateException('参数错误');
$pink = $pink->toArray();
if (isset($pink['is_refund']) && $pink['is_refund']) {
if ($pink['is_refund'] != $pink['id']) {
$id = $pink['is_refund'];
return $this->getPinkInfo($uid, $id);
} else {
throw new ValidateException('订单已退款');
}
}
[$pinkAll, $pinkT, $count, $idAll, $uidAll] = $pinkService->getPinkMemberAndPinkK($pink);
if ($pinkT['status'] == 2) {
$pinkBool = 1;
$is_ok = 1;
} else if ($pinkT['status'] == 3) {
$pinkBool = -1;
$is_ok = 0;
} else {
if ($count < 1) {//组团完成
$is_ok = 1;
$pinkBool = $pinkService->pinkComplete($uidAll, $idAll, $user['uid'], $pinkT);
} else {
$pinkBool = $pinkService->pinkFail($pinkAll, $pinkT, $pinkBool);
}
//更新pinkT 数据 可能成功或失败
$pinkT = $pinkService->getPinkUserOne($pinkT['id']);
}
if (!empty($pinkAll)) {
foreach ($pinkAll as $v) {
if ($v['uid'] == $user['uid']) $userBool = 1;
}
}
if ($pinkT['uid'] == $user['uid']) $userBool = 1;
$combinationOne = $this->getCombinationOne($pink['cid']);
if (!$combinationOne) {
throw new ValidateException('拼团不存在或已下架,请手动申请退款!');
}
$combinationOne = $combinationOne->hidden(['mer_id', 'images', 'attr', 'info', 'sort', 'sales', 'stock', 'add_time', 'is_host', 'is_show', 'is_del', 'combination', 'mer_use', 'is_postage', 'postage', 'start_time', 'stop_time', 'cost', 'browse'])->toArray();
$combinationOne['pink_count'] = max((int)bcsub((string)$combinationOne['quota_show'], (string)$combinationOne['quota'], 0), 0);
$data['userInfo']['uid'] = $user['uid'];
$data['userInfo']['nickname'] = $user['nickname'];
$data['userInfo']['avatar'] = $user['avatar'];
$data['is_ok'] = $is_ok;
$data['userBool'] = $userBool;
$data['pinkBool'] = $pinkBool;
$delivery_type = $combinationOne['delivery_type'] ?? [];
$combinationOne['delivery_type'] = is_string($delivery_type) ? explode(',', $delivery_type) : $delivery_type;
$data['store_combination'] = $combinationOne;
$data['pinkT'] = $pinkT;
$data['pinkAll'] = $pinkAll;
$data['count'] = $count <= 0 ? 0 : $count;
$data['store_combination_host'] = $this->dao->getCombinationHost();
foreach ($data['store_combination_host'] as &$item) {
$item['pink_count'] = max((int)bcsub((string)$item['quota_show'], (string)$item['quota'], 0), 0);
}
$data['current_pink_order'] = $pinkService->getCurrentPink($id, $user['uid']);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetail($combinationOne['id'], $user['uid'], 0, 3, $combinationOne['product_id']);
foreach ($productValue as $k => $v) {
$productValue[$k]['product_stock'] = $storeProductAttrValueServices->value(['product_id' => $combinationOne['product_id'], 'suk' => $v['suk'], 'type' => 0], 'stock');
}
$data['store_combination']['productAttr'] = $productAttr;
$data['store_combination']['productValue'] = $productValue;
$data['store_func_status'] = (int)(sys_config('store_func_status', 1));
$data['store_self_mention'] = $data['store_func_status'] ? (int)(sys_config('store_self_mention', 0)) : 0;//门店自提是否开启
return $data;
}
/**
* 验证拼团下单库存限量
* @param int $uid
* @param int $combinationId
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkCombinationStock(int $uid, int $combinationId, int $cartNum = 1, string $unique = '')
{
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
if ($unique == '') {
$unique = $attrValueServices->value(['product_id' => $combinationId, 'type' => 3], 'unique');
}
$attrInfo = $attrValueServices->getOne(['product_id' => $combinationId, 'unique' => $unique, 'type' => 3]);
if (!$attrInfo || $attrInfo['product_id'] != $combinationId) {
throw new ValidateException('请选择有效的商品属性');
}
$StoreCombinationInfo = $productInfo = $this->getCombinationOne($combinationId, '*,title as store_name');
if (!$StoreCombinationInfo) {
throw new ValidateException('该商品已下架或删除');
}
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$userBuyCount = $orderServices->getBuyCount($uid, 3, $combinationId);
if ($StoreCombinationInfo['once_num'] < $cartNum) {
throw new ValidateException('每个订单限购' . $StoreCombinationInfo['once_num'] . '件');
}
if ($StoreCombinationInfo['num'] < ($userBuyCount + $cartNum)) {
throw new ValidateException('每人总共限购' . $StoreCombinationInfo['num'] . '件');
}
if ($cartNum > $attrInfo['quota']) {
throw new ValidateException('该商品库存不足' . $cartNum);
}
return [$attrInfo, $unique, $productInfo];
}
/**
* 拼团统计
* @param $id
* @return array
*/
public function combinationStatistics(int $id)
{
/** @var StorePinkServices $pinkServices */
$pinkServices = app()->make(StorePinkServices::class);
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$people_count = $pinkServices->getDistinctCount([['cid', '=', $id]], 'uid', false);
$spread_count = $pinkServices->getDistinctCount([['cid', '=', $id], ['k_id', '>', 0]], 'uid', false);
$start_count = $pinkServices->count(['cid' => $id, 'k_id' => 0]);
$success_count = $pinkServices->count(['cid' => $id, 'k_id' => 0, 'status' => 2]);
$pay_price = $orderServices->sum(['type' => 3, 'activity_id' => $id, 'paid' => 1, 'pid' => [0, -1]], 'pay_price', true);
$pay_count = $orderServices->getDistinctCount(['type' => 3, 'activity_id' => $id, 'paid' => 1, 'pid' => [0, -1]], 'uid');
return compact('people_count', 'spread_count', 'start_count', 'success_count', 'pay_price', 'pay_count');
}
/**
* 拼团订单
* @param $id
* @param array $where
* @return array
*/
public function combinationStatisticsOrder(int $id, array $where = [])
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->activityStatisticsOrder($id, 3, $where, $page, $limit);
$where['type'] = 3;
$where['activity_id'] = $id;
$count = $orderServices->count($where);
foreach ($list as &$item) {
if ($item['is_del'] || $item['is_system_del']) {
$item['status'] = '已删除';
} else if ($item['paid'] == 0 && $item['status'] == 0) {
$item['status'] = '待付款';
} else if ($item['paid'] == 1 && $item['status'] == 4 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '部分发货';
} else if ($item['paid'] == 1 && $item['refund_status'] == 2) {
$item['status'] = '已退款';
} else if ($item['paid'] == 1 && $item['status'] == 5 && $item['refund_status'] == 0) {
$item['status'] = $item['shipping_type'] == 2 ? '部分核销' : '部分收货';
$item['_status'] = 12;//已支付 部分核销
} else if ($item['paid'] == 1 && $item['refund_status'] == 1) {
$item['status'] = '申请退款';
} else if ($item['paid'] == 1 && $item['refund_status'] == 4) {
$item['status'] = '退款中';
} else if ($item['paid'] == 1 && $item['status'] == 0 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '未发货';
$item['_status'] = 2;//已支付 未发货
} else if ($item['paid'] == 1 && in_array($item['status'], [0, 1]) && $item['shipping_type'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '未核销';
} else if ($item['paid'] == 1 && in_array($item['status'], [1, 5]) && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '待收货';
} else if ($item['paid'] == 1 && $item['status'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '待评价';
} else if ($item['paid'] == 1 && $item['status'] == 3 && $item['refund_status'] == 0) {
$item['status'] = '已完成';
} else if ($item['paid'] == 1 && $item['refund_status'] == 3) {
$item['status'] = '部分退款';
} else {
$item['status'] = '未知';
}
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
$item['pay_time'] = $item['pay_time'] ? date('Y-m-d H:i:s', $item['pay_time']) : '';
}
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,806 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\combination;
use app\jobs\activity\pink\AuthPinkFail;
use app\services\BaseServices;
use app\dao\activity\combination\StorePinkDao;
use app\services\order\StoreOrderRefundServices;
use app\services\order\StoreOrderServices;
use app\services\other\QrcodeServices;
use app\services\user\UserServices;
use app\jobs\activity\pink\PinkJob;
use app\jobs\notice\template\RoutineTemplateJob;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 拼团
* Class StorePinkServices
* @package app\services\activity\combination
* @mixin StorePinkDao
*/
class StorePinkServices extends BaseServices
{
const OXPJOHRW = '/G6B.Z';
/**
* @var StorePinkDao
*/
#[Inject]
protected StorePinkDao $dao;
/**
* @param array $where
* @return array
*/
public function systemPage(array $where)
{
$where['k_id'] = 0;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$time = time();
foreach ($list as &$item) {
$item['count_people'] = $this->dao->count(['k_id' => $item['id']]) + 1;
//状态为进行中,实际已经结束的
if ($item['status'] == 1 && $item['stop_time'] < $time) {
$item['status'] = $item['count_people'] >= $item['people'] ? 2 : 3;
}
$item['_add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', (int)$item['add_time']) : '';
$item['_stop_time'] = $item['stop_time'] ? date('Y-m-d H:i:s', (int)$item['stop_time']) : '';
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 拼团列表头部
* @return array
*/
public function getStatistics()
{
$res = [
['col' => 6, 'count' => $this->dao->count(), 'name' => '参与人数(人)', 'className' => 'ios-speedometer-outline'],
['col' => 6, 'count' => $this->dao->count(['k_id' => 0, 'status' => 2]), 'name' => '成团数量(个)', 'className' => 'md-rose'],
];
return compact('res');
}
/**
* 参团人员
* @param int $id
* @return array
*/
public function getPinkMember(int $id)
{
return $this->dao->getList(['k_id' => $id, 'is_refund' => 0]);
}
/**
* 拼团退款
* @param $id
* @return bool
*/
public function setRefundPink($order)
{
$res = true;
if ($order['pink_id']) {
$id = $order['pink_id'];
} else {
return true;
}
//正在拼团 团长
$count = $this->dao->getOne(['id' => $id, 'uid' => $order['uid']]);
//正在拼团 团员
$countY = $this->dao->getOne(['k_id' => $id, 'uid' => $order['uid']]);
if (!$count && !$countY) {
return $res;
}
if ($count) {//团长
//判断团内是否还有其他人 如果有 团长为第二个进团的人
$kCount = $this->dao->getPinking(['k_id' => $id]);
if ($kCount) {
$res11 = $this->dao->update($id, ['k_id' => $kCount['id']], 'k_id');
$res12 = $this->dao->update($kCount['id'], ['stop_time' => $count['add_time'] + 86400]);
$res1 = $res11 && $res12;
$res2 = $this->dao->update($id, ['stop_time' => time() - 1, 'is_refund' => $kCount['id'], 'status' => 3]);
} else {
$res1 = true;
$res2 = $this->dao->update($id, ['stop_time' => time() - 1, 'is_refund' => $id, 'status' => 3]);
}
//修改结束时间为前一秒
$res = $res1 && $res2;
} else if ($countY) {//团员
$res = $this->dao->update($countY['id'], ['stop_time' => time() - 1, 'is_refund' => $id, 'status' => 3]);
}
if ($res) {
CacheService::setStock(md5((string)$id), 1, 3, false);
}
return $res;
}
/**
* 拼团详情查看拼团列表
* @param int $id
* @param bool $type
* @param int $status
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getPinkList(int $id, bool $type, int $status = -1)
{
$where['cid'] = $id;
$where['k_id'] = 0;
$where['is_refund'] = 0;
if ($status !== -1) {
$where['status'] = $status;
}
$list = $this->dao->pinkList($where);
$ids = array_column($list, 'id');
$counts = $this->dao->getPinkPeopleCount($ids);
if ($type) {
$pinkAll = [];
foreach ($list as &$v) {
$v['count'] = $v['people'] - $counts[$v['id']];
$v['h'] = date('H', (int)$v['stop_time']);
$v['i'] = date('i', (int)$v['stop_time']);
$v['s'] = date('s', (int)$v['stop_time']);
$pinkAll[] = $v['id'];//开团团长ID
$v['stop_time'] = (int)$v['stop_time'];
}
return [$list, $pinkAll];
}
return $list;
}
/**
* 获取成团列表信息
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getPinkOkList(int $uid)
{
$list = $this->dao->successList($uid, 'id,nickname', 0, 100);
$msg = [];
foreach ($list as &$item) {
if (isset($item['nickname'])) $msg[] = $item['nickname'] .= '拼团成功';
}
return $msg;
}
/**
* 查找拼团信息
* @param $pink
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getPinkMemberAndPinkK($pink)
{
//查找拼团团员和团长
if ($pink['k_id']) {
$pinkAll = $this->dao->getPinkUserList(['k_id' => $pink['k_id'], 'is_refund' => 0]);
$pinkT = $this->dao->getPinkUserOne($pink['k_id']);
} else {
$pinkAll = $this->dao->getPinkUserList(['k_id' => $pink['id'], 'is_refund' => 0]);
$pinkT = $pink;
}
$count = count($pinkAll) + 1;
$count = $pinkT['people'] - $count;
$idAll = [];
$uidAll = [];
//收集拼团用户id和拼团id
foreach ($pinkAll as $k => $v) {
$idAll[$k] = $v['id'];
$uidAll[$k] = $v['uid'];
}
$idAll[] = $pinkT['id'];
$uidAll[] = $pinkT['uid'];
return [$pinkAll, $pinkT, $count, $idAll, $uidAll];
}
/**
* 拼团失败
* @param $pinkAll
* @param $pinkT
* @param $pinkBool
* @param bool $isRunErr
* @param bool $isIds
* @return array|int
*/
public function pinkFail($pinkAll, $pinkT, $pinkBool, $isRunErr = true, $isIds = false)
{
/** @var StoreOrderServices $orderService */
$orderService = app()->make(StoreOrderServices::class);
/** @var StoreOrderRefundServices $orderRefundService */
$orderRefundService = app()->make(StoreOrderRefundServices::class);
$pinkIds = [];
try {
if ($pinkT['stop_time'] < time()) {//拼团时间超时 退款
$virtual = $this->virtualCombination($pinkT['id']);
if ($virtual) return 1;
$pinkBool = -1;
$pinkAll[] = $pinkT;
$oids = array_column($pinkAll, 'order_id_key');
$orders = $orderService->getColumn([['id', 'in', $oids]], '*', 'id');
$refundData = [
'refund_reason' => '拼团时间超时',
'refund_explain' => '拼团时间超时',
'refund_img' => json_encode([]),
];
$refundeOrder = $orderRefundService->getColumn([
['store_order_id', 'IN', $oids],
['refund_type', 'in', [0, 1, 2, 4, 5]],
['is_cancel', '=', 0],
['is_del', '=', 0]
], 'id,store_order_id', 'store_order_id');
foreach ($pinkAll as $v) {
if (isset($orders[$v['order_id_key']]) && $order = $orders[$v['order_id_key']]) {
if (in_array($v['order_id_key'], $refundeOrder)) {
continue;
}
$res1 = $res2 = true;
try {
//申请退款
$refundId = $orderRefundService->applyRefund((int)$order['id'], (int)$order['uid'], $order, [], 1, (float)$order['pay_price'], $refundData);
if ($refundId) {//申请退款成功,自动退款
$refund_data = ['pay_price' => $order['pay_price'], 'refund_price' => $order['pay_price']];
$res1 = $orderRefundService->agreeRefund((int)$refundId, $refund_data);
//修改退款单状态
$res1 = $res1 && $orderRefundService->update((int)$refundId, ['refund_type' => 6, 'refunded_price' => $order['pay_price'], 'refunded_time' => time()]);
//修改订单状态
$res1 = $res1 && $orderService->update((int)$order['id'], ['refund_status' => 2, 'refund_type' => 6]);
}
} catch (\Throwable $e) {
}
$res2 = $this->dao->getCount([['uid', '=', $v['uid']], ['is_tpl', '=', 0], ['k_id|id', '=', $pinkT['id']]]);
if ($res1 && $res2) {
if ($isIds) $pinkIds[] = $v['id'];
$this->orderPinkAfterNo($pinkT['uid'], $pinkT['id'], false, $orders[$v['order_id_key']]['is_channel']);
} else {
if ($isRunErr) return $pinkBool;
}
}
}
}
if ($isIds) return $pinkIds;
} catch (\Exception $e) {
\think\facade\Log::error('拼团超时处理失败,原因:' . $e->getMessage());
}
return $pinkBool;
}
/**
* 失败发送消息和修改状态
* @param $uid
* @param $pid
* @param bool $isRemove
* @param $channel
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function orderPinkAfterNo($uid, $pid, $isRemove = false, $channel = '')
{
$this->dao->update([['id|k_id', '=', $pid]], ['status' => 3, 'stop_time' => time()]);
$pink = $this->dao->getOne([['id|k_id', '=', $pid], ['uid', '=', $uid]], '*', ['getProduct']);
if ($isRemove) {
event('notice.notice', [['uid' => $uid, 'pink' => $pink, 'user_type' => $channel], 'send_order_pink_clone']);
} else {
event('notice.notice', [['uid' => $uid, 'pink' => $pink, 'user_type' => $channel], 'send_order_pink_fial']);
}
}
/**
* 判断拼团状态
* @param $pinkId
* @return bool
*/
public function isPinkStatus($pinkId)
{
if (!$pinkId) return false;
$stopTime = $this->dao->value(['id' => $pinkId], 'stop_time');
if ($stopTime < time()) return true; //拼团结束
else return false;//拼团未结束
}
/**
* 获取拼团order_id
* @param int $id
* @param int $uid
* @return mixed
*/
public function getCurrentPink(int $id, int $uid)
{
$oid = $this->dao->value(['id' => $id, 'uid' => $uid], 'order_id_key');
if (!$oid) $oid = $this->dao->value(['k_id' => $id, 'uid' => $uid], 'order_id_key');
/** @var StoreOrderServices $orderService */
$orderService = app()->make(StoreOrderServices::class);
return $orderService->value(['id' => $oid], 'order_id');
}
/**
* 拼团成功
* @param $uidAll
* @param $idAll
* @param $uid
* @param $pinkT
* @return int
*/
public function pinkComplete($uidAll, $idAll, $uid, $pinkT)
{
$pinkBool = 6;
try {
if (!$this->dao->getCount([['id', 'in', $idAll], ['is_refund', '=', 1]])) {
$this->dao->update([['id', 'in', $idAll]], ['stop_time' => time(), 'status' => 2]);
if (in_array($uid, $uidAll)) {
if ($this->dao->getCount([['uid', 'in', $uidAll], ['is_tpl', '=', 0], ['k_id|id', '=', $pinkT['id']]]))
$this->orderPinkAfter($uidAll, $pinkT['id']);
$pinkBool = 1;
} else $pinkBool = 3;
}
return $pinkBool;
} catch (\Exception $e) {
return $pinkBool;
}
}
/**
* 拼团成功修改
* @param $uidAll
* @param $pid
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function orderPinkAfter($uidAll, $pid)
{
//发送消息之前去除虚拟用户
foreach ($uidAll as $key => $uid) {
if ($uid == 0) unset($uidAll[$key]);
}
/** @var StoreCombinationServices $storeCombinationServices */
$storeCombinationServices = app()->make(StoreCombinationServices::class);
$pinkInfo = $this->dao->get((int)$pid, ['cid', 'nickname', 'uid']);
$title = $storeCombinationServices->value(['id' => $pinkInfo['cid']], 'title');
$pinkList = $this->dao->getColumn([['id|k_id', '=', $pid], ['uid', '<>', 0]], '*', 'uid');
$order_ids = array_column($pinkList, 'order_id');
/** @var StoreOrderServices $orderService */
$orderService = app()->make(StoreOrderServices::class);
$order_channels = $orderService->getColumn([['order_id', 'in', $order_ids]], 'is_channel', 'order_id');
if (!$pinkList) return false;
foreach ($pinkList as $item) {
//用户发送消息
event('notice.notice', [
[
'list' => $item,
'nickname' => $pinkInfo['nickname'],
'title' => $title,
'user_type' => $order_channels[$item['order_id']],
'url' => '/pages/users/order_details/index?order_id=' . $item['order_id']
], 'order_user_groups_success']);
}
$this->dao->update([['uid', 'in', $uidAll], ['id|k_id', '=', $pid]], ['is_tpl' => 1]);
}
/**
* 创建拼团
* @param $order
* @return mixed
*/
public function createPink(array $orderInfo)
{
/** @var StoreCombinationServices $services */
$services = app()->make(StoreCombinationServices::class);
$product = $services->getOne(['id' => $orderInfo['activity_id']], 'effective_time,title,people');
if (!$product) {
return false;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->get($orderInfo['uid']);
if ($orderInfo['pink_id']) {
//拼团存在
$res = false;
$pink['uid'] = $orderInfo['uid'];//用户id
$pink['nickname'] = $userInfo['nickname'];
$pink['avatar'] = $userInfo['avatar'];
if ($this->isPinkBe($pink, $orderInfo['pink_id'])) return false;
$pink['order_id'] = $orderInfo['order_id'];//订单id 生成
$pink['order_id_key'] = $orderInfo['id'];//订单id 数据库id
$pink['total_num'] = $orderInfo['total_num'];//购买个数
$pink['total_price'] = $orderInfo['pay_price'];//总金额
$pink['k_id'] = $orderInfo['pink_id'];//拼团id
foreach ($orderInfo['cartInfo'] as $v) {
$pink['cid'] = $v['activity_id'];//拼团商品id
$pink['pid'] = $v['product_id'];//商品id
$pink['people'] = $product['people'];//几人拼团
$pink['price'] = $v['productInfo']['price'];//单价
$pink['stop_time'] = 0;//结束时间
$pink['add_time'] = time();//开团时间
$res = $this->save($pink);
}
// 拼团团成功发送模板消息
event('notice.notice', [['orderInfo' => $orderInfo, 'title' => $product['title'], 'pink' => $pink], 'can_pink_success']);
//处理拼团完成
[$pinkAll, $pinkT, $count, $idAll, $uidAll] = $this->getPinkMemberAndPinkK($pink);
if ($pinkT['status'] == 1) {
if (!$count)//组团完成
$this->pinkComplete($uidAll, $idAll, $pink['uid'], $pinkT);
else
$this->pinkFail($pinkAll, $pinkT, 0);
}
if ($res) return true;
else return false;
} else {
//创建拼团
$res = false;
$pink['uid'] = $orderInfo['uid'];//用户id
$pink['nickname'] = $userInfo['nickname'];
$pink['avatar'] = $userInfo['avatar'];
$pink['order_id'] = $orderInfo['order_id'];//订单id 生成
$pink['order_id_key'] = $orderInfo['id'];//订单id 数据库id
$pink['total_num'] = $orderInfo['total_num'];//购买个数
$pink['total_price'] = $orderInfo['pay_price'];//总金额
$pink['k_id'] = 0;//拼团id
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
foreach ($orderInfo['cartInfo'] as $v) {
$pink['cid'] = $v['activity_id'];//拼团商品id
$pink['pid'] = $v['product_id'];//商品id
$pink['people'] = $product['people'];//几人拼团
$pink['price'] = $v['productInfo']['price'];//单价
$pink['stop_time'] = time() + $product->effective_time * 3600;//结束时间
$pink['add_time'] = time();//开团时间
$res1 = $this->dao->save($pink);
$res2 = $orderServices->update($orderInfo['id'], ['pink_id' => $res1['id']]);
$res = $res1 && $res2;
$pink['id'] = $res1['id'];
}
$number = (int)bcsub((string)$product['people'], '1', 0);
if ($number) CacheService::setStock(md5((string)$pink['id']), $number, 3);
PinkJob::dispatchSece(($product->effective_time * 3600) + 60, [$pink['id']]);
event('notice.notice', [['orderInfo' => $orderInfo, 'title' => $product['title'], 'pink' => $pink], 'open_pink_success']);
if ($res) return true;
else return false;
}
}
/**
* 是否拼团
* @param array $data
* @param int $id
* @return int
*/
public function isPinkBe(array $data, int $id)
{
$data['id'] = $id;
$count = $this->dao->getCount($data);
if ($count) return $count;
$data['k_id'] = $id;
$count = $this->dao->getCount($data);
if ($count) return $count;
else return 0;
}
/**
* 参加拼团成功发送模板消息
* @param array $order
* @param string $openid
* @param string $title
* @param $pink
* @return mixed
*/
public function joinPinkSuccessSend(array $order, string $openid, string $title, $pink)
{
if ($order['is_channel'] == 1) {
/** @var UserServices $services */
$services = app()->make(UserServices::class);
$nickname = $services->value(['uid' => $order['uid']], 'nickname');
RoutineTemplateJob::dispatchDo('sendPinkSuccess', [$openid, $title, $nickname, $pink['add_time'], $pink['people'], '/pages/users/order_details/index?order_id=' . $pink['order_id']]);
}
return true;
}
/**
* 开团发送模板消息
* @param array $order
* @param string $openid
* @param string $title
* @param $pink
* @return mixed
*/
public function pinkSuccessSend(array $order, string $openid, string $title, $pink)
{
if ($order['is_channel'] == 1) {
/** @var UserServices $services */
$services = app()->make(UserServices::class);
$nickname = $services->value(['uid' => $order['uid']], 'nickname');
RoutineTemplateJob::dispatchDo('sendPinkSuccess', [$openid, $title, $nickname, $pink['add_time'], $pink['people'], '/pages/users/order_details/index?order_id=' . $pink['order_id']]);
}
return true;
}
/**
* 取消拼团
* @param int $uid
* @param int $cid
* @param int $pink_id
* @param null $nextPinkT
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function removePink(int $uid, int $cid, int $pink_id, $nextPinkT = null)
{
$pinkT = $this->dao->getOne([
['uid', '=', $uid],
['id', '=', $pink_id],
['cid', '=', $cid],
['k_id', '=', 0],
['is_refund', '=', 0],
['status', '=', 1],
['stop_time', '>', time()],
]);
if (!$pinkT) throw new ValidateException('未查到拼团信息,无法取消');
[$pinkAll, $pinkT, $count, $idAll, $uidAll] = $this->getPinkMemberAndPinkK($pinkT);
if (count($pinkAll)) {
$count = $pinkT['people'] - ($this->dao->count(['k_id' => $pink_id, 'is_refund' => 0]) + 1);
if ($count) {
//拼团未完成,拼团有成员取消开团取 紧跟团长后拼团的人
if (isset($pinkAll[0])) $nextPinkT = $pinkAll[0];
} else {
//拼团完成
$this->PinkComplete($uidAll, $idAll, $uid, $pinkT);
throw new ValidateException('拼团已完成,无法取消');
}
}
/** @var StoreOrderServices $orderService */
$orderService = app()->make(StoreOrderServices::class);
/** @var StoreOrderRefundServices $orderRefundService */
$orderRefundService = app()->make(StoreOrderRefundServices::class);
//取消开团
$order = $orderService->get($pinkT['order_id_key']);
$refundData = [
'refund_reason' => '用户手动取消拼团',
'refund_explain' => '用户手动取消拼团',
'refund_img' => json_encode([]),
];
try {
$res1 = $orderRefundService->applyRefund((int)$order['id'], (int)$order['uid'], $order, [], 1, (float)$order['pay_price'], $refundData);
} catch (\Throwable $e) {
$res1 = true;
}
$res2 = $this->dao->getCount([['uid', '=', $pinkT['uid']], ['k_id|id', '=', $pinkT['id']]]);
if ($res1 && $res2) {
$this->orderPinkAfterNo($pinkT['uid'], $pinkT['id'], true, $order->is_channel);
}
//当前团有人的时候
if (is_array($nextPinkT)) {
$this->dao->update($nextPinkT['id'], ['k_id' => 0, 'status' => 1, 'stop_time' => $pinkT['stop_time']]);
$this->dao->update($pinkT['id'], ['k_id' => $nextPinkT['id']], 'k_id');
$orderService->update($nextPinkT['order_id'], ['pink_id' => $nextPinkT['id']], 'order_id');
}
return true;
}
/**
* 修改到期的拼团状态
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function statusPink(int $page, int $limit)
{
$pinkListEnd = $this->dao->pinkListEnd()->page($page, $limit)->select()->toArray();
foreach ($pinkListEnd as $key => $pink) {
[$pinkAll, $pinkT, $count, $idAll, $uidAll] = $this->getPinkMemberAndPinkK($pink);
$this->pinkFail($pinkAll, $pinkT, 0);
}
return true;
}
/**
* 放入自动取消拼团队列
*/
public function useStatusPink()
{
$pinkEndCount = $this->dao->pinkListEnd()->count();
if (!$pinkEndCount) {
return;
}
$pages = ceil($pinkEndCount / 100);
for ($i = 1; $i <= $pages; $i++) {
AuthPinkFail::dispatch([$i, 100]);
}
}
/**
* 拼团成功
* @param array $pinkRegimental 成功的团长编号
* @return bool
* @throws \Exception
*/
public function successPinkEdit(array $pinkRegimental)
{
if (!count($pinkRegimental)) return true;
foreach ($pinkRegimental as $key => &$item) {
$pinkList = $this->dao->getColumn(['k_id' => $item], 'id', 'id');
$pinkList[] = $item;
$pinkList = implode(',', $pinkList);
$this->dao->update([['id', 'in', $pinkList]], ['stop_time' => time(), 'status' => 2]);
$pinkUidList = $this->dao->getColumn([['id', 'in', $pinkList], ['is_tpl', '=', 0]], 'uid', 'uid');
if (count($pinkUidList)) $this->orderPinkAfter($pinkUidList, $item);//发送模板消息
}
return true;
}
/**
* 拼团失败
* @param array $pinkRegimental 失败的团长编号
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function failPinkEdit(array $pinkRegimental)
{
if (!count($pinkRegimental)) return true;
foreach ($pinkRegimental as $key => &$item) {
$pinkList = $this->dao->getColumn(['k_id' => $item], 'id', 'id');
$pinkList[] = $item;
$pinkList = implode(',', $pinkList);
$refundPinkList = $this->dao->getColumn([['id', 'in', $pinkList]], 'order_id,uid', 'id');
if ($refundPinkList) {
/** @var StoreOrderRefundServices $orderRefundService */
$orderRefundService = app()->make(StoreOrderRefundServices::class);
$refundData = [
'refund_reason' => '拼团时间超时',
'refund_explain' => '拼团时间超时',
'refund_img' => json_encode([]),
];
foreach ($refundPinkList as $key => &$order) {
try {
$orderRefundService->applyRefund((int)$order['id'], (int)$order['uid'], $order, [], 1, (float)$order['pay_price'], $refundData);//申请退款
} catch (\Throwable $e) {
}
}
}
$this->dao->update([['id', 'in', $pinkList]], ['status' => 3]);
// $pinkUidList = $this->dao->getColumn([['id', 'in', $pinkList], ['is_tpl', '=', 0]], 'uid', 'uid');
}
return true;
}
/**
* 虚拟拼团
* @param $pinkId
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function virtualCombination($pinkId)
{
$pinkInfo = $this->dao->get($pinkId);
$people = $pinkInfo['people'];
$count = $this->dao->count(['k_id' => $pinkId]) + 1;
$percent1 = bcdiv((string)$count, (string)$people, 2) * 100;
/** @var StoreCombinationServices $services */
$services = app()->make(StoreCombinationServices::class);
$percent2 = $services->value(['id' => $pinkInfo['cid']], 'virtual');
if ($percent1 >= $percent2) {
$time = time();
$num = $people - $count;
$data = [];
mt_srand();
for ($i = 0; $i < $num; $i++) {
$data[$i]['uid'] = 0;
$data[$i]['nickname'] = substr(md5(time() . rand(1000, 9999)), 0, 12);
$data[$i]['avatar'] = sys_config('h5_avatar');
$data[$i]['order_id'] = 0;
$data[$i]['order_id_key'] = 0;
$data[$i]['total_num'] = 0;
$data[$i]['total_price'] = 0;
$data[$i]['cid'] = $pinkInfo['cid'];
$data[$i]['pid'] = $pinkInfo['pid'];
$data[$i]['people'] = $people;
$data[$i]['price'] = 0;
$data[$i]['add_time'] = $time;
$data[$i]['stop_time'] = $time;
$data[$i]['k_id'] = $pinkInfo['id'];
$data[$i]['is_tpl'] = 1;
$data[$i]['is_refund'] = 0;
$data[$i]['status'] = 2;
$data[$i]['is_virtual'] = 1;
}
//添加虚拟团员
$this->dao->saveAll($data);
//更改团员状态为拼团成功
$this->dao->update($pinkId, ['stop_time' => $time, 'status' => 2], 'k_id');
//更改团长为拼团成功
$this->dao->update($pinkId, ['stop_time' => $time, 'status' => 2]);
$uidAll = $this->dao->getColumn([['id|k_id', '=', $pinkId]], 'uid');
$this->orderPinkAfter($uidAll, $pinkId);
return true;
} else {
return false;
}
}
/**
* 获取拼团海报详情信息
* @param int $id
* @param $user
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function posterInfo(int $id, $user)
{
$pinkInfo = $this->dao->get($id);
/** @var StoreCombinationServices $combinationService */
$combinationService = app()->make(StoreCombinationServices::class);
$storeCombinationInfo = $combinationService->getOne(['id' => $pinkInfo['cid']], '*', ['getPrice']);
$data['title'] = $storeCombinationInfo['title'];
$data['url'] = '';
$data['image'] = $storeCombinationInfo['image'];
$data['price'] = $pinkInfo['price'];
$data['label'] = $pinkInfo['people'] . '人团';
if ($pinkInfo['k_id']) $pinkAll = $this->getPinkMember($pinkInfo['k_id']);
else $pinkAll = $this->getPinkMember($pinkInfo['id']);
$count = count($pinkAll);
$data['msg'] = '划线价¥' . $storeCombinationInfo['product_price'] . ' 还差' . ($pinkInfo['people'] - $count) . '人拼团成功';
try {
$uid = (int)$user['uid'];
if (request()->isRoutine()) {
//小程序
$name = $id . '_' . $uid . '_' . $user['is_promoter'] . '_pink_share_routine.jpg';
/** @var QrcodeServices $QrcodeService */
$QrcodeService = app()->make(QrcodeServices::class);
//生成小程序地址
$urlCode = $QrcodeService->getRoutineQrcodePath($id, $uid, 31, $name);
$data['url'] = $urlCode;
} else {
if (sys_config('share_qrcode', 0) && request()->isWechat()) {
/** @var QrcodeServices $qrcodeService */
$qrcodeService = app()->make(QrcodeServices::class);
$data['url'] = $qrcodeService->getTemporaryQrcode('pink-' . $id . '-' . $uid, $uid)->url;
}
}
} catch (\Throwable $e) {
}
return $data;
}
}

View File

@@ -0,0 +1,947 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\coupon;
use app\services\BaseServices;
use app\dao\activity\coupon\StoreCouponIssueDao;
use app\services\order\StoreCartServices;
use app\services\other\queue\QueueServices;
use app\services\product\brand\StoreBrandServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\product\product\StoreProductServices;
use app\services\user\member\MemberCardServices;
use app\services\user\member\MemberRightServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder;
use crmeb\traits\OptionTrait;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
*
* Class StoreCouponIssueServices
* @package app\services\activity\coupon
* @mixin StoreCouponIssueDao
*/
class StoreCouponIssueServices extends BaseServices
{
use OptionTrait;
public array $_couponType = [0 => "通用券", 1 => "品类券", 2 => '商品券', 3 => '品牌券'];
/**
* @var StoreCouponIssueDao
*/
#[Inject]
protected StoreCouponIssueDao $dao;
/**
* 获取已发布列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCouponIssueList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, $page, $limit);
foreach ($list as &$item) {
if (!$item['coupon_time']) {
$item['coupon_time'] = ceil(($item['end_use_time'] - $item['start_use_time']) / '86400');
}
$item['coupon_time'] = $item['coupon_time'] . '天';
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取会员优惠券列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getMemberCouponIssueList(array $where, int $page, int $limit, array $with = [])
{
return $this->dao->getValidList($where, '*', $page, $limit, $with);
}
/**
* 新增优惠券
* @param $data
* @return bool
*/
public function saveCoupon($data)
{
$data['start_use_time'] = strtotime((string)$data['start_use_time']);
$data['end_use_time'] = strtotime((string)$data['end_use_time']);
$data['start_time'] = strtotime((string)$data['start_time']);
$data['end_time'] = strtotime((string)$data['end_time']);
$data['title'] = $data['coupon_title'];
$data['remain_count'] = $data['total_count'];
$data['add_time'] = time();
if ($data['id'] != 0) {
$res = $this->dao->update($data['id'], $data);
app()->make(StoreCouponUserServices::class)->update(
['cid' => $data['id'], 'status' => 0],
['coupon_title' => $data['coupon_title'], 'coupon_price' => $data['coupon_price']],
);
} else {
$res = $this->dao->save($data);
if ($data['product_id'] !== '' && $res) {
$productIds = explode(',', $data['product_id']);
$couponData = [];
foreach ($productIds as $product_id) {
$couponData[] = ['product_id' => $product_id, 'coupon_id' => $res->id];
}
/** @var StoreCouponProductServices $storeCouponProductService */
$storeCouponProductService = app()->make(StoreCouponProductServices::class);
$storeCouponProductService->saveAll($couponData);
}
}
if (!$res) throw new AdminException('添加优惠券失败!');
return true;
}
/**
* 修改状态
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm(int $id)
{
$issueInfo = $this->dao->get($id);
if (-1 == $issueInfo['status'] || 1 == $issueInfo['is_del']) throw new ValidateException('状态错误,无法修改');
$f = [FormBuilder::radio('status', '是否开启', $issueInfo['status'])->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]])];
return create_form('状态修改', $f, $this->url('/marketing/coupon/released/status/' . $id), 'PUT');
}
/**
* 领取记录
* @param int $id
* @return array
*/
public function issueLog(int $id)
{
$coupon = $this->dao->get($id);
if (!$coupon) {
throw new ValidateException('优惠券不存在');
}
if ($coupon['category'] != 2) {
/** @var StoreCouponIssueUserServices $storeCouponIssueUserService */
$storeCouponIssueUserService = app()->make(StoreCouponIssueUserServices::class);
return $storeCouponIssueUserService->issueLog(['issue_coupon_id' => $id]);
} else {//会员券
/** @var StoreCouponUserServices $storeCouponUserService */
$storeCouponUserService = app()->make(StoreCouponUserServices::class);
return $storeCouponUserService->issueLog(['cid' => $id]);
}
}
/**
* 保存赠送给用户优惠券
* @param int $uid
* @param array $couponList
* @param string $type
* @return array
*/
public function saveGiveCoupon(int $uid, array $couponList, string $type = 'get')
{
if (!$uid || !$couponList) {
return [];
}
$couponData = [];
$issueUserData = [];
$time = time();
/** @var StoreCouponIssueUserServices $issueUser */
$issueUser = app()->make(StoreCouponIssueUserServices::class);
foreach ($couponList as $item) {
$data['cid'] = $item['id'];
$data['uid'] = $uid;
$data['coupon_title'] = $item['title'];
$data['coupon_price'] = $item['coupon_price'];
$data['use_min_price'] = $item['use_min_price'];
if ($item['coupon_time']) {
$data['add_time'] = $time;
$data['end_time'] = $data['add_time'] + $item['coupon_time'] * 86400;
} else {
$data['add_time'] = $item['start_use_time'];
$data['end_time'] = $item['end_use_time'];
}
$data['type'] = $type;
$issue['uid'] = $uid;
$issue['issue_coupon_id'] = $item['id'];
$issue['add_time'] = $time;
$issueUserData[] = $issue;
$couponData[] = $data;
if (!$item['is_permanent']) {
$remain_count = $item['remain_count'] > 0 ? $item['remain_count'] - 1 : 0;
$this->dao->update($item['id'], ['remain_count' => $remain_count]);
}
unset($data);
unset($issue);
}
if ($couponData) {
/** @var StoreCouponUserServices $storeCouponUser */
$storeCouponUser = app()->make(StoreCouponUserServices::class);
if (!$storeCouponUser->saveAll($couponData)) {
throw new AdminException('发劵失败');
}
}
if ($issueUserData) {
if (!$issueUser->saveAll($issueUserData)) {
throw new AdminException('发劵失败');
}
}
return $couponData;
}
/**
* 新人礼赠送优惠券
* @param int $uid
*/
public function newcomerGiveCoupon(int $uid)
{
if (!sys_config('newcomer_status')) {
return false;
}
$status = sys_config('register_coupon_status');
if (!$status) {//未开启
return true;
}
$couponIds = sys_config('register_give_coupon', []);
if (!$couponIds) {
return true;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
return true;
}
$couponList = $this->dao->getGiveCoupon([['id', 'IN', $couponIds]]);
if ($couponList) {
$this->saveGiveCoupon($uid, $couponList, 'newcomer');
}
return true;
}
/**
* 会员卡激活赠送优惠券
* @param int $uid
*/
public function levelGiveCoupon(int $uid)
{
$status = sys_config('level_activate_status');
if (!$status) {//是否需要激活
return true;
}
$status = sys_config('level_coupon_status');
if (!$status) {//未开启
return true;
}
$couponIds = sys_config('level_give_coupon', []);
if (!$couponIds) {
return true;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
return true;
}
$couponList = $this->dao->getGiveCoupon([['id', 'IN', $couponIds]]);
if ($couponList) {
$this->saveGiveCoupon($uid, $couponList, 'activate_level');
}
return true;
}
/**
* 关注送优惠券
* @param int $uid
*/
public function userFirstSubGiveCoupon(int $uid)
{
$couponList = $this->dao->getGiveCoupon(['receive_type' => 2]);
if ($couponList) {
$this->saveGiveCoupon($uid, $couponList, 'user_first');
}
return true;
}
/**
* 下单之后赠送
* @param $uid
*/
public function orderPayGiveCoupon($uid, $coupon_issue_ids)
{
if (!$coupon_issue_ids) return [];
$couponList = $this->dao->getGiveCoupon([['id', 'IN', $coupon_issue_ids]]);
$couponData = [];
if ($couponList) {
$couponData = $this->saveGiveCoupon($uid, $couponList, 'order');
}
return $couponData;
}
/**
* 获取商品关联
* @param int $uid
* @param int $product_id
* @param string $field
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductCouponList(int $uid, int $product_id, string $field = '*', int $limit = 0)
{
if ($limit) {
$page = 1;
} else {
[$page, $limit] = $this->getPageValue();
}
/** @var StoreProductServices $storeProductService */
$storeProductService = app()->make(StoreProductServices::class);
/** @var StoreProductCategoryServices $storeCategoryService */
$storeCategoryService = app()->make(StoreProductCategoryServices::class);
$productInfo = $storeProductService->getCacheProductInfo($product_id);
$cateId = explode(',', $productInfo['cate_id']);
$cateId = array_merge($cateId, $storeCategoryService->cateIdByPid($cateId));
$cateId = array_diff($cateId, [0]);
$brandIds = [];
if ($productInfo['brand_id']) {
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
$brandInfo = $storeBrandServices->get((int)$productInfo['brand_id'], ['id', 'pid']);
if ($brandInfo) {
$brandIds = $brandInfo->toArray();
$brandIds = array_diff($brandIds, [0]);
}
}
$where = ['product_id' => $productInfo['pid'] ?: $product_id, 'cate_id' => $cateId, 'brand_id' => $brandIds];
$list = $this->dao->getIssueCouponListNew($uid, $where, $field, $page, $limit);
foreach ($list as &$item) {
$item['coupon_price'] = floatval($item['coupon_price']);
$item['use_min_price'] = floatval($item['use_min_price']);
$item['is_use'] = $uid && isset($item['used']);
$item['use_status'] = 1;
if (isset($item['used']) && $item['used']) {
$item['use_status'] = $uid && isset($item['used'][0]['status']) && $item['used'][0]['status'] == '未使用' ? 1 : 0;
}
$item['start_time'] = $item['start_time'] ? date('Y-m-d', $item['start_time']) : 0;
$item['end_time'] = $item['end_time'] ? date('Y-m-d', $item['end_time']) : date('Y-m-d', time() + 86400);
}
return $list;
}
/**
* 获取优惠券列表
* @param int $uid
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getIssueCouponList(int $uid, array $where, string $field = '*', bool $is_product = false)
{
/** @var StoreProductServices $storeProductService */
$storeProductService = app()->make(StoreProductServices::class);
/** @var StoreProductCategoryServices $storeCategoryService */
$storeCategoryService = app()->make(StoreProductCategoryServices::class);
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
$cateId = 0;
$bandId = 0;
$product_id = 0;
$coupon_where = [];
if ((int)$where['product_id']) {//存在商品
$productInfo = $storeProductService->get((int)$where['product_id'], ['id,cate_id', 'brand_id']);
$product_id = $where['product_id'];
if ($productInfo) {
$cateId = $productInfo['cate_id'];
if ($cateId) {
$cateId = explode(',', $cateId);
$cateId = array_merge($cateId, $storeCategoryService->cateIdByPid($cateId));
$cateId = array_diff($cateId, [0]);
}
if ($productInfo['brand_id']) {
$brandInfo = $storeBrandServices->get((int)$productInfo['brand_id'], ['id', 'pid']);
if ($brandInfo) {
$bandId = $brandInfo->toArray();
$bandId = array_diff($bandId, [0]);
}
}
if ($where['type'] == 1) {
$coupon_where['cate_id'] = $cateId;
} elseif ($where['type'] == 2) {
$coupon_where['product_id'] = $where['product_id'];
} elseif ($where['type'] == 3) {
$coupon_where['brand_id'] = $where['brand_id'];
} else {
$coupon_where = ['product_id' => $product_id, 'cate_id' => $cateId, 'brand_id' => $bandId];
}
}
}
$where = array_merge($where, $coupon_where);
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getIssueCouponListNew($uid, $where, $field, $page, $limit);
if ($list) {
$limit = 1;
$field = ['id', 'image', 'store_name', 'price', 'IFNULL(sales,0) + IFNULL(ficti,0) as sales'];
$categoryIds = array_unique(array_column($list, 'category_id'));
$brandIds = array_unique(array_column($list, 'brand_id'));
$category = $brands = [];
if ($categoryIds) {
$category = $storeCategoryService->getColumn([['id', 'IN', $categoryIds]], 'pid,cate_name', 'id');
}
if ($brandIds) {
$brands = $storeBrandServices->getColumn([['id', 'IN', $brandIds]], 'id,pid', 'id');
}
foreach ($list as &$item) {
$item['coupon_price'] = floatval($item['coupon_price']);
$item['use_min_price'] = floatval($item['use_min_price']);
$item['is_use'] = false;
if ($uid && $item['used'] != null && count($item['used']) >= $item['receive_limit']) $item['is_use'] = true;
$item['start_time'] = $item['end_time'] = '';
if ($item['coupon_time']) {//领取几天内使用
if (isset($item['used']['end_time']) && isset($item['used']['start_time'])) {
$item['start_time'] = $item['used']['start_time'] ? date('Y/m/d', $item['used']['start_time']) : '';
$item['end_time'] = $item['used']['end_time'] ? date('Y/m/d', $item['used']['end_time']) : '';
}
} else {//指定时间段使用
$item['start_time'] = $item['start_use_time'] ? date('Y/m/d', $item['start_use_time']) : '';
$item['end_time'] = $item['end_use_time'] ? date('Y/m/d', $item['end_use_time']) : '';
}
if (!$is_product) continue;
$product_where = [];
$product_where['is_show'] = 1;
$product_where['is_del'] = 0;
$product_where['is_verify'] = 1;
$product_where['use_min_price'] = $item['use_min_price'];
switch ($item['type']) {
case 0:
break;
case 1://品类券
$product_where['salesOrder'] = 'desc';
$category_type = ($category[$item['category_id'] ?? 0]['pid'] ?? 0) == 0 ? 1 : 2;
if ($category_type == 1) {
$product_where['cid'] = $item['category_id'];
} else {
$product_where['sid'] = $item['category_id'];
}
break;
case 2://商品劵
$product_where['salesOrder'] = 'desc';
$product_where['ids'] = $item['product_id'];
break;
case 3://品牌券
$product_where['salesOrder'] = 'desc';
$product_where['brand_id'] = $brands[$item['brand_id']] ?? [];
break;
default:
$item['products'] = [];
}
$item['products'] = get_thumb_water($storeProductService->getSearchList($product_where, 0, $limit, $field, $item['type'] == 0 ? 'rand' : '', []));
}
}
$data['list'] = $list;
$data['count'] = $this->dao->getIssueCouponCount($where['product_id'], $cateId, $bandId);
return $data;
}
/**
* 给用户发送优惠券
* @param int $uid
* @param int $id
* @param bool $more
* @param string $type
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function issueUserCoupon(int $uid, int $id, bool $more = true, string $type = 'get')
{
if (!$uid || !$id) {
throw new ValidateException('参数异常');
}
$issueCouponInfo = $this->dao->getInfo((int)$id);
if (!$issueCouponInfo) {
throw new ValidateException('领取的优惠劵已领完或已过期!');
}
if ($issueCouponInfo['remain_count'] <= 0 && !$issueCouponInfo['is_permanent']) {
throw new ValidateException('抱歉优惠券已经领取完了!');
}
//领取付费会员券
if ($issueCouponInfo['category'] == 2) {
/** @var MemberRightServices $memberRightService */
$memberRightService = app()->make(MemberRightServices::class);
if (!$memberRightService->getMemberRightStatus("coupon")) {
throw new ValidateException('暂时无法领取!');
}
$userServices = app()->make(UserServices::class);
if (!$userServices->checkUserIsSvip($uid)) {
throw new ValidateException('请先购买付费会员后领取!');
}
}
/** @var StoreCouponIssueUserServices $issueUserService */
$issueUserService = app()->make(StoreCouponIssueUserServices::class);
/** @var StoreCouponUserServices $couponUserService */
$couponUserService = app()->make(StoreCouponUserServices::class);
//是否多次领取
$receiveCount = $issueUserService->getCount(['uid' => $uid, 'issue_coupon_id' => $id]);
if (!$more) {
if ($receiveCount >= $issueCouponInfo['receive_limit']) {
throw new ValidateException('已领取过该优惠劵!');
}
}
return $this->transaction(function () use ($issueUserService, $uid, $id, $couponUserService, $issueCouponInfo, $type, $receiveCount) {
$issueUserService->save(['uid' => $uid, 'issue_coupon_id' => $id, 'add_time' => time()]);
$res = $couponUserService->addUserCoupon($uid, $issueCouponInfo, $type);
if ($issueCouponInfo['total_count'] > 0) {
$issueCouponInfo['remain_count'] -= 1;
$issueCouponInfo->save();
}
$res['receive_count'] = $receiveCount + 1;
return $res;
});
}
/**
* 会员发放优惠期券
* @param $id
* @param $uid
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function memberIssueUserCoupon($id, $uid)
{
$issueCouponInfo = $this->dao->getInfo((int)$id);
if ($issueCouponInfo) {
/** @var StoreCouponUserServices $couponUserService */
$couponUserService = app()->make(StoreCouponUserServices::class);
if ($issueCouponInfo->remain_count >= 0 || $issueCouponInfo->is_permanent) {
$this->transaction(function () use ($uid, $id, $couponUserService, $issueCouponInfo) {
$couponUserService->addMemberUserCoupon($uid, $issueCouponInfo, "send");
// 如果会员劵需要限制数量时打开
if ($issueCouponInfo['total_count'] > 0) {
$issueCouponInfo['remain_count'] -= 1;
$issueCouponInfo->save();
}
});
}
}
}
/**
* 后台发送优惠券
* @param $coupon
* @param $user
* @return bool
*/
public function setCoupon($coupon, $user, $redisKey, $queueId, $is_single = false)
{
if ((!$redisKey || !$queueId || !$user) && !$is_single) return false;
$data = [];
$issueData = [];
/** @var StoreCouponUserServices $storeCouponUser */
$storeCouponUser = app()->make(StoreCouponUserServices::class);
/** @var StoreCouponIssueUserServices $storeCouponIssueUser */
$storeCouponIssueUser = app()->make(StoreCouponIssueUserServices::class);
/** @var QueueServices $queueService */
$queueService = app()->make(QueueServices::class);
$i = 0;
$time = time();
$remainCount = 0;
if (!$coupon['is_permanent']) {
$count = count($user);
$remain_count = $this->dao->value(['id' => $coupon['id']], 'remain_count');
if ($remain_count < $count) {
$user = array_slice($user, 0, $remain_count);
} else {
$remainCount = $remain_count - $count;
}
}
foreach ($user as $k => $v) {
$data[$i]['cid'] = $coupon['id'];
$data[$i]['uid'] = $v;
$data[$i]['coupon_title'] = $coupon['title'];
$data[$i]['coupon_price'] = $coupon['coupon_price'];
$data[$i]['use_min_price'] = $coupon['use_min_price'];
$data[$i]['add_time'] = $time;
if ($coupon['coupon_time']) {
$data[$i]['start_time'] = $time;
$data[$i]['end_time'] = $time + $coupon['coupon_time'] * 86400;
} else {
$data[$i]['start_time'] = $coupon['start_use_time'];
$data[$i]['end_time'] = $coupon['end_use_time'];
}
$data[$i]['type'] = 'send';
$issueData[$i]['uid'] = $v;
$issueData[$i]['issue_coupon_id'] = $coupon['id'];
$issueData[$i]['add_time'] = time();
$i++;
}
$res = $this->transaction(function () use ($data, $issueData, $storeCouponUser, $storeCouponIssueUser) {
$res1 = $res2 = true;
if ($data) {
$res1 = $storeCouponUser->saveAll($data);
}
if ($issueData) {
$res2 = $storeCouponIssueUser->saveAll($issueData);
}
return $res1 && $res2;
});
if (!$res) {
//发券失败后将队列状态置为失败
$queueService->setQueueFail($queueId['id'], $redisKey);
} else {
if (!$coupon['is_permanent']) {
$this->dao->update($coupon['id'], ['remain_count' => $remainCount]);
}
if ($is_single) return true;
//发券成功的用户踢出集合
$queueService->doSuccessSremRedis($user, $redisKey, $queueId['type']);
}
return true;
}
/**
* 获取下单可使用的优惠券列表
* @param int $uid
* @param $cartId
* @param bool $new
* @param int $shipping_type
* @return array
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function beUseableCouponList(int $uid, $cartId, bool $new, int $shipping_type = 1)
{
/** @var StoreCartServices $services */
$services = app()->make(StoreCartServices::class);
$cartGroup = $services->getUserProductCartListV1($uid, $cartId, $new, [], $shipping_type);
if (isset($cartGroup['deduction']['activity_id']) && !$cartGroup['deduction']['activity_id'] && isset($cartGroup['deduction']['type']) && $cartGroup['deduction']['type'] != 6) {
/** @var StoreCouponUserServices $coupServices */
$coupServices = app()->make(StoreCouponUserServices::class);
return $coupServices->getUseableCouponList($uid, $cartGroup);
} else {
return [];
}
}
/**获取单个优惠券类型
* @param array $where
* @return mixed
*/
public function getOne(array $where)
{
if (!$where) throw new AdminException('参数缺失!');
return $this->dao->getOne($where);
}
/**
* 俩时间相差月份
* @param $date1
* @param $date2
* @return float|int
*/
public function getMonthNum($date1, $date2)
{
$date1_stamp = strtotime($date1);
$date2_stamp = strtotime($date2);
[$date_1['y'], $date_1['m']] = explode("-", date('Y-m', $date1_stamp));
[$date_2['y'], $date_2['m']] = explode("-", date('Y-m', $date2_stamp));
return abs($date_1['y'] - $date_2['y']) * 12 + $date_2['m'] - $date_1['m'];
}
/**
* 给会员发放优惠券
* @param $uid
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function sendMemberCoupon($uid, $couponId = 0)
{
if (!$uid) return false;
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
/** @var StoreCouponUserServices $couponUserService */
$couponUserService = app()->make(StoreCouponUserServices::class);
/** @var MemberRightServices $memberRightService */
$memberRightService = app()->make(MemberRightServices::class);
/** @var MemberCardServices $memberCardService */
$memberCardService = app()->make(MemberCardServices::class);
//看付费会员是否开启
$isOpenMember = $memberCardService->isOpenMemberCardCache();
if (!$isOpenMember) return false;
$userInfo = $userService->getUserCacheInfo((int)$uid);
//看是否会员过期
if ($userInfo['is_ever_level'] == 0 && $userInfo['is_money_level'] > 0 && $userInfo['overdue_time'] < time()) {
return false;
}
//看是否开启会员送券
$isSendCoupon = $memberRightService->getMemberRightStatus("coupon");
if (!$isSendCoupon) return false;
if ($userInfo && (($userInfo['is_money_level'] > 0) || $userInfo['is_ever_level'] == 1)) {
$monthNum = $this->getMonthNum(date('Y-m-d H:i:s', time()), date('Y-m-d H:i:s', $userInfo['overdue_time']));
if ($couponId) {//手动点击领取
$couponWhere['id'] = $couponId;
$couponInfo = $this->getMemberCouponIssueList($couponWhere, 0, 0);
} else {//主动批量发放
$couponWhere['status'] = 1;
$couponWhere['category'] = 2;
$couponWhere['is_del'] = 0;
$couponInfo = $this->getMemberCouponIssueList($couponWhere, 0, 0);
}
if ($couponInfo) {
$couponIds = array_column($couponInfo, 'id');
$couponUserMonth = $couponUserService->memberCouponUserGroupBymonth(['uid' => $uid, 'couponIds' => $couponIds]);
$getTime = array();
if ($couponUserMonth) {
$getTime = array_column($couponUserMonth, 'num', 'time');
}
// 判断这个月是否领取过,而且领全了
//if (in_array(date('Y-m', time()), $getTime)) return false;
$timeKey = date('Y-m', time());
if (array_key_exists($timeKey, $getTime) && $getTime[$timeKey] == count($couponIds)) return false;
//判断是否领完所有月份
if (count($getTime) >= $monthNum && (array_key_exists($timeKey, $getTime) && $getTime[$timeKey] == count($couponIds)) && $userInfo['is_ever_level'] != 1 && $monthNum > 0) return false;
foreach ($couponInfo as $cv) {
//看之前是否手动领取过某一张,领取过就不再领取。
if (!$couponUserService->getUserCounponByMonth(['uid' => $uid, 'cid' => $cv['id']])) {
$this->memberIssueUserCoupon($cv['id'], $uid);
}
}
}
}
return true;
}
/**
* 获取今日新增优惠券
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getTodayCoupon(int $uid)
{
$receive_type = [1, 4];
if ($uid) {//用户登录 && 不是付费会员
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
if (!$userService->checkUserIsSvip($uid)) {
$receive_type = [1];
}
}
$list = $this->dao->getTodayCoupon($receive_type);
foreach ($list as $key => &$item) {
$item['start_time'] = $item['start_time'] ? date('Y-m-d', $item['start_time']) : 0;
$item['end_time'] = $item['end_time'] ? date('Y-m-d', $item['end_time']) : 0;
$item['coupon_price'] = floatval($item['coupon_price']);
$item['use_min_price'] = floatval($item['use_min_price']);
}
return $list;
}
/**
* 获取新人券
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getNewCoupon()
{
$list = $this->dao->getNewCoupon();
foreach ($list as &$item) {
$item['start_time'] = $item['start_time'] ? date('Y-m-d', $item['start_time']) : 0;
$item['end_time'] = $item['end_time'] ? date('Y-m-d', $item['end_time']) : 0;
$item['coupon_price'] = floatval($item['coupon_price']);
$item['use_min_price'] = floatval($item['use_min_price']);
}
return $list;
}
/**
* 获取可以使用优惠券
* @param int $uid
* @param array $cartList
* @param array $promotions
* @param bool $isMax
* @return array
*/
public function getCanUseCoupon(int $uid, array $cartList = [], array $promotions = [], bool $isMax = true)
{
$where = ['receive_type' => 1, 'category' => 1];
//传入是付费会员 获取付费会员券
$isVip = $this->getItem('isVip', 0);
if ($uid && !$isVip) {
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$isVip = $userService->checkUserIsSvip($uid);
}
if ($isVip) {
unset($where['category']);
}
/** @var StoreCouponUserServices $coupServices */
$coupServices = app()->make(StoreCouponUserServices::class);
//用户持有有效券
$userCoupons = $coupServices->getUserAllCoupon($uid);
if ($userCoupons) {
$where['not_id'] = array_column($userCoupons, 'cid');
}
$counpons = $this->dao->getValidList($where, '*', 0, 0, ['used' => function ($query) use ($uid) {
$query->where('uid', $uid);
}]);
//用户已经领取的 && 没有领取的
$counpons = array_merge($counpons, $userCoupons);
$result = [];
if ($counpons) {
$promotionsList = [];
if ($promotions) {
$promotionsList = array_combine(array_column($promotions, 'id'), $promotions);
}
$isOverlay = function ($cart) use ($promotionsList) {
$productInfo = $cart['productInfo'] ?? [];
if (!$productInfo) {
return false;
}
if (isset($cart['promotions_id']) && $cart['promotions_id']) {
foreach ($cart['promotions_id'] as $key => $promotions_id) {
$promotions = $promotionsList[$promotions_id] ?? [];
if ($promotions && $promotions['promotions_type'] != 4) {
$overlay = is_string($promotions['overlay']) ? explode(',', $promotions['overlay']) : $promotions['overlay'];
if (!in_array(5, $overlay)) {
return false;
}
}
}
}
return true;
};
foreach ($counpons as $coupon) {
if (isset($coupon['used']) && $coupon['used']) {//所有优惠券中 已经领取跳过
continue;
}
$price = 0;
$count = 0;
if (isset($coupon['applicable_type'])) {//已经领取 有效优惠券
$coupon['type'] = $coupon['applicable_type'];
$coupon['used'] = ['id' => $coupon['id'], 'cid' => $coupon['cid']];
}
//&& in_array($promotions['promotions_type'], $overlay)
switch ($coupon['type']) {
case 0:
foreach ($cartList as $cart) {
if (!$isOverlay($cart)) continue;
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
break;
case 1://品类券
/** @var StoreProductCategoryServices $storeCategoryServices */
$storeCategoryServices = app()->make(StoreProductCategoryServices::class);
$cateGorys = $storeCategoryServices->getAllById((int)$coupon['category_id']);
if ($cateGorys) {
$cateIds = array_column($cateGorys, 'id');
foreach ($cartList as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
}
break;
case 2:
foreach ($cartList as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['product_id']) && in_array($cart['product_id'], explode(',', $coupon['product_id']))) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
break;
case 3://品牌券
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
$brands = $storeBrandServices->getAllById((int)$coupon['brand_id']);
if ($brands) {
$brandIds = array_column($brands, 'id');
foreach ($cartList as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['productInfo']['brand_id']) && in_array($cart['productInfo']['brand_id'], $brandIds)) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
}
break;
}
if ($count && $coupon['use_min_price'] <= $price) {
//满减券
if ($coupon['coupon_type'] == 1) {
$couponPrice = $coupon['coupon_price'] > $price ? $price : $coupon['coupon_price'];
} else {
if ($coupon['coupon_price'] <= 0) {//0折
$couponPrice = $price;
} else if ($coupon['coupon_price'] >= 100) {
$couponPrice = 0;
} else {
$truePrice = (float)bcmul((string)$price, bcdiv((string)$coupon['coupon_price'], '100', 2), 2);
$couponPrice = (float)bcsub((string)$price, (string)$truePrice, 2);
}
}
$coupon['coupon_price'] = floatval($coupon['coupon_price']);
$coupon['use_min_price'] = floatval($coupon['use_min_price']);
$coupon['true_coupon_price'] = $couponPrice;
$result[] = $coupon;
}
}
}
if ($result) {
$trueCouponPrice = array_column($result, 'true_coupon_price');
array_multisort($trueCouponPrice, SORT_DESC, $result);
}
//最优的一个
if ($isMax) {
$useCoupon = $result[0] ?? [];
return $useCoupon;
}
return $result;
}
}

View File

@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\coupon;
use app\services\BaseServices;
use app\dao\activity\coupon\StoreCouponIssueUserDao;
use think\annotation\Inject;
/**
* Class StoreCouponIssueUserServices
* @package app\services\activity\coupon
* @mixin StoreCouponIssueUserDao
*/
class StoreCouponIssueUserServices extends BaseServices
{
/**
* @var StoreCouponIssueUserDao
*/
#[Inject]
protected StoreCouponIssueUserDao $dao;
/**
* 获取列表
* @param array $where
* @return array
*/
public function issueLog(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\coupon;
use app\services\BaseServices;
use app\dao\activity\coupon\StoreCouponProductDao;
use think\annotation\Inject;
/**
* Class StoreCouponProductServices
* @package app\services\activity\coupon
* @mixin StoreCouponProductDao
*/
class StoreCouponProductServices extends BaseServices
{
/**
* @var StoreCouponProductDao
*/
#[Inject]
protected StoreCouponProductDao $dao;
}

View File

@@ -0,0 +1,197 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\coupon;
use app\dao\activity\coupon\StoreCouponDao;
use app\services\BaseServices;
use app\services\product\category\StoreProductCategoryServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* Class StoreCouponService
* @package app\services\activity\coupon
* @mixin StoreCouponDao
*/
class StoreCouponService extends BaseServices
{
/**
* @var StoreCouponDao
*/
#[Inject]
protected StoreCouponDao $dao;
/**
* 获取列表
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 添加优惠券表单
* @param int $type
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm(int $type)
{
$f[] = Form::input('title', '优惠券名称');
switch ($type) {
case 1://品类券
$options = function () {
/** @var StoreProductCategoryServices $storeCategoryService */
$storeCategoryService = app()->make(StoreProductCategoryServices::class);
$list = $storeCategoryService->getTierList(1, 1);
$menus = [];
foreach (sort_list_tier($list) as $menu) {
$menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name'], 'disabled' => false];
}
return $menus;
};
$f[] = Form::select('category_id', '选择品类')->setOptions(Form::setOptions($options))->filterable(1)->col(12);
break;
case 2://商品券
$f[] = Form::frameImages('image', '商品', Url::buildUrl('admin/store.StoreProduct/index', array('fodder' => 'image', 'type' => 'many')))->icon('ios-add')->width('960px')->height('430px')->modal(['footer-hide' => true])->props(['srcKey' => 'image']);
$f[] = Form::hidden('product_id', '');
break;
}
$f[] = Form::number('coupon_price', '优惠券面值', 0)->min(0);
$f[] = Form::number('use_min_price', '优惠券最低消费', 0)->min(0);
$f[] = Form::number('coupon_time', '优惠券有效期限', 0)->min(0);
$f[] = Form::number('sort', '排序', 1)->min(0);
$f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
$f[] = Form::hidden('type', $type);
return create_form('添加优惠券', $f, Url::buildUrl('/marketing/coupon/save'), 'POST');
}
/**
* 优惠卷模板修改表单
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function createIssue(int $id)
{
$res = $this->dao->getOne(['id' => $id, 'status' => 1, 'is_del' => 0]);
if (!$res) throw new AdminException('发布的优惠劵已失效或不存在!');
$f = [];
$f[] = Form::input('id', '优惠劵ID', $id)->disabled(1);
$f[] = Form::input('coupon_title', '优惠劵名称', $res['title'])->disabled(1);
$f[] = Form::dateTimeRange('range_date', '领取时间')->placeholder('不填为永久有效');
$f[] = Form::radio('is_permanent', '是否限量', 1)->options([['label' => '不限量', 'value' => 1], ['label' => '限量', 'value' => 0]]);
$f[] = Form::number('count', '发布数量', 0)->min(0)->placeholder('不填或填0,为不限量');
$f[] = Form::radio('is_type', '优惠券类型', 0)->options([['label' => '普通券', 'value' => 0], ['label' => '赠送券', 'value' => 1], ['label' => '新人券', 'value' => 2]]);
$f[] = Form::number('full_reduction', '满赠金额', 0)->min(0)->placeholder('赠送优惠券的最低消费金额');
$f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
return create_form('发布优惠券', $f, $this->url('/marketing/coupon/issue/' . $id), 'POST');
}
/**
* 发布优惠券
* @param int $id
* @param int $_id
* @param string $coupon_title
* @param array $rangeTime
* @param int $count
* @param int $status
* @param int $is_permanent
* @param float $full_reduction
* @param int $is_give_subscribe
* @param int $is_full_give
* @param int $is_type
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function upIssue(int $id, int $_id, string $coupon_title, array $rangeTime, int $count, int $status, int $is_permanent, float $full_reduction, int $is_give_subscribe, int $is_full_give, int $is_type)
{
if ($is_type == 1) {
$is_full_give = 1;
} elseif ($is_type == 2) {
$is_give_subscribe = 1;
}
if ($_id != $id) throw new AdminException('操作失败,信息不对称');
if (!$count) $count = 0;
$couponInfo = $this->dao->getOne(['id' => $id, 'status' => 1, 'is_del' => 0]);
if (!$couponInfo) throw new AdminException('发布的优惠劵已失效或不存在!');
if (count($rangeTime) != 2) throw new AdminException('请选择正确的时间区间');
[$startTime, $endTime] = $rangeTime;
if (!$startTime) $startTime = 0;
if (!$endTime) $endTime = 0;
if (!$startTime && $endTime) throw new AdminException('请选择正确的开始时间');
if ($startTime && !$endTime) throw new AdminException('请选择正确的结束时间');
$data['cid'] = $id;
$data['coupon_title'] = $coupon_title;
$data['start_time'] = strtotime($startTime);
$data['end_time'] = strtotime($endTime);
$data['total_count'] = $count;
$data['remain_count'] = $count;
$data['is_permanent'] = $is_permanent;
$data['status'] = $status;
$data['is_give_subscribe'] = $is_give_subscribe;
$data['is_full_give'] = $is_full_give;
$data['full_reduction'] = $full_reduction;
$data['is_del'] = 0;
$data['add_time'] = time();
$data['title'] = $couponInfo['title'];
$data['integral'] = $couponInfo['integral'];
$data['coupon_price'] = $couponInfo['coupon_price'];
$data['use_min_price'] = $couponInfo['use_min_price'];
$data['coupon_time'] = $couponInfo['coupon_time'];
$data['product_id'] = $couponInfo['product_id'];
$data['category_id'] = $couponInfo['category_id'];
$data['type'] = $couponInfo->getData('type');
/** @var StoreCouponIssueServices $storeCouponIssueService */
$storeCouponIssueService = app()->make(StoreCouponIssueServices::class);
$res = $storeCouponIssueService->save($data);
$productIds = explode(',', $data['product_id']);
if (count($productIds)) {
$couponData = [];
foreach ($productIds as $product_id) {
$couponData[] = ['product_id' => $product_id, 'coupon_id' => $res->id];
}
/** @var StoreCouponProductServices $storeCouponProductService */
$storeCouponProductService = app()->make(StoreCouponProductServices::class);
$storeCouponProductService->saveAll($couponData);
}
if (!$res) throw new AdminException('发布优惠劵失败!');
}
/**
* 优惠券失效
* @param int $id
*/
public function invalid(int $id)
{
$res = $this->dao->update($id, ['status' => 0]);
if (!$res) throw new AdminException('操作失败');
/** @var StoreCouponIssueServices $storeCouponIssueService */
$storeCouponIssueService = app()->make(StoreCouponIssueServices::class);
$storeCouponIssueService->update($id, ['status' => -1], 'cid');
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\coupon;
use app\dao\activity\coupon\StoreCouponUserCouponDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 根据下单金额获取用户能使用的优惠卷
* Class StoreCouponUserCouponServices
* @package app\services\activity\coupon
* @mixin StoreCouponUserCouponDao
*/
class StoreCouponUserCouponServices extends BaseServices
{
/**
* @var StoreCouponUserCouponDao
*/
#[Inject]
protected StoreCouponUserCouponDao $dao;
}

View File

@@ -0,0 +1,497 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\coupon;
use app\services\activity\promotions\StorePromotionsServices;
use app\services\BaseServices;
use app\services\product\brand\StoreBrandServices;
use app\services\user\UserServices;
use app\dao\activity\coupon\StoreCouponUserDao;
use app\services\product\category\StoreProductCategoryServices;
use think\annotation\Inject;
/**
* Class StoreCouponUserServices
* @package app\services\activity\coupon
* @mixin StoreCouponUserDao
*/
class StoreCouponUserServices extends BaseServices
{
/**
* @var StoreCouponUserDao
*/
#[Inject]
protected StoreCouponUserDao $dao;
/**
* 获取列表
* @param array $where
* @return array
*/
public function issueLog(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, 'uid,add_time', ['userInfo'], $page, $limit);
foreach ($list as &$item) {
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', ['issue'], $page, $limit);
$count = 0;
if ($list) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userAll = $userServices->getColumn([['uid', 'IN', array_column($list, 'uid')]], 'uid,nickname', 'uid');
foreach ($list as &$item) {
$item['nickname'] = $userAll[$item['uid']]['nickname'] ?? '';
}
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取用户优惠券
* @param int $id
* @param int $status
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserCouponList(int $id, int $status = -1)
{
[$page, $limit] = $this->getPageValue();
$where = ['uid' => $id];
if ($status != -1) $where['status'] = $status;
$list = $this->dao->getList($where, '*', ['issue'], $page, $limit);
foreach ($list as &$item) {
$item['_add_time'] = date('Y-m-d H:i:s', $item['add_time']);
$item['_end_time'] = date('Y-m-d H:i:s', $item['end_time']);
if (!$item['coupon_time']) {
$item['coupon_time'] = ceil(($item['end_use_time'] - $item['start_use_time']) / '86400');
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 恢复优惠券
* @param int $id
* @return bool|mixed
*/
public function recoverCoupon(int $id)
{
$status = $this->dao->value(['id' => $id], 'status');
if ($status) return $this->dao->update($id, ['status' => 0, 'use_time' => 0]);
else return true;
}
/**
* 过期优惠卷失效
*/
public function checkInvalidCoupon($uid = 0)
{
$this->dao->update([['uid', '=', $uid], ['end_time', '<', time()], ['status', '=', '0']], ['status' => 2]);
}
/**
* 获取用户有效优惠劵数量
* @param int $uid
* @return int
*/
public function getUserValidCouponCount(int $uid)
{
$this->checkInvalidCoupon($uid);
return $this->dao->getCount(['uid' => $uid, 'status' => 0]);
}
/**
* 下单页面显示可用优惠券
* @param $uid
* @param $cartGroup
* @param $price
* @return array
*/
public function getUseableCouponList(int $uid, array $cartGroup)
{
$userCoupons = $this->dao->getUserAllCoupon($uid);
$result = [];
if ($userCoupons) {
$cartInfo = $cartGroup['valid'];
$promotions = $cartGroup['promotions'] ?? [];
$promotionsList = [];
if ($promotions) {
$promotionsList = array_combine(array_column($promotions, 'id'), $promotions);
}
$isOverlay = function ($cart) use ($promotionsList) {
$productInfo = $cart['productInfo'] ?? [];
if (!$productInfo) {
return false;
}
if (isset($cart['promotions_id']) && $cart['promotions_id']) {
foreach ($cart['promotions_id'] as $key => $promotions_id) {
$promotions = $promotionsList[$promotions_id] ?? [];
if ($promotions && $promotions['promotions_type'] != 4) {
$overlay = is_string($promotions['overlay']) ? explode(',', $promotions['overlay']) : $promotions['overlay'];
if (!in_array(5, $overlay)) {
return false;
}
}
}
}
return true;
};
/** @var StoreProductCategoryServices $storeCategoryServices */
$storeCategoryServices = app()->make(StoreProductCategoryServices::class);
/** @var StoreBrandServices $storeBrandServices */
$storeBrandServices = app()->make(StoreBrandServices::class);
foreach ($userCoupons as $coupon) {
$price = 0;
$count = 0;
switch ($coupon['applicable_type']) {
case 0:
foreach ($cartInfo as $cart) {
if (!$isOverlay($cart)) continue;
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
break;
case 1://品类券
$cateGorys = $storeCategoryServices->getAllById((int)$coupon['category_id']);
if ($cateGorys) {
$cateIds = array_column($cateGorys, 'id');
foreach ($cartInfo as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
}
break;
case 2:
foreach ($cartInfo as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['product_id']) && in_array($cart['product_id'], explode(',', $coupon['product_id']))) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
break;
case 3://品牌
$brands = $storeBrandServices->getAllById((int)$coupon['brand_id']);
if ($brands) {
$brandIds = array_column($brands, 'id');
foreach ($cartInfo as $cart) {
if (!$isOverlay($cart)) continue;
if (isset($cart['productInfo']['brand_id']) && in_array($cart['productInfo']['brand_id'], $brandIds)) {
$price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
$count++;
}
}
}
break;
}
if ($count && $coupon['use_min_price'] <= $price) {
$coupon_data['id'] = $coupon['id'];
$coupon_data['cid'] = $coupon['cid'];
$coupon_data['type'] = $coupon['applicable_type'];
$coupon_data['applicable_type'] = $coupon['applicable_type'];
$coupon_data['receive_type'] = $coupon['receive_type'];
$coupon_data['start_time'] = $coupon['start_time'] ? date('Y-m-d', $coupon['start_time']) : date('Y-m-d', $coupon['add_time']);
$coupon_data['add_time'] = date('Y-m-d', $coupon['add_time']);
$coupon_data['end_time'] = date('Y-m-d', $coupon['end_time']);
$coupon_data['title'] = $coupon['coupon_title'];
$coupon_data['use_min_price'] = floatval($coupon['use_min_price']);
$coupon_data['coupon_type'] = $coupon['coupon_type'];
$coupon_data['coupon_price'] = floatval($coupon['coupon_price']);
$result[] = $coupon_data;
}
}
}
return $result;
}
/**
* 用户领取优惠券
* @param $uid
* @param $issueCouponInfo
* @param string $type
* @return mixed
*/
public function addUserCoupon($uid, $issueCouponInfo, string $type = 'get')
{
$data = [];
$data['cid'] = $issueCouponInfo['id'];
$data['uid'] = $uid;
$data['coupon_title'] = $issueCouponInfo['title'];
$data['coupon_price'] = $issueCouponInfo['coupon_price'];
$data['use_min_price'] = $issueCouponInfo['use_min_price'];
$data['add_time'] = time();
if ($issueCouponInfo['coupon_time']) {
$data['start_time'] = $data['add_time'];
$data['end_time'] = $data['add_time'] + $issueCouponInfo['coupon_time'] * 86400;
} else {
$data['start_time'] = $issueCouponInfo['start_use_time'];
$data['end_time'] = $issueCouponInfo['end_use_time'];
}
$data['type'] = $type;
return $this->dao->save($data);
}
/**会员领取优惠券
* @param $uid
* @param $issueCouponInfo
* @param string $type
* @return mixed
*/
public function addMemberUserCoupon($uid, $issueCouponInfo, $type = 'get')
{
$data = [];
$data['cid'] = $issueCouponInfo['id'];
$data['uid'] = $uid;
$data['coupon_title'] = $issueCouponInfo['title'];
$data['coupon_price'] = $issueCouponInfo['coupon_price'];
$data['use_min_price'] = $issueCouponInfo['use_min_price'];
$data['add_time'] = time();
$data['start_time'] = strtotime(date('Y-m-d 00:00:00', time()));
$data['end_time'] = strtotime(date('Y-m-d 23:59:59', strtotime('+30 day')));
$data['type'] = $type;
return $this->dao->save($data);
}
/**
* 获取用户已领取的优惠卷
* @param int $uid
* @param $type
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserCounpon(int $uid, $types, $type)
{
$where = [];
$where['uid'] = $uid;
switch ($types) {
case 1:
$where['status'] = 1;
break;
case 2:
$where['status'] = 2;
break;
default:
$where['status'] = 0;
break;
}
$where['issue_type'] = $type;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getCouponListByOrder($where, 'status ASC,add_time DESC', $page, $limit);
/** @var StoreProductCategoryServices $categoryServices */
$categoryServices = app()->make(StoreProductCategoryServices::class);
$category = $categoryServices->getColumn([], 'pid,cate_name', 'id');
foreach ($list as &$item) {
if ($item['category_id'] && isset($category[$item['category_id']])) {
$item['category_type'] = $category[$item['category_id']]['pid'] == 0 ? 1 : 2;
$item['category_name'] = $category[$item['category_id']]['cate_name'];
} else {
$item['category_type'] = '';
$item['category_name'] = '';
}
}
return $list ? $this->tidyCouponList($list) : [];
}
/**
* 我的优惠券数量
* @param int $uid
* @return array
* @throws \think\db\exception\DbException
*/
public function getUserCounponNum(int $uid): array
{
$data['not_used'] = $this->dao->count(['uid' => $uid, 'status' => 0]); // 未使用
$data['used'] = $this->dao->count(['uid' => $uid, 'status' => 1]);// 已使用
$data['expired'] = $this->dao->count(['uid' => $uid, 'status' => 2]);// 已过期
return $data;
}
/**
* 格式化优惠券
* @param $couponList
* @return mixed
*/
public function tidyCouponList($couponList)
{
$time = time();
foreach ($couponList as &$coupon) {
if ($coupon['status'] == '已使用') {
$coupon['_type'] = 0;
$coupon['_msg'] = '已使用';
$coupon['pc_type'] = 0;
$coupon['pc_msg'] = '已使用';
} else if ($coupon['status'] == '已过期') {
$coupon['is_fail'] = 1;
$coupon['_type'] = 0;
$coupon['_msg'] = '已过期';
$coupon['pc_type'] = 0;
$coupon['pc_msg'] = '已过期';
} else if ($coupon['end_time'] < $time) {
$coupon['is_fail'] = 1;
$coupon['_type'] = 0;
$coupon['_msg'] = '已过期';
$coupon['pc_type'] = 0;
$coupon['pc_msg'] = '已过期';
} else if ($coupon['start_time'] > $time) {
$coupon['_type'] = 0;
$coupon['_msg'] = '未开始';
$coupon['pc_type'] = 1;
$coupon['pc_msg'] = '未开始';
} else {
if ($coupon['start_time'] + 3600 * 24 > $time) {
$coupon['_type'] = 2;
$coupon['_msg'] = '立即使用';
$coupon['pc_type'] = 1;
$coupon['pc_msg'] = '可使用';
} else {
$coupon['_type'] = 1;
$coupon['_msg'] = '立即使用';
$coupon['pc_type'] = 1;
$coupon['pc_msg'] = '可使用';
}
}
$coupon['add_time'] = $coupon['_add_time'] = $coupon['start_time'] ? date('Y/m/d', $coupon['start_time']) : date('Y/m/d', $coupon['add_time']);
$coupon['end_time'] = $coupon['_end_time'] = date('Y/m/d', $coupon['end_time']);
$coupon['use_min_price'] = floatval($coupon['use_min_price']);
$coupon['coupon_price'] = floatval($coupon['coupon_price']);
}
return $couponList;
}
/**
* 获取会员优惠券列表
* @param $uid
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getMemberCoupon($uid)
{
if (!$uid) return [];
[$page, $limit] = $this->getPageValue();
if (!$page && !$limit) {
$page = 0;
$limit = 4;
}
/** @var StoreCouponIssueServices $couponIssueService */
$couponIssueService = app()->make(StoreCouponIssueServices::class);
$couponWhere['category'] = 2;
$couponInfo = $couponIssueService->getMemberCouponIssueList($couponWhere, $page, $limit, ['used' => function ($query) use ($uid) {
$query->where('uid', $uid);
}]);
if ($couponInfo) {
$time = time();
foreach ($couponInfo as $k => &$coupon) {
$coupon['type_name'] = $couponIssueService->_couponType[$coupon['type']];
$coupon['is_use'] = $uid ? isset($coupon['used']) : false;
$coupon['used'] = $coupon['used'][0] ?? [];
if (isset($coupon['used']) && $coupon['used']) {
if ($coupon['used']['status'] == '已使用') {
$coupon['used']['_type'] = 0;
$coupon['used']['_msg'] = '已使用';
$coupon['used']['pc_type'] = 0;
$coupon['used']['pc_msg'] = '已使用';
} else if ($coupon['used']['status'] == '已过期') {
$coupon['used']['is_fail'] = 1;
$coupon['used']['_type'] = 0;
$coupon['used']['_msg'] = '已过期';
$coupon['used']['pc_type'] = 0;
$coupon['used']['pc_msg'] = '已过期';
} else if ($coupon['used']['end_time'] < $time) {
$coupon['used']['is_fail'] = 1;
$coupon['used']['_type'] = 0;
$coupon['used']['_msg'] = '已过期';
$coupon['used']['pc_type'] = 0;
$coupon['used']['pc_msg'] = '已过期';
} else if ($coupon['used']['start_time'] > $time) {
$coupon['used']['_type'] = 0;
$coupon['used']['_msg'] = '未开始';
$coupon['used']['pc_type'] = 1;
$coupon['used']['pc_msg'] = '未开始';
} else {
if ($coupon['used']['start_time'] + 3600 * 24 > $time) {
$coupon['used']['_type'] = 2;
$coupon['used']['_msg'] = '立即使用';
$coupon['used']['pc_type'] = 1;
$coupon['used']['pc_msg'] = '可使用';
} else {
$coupon['used']['_type'] = 1;
$coupon['used']['_msg'] = '立即使用';
$coupon['used']['pc_type'] = 1;
$coupon['used']['pc_msg'] = '可使用';
}
}
$coupon['used']['end_time'] = $coupon['used']['_end_time'] = date('Y/m/d', $coupon['used']['end_time']);
$coupon['used']['_add_time'] = $coupon['used']['start_time'] ? date('Y/m/d', $coupon['used']['start_time']) : date('Y/m/d', $coupon['add_time']);
}
$coupon['add_time'] = date('Y/m/d', $coupon['add_time']);
$coupon['start_use_time'] = date('Y/m/d', $coupon['start_use_time']);
$coupon['end_use_time'] = date('Y/m/d', $coupon['end_use_time']);
$coupon['use_min_price'] = floatval($coupon['use_min_price']);
$coupon['coupon_price'] = floatval($coupon['coupon_price']);
}
}
return $couponInfo ?: [];
}
/**
* 使用优惠券验证
* @param int $couponId
* @param int $uid
* @param array $cartInfo
* @param array $promotions
* @return bool
*/
public function useCoupon(int $couponId, int $uid, array $cartInfo, array $promotions = [])
{
if (!$couponId || !$uid || !$cartInfo) {
return true;
}
/** @var StorePromotionsServices $promotionsServices */
$promotionsServices = app()->make(StorePromotionsServices::class);
[$couponInfo, $couponPrice] = $promotionsServices->useCoupon($couponId, $uid, $cartInfo, $promotions);
if ($couponInfo) {
$this->dao->useCoupon($couponId);
}
return true;
}
}

View File

@@ -0,0 +1,87 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\discounts;
use app\dao\activity\discounts\StoreDiscountsProductsDao;
use app\services\BaseServices;
use app\services\product\sku\StoreProductAttrResultServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 优惠套餐商品
* Class StoreDiscountsProductsServices
* @package app\services\activity\discounts
* @mixin StoreDiscountsProductsDao
*/
class StoreDiscountsProductsServices extends BaseServices
{
/**
* @var StoreDiscountsProductsDao
*/
#[Inject]
protected StoreDiscountsProductsDao $dao;
/**
* 删除规格和优惠商品
* @param $discount_id
* @return mixed
*/
public function del($discount_id)
{
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$ids = $this->dao->getColumn(['discount_id' => $discount_id], 'id');
foreach ($ids as $id) {
$storeProductAttrServices->del($id, 5);
$storeProductAttrResultServices->del($id, 5);
$storeProductAttrValueServices->del($id, 5);
}
return $this->dao->delete($discount_id, 'discount_id');
}
/**
* 下单|加入购物车验证套餐商品库存
* @param int $uid
* @param int $discount_product_id
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkDiscountsStock(int $uid, int $discount_product_id, int $cartNum = 1, string $unique = '')
{
$discountProductInfo = $this->dao->getDiscountProductInfo($discount_product_id, '*,title as store_name', ['product']);
if (!$discountProductInfo) {
throw new ValidateException('该商品已下架或删除');
}
$discountProductInfo['temp_id'] = $discountProductInfo['p_temp_id'] ?? 0;
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
$attrInfo = $attrValueServices->getOne(['product_id' => $discount_product_id, 'unique' => $unique, 'type' => 5]);
if (!$attrInfo || $attrInfo['product_id'] != $discount_product_id) {
throw new ValidateException('请选择有效的商品属性');
}
return [$attrInfo, $unique, $discountProductInfo];
}
}

View File

@@ -0,0 +1,448 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\discounts;
use app\dao\activity\discounts\StoreDiscountsDao;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrResultServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\user\label\UserLabelServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
/**
* 优惠套餐
* Class StoreDiscountsServices
* @package app\services\activity\discounts
* @mixin StoreDiscountsDao
*/
class StoreDiscountsServices extends BaseServices
{
/**
* 商品活动类型
*/
const TYPE = 5;
/**
* @var StoreDiscountsDao
*/
#[Inject]
protected StoreDiscountsDao $dao;
/**
* 保存数据
* @param $data
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveDiscounts($data)
{
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
/** @var StoreDiscountsProductsServices $storeDiscountsProductsServices */
$storeDiscountsProductsServices = app()->make(StoreDiscountsProductsServices::class);
return $this->transaction(function () use ($data, $storeProductAttrServices, $storeDiscountsProductsServices) {
//添加优惠套餐
$discountsData['title'] = $data['title'];
$discountsData['image'] = $data['image'];
$discountsData['type'] = $data['type'];
$discountsData['is_limit'] = $data['is_limit'];
$discountsData['limit_num'] = $data['is_limit'] ? $data['limit_num'] : 0;
$discountsData['link_ids'] = implode(',', $data['link_ids']);
$discountsData['is_time'] = $data['is_time'];
$discountsData['start_time'] = $data['is_time'] ? strtotime($data['time'][0]) : 0;
$discountsData['stop_time'] = $data['is_time'] ? strtotime($data['time'][1]) + 86399 : 0;
$discountsData['sort'] = $data['sort'];
$discountsData['add_time'] = time();
$discountsData['free_shipping'] = $data['free_shipping'];
$discountsData['status'] = $data['status'];
$discountsData['freight'] = $data['freight'];
$discountsData['postage'] = $data['postage'];
$discountsData['custom_form'] = $data['custom_form'] ? json_encode($data['custom_form']) : '';
$discountsData['system_form_id'] = $data['system_form_id'] ?? 0;
$product_ids = [];
foreach ($data['products'] as $product) {
if ($product['type']) {
$product_ids = [];
$product_ids[] = $product['product_id'];
break;
} else {
$product_ids[] = $product['product_id'];
}
}
$discountsData['product_ids'] = implode(',', $product_ids);
if ($data['id']) {
unset($discountsData['add_time']);
$this->dao->update($data['id'], $discountsData);
$storeDiscountsProductsServices->del($data['id']);
$discountsId = $data['id'];
} else {
$discountsId = $this->dao->save($discountsData)->id;
}
if (!$discountsId) throw new AdminException('添加失败');
if ($discountsData['is_limit']) CacheService::setStock(md5($discountsId), (int)$discountsData['limit_num'], 5);
//添加优惠套餐内商品
/** @var StoreProductServices $productService */
$productService = app()->make(StoreProductServices::class);
foreach ($data['products'] as $item) {
$stock = $productService->value(['id' => $item['product_id'], 'is_del' => 0, 'is_show' => 1], 'stock') ?? 0;
if ((int)$stock <= 0) {
throw new AdminException('商品库存不足,请选择其他商品');
}
$productData = [];
$productData['discount_id'] = $discountsId;
$productData['product_id'] = $item['product_id'];
$productData['product_type'] = $item['product_type'] ?? 0;
$productData['title'] = $item['store_name'];
$productData['image'] = $item['image'];
$productData['type'] = $item['type'];
$productData['temp_id'] = $item['temp_id'];
$discountsProducts = $storeDiscountsProductsServices->save($productData);
$storeProductAttrServices->setItem('store_product_id', $item['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($item['items'], $item['attr'], (int)$discountsProducts->id, 5);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$discountsProducts->id, 5);
if (!$discountsProducts || !$valueGroup) throw new AdminException('添加失败');
}
return true;
});
}
/**
* 获取列表
* @param array $where
* @return array
*/
public function getList($where = [])
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, ['products'], $page, $limit);
$count = $this->dao->count($where + ['is_del' => 0]);
$time = time();
foreach ($list as &$item) {
if (!$this->checkDiscount($item, 0) || ($item['stop_time'] && $item['stop_time'] < $time)) {
$item['status'] = 0;
$this->dao->update(['id' => $item['id']], ['status' => 0]);
}
$item['start_time'] = $item['start_time'] ? date('Y-m-d', $item['start_time']) : 0;
$item['stop_time'] = $item['stop_time'] ? date('Y-m-d', $item['stop_time']) : 0;
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
}
return compact('list', 'count');
}
/**
* 获取详情
* @param $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo($id)
{
$discounts = $this->dao->get($id);
if ($discounts) {
$discounts = $discounts->toArray();
} else {
throw new AdminException('套餐商品未找到!');
}
/** @var StoreDiscountsProductsServices $discountsProducts */
$discountsProducts = app()->make(StoreDiscountsProductsServices::class);
/** @var UserLabelServices $userLabelServices */
$userLabelServices = app()->make(UserLabelServices::class);
$discounts['products'] = $discountsProducts->getList(['discount_id' => $discounts['id']]);
foreach ($discounts['products'] as &$item) {
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
$discountsResult = $storeProductAttrResultServices->value(['product_id' => $item['id'], 'type' => 5], 'result');
$item['items'] = json_decode($discountsResult, true)['attr'];
$item['attr'] = $this->getattr($item['items'], $item['id'], 5);
$item['store_name'] = $item['title'];
}
if ($discounts['start_time']) {
$discounts['time'] = [date('Y-m-d', $discounts['start_time']), date('Y-m-d', $discounts['stop_time'])];
} else {
$discounts['time'] = [];
}
$link_ids = explode(',', $discounts['link_ids']);
$discounts['link_ids'] = $userLabelServices->getLabelList(['ids' => $link_ids], ['id', 'label_name']);
return $discounts;
}
/**
* 获取规格
* @param $attr
* @param $id
* @param $type
* @return array
*/
public function getattr($attr, $id, $type)
{
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
foreach ($attr as $k=>$v){
foreach ($v['detail'] as $key=>$item){
if(isset($item['value'])){
$attr[$k]['detail'][$key] = $item['value'];
}
}
}
$value = attr_format($attr)[1];
$valueNew = [];
$count = 0;
foreach ($value as $key => $item) {
$detail = $item['detail'];
$suk = implode(',', $item['detail']);
$sukValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => $type, 'suk' => $suk], 'bar_code,code,cost,price,ot_price,stock,image as pic,weight,volume,brokerage,brokerage_two,quota,quota_show', 'suk');
if (count($sukValue)) {
$valueNew[$count]['value'] = '';
foreach (array_values($detail) as $k => $v) {
$valueNew[$count]['value' . ($k + 1)] = $v;
$valueNew[$count]['value'] .= $valueNew[$count]['value'] == '' ? $v : ',' . $v;
}
$valueNew[$count]['detail'] = $detail;
$valueNew[$count]['pic'] = $sukValue[$suk]['pic'] ?? '';
$valueNew[$count]['price'] = $sukValue[$suk]['price'] ? floatval($sukValue[$suk]['price']) : 0;
$valueNew[$count]['cost'] = $sukValue[$suk]['cost'] ? floatval($sukValue[$suk]['cost']) : 0;
$valueNew[$count]['ot_price'] = isset($sukValue[$suk]['ot_price']) ? floatval($sukValue[$suk]['ot_price']) : 0;
$valueNew[$count]['stock'] = $sukValue[$suk]['stock'] ? intval($sukValue[$suk]['stock']) : 0;
// $valueNew[$count]['quota'] = $sukValue[$suk]['quota'] ? intval($sukValue[$suk]['quota']) : 0;
$valueNew[$count]['quota'] = isset($sukValue[$suk]['quota_show']) && $sukValue[$suk]['quota_show'] ? intval($sukValue[$suk]['quota_show']) : 0;
$valueNew[$count]['bar_code'] = $sukValue[$suk]['bar_code'] ?? '';
$valueNew[$count]['code'] = $sukValue[$suk]['code'] ?? '';
$valueNew[$count]['weight'] = $sukValue[$suk]['weight'] ? floatval($sukValue[$suk]['weight']) : 0;
$valueNew[$count]['volume'] = $sukValue[$suk]['volume'] ? floatval($sukValue[$suk]['volume']) : 0;
$valueNew[$count]['brokerage'] = $sukValue[$suk]['brokerage'] ? floatval($sukValue[$suk]['brokerage']) : 0;
$valueNew[$count]['brokerage_two'] = $sukValue[$suk]['brokerage_two'] ? floatval($sukValue[$suk]['brokerage_two']) : 0;
$count++;
}
}
return $valueNew;
}
/**
* 修改状态
* @param $id
* @param $status
* @return mixed
*/
public function setStatus($id, $status)
{
$discounts = $this->dao->get($id, ['*'], ['products']);
if ($discounts) {
$discounts = $discounts->toArray();
} else {
throw new AdminException('套餐商品未找到!');
}
//上架
if ($status) {
if ($discounts['stop_time'] && $discounts['stop_time'] < time()) {
throw new AdminException('套餐活动已结束');
}
if (!$this->checkDiscount($discounts, 0)) {
throw new AdminException('套餐内商品已下架或者库存不足');
}
}
return $this->dao->update($id, ['status' => $status]);
}
/**
* 删除商品
* @param $id
* @return mixed
*/
public function del($id)
{
return $this->dao->update($id, ['is_del' => 1]);
}
/**
* 获取优惠套餐列表
* @param int $product_id
* @param int $uid
* @param int $limit
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getDiscounts(int $product_id, int $uid, int $limit = 0, string $field = '*')
{
if ($limit) {
$page = 0;
} else {
[$page, $limit] = $this->getPageValue();
}
$list = $this->dao->getDiscounts($product_id, $field, $page, $limit);
foreach ($list as $key => &$discounts) {
$discounts = $this->checkDiscount($discounts, $uid);
if (!$discounts) unset($list[$key]);
$maxDiscountsPrice = 0;
if (isset($discounts['products']) && $discounts['products']) {
$oneProductMax = [];
foreach ($discounts['products'] as $product) {
$attrInfo = $product['productValue'] ?? [];
if ($attrInfo) {
$one = 0;
foreach ($attrInfo as $attr) {
$one = max($one, (float)bcsub((string)($attr['product_price'] ?? 0), (string)($attr['price'] ?? 0), 2));
}
$oneProductMax[] = $one;
}
}
$maxDiscountsPrice = array_sum($oneProductMax);
}
$discounts['max_discounts_price'] = $maxDiscountsPrice;
}
return array_merge($list);
}
/**
* 购买|退款处理套餐限量
* @param int $discountId
* @param bool $is_dec
* @return bool
*/
public function changeDiscountLimit(int $discountId, bool $is_dec = true)
{
$is_limit = $this->dao->value(['id' => $discountId], 'is_limit');
$res = true;
//开启限量
if ($is_limit) {
if ($is_dec) $res = $res && $this->dao->decLimitNum($discountId);
else $res = $res && $this->dao->incLimitNum($discountId);
}
return $res;
}
/**
* 优惠套餐库存减少
* @param int $num
* @param int $discountId
* @param int $discount_product_id
* @param int $product_id
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decDiscountStock(int $num, int $discountId, int $discount_product_id, int $product_id, string $unique, int $store_id = 0)
{
if (!$discountId || !$discount_product_id || !$product_id) return false;
$res = true;
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//套餐商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $discount_product_id, 'type' => 5], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = false !== $services->decProductStock($num, $product_id, (string)$productUnique);
}
return $res;
}
/**
* 加库存减销量
* @param int $num
* @param int $discountId 套餐id
* @param int $discount_product_id 套餐商品id
* @param int $product_id 原商品ID
* @param string $unique
* @return bool
*/
public function incDiscountStock(int $num, int $discountId, int $discount_product_id, int $product_id, string $unique, int $store_id = 0)
{
if (!$discountId || !$discount_product_id || !$product_id) return false;
$res = true;
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//增加当前普通商品sku的库存,减去销量
//套餐商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $discount_product_id, 'type' => 5], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//增加普通商品库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
return $res;
}
/**
* 判断优惠套餐显示
* @param $discount
* @param $uid
* @return false
*/
public function checkDiscount($discount, $uid)
{
$discount = is_object($discount) ? $discount->toArray() : $discount;
if ($discount['is_limit'] && $discount['limit_num'] <= 0) return false;
if (isset($discount['products']) && $discount['products']) {
/** @var StoreProductServices $productService */
$productService = app()->make(StoreProductServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$minPrice = 0;
foreach ($discount['products'] as $key => &$item) {
$stock = $productService->value(['id' => $item['product_id'], 'is_del' => 0, 'is_show' => 1], 'stock') ?? 0;
try {
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetailCache($item['id'], $uid, 0, 5, $item['product_id']);
} catch (\Throwable $e) {
return false;
}
if ($discount['type']) {
if ($item['type']) {
if ($stock == 0) {
return false;
} else {
if (!array_sum(array_column($productValue, 'product_stock'))) return false;
}
} else {
if (!array_sum(array_column($productValue, 'product_stock'))) unset($discount['products'][$key]);
}
} else {
if ($stock == 0) {
return false;
} else {
if (!array_sum(array_column($productValue, 'product_stock'))) return false;
}
}
$item['productAttr'] = $productAttr;
$item['productValue'] = $productValue;
$minPrice += min(array_column($productValue, 'price'));
}
if (!count($discount['products'])) return false;
$discount['min_price'] = $minPrice;
$discount['products'] = array_merge($discount['products']);
}
return $discount;
}
}

View File

@@ -0,0 +1,938 @@
<?php
declare (strict_types=1);
namespace app\services\activity\holiday;
use app\dao\activity\holiday\HolidayGiftPushDao;
use app\jobs\activity\HolidayGiftJob;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\BaseServices;
use app\services\user\UserServices;
use app\services\user\UserBillServices;
use app\services\user\UserMoneyServices;
use app\services\activity\coupon\CouponIssueServices;
use app\services\message\NoticeService;
use crmeb\exceptions\AdminException;
use crmeb\exceptions\ApiException;
use think\exception\ValidateException;
use think\facade\Log;
/**
* 节日有礼推送记录服务
* Class HolidayGiftPushServices
* @package app\services\activity
* @mixin HolidayGiftPushDao
*/
class HolidayGiftPushServices extends BaseServices
{
/**
* 构造方法
* HolidayGiftPushServices constructor.
* @param HolidayGiftPushDao $dao
*/
public function __construct(HolidayGiftPushDao $dao)
{
$this->dao = $dao;
}
/**
* 获取节日有礼推送记录列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftPushList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getHolidayGiftPushList($where, '*', $page, $limit);
$count = $this->dao->count($where);
if ($list) {
// 获取用户信息
$uids = array_column($list, 'uid');
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$userInfos = $userService->getUserList(['uid' => $uids], 'uid,nickname,avatar,phone');
$userInfos = array_combine(array_column($userInfos, 'uid'), $userInfos);
// 获取节日有礼活动信息
$giftIds = array_column($list, 'gift_id');
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
$giftInfos = $giftService->getHolidayGiftList(['id' => $giftIds])['list'];
$giftInfos = array_combine(array_column($giftInfos, 'id'), $giftInfos);
foreach ($list as &$item) {
$item['user_info'] = $userInfos[$item['uid']] ?? [];
$item['gift_info'] = $giftInfos[$item['gift_id']] ?? [];
}
}
return compact('list', 'count');
}
/**
* 获取节日有礼推送记录详情
* @param int $id
* @param string $field
* @return array|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftPushInfo(int $id, string $field = '*')
{
if (!$id) {
throw new AdminException('参数错误');
}
$info = $this->dao->get($id, $field);
if (!$info) {
throw new AdminException('推送记录不存在');
}
return $info;
}
/**
* 获取用户的节日有礼推送记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserPushRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$where = ['uid' => $uid];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getHolidayGiftPushList($where);
}
/**
* 获取用户今日的节日有礼推送记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserTodayPushRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$where = [
['uid', '=', $uid],
['push_time', '>', strtotime(date('Y-m-d'))]
];
if ($giftId) {
$where[] = ['gift_id', '=', $giftId];
}
return $this->dao->getHolidayGiftPushList($where);
}
/**
* 获取用户本周的节日有礼推送记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserWeekPushRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$weekStart = strtotime(date('Y-m-d', strtotime('this week Monday')));
$where = [
'uid' => $uid,
'push_time' => ['>=', $weekStart]
];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getHolidayGiftPushList($where);
}
/**
* 获取用户本月的节日有礼推送记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserMonthPushRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$monthStart = strtotime(date('Y-m-01'));
$where = [
'uid' => $uid,
'push_time' => ['>=', $monthStart]
];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getHolidayGiftPushList($where);
}
/**
* 更新节日有礼推送记录状态
* @param int $id 记录ID
* @param int $status 状态:0未读,1已读
* @return mixed
*/
public function updatePushStatus(int $id, int $status)
{
if (!$id) {
throw new AdminException('参数错误');
}
return $this->dao->update($id, ['status' => $status]);
}
/**
* 删除节日有礼推送记录
* @param int $id 记录ID
* @return mixed
*/
public function deletePush(int $id)
{
if (!$id) {
throw new AdminException('参数错误');
}
return $this->dao->update($id, []);
}
/**
* 批量删除节日有礼推送记录
* @param array $ids 记录ID数组
* @return mixed
*/
public function batchDeletePush(array $ids)
{
if (!$ids) {
throw new AdminException('参数错误');
}
return $this->dao->batchUpdate($ids, []);
}
/**
* 统计节日有礼推送数据
* @param array $where
* @return array
*/
public function getPushStatistics(array $where = [])
{
// 总推送人数
$totalUsers = $this->dao->distinct(true)->field('uid')->where($where)->count();
// 总推送次数
$totalPushes = $this->dao->count($where);
// 今日推送人数
$todayWhere = $where;
$todayWhere['push_time'] = ['>=', strtotime(date('Y-m-d'))];
$todayUsers = $this->dao->distinct(true)->field('uid')->where($todayWhere)->count();
// 今日推送次数
$todayPushes = $this->dao->count($todayWhere);
// 昨日推送人数
$yesterdayStart = strtotime(date('Y-m-d', strtotime('-1 day')));
$yesterdayEnd = strtotime(date('Y-m-d')) - 1;
$yesterdayWhere = $where;
$yesterdayWhere['push_time'] = ['between', [$yesterdayStart, $yesterdayEnd]];
$yesterdayUsers = $this->dao->distinct(true)->field('uid')->where($yesterdayWhere)->count();
// 昨日推送次数
$yesterdayPushes = $this->dao->count($yesterdayWhere);
// 本月推送人数
$monthWhere = $where;
$monthWhere['push_time'] = ['>=', strtotime(date('Y-m-01'))];
$monthUsers = $this->dao->distinct(true)->field('uid')->where($monthWhere)->count();
// 本月推送次数
$monthPushes = $this->dao->count($monthWhere);
// 按推送渠道统计
$channelStats = [];
$pushTypes = [1 => '短信', 2 => '公众号', 3 => '弹框广告'];
foreach ($pushTypes as $type => $name) {
$typeWhere = $where;
$typeWhere['push_type'] = $type;
$channelStats[$type] = [
'name' => $name,
'count' => $this->dao->count($typeWhere),
'users' => $this->dao->distinct(true)->field('uid')->where($typeWhere)->count()
];
}
return [
'total_users' => $totalUsers,
'total_pushes' => $totalPushes,
'today_users' => $todayUsers,
'today_pushes' => $todayPushes,
'yesterday_users' => $yesterdayUsers,
'yesterday_pushes' => $yesterdayPushes,
'month_users' => $monthUsers,
'month_pushes' => $monthPushes,
'channel_stats' => $channelStats
];
}
/**
* 检查用户是否可以推送节日有礼消息
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkUserCanPush(int $uid, array $giftInfo)
{
if (!$uid || !$giftInfo) {
return false;
}
// 获取用户推送记录
$pushRecords = $this->getUserPushRecord($uid, $giftInfo['id']);
if (!$pushRecords) {
return true; // 没有推送记录,可以推送
}
// 根据推送频次检查是否可以推送
switch ($giftInfo['push_frequency']) {
case 1: // 永久一次
return false; // 已经推送过,不能再推送
case 2: // 每次进入
return true; // 每次都可以推送
case 3: // 每天
// 检查今天是否已经推送过
$todayRecords = $this->getUserTodayPushRecord($uid, $giftInfo['id']);
return empty($todayRecords);
case 4: // 每月
// 检查本月是否已经推送过
$monthRecords = $this->getUserMonthPushRecord($uid, $giftInfo['id']);
return empty($monthRecords);
case 5: // 每周
// 检查今天是否是设置的星期几,以及本周是否已经推送过
$weekDay = date('N'); // 1-7 表示周一到周日
$weekDays = explode(',', $giftInfo['push_week_days']);
if (!in_array($weekDay, $weekDays)) {
return false; // 今天不是设置的星期几,不能推送
}
// 检查本周是否已经推送过
$weekRecords = $this->getUserWeekPushRecord($uid, $giftInfo['id']);
return empty($weekRecords);
default:
return false;
}
}
/**
* 检查用户是否在推送时段内
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkPushTimeRange(array $giftInfo)
{
// 如果是全时段,直接返回true
if ($giftInfo['push_time_type'] == 1) {
return true;
}
// 指定时段,需要检查当前时间是否在时段内
$now = date('H:i');
$startTime = $giftInfo['push_start_time'];
$endTime = $giftInfo['push_end_time'];
return $now >= $startTime && $now <= $endTime;
}
/**
* 用户是否包邮,双倍积分
* @return
* User: liusl
* DateTime: 2025/8/5 16:18
*/
public function receiveHolidayGift(int $uid, int $type)
{
//$type 1: 是否包邮 2: 双倍积分
$holidayGiftRecordServices = app()->make(HolidayGiftRecordServices::class);
//已经领取的活动
$recordList = $holidayGiftRecordServices->selectList(['uid' => $uid]);
if (!$recordList) {
return false;
}
$recordList = $recordList->toArray();
$integral_multiple = 0;
foreach ($recordList as $record) {
$giftInfo = json_decode($record['gift_content'], true);
// 检查json_decode是否失败
if ($giftInfo === null) {
continue;
}
if ($this->checkAdvancePush($giftInfo, true, $uid)) {
if ($type == 1) {
// 检查是否包含包邮权益(类型5)
if (isset($giftInfo['gift_type']) && in_array(5, $giftInfo['gift_type'])) {
return true;
}
} elseif ($type == 2) {
// 检查是否包含双倍积分权益(类型3)
if (isset($giftInfo['gift_type']) && in_array(3, $giftInfo['gift_type']) && $giftInfo['integral_multiple']) {
$integral_multiple = max($integral_multiple, (int)$giftInfo['integral_multiple']);
}
}
}
}
// 默认返回false
return $type == 2 && $integral_multiple ? $integral_multiple : false;
}
/**
* 检查是否需要提前推送或当天推送
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkAdvancePush(array $giftInfo, bool $is_advance = false, int $uid = 0)
{
if ($giftInfo['task_type'] == 2) {
// 根据活动日期类型检查
switch ($giftInfo['activity_date_type']) {
case 1: // 自定义日期
return $this->checkCustomDateAdvancePush($giftInfo, $is_advance);
case 2: // 每月
return $this->checkMonthlyAdvancePush($giftInfo, $is_advance);
case 3: // 每周
return $this->checkWeeklyAdvancePush($giftInfo, $is_advance);
default:
return false;
}
} else {
//生日
if ($giftInfo['task_type'] == 1) { // 生日任务
$user = app()->make(UserServices::class)->getUserCacheInfo($uid);
if(!$user['birthday']){
return false;
}
$today = strtotime(date('Y-m-d'));
$userBirthday = strtotime($user['birthday']);
// 如果 is_advance 为 false,需要判断提前推送
if (!$is_advance && $giftInfo['advance_push'] == 1 && $giftInfo['advance_days'] > 0) {
$advanceDays = (int)$giftInfo['advance_days'];
//生日当天 - 提前推送
if ($giftInfo['birthday_type'] == 1) {
$thisYearBirthday = strtotime(date('Y') . '-' . date('m-d', $userBirthday));
// 如果今年生日已过,计算明年生日
if ($thisYearBirthday < $today) {
$thisYearBirthday = strtotime((date('Y') + 1) . '-' . date('m-d', $userBirthday));
}
$advancePushDate = $thisYearBirthday - ($advanceDays * 24 * 60 * 60);
if ($today < $advancePushDate || $today > $thisYearBirthday) {
return false;
}
}
//生日当周 - 提前推送
elseif ($giftInfo['birthday_type'] == 2) {
$thisYearBirthday = strtotime(date('Y') . '-' . date('m-d', $userBirthday));
if ($thisYearBirthday < $today) {
$thisYearBirthday = strtotime((date('Y') + 1) . '-' . date('m-d', $userBirthday));
}
$birthdayWeekStart = strtotime('last monday', $thisYearBirthday);
$birthdayWeekEnd = strtotime('next sunday', $birthdayWeekStart);
$advancePushDate = $birthdayWeekStart - ($advanceDays * 24 * 60 * 60);
if ($today < $advancePushDate || $today > $birthdayWeekEnd) {
return false;
}
}
//生日当月 - 提前推送
elseif ($giftInfo['birthday_type'] == 3) {
$thisYearBirthday = strtotime(date('Y') . '-' . date('m-d', $userBirthday));
if ($thisYearBirthday < $today) {
$thisYearBirthday = strtotime((date('Y') + 1) . '-' . date('m-d', $userBirthday));
}
$birthdayMonth = date('Y-m', $thisYearBirthday);
$monthStart = strtotime($birthdayMonth . '-01');
$monthEnd = strtotime($birthdayMonth . '-' . date('t', $thisYearBirthday));
$advancePushDate = $monthStart - ($advanceDays * 24 * 60 * 60);
if ($today < $advancePushDate || $today > $monthEnd) {
return false;
}
}
} else {
// 原有逻辑:不提前推送
$todayMd = date('m-d');
$birthday = date('m-d', $userBirthday);
//生日当天
if ($giftInfo['birthday_type'] == 1) {
if ($todayMd != $birthday) {
return false;
}
}
//生日当周
elseif ($giftInfo['birthday_type'] == 2) {
$birthday = strtotime(date('Y').'-'.date('m-d', $userBirthday));
$birthdayWeekStart = strtotime('last monday', $birthday);
$birthdayWeekEnd = strtotime('next sunday', $birthdayWeekStart);
$todayTimestamp = strtotime('today');
if ($todayTimestamp <= $birthdayWeekStart || $todayTimestamp >= $birthdayWeekEnd) {
return false;
}
}
//生日当月
elseif ($giftInfo['birthday_type'] == 3) {
if (date('m') != date('m', $userBirthday)) {
return false;
}
}
}
}
}
return true;
}
/**
* 检查自定义日期的提前推送
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
private function checkCustomDateAdvancePush(array $giftInfo, $is_advance = false)
{
$currentDate = strtotime(date('Y-m-d'));
$startDate = $giftInfo['activity_start_date'];
$endDate = $giftInfo['activity_end_date'];
// 如果没有设置提前推送,检查是否在活动日期范围内
if (empty($giftInfo['advance_push']) || empty($giftInfo['advance_days']) || $is_advance) {
return $currentDate >= $startDate && $currentDate <= $endDate;
}
// 计算提前推送日期
$currentYear = date('Y');
$startTimestamp = strtotime($currentYear . '-' . $startDate);
$currentTimestamp = strtotime($currentYear . '-' . $currentDate);
// 如果开始日期已经过了今年,则计算明年的日期
if ($startTimestamp < $currentTimestamp) {
$startTimestamp = strtotime(($currentYear + 1) . '-' . $startDate);
}
// 计算天数差
$daysDiff = round(($startTimestamp - $currentTimestamp) / 86400);
// 检查是否符合提前推送天数或在活动期间
return $daysDiff == $giftInfo['advance_days'] || ($currentDate >= $startDate && $currentDate <= $endDate);
}
/**
* 检查每月活动的推送
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
private function checkMonthlyAdvancePush(array $giftInfo, bool $is_advance = false)
{
$currentDay = (int)date('d');
$monthDays = is_array($giftInfo['activity_month_days']) ? $giftInfo['activity_month_days'] : explode(',', $giftInfo['activity_month_days']);
$monthDays = array_map('intval', $monthDays);
// 如果没有设置提前推送,检查今天是否是活动日期
if (empty($giftInfo['advance_push']) || empty($giftInfo['advance_days']) || $is_advance) {
return in_array($currentDay, $monthDays);
}
// 检查是否有活动日期需要提前推送
foreach ($monthDays as $day) {
$daysDiff = $day - $currentDay;
if ($daysDiff < 0) {
// 如果活动日期已过,计算下个月的天数差
$daysInMonth = (int)date('t');
$daysDiff = $daysInMonth - $currentDay + $day;
}
if ($daysDiff == $giftInfo['advance_days']) {
return true;
}
}
// 检查今天是否是活动日期
return in_array($currentDay, $monthDays);
}
/**
* 检查每周活动的推送
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
private function checkWeeklyAdvancePush(array $giftInfo, bool $is_advance = false)
{
$currentWeekDay = (int)date('N'); // 1-7表示周一到周日
$weekDays = is_array($giftInfo['activity_week_days']) ? $giftInfo['activity_week_days'] : explode(',', $giftInfo['activity_week_days']);
$weekDays = array_map('intval', $weekDays);
// 如果没有设置提前推送,检查今天是否是活动日期
if (empty($giftInfo['advance_push']) || empty($giftInfo['advance_days']) || $is_advance) {
return in_array($currentWeekDay, $weekDays);
}
// 检查是否有活动日期需要提前推送
foreach ($weekDays as $weekDay) {
$daysDiff = $weekDay - $currentWeekDay;
if ($daysDiff < 0) {
// 如果活动日期已过,计算下周的天数差
$daysDiff = 7 + $daysDiff;
}
if ($daysDiff == $giftInfo['advance_days']) {
return true;
}
}
// 检查今天是否是活动日期
return in_array($currentWeekDay, $weekDays);
}
/**
* 检查用户是否符合节日有礼活动条件
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkUserCondition(int $uid, array $giftInfo)
{
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
return $giftService->checkUserCondition($uid, $giftInfo);
}
/**
* 保存用户节日有礼推送记录
* @param array $ids 活动ID数组
* @param int $uid 用户ID
* @param array $data 附加数据
* @return bool
*/
public function saveGiftPush(array $ids, int $uid, array $data = [])
{
if (!$ids) return true;
$saveData = [];
foreach ($ids as $id) {
$data['gift_id'] = $id;
$data['uid'] = $uid;
$data['push_time'] = $data['add_time'] = time();
$saveData[] = $data;
}
$this->dao->saveAll($saveData);
return true;
}
/**
* 定时任务处理节日有礼活动
* 获取当前活动日期的活动,判断给用户赠送积分、余额等权益
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function handleHolidayGiftTask()
{
try {
// 获取当前有效的节日有礼活动
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
$activeGifts = $giftService->getActiveHolidayGifts();
if (empty($activeGifts)) {
return true;
}
foreach ($activeGifts as $giftInfo) {
// 获取符合条件的用户并执行推送
$this->processGiftForUsers($giftInfo);
}
return true;
} catch (\Exception $e) {
// 记录错误日志
Log::error('节日有礼定时任务执行失败: ' . $e->getMessage());
return false;
}
}
/**
* 为符合条件的用户处理节日有礼
* @param array $giftInfo 节日有礼活动信息
* @return
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
private function processGiftForUsers(array $giftInfo)
{
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
// 获取符合条件的用户
$userIds = $this->getEligibleUsers($giftInfo);
if (empty($userIds)) {
return true;
}
$successCount = 0;
$failCount = 0;
// 将用户处理任务分发到队列中,避免大量用户时的性能问题
foreach ($userIds as $uid) {
// try {
// $this->grantUserBenefits($uid, $giftInfo);
// $this->recordPushLog($uid, $giftInfo);
// 分发到队列处理
HolidayGiftJob::dispatch('doJob', [$uid, $giftInfo]);
$successCount++;
// } catch (\Exception $e) {
// Log::error("用户{$uid}节日有礼队列分发失败: " . $e->getMessage());
// $failCount++;
// }
}
// 记录处理结果日志
Log::info("节日有礼活动[{$giftInfo['id']}]已分发到队列处理,总用户数:" . count($userIds) . ",成功分发:{$successCount}人,分发失败:{$failCount}人");
return [
'dispatched' => $successCount,
'dispatch_failed' => $failCount,
'total' => count($userIds)
];
}
/**
* 获取符合条件的用户ID列表
* @param array $giftInfo 节日有礼活动信息
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getEligibleUsers(array $giftInfo)
{
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$where = ['status' => 1, 'is_del' => 0];
// 如果是生日任务,需要特殊处理
if ($giftInfo['task_type'] == 1) { // 生日任务
$today = date('m-d');
if ($giftInfo['birthday_type'] == 1) { // 阳历生日
$where['birthday'] = ['like', '%' . $today];
}
}
$query = $userService->search($where)
->when($giftInfo['task_type'] == 2 && $giftInfo['push_user_type'] == 2, function ($query) use ($giftInfo) {
//自定义活动和指定人群
if ($giftInfo['condition_type'] == 1) {
//满足任一条件
} else {
//满足全部条件
$query->when($giftInfo['user_level'], function ($query) use ($giftInfo) {
$query->whereIn('level', $giftInfo['user_level']);
})->when($giftInfo['user_tag'], function ($query) use ($giftInfo) {
//1等级会员,2付费会员,3推广员,4采购商
if (in_array(1, $giftInfo['user_tag'])) {
$query->where('level', '>', 0);
}
if (in_array(2, $giftInfo['user_tag'])) {
$query->where(function ($query) {
$query->where(function ($query) {
$query->where('is_money_level', '>', 0)->where('overdue_time', '>', time());
})->whereOr(function ($query) {
$query->where('is_ever_level', '>', 0);
});
});
}
if (in_array(3, $giftInfo['user_tag'])) {
$query->where('is_promoter', 1);
}
if (in_array(4, $giftInfo['user_tag'])) {
$query->where('is_channel', 1);
}
})->when($giftInfo['user_label'], function ($query) use ($giftInfo) {
$user_label = $giftInfo['user_label'];
$query->whereIn('uid', function ($q) use ($user_label) {
$q->name('user_label_relation')->whereIn('label_id', $user_label)->field('uid')->select();
});
});
}
});
$users = $query->field('uid')->select()->toArray();
return array_column($users, 'uid');
}
/**
* 给用户赠送权益
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return array|bool
*/
public function grantUserBenefits(int $uid, array $giftInfo)
{
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$grantResults = true;
Log::info("开始为用户{$uid}赠送节日有礼权益,活动ID:{$giftInfo['id']}");
// 赠送积分
if (in_array(2, $giftInfo['gift_type'])) { // 积分
if (!empty($giftInfo['integral'])) {
$userInfo = $userService->getUserInfo($uid);
if ($userInfo) {
$newIntegral = bcadd((string)$userInfo['integral'], (string)$giftInfo['integral'], 0);
/** @var UserBillServices $userBillService */
$userBillService = app()->make(UserBillServices::class);
$billResult = $userBillService->income('holiday_gift_integral', $uid, (int)$giftInfo['integral'], (int)$newIntegral, 0, 0, '节日有礼赠送积分');
$updateResult = $userService->update($uid, ['integral' => $newIntegral]);
$integralSuccess = $billResult && $updateResult;
if ($integralSuccess) {
Log::info("用户{$uid}积分赠送成功,赠送积分:{$giftInfo['integral']}");
} else {
Log::error("用户{$uid}积分赠送失败,赠送积分:{$giftInfo['integral']}");
$grantResults = false;
}
} else {
Log::error("用户{$uid}不存在,积分赠送失败");
$grantResults = false;
}
}
}
// 赠送余额
if (in_array(4, $giftInfo['gift_type'])) { // 余额
if (!empty($giftInfo['balance'])) {
$userInfo = $userService->getUserInfo($uid);
if ($userInfo) {
$newMoney = bcadd((string)$userInfo['now_money'], (string)$giftInfo['balance'], 2);
/** @var UserMoneyServices $userMoneyService */
$userMoneyService = app()->make(UserMoneyServices::class);
$billResult = $userMoneyService->income('holiday_gift_money', $uid, $giftInfo['balance'], $newMoney, 0, '', '节日有礼赠送余额');
$updateResult = $userService->update($uid, ['now_money' => $newMoney]);
$moneySuccess = $billResult && $updateResult;
if ($moneySuccess) {
Log::info("用户{$uid}余额赠送成功,赠送余额:{$giftInfo['balance']}");
} else {
Log::error("用户{$uid}余额赠送失败,赠送余额:{$giftInfo['balance']}");
$grantResults = false;
}
} else {
Log::error("用户{$uid}不存在,余额赠送失败");
$grantResults = false;
}
}
}
// 赠送优惠券
if (in_array(1, $giftInfo['gift_type'])) {
if (!empty($giftInfo['coupon_ids'])) {
try {
/** @var StoreCouponIssueServices $issueService */
$issueService = app()->make(StoreCouponIssueServices::class);
$couponList = $issueService->search([])->whereIn('id', $giftInfo['coupon_ids'])->select()->toArray();
if ($couponList) {
/** @var StoreCouponIssueServices $storeCouponIssueServices */
$storeCouponIssueServices = app()->make(StoreCouponIssueServices::class);
$couponSuccess = true;
foreach ($couponList as $coupon) {
$res = $storeCouponIssueServices->setCoupon($coupon, [$uid], '', '', true);
if (!$res) {
$couponSuccess = false;
Log::error("用户{$uid}优惠券赠送失败,优惠券ID:{$coupon['id']}");
}
}
if ($couponSuccess) {
Log::info("用户{$uid}优惠券赠送成功,优惠券IDs:" . implode(',', $giftInfo['coupon_ids']));
} else {
$grantResults = false;
}
} else {
Log::error("用户{$uid}优惠券赠送失败,未找到有效优惠券");
$grantResults = false;
}
} catch (\Exception $e) {
Log::error("用户{$uid}优惠券赠送异常:" . $e->getMessage());
$grantResults = false;
}
}
}
if ($grantResults) {
Log::info("用户{$uid}节日有礼权益赠送完成,活动ID:{$giftInfo['id']}");
} else {
Log::error("用户{$uid}节日有礼权益赠送失败,活动ID:{$giftInfo['id']}");
}
return $grantResults;
}
/**
* 记录推送日志
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @param array $grantResults 赠送结果详情
* @return bool|\crmeb\basic\BaseModel|\think\Model
*/
public function recordPushLog(int $uid, array $giftInfo)
{
try {
// 记录详细的礼品记录到 holiday_gift_record 表
/** @var HolidayGiftRecordServices $recordService */
$recordService = app()->make(HolidayGiftRecordServices::class);
$recordData = [
'uid' => $uid,
'gift_id' => $giftInfo['id'],
'gift_type' => implode(',', $giftInfo['gift_type']),
'gift_content' => json_encode($giftInfo),
'receive_time' => time(),
'add_time' => time()
];
$recordResult = $recordService->dao->save($recordData);
if (!$recordResult) {
Log::error("节日有礼记录保存失败: 用户ID {$uid}, 活动ID {$giftInfo['id']}");
return false;
}
return $recordResult;
} catch (\Exception $e) {
Log::error("节日有礼记录保存异常: " . $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,414 @@
<?php
declare (strict_types=1);
namespace app\services\activity\holiday;
use app\dao\activity\holiday\HolidayGiftRecordDao;
use app\services\BaseServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\exceptions\ApiException;
use think\exception\ValidateException;
/**
* 节日有礼领取记录服务
* Class HolidayGiftRecordServices
* @package app\services\activity
* @mixin HolidayGiftRecordDao
*/
class HolidayGiftRecordServices extends BaseServices
{
/**
* 构造方法
* HolidayGiftRecordServices constructor.
* @param HolidayGiftRecordDao $dao
*/
public function __construct(HolidayGiftRecordDao $dao)
{
$this->dao = $dao;
}
/**
* 获取节日有礼领取记录列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftRecordList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getHolidayGiftRecordList($where, '*', $page, $limit);
$count = $this->dao->count($where);
if ($list) {
// 获取用户信息
$uids = array_column($list, 'uid');
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$userInfos = $userService->getUserList(['uid' => $uids], 'uid,nickname,avatar,phone');
$userInfos = array_combine(array_column($userInfos, 'uid'), $userInfos);
// 获取节日有礼活动信息
$giftIds = array_column($list, 'gift_id');
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
$giftInfos = $giftService->getHolidayGiftList(['id' => $giftIds])['list'];
$giftInfos = array_combine(array_column($giftInfos, 'id'), $giftInfos);
foreach ($list as &$item) {
$item['user_info'] = $userInfos[$item['uid']] ?? [];
$item['gift_info'] = $giftInfos[$item['gift_id']] ?? [];
}
}
return compact('list', 'count');
}
/**
* 获取节日有礼领取记录详情
* @param int $id
* @param string $field
* @return array|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftRecordInfo(int $id, string $field = '*')
{
if (!$id) {
throw new AdminException('参数错误');
}
$info = $this->dao->get($id, $field);
if (!$info) {
throw new AdminException('领取记录不存在');
}
return $info;
}
/**
* 获取用户领取的节日有礼记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID
* @return array|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserGiftRecord(int $uid, int $giftId)
{
if (!$uid || !$giftId) {
return null;
}
return $this->dao->getOne(['uid' => $uid, 'gift_id' => $giftId, 'is_del' => 0]);
}
/**
* 获取用户今日领取的节日有礼记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserTodayGiftRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$where = [
'uid' => $uid,
'is_del' => 0,
'receive_time' => ['>=', strtotime(date('Y-m-d'))]
];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getList($where);
}
/**
* 获取用户本周领取的节日有礼记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserWeekGiftRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$weekStart = strtotime(date('Y-m-d', strtotime('this week Monday')));
$where = [
'uid' => $uid,
'is_del' => 0,
'receive_time' => ['>=', $weekStart]
];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getList($where);
}
/**
* 获取用户本月领取的节日有礼记录
* @param int $uid 用户ID
* @param int $giftId 节日有礼活动ID,可选
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserMonthGiftRecord(int $uid, int $giftId = 0)
{
if (!$uid) {
return [];
}
$monthStart = strtotime(date('Y-m-01'));
$where = [
'uid' => $uid,
'is_del' => 0,
'receive_time' => ['>=', $monthStart]
];
if ($giftId) {
$where['gift_id'] = $giftId;
}
return $this->dao->getList($where);
}
/**
* 更新节日有礼领取记录状态
* @param int $id 记录ID
* @param int $status 状态:0未使用,1已使用
* @return mixed
*/
public function updateRecordStatus(int $id, int $status)
{
if (!$id) {
throw new AdminException('参数错误');
}
$data = ['status' => $status];
if ($status == 1) {
$data['use_time'] = time();
}
return $this->dao->update($id, $data);
}
/**
* 删除节日有礼领取记录
* @param int $id 记录ID
* @return mixed
*/
public function deleteRecord(int $id)
{
if (!$id) {
throw new AdminException('参数错误');
}
return $this->dao->update($id, ['is_del' => 1]);
}
/**
* 批量删除节日有礼领取记录
* @param array $ids 记录ID数组
* @return mixed
*/
public function batchDeleteRecord(array $ids)
{
if (!$ids) {
throw new AdminException('参数错误');
}
return $this->dao->batchUpdate($ids, ['is_del' => 1]);
}
/**
* 统计节日有礼领取数据
* @param array $where
* @return array
*/
public function getRecordStatistics(array $where = [])
{
// 总领取人数
$totalUsers = $this->dao->distinct(true)->field('uid')->where($where)->count();
// 总领取次数
$totalRecords = $this->dao->count($where);
// 今日领取人数
$todayWhere = $where;
$todayWhere['receive_time'] = ['>=', strtotime(date('Y-m-d'))];
$todayUsers = $this->dao->distinct(true)->field('uid')->where($todayWhere)->count();
// 今日领取次数
$todayRecords = $this->dao->count($todayWhere);
// 昨日领取人数
$yesterdayStart = strtotime(date('Y-m-d', strtotime('-1 day')));
$yesterdayEnd = strtotime(date('Y-m-d')) - 1;
$yesterdayWhere = $where;
$yesterdayWhere['receive_time'] = ['between', [$yesterdayStart, $yesterdayEnd]];
$yesterdayUsers = $this->dao->distinct(true)->field('uid')->where($yesterdayWhere)->count();
// 昨日领取次数
$yesterdayRecords = $this->dao->count($yesterdayWhere);
// 本月领取人数
$monthWhere = $where;
$monthWhere['receive_time'] = ['>=', strtotime(date('Y-m-01'))];
$monthUsers = $this->dao->distinct(true)->field('uid')->where($monthWhere)->count();
// 本月领取次数
$monthRecords = $this->dao->count($monthWhere);
return [
'total_users' => $totalUsers,
'total_records' => $totalRecords,
'today_users' => $todayUsers,
'today_records' => $todayRecords,
'yesterday_users' => $yesterdayUsers,
'yesterday_records' => $yesterdayRecords,
'month_users' => $monthUsers,
'month_records' => $monthRecords
];
}
/**
* 获取用户可用的多倍积分记录
* @param int $uid 用户ID
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserAvailableMultipleIntegral(int $uid)
{
if (!$uid) {
return [];
}
// 查询用户领取的多倍积分记录,且未使用的
$where = [
'uid' => $uid,
'is_del' => 0,
'status' => 0,
'gift_type' => ['like', '%3%'] // 包含多倍积分类型
];
$list = $this->dao->getList($where);
if (!$list) {
return [];
}
$now = time();
$result = [];
// 获取节日有礼活动信息
$giftIds = array_column($list, 'gift_id');
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
$giftInfos = $giftService->getHolidayGiftList(['id' => $giftIds])['list'];
$giftInfos = array_combine(array_column($giftInfos, 'id'), $giftInfos);
foreach ($list as $item) {
$giftInfo = $giftInfos[$item['gift_id']] ?? null;
if (!$giftInfo) {
continue;
}
// 检查活动是否有效
if ($giftInfo['status'] != 1 || $giftInfo['is_del'] == 1) {
continue;
}
// 检查活动时间(如果不是永久活动)
if ((!isset($giftInfo['is_permanent']) || $giftInfo['is_permanent'] != 1) &&
($now < $giftInfo['start_time'] || $now > $giftInfo['end_time'])) {
continue;
}
// 添加到结果中
$result[] = [
'record_id' => $item['id'],
'gift_id' => $item['gift_id'],
'gift_name' => $giftInfo['name'],
'integral_multiple' => $giftInfo['integral_multiple'],
'receive_time' => $item['receive_time'],
'end_time' => $giftInfo['end_time']
];
}
return $result;
}
/**
* 获取用户可用的全场包邮记录
* @param int $uid 用户ID
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserAvailableFreeShipping(int $uid)
{
if (!$uid) {
return [];
}
// 查询用户领取的全场包邮记录,且未使用的
$where = [
'uid' => $uid,
'is_del' => 0,
'status' => 0,
'gift_type' => ['like', '%5%'] // 包含全场包邮类型
];
$list = $this->dao->getList($where);
if (!$list) {
return [];
}
$now = time();
$result = [];
// 获取节日有礼活动信息
$giftIds = array_column($list, 'gift_id');
/** @var HolidayGiftServices $giftService */
$giftService = app()->make(HolidayGiftServices::class);
$giftInfos = $giftService->getHolidayGiftList(['id' => $giftIds])['list'];
$giftInfos = array_combine(array_column($giftInfos, 'id'), $giftInfos);
foreach ($list as $item) {
$giftInfo = $giftInfos[$item['gift_id']] ?? null;
if (!$giftInfo) {
continue;
}
// 检查活动是否有效
if ($giftInfo['status'] != 1 || $giftInfo['is_del'] == 1) {
continue;
}
// 检查活动时间(如果不是永久活动)
if ((!isset($giftInfo['is_permanent']) || $giftInfo['is_permanent'] != 1) &&
($now < $giftInfo['start_time'] || $now > $giftInfo['end_time'])) {
continue;
}
// 添加到结果中
$result[] = [
'record_id' => $item['id'],
'gift_id' => $item['gift_id'],
'gift_name' => $giftInfo['name'],
'receive_time' => $item['receive_time'],
'end_time' => isset($giftInfo['is_permanent']) && $giftInfo['is_permanent'] == 1 ? 0 : $giftInfo['end_time']
];
}
return $result;
}
}

View File

@@ -0,0 +1,747 @@
<?php
declare (strict_types=1);
namespace app\services\activity\holiday;
use app\dao\activity\holiday\HolidayGiftDao;
use app\model\activity\holiday\HolidayGift;
use app\services\BaseServices;
use app\services\product\product\StoreProductCouponServices;
use app\services\user\label\UserLabelRelationServices;
use app\services\user\label\UserLabelServices;
use app\services\user\level\UserLevelServices;
use app\services\user\UserServices;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\activity\coupon\StoreCouponUserServices;
use app\services\user\UserBillServices;
use crmeb\exceptions\AdminException;
use crmeb\exceptions\ApiException;
use think\exception\ValidateException;
use think\facade\Db;
/**
* 节日有礼服务
* Class HolidayGiftServices
* @package app\services\activity
* @mixin HolidayGiftDao
*/
class HolidayGiftServices extends BaseServices
{
/**
* 构造方法
* HolidayGiftServices constructor.
* @param HolidayGiftDao $dao
*/
public function __construct(HolidayGiftDao $dao)
{
$this->dao = $dao;
}
/**
* 获取节日有礼列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getHolidayGiftList($where, '*', $page, $limit);
$count = $this->dao->count($where);
foreach ($list as &$item) {
$item['start_time'] = date('Y-m-d H:i:s', $item['start_time']);
$item['end_time'] = date('Y-m-d H:i:s', $item['end_time']);
// 使用辅助方法转换数值为文本描述
$this->formatHolidayGiftData($item);
}
return compact('list', 'count');
}
/**
* 获取节日有礼详情
* @param int $id
* @param string $field
* @return array|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getHolidayGiftInfo(int $id, array $field = ['*'])
{
if (!$id) {
throw new AdminException('参数错误');
}
$info = $this->dao->get($id, $field);
if (!$info) {
throw new AdminException('节日有礼不存在');
}
$info = $info->toArray();
if ($info['is_permanent'] == 0) {
$start_time = $info['start_time'] ? date('Y-m-d H:i:s', (int)$info['start_time']) : '';
$end_time = $info['start_time'] ? date('Y-m-d H:i:s', (int)$info['end_time']) : '';
$info['start_time'] = [$start_time, $end_time];
}
$info['activity_date'] = [];
if ($info['activity_date_type'] == 1) {
$activity_start_date = $info['activity_start_date'] ? date('Y-m-d H:i:s', (int)$info['activity_start_date']) : '';
$activity_end_date = $info['activity_end_date'] ? date('Y-m-d H:i:s', (int)$info['activity_end_date']) : '';
if ($activity_start_date && $activity_end_date) {
$info['activity_date'] = [
$activity_start_date,
$activity_end_date
];
}
}
$coupon_ids = $info['coupon_ids'];
$user_label = $info['user_label'];
if ($coupon_ids) {
$info['coupon'] = app()->make(StoreCouponIssueServices::class)->search([])->whereIn('id', $coupon_ids)->field('id,coupon_title as title')->select()->toArray();
}
if ($user_label) {
$info['label'] = app()->make(UserLabelServices::class)->search([])->whereIn('id', $user_label)->field('id,label_name')->select()->toArray();
}
return $info;
}
/**
* 格式化节日有礼数据,添加文本描述
* @param array &$data 节日有礼数据
* @return void
*/
protected function formatHolidayGiftData(array &$data)
{
// 基本信息
if (isset($data['task_type'])) {
$data['task_type_text'] = HolidayGift::TASK_TYPE[$data['task_type']] ?? '';
}
if (isset($data['birthday_type'])) {
$data['birthday_type_text'] = HolidayGift::BIRTHDAY_TYPE[$data['birthday_type']] ?? '';
}
if (isset($data['status'])) {
$data['status_text'] = HolidayGift::STATUS[$data['status']] ?? '';
}
if (isset($data['is_permanent'])) {
$data['is_permanent_text'] = HolidayGift::IS_PERMANENT[$data['is_permanent']] ?? '';
}
// 推送相关
if (isset($data['push_time_type'])) {
$data['push_time_type_text'] = HolidayGift::PUSH_TIME_TYPE[$data['push_time_type']] ?? '';
}
if (isset($data['push_user_type'])) {
$data['push_user_type_text'] = HolidayGift::PUSH_USER_TYPE[$data['push_user_type']] ?? '';
}
if (isset($data['push_frequency'])) {
$data['push_frequency_text'] = HolidayGift::PUSH_FREQUENCY[$data['push_frequency']] ?? '';
}
if (isset($data['advance_push'])) {
$data['advance_push_text'] = HolidayGift::ADVANCE_PUSH[$data['advance_push']] ?? '';
}
if (isset($data['push_status'])) {
$data['push_status_text'] = HolidayGift::PUSH_STATUS[$data['push_status']] ?? '';
}
// 条件和礼品相关
if (isset($data['condition_type'])) {
$data['condition_type_text'] = HolidayGift::CONDITION_TYPE[$data['condition_type']] ?? '';
}
if (isset($data['gift_type'])) {
if (is_array($data['gift_type'])) {
$data['gift_type_text'] = [];
foreach ($data['gift_type'] as $type) {
$data['gift_type_text'][] = HolidayGift::GIFT_TYPE[$type] ?? '';
}
} else {
$data['gift_type_text'] = HolidayGift::GIFT_TYPE[$data['gift_type']] ?? '';
}
}
// 渠道和页面相关
if (isset($data['push_channel'])) {
if (is_array($data['push_channel'])) {
$data['push_channel_text'] = [];
foreach ($data['push_channel'] as $channel) {
$data['push_channel_text'][] = HolidayGift::PUSH_CHANNEL[$channel] ?? '';
}
} else {
$data['push_channel_text'] = HolidayGift::PUSH_CHANNEL[$data['push_channel']] ?? '';
}
}
if (isset($data['show_page'])) {
if (is_array($data['show_page'])) {
$data['show_page_text'] = [];
foreach ($data['show_page'] as $page) {
$data['show_page_text'][] = HolidayGift::SHOW_PAGE[$page] ?? '';
}
} else {
$data['show_page_text'] = HolidayGift::SHOW_PAGE[$data['show_page']] ?? '';
}
}
}
/**
* 保存节日有礼数据
* @param array $data
* @return mixed
*/
public function saveHolidayGift(array $data)
{
// 数据验证
$this->checkHolidayGiftData($data);
$data['add_time'] = time();
$data['update_time'] = time();
return $this->dao->save($data);
}
/**
* 更新节日有礼数据
* @param int $id
* @param array $data
* @return mixed
*/
public function updateHolidayGift(int $id, array $data)
{
// 数据验证
$this->checkHolidayGiftData($data);
$data['update_time'] = time();
return $this->dao->update($id, $data);
}
/**
* 删除节日有礼
* @param int $id
* @return mixed
*/
public function deleteHolidayGift(int $id)
{
if (!$id) {
throw new AdminException('参数错误');
}
return $this->dao->update($id, ['is_del' => 1]);
}
/**
* 修改节日有礼状态
* @param int $id
* @param int $status
* @return mixed
*/
public function setStatus(int $id, int $status)
{
if (!$id) {
throw new AdminException('参数错误');
}
return $this->dao->update($id, ['status' => $status]);
}
/**
* 验证节日有礼数据
* @param array $data
* @return bool
*/
public function checkHolidayGiftData(array $data)
{
// 验证活动名称
if (!isset($data['name']) || !$data['name']) {
throw new AdminException('请输入活动名称');
}
// 验证活动时间(如果不是永久活动)
if (!isset($data['is_permanent']) || $data['is_permanent'] != 1) {
if (!isset($data['start_time']) || !$data['start_time']) {
throw new AdminException('请选择开始时间');
}
if (!isset($data['end_time']) || !$data['end_time']) {
throw new AdminException('请选择结束时间');
}
if ($data['start_time'] >= $data['end_time']) {
throw new AdminException('结束时间必须大于开始时间');
}
}
// 验证任务类型
if (!isset($data['task_type']) || !in_array($data['task_type'], array_keys(HolidayGift::TASK_TYPE))) {
throw new AdminException('请选择正确的任务类型');
}
// 根据任务类型验证相关数据
if ($data['task_type'] == array_search('用户生日', HolidayGift::TASK_TYPE)) { // 用户生日
if (!isset($data['birthday_type']) || !in_array((int)$data['birthday_type'], array_keys(HolidayGift::BIRTHDAY_TYPE))) {
throw new AdminException('请选择正确的生日类型');
}
} else { // 活动日期
// 验证活动日期类型
if (!isset($data['activity_date_type']) || !in_array((int)$data['activity_date_type'], array_keys(HolidayGift::ACTIVITY_DATE_TYPE))) {
throw new AdminException('请选择正确的活动日期类型');
}
// 根据活动日期类型验证相关数据
switch ($data['activity_date_type']) {
case 1: // 自定义日期
if (!isset($data['activity_start_date']) || !$data['activity_start_date']) {
throw new AdminException('请选择活动开始日期');
}
if (!isset($data['activity_end_date']) || !$data['activity_end_date']) {
throw new AdminException('请选择活动结束日期');
}
break;
case 2: // 每月
if (!isset($data['activity_month_days']) || (is_array($data['activity_month_days']) && empty($data['activity_month_days'])) || !$data['activity_month_days']) {
throw new AdminException('请选择每月活动日期');
}
// 验证月份日期范围 1-31
$monthDays = is_array($data['activity_month_days']) ? $data['activity_month_days'] : explode(',', $data['activity_month_days']);
foreach ($monthDays as $day) {
if (!is_numeric($day) || $day < 1 || $day > 31) {
throw new AdminException('每月活动日期必须在1-31之间');
}
}
break;
case 3: // 每周
if (!isset($data['activity_week_days']) || (is_array($data['activity_week_days']) && empty($data['activity_week_days'])) || !$data['activity_week_days']) {
throw new AdminException('请选择每周活动日期');
}
// 验证周几范围 1-7
$weekDays = is_array($data['activity_week_days']) ? $data['activity_week_days'] : explode(',', $data['activity_week_days']);
foreach ($weekDays as $day) {
if (!is_numeric($day) || $day < 1 || $day > 7) {
throw new AdminException('每周活动日期必须在1-7之间(1表示周一,7表示周日)');
}
}
break;
}
}
// 验证提前推送
if (isset($data['advance_push']) && $data['advance_push']) {
if (!isset($data['advance_days']) || $data['advance_days'] <= 0) {
throw new AdminException('请输入正确的提前推送天数');
}
}
// 验证推送时段
if (isset($data['push_time_type']) && $data['push_time_type'] == array_search('指定时段', HolidayGift::PUSH_TIME_TYPE)) {
if (!isset($data['push_start_time']) || !$data['push_start_time']) {
throw new AdminException('请选择推送开始时间');
}
if (!isset($data['push_end_time']) || !$data['push_end_time']) {
throw new AdminException('请选择推送结束时间');
}
// 验证时间格式 HH:mm:ss 或 HH:mm
if (!preg_match('/^([0-1][0-9]|2[0-3]):([0-5][0-9])(:[0-5][0-9])?$/', $data['push_start_time']) ||
!preg_match('/^([0-1][0-9]|2[0-3]):([0-5][0-9])(:[0-5][0-9])?$/', $data['push_end_time'])) {
throw new AdminException('推送时间格式不正确,应为HH:mm:ss或HH:mm格式');
}
}
// 验证推送人群
if (isset($data['push_user_type']) && $data['push_user_type'] == array_search('指定人群', HolidayGift::PUSH_USER_TYPE)) {
if ((!isset($data['user_level']) || !$data['user_level']) &&
(!isset($data['user_label']) || (is_array($data['user_label']) && empty($data['user_label'])) || !$data['user_label']) &&
(!isset($data['user_tag']) || (is_array($data['user_tag']) && empty($data['user_tag'])) || !$data['user_tag'])) {
throw new AdminException('请至少选择一种用户筛选条件');
}
}
// 验证赠送内容
// if (!isset($data['gift_type']) || !$data['gift_type']) {
// throw new AdminException('请选择赠送内容');
// }
$giftTypes = is_array($data['gift_type']) ? $data['gift_type'] : explode(',', $data['gift_type']);
foreach ($giftTypes as $type) {
if ($type == array_search('优惠券', HolidayGift::GIFT_TYPE) && (!isset($data['coupon_ids']) || (is_array($data['coupon_ids']) && empty($data['coupon_ids'])) || !$data['coupon_ids'])) {
throw new AdminException('请选择赠送的优惠券');
}
if ($type == array_search('积分', HolidayGift::GIFT_TYPE) && (!isset($data['integral']) || $data['integral'] <= 0)) {
throw new AdminException('请输入正确的赠送积分数量');
}
if ($type == array_search('多倍积分', HolidayGift::GIFT_TYPE) && (!isset($data['integral_multiple']) || $data['integral_multiple'] <= 1)) {
throw new AdminException('请输入正确的积分倍数,必须大于1');
}
if ($type == array_search('余额', HolidayGift::GIFT_TYPE) && (!isset($data['balance']) || $data['balance'] <= 0)) {
throw new AdminException('请输入正确的赠送余额');
}
}
// 验证推送渠道
if (!isset($data['push_channel']) || (is_array($data['push_channel']) && empty($data['push_channel'])) || !$data['push_channel']) {
throw new AdminException('请选择推送渠道');
}
$pushChannels = is_array($data['push_channel']) ? $data['push_channel'] : explode(',', $data['push_channel']);
foreach ($pushChannels as $channel) {
if ($channel == array_search('公众号', HolidayGift::PUSH_CHANNEL) && (!isset($data['wechat_image']) || (is_array($data['wechat_image']) && empty($data['wechat_image'])) || !$data['wechat_image'])) {
throw new AdminException('请上传公众号推送图片');
}
}
// 验证推送频次
if (!isset($data['push_frequency']) || !in_array((int)$data['push_frequency'], array_keys(HolidayGift::PUSH_FREQUENCY))) {
throw new AdminException('请选择正确的推送频次');
}
if ($data['push_frequency'] == array_search('每周', HolidayGift::PUSH_FREQUENCY) && (!isset($data['push_week_days']) || (is_array($data['push_week_days']) && empty($data['push_week_days'])) || !$data['push_week_days'])) {
throw new AdminException('请选择每周推送的星期几');
}
// 验证应用界面
if (!isset($data['show_page']) || (is_array($data['show_page']) && empty($data['show_page'])) || !$data['show_page']) {
throw new AdminException('请选择应用界面');
}
$showPages = is_array($data['show_page']) ? $data['show_page'] : explode(',', $data['show_page']);
foreach ($showPages as $page) {
if ($page == array_search('专题页面', HolidayGift::SHOW_PAGE) && (!isset($data['topic_ids']) || (is_array($data['topic_ids']) && empty($data['topic_ids'])) || !$data['topic_ids'])) {
throw new AdminException('请选择专题页面');
}
}
return true;
}
/**
* 获取活动中的节日有礼
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getActiveHolidayGift(array $where = [])
{
return $this->dao->getActiveHolidayGift($where);
}
/**
* 获取用户生日相关的节日有礼活动
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getBirthdayHolidayGift(array $where = [])
{
return $this->dao->getBirthdayHolidayGift($where);
}
/**
* 获取活动日期相关的节日有礼活动
* @param string $date 日期,格式:MM-DD
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCustomDateHolidayGift(string $date, array $where = [])
{
return $this->dao->getCustomDateHolidayGift($date, $where);
}
/**
* 获取每月活动日期相关的节日有礼活动
* @param int $day 日期,1-31
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getMonthlyHolidayGift(int $day, array $where = [])
{
return $this->dao->getMonthlyHolidayGift($day, $where);
}
/**
* 获取每周活动日期相关的节日有礼活动
* @param int $weekDay 周几,1-7表示周一到周日
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getWeeklyHolidayGift(int $weekDay, array $where = [])
{
return $this->dao->getWeeklyHolidayGift($weekDay, $where);
}
/**
* 获取当前日期相关的所有节日有礼活动
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getTodayHolidayGift(array $where = [])
{
$activities = [];
$today = date('m-d'); // MM-DD格式
$todayDay = (int)date('d'); // 当前日期
$todayWeek = (int)date('N'); // 当前周几,1-7表示周一到周日
// 获取自定义日期活动
$customDateActivities = $this->getCustomDateHolidayGift($today, $where);
$activities = array_merge($activities, $customDateActivities);
// 获取每月活动
$monthlyActivities = $this->getMonthlyHolidayGift($todayDay, $where);
$activities = array_merge($activities, $monthlyActivities);
// 获取每周活动
$weeklyActivities = $this->getWeeklyHolidayGift($todayWeek, $where);
$activities = array_merge($activities, $weeklyActivities);
// 去重(根据ID)
$uniqueActivities = [];
$ids = [];
foreach ($activities as $activity) {
if (!in_array($activity['id'], $ids)) {
$uniqueActivities[] = $activity;
$ids[] = $activity['id'];
}
}
return $uniqueActivities;
}
/**
* 检查用户是否符合节日有礼活动条件
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkUserCondition(int $uid, array $giftInfo)
{
// 如果是全部人群,直接返回true
if ($giftInfo['push_user_type'] == 1) {
return true;
}
// 指定人群,需要检查条件
$isLevelMatch = $isLabelMatch = $isTagMatch = true;
// 检查用户等级
if (!empty($giftInfo['user_level'])) {
$isLevelMatch = false;
$userLevelService = app()->make(UserLevelServices::class);
$userLevel = $userLevelService->getUserLevel($uid);
if ($userLevel) {
$levelIds = is_string($giftInfo['user_level']) ? explode(',', $giftInfo['user_level']) : $giftInfo['user_level'];
$isLevelMatch = in_array($userLevel['level_id'], $levelIds);
}
}
// 检查用户标签
if (!empty($giftInfo['user_label'])) {
$isLabelMatch = false;
$userLabelRelationService = app()->make(UserLabelRelationServices::class);
$userLabels = $userLabelRelationService->getUserLabels($uid);
if ($userLabels) {
$labelIds = is_string($giftInfo['user_label']) ? explode(',', $giftInfo['user_label']) : $giftInfo['user_label'];
$isLabelMatch = (bool)empty(array_diff($labelIds, $userLabels));
}
}
// 检查客户身份
if (!empty($giftInfo['user_tag'])) {
$isTagMatch = false;
$userService = app()->make(UserServices::class);
$userTag = $userService->checkUserTag($uid);
if ($userTag) {
$tagIds = is_string($giftInfo['user_tag']) ? explode(',', $giftInfo['user_tag']) : $giftInfo['user_tag'];
$isTagMatch = (bool)empty(array_diff($tagIds, $userTag));
}
}
// 根据条件满足类型返回结果
if ($giftInfo['condition_type'] == 1) { // 满足任一条件
return $isLevelMatch || $isLabelMatch || $isTagMatch;
} else { // 满足全部条件
return $isLevelMatch && $isLabelMatch && $isTagMatch;
}
}
/**
* 检查弹框广告是否符合节日有礼活动条件
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkPopupAdCondition(int $uid, array $giftInfo)
{
// 检查推送渠道是否包含弹框广告
$pushChannels = is_array($giftInfo['push_channel']) ? $giftInfo['push_channel'] : explode(',', $giftInfo['push_channel']);
$popupAdChannel = 3;
if (!in_array($popupAdChannel, $pushChannels)) {
return false;
}
// 根据推送频次检查是否可以推送
/** @var HolidayGiftPushServices $pushService */
$pushService = app()->make(HolidayGiftPushServices::class);
switch ($giftInfo['push_frequency']) {
case 1: // 永久一次
// 检查是否已经推送过
$pushRecord = $pushService->getUserPushRecord($uid, $giftInfo['id']);
return empty($pushRecord);
case 2: // 每次进入
return true; // 每次都可以推送
case 3: // 每天
// 检查今天是否已经推送过
$todayRecord = $pushService->getUserTodayPushRecord($uid, $giftInfo['id']);
return empty($todayRecord);
case 4: // 每月
// 检查本月是否已经推送过
$monthRecord = $pushService->getUserMonthPushRecord($uid, $giftInfo['id']);
return empty($monthRecord);
case 5: // 每周
// 检查今天是否是设置的星期几
$weekDay = date('N'); // 1-7 表示周一到周日
$weekDays = explode(',', $giftInfo['push_week_days']);
if (!in_array($weekDay, $weekDays)) {
return false; // 今天不是设置的星期几,不能推送
}
// 检查本周是否已经推送过
$weekRecord = $pushService->getUserWeekPushRecord($uid, $giftInfo['id']);
return empty($weekRecord);
default:
return false;
}
}
/**
* 检查用户是否在推送时段内
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function checkPushTimeRange(array $giftInfo, int $uid = 0)
{
// 如果是全时段,直接返回true
if ($giftInfo['push_time_type'] == 1) {
return true;
}
// 指定时段,需要检查当前时间是否在时段内
$now = date('H:i');
$startTime = $giftInfo['push_start_time'];
$endTime = $giftInfo['push_end_time'];
return $now >= $startTime && $now <= $endTime;
}
/**
* 获取当前有效的节日有礼活动
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getActiveHolidayGifts()
{
$currentTime = time();
$where = [
'status' => 1,
'is_del' => 0,
'is_valid' => 1,
];
$list = $this->dao->search($where)
->select()
->toArray();
return $list;
}
/**
* 推送节日有礼消息
* @param int $uid 用户ID
* @param array $giftInfo 节日有礼活动信息
* @return bool
*/
public function pushHolidayGiftMessage(int $uid, array $giftInfo)
{
if (!$uid || !$giftInfo) {
return false;
}
// 获取用户信息
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$userInfo = $userService->getUserInfo($uid);
if (!$userInfo) {
return false;
}
// 准备推送记录数据
$pushData = [
'gift_id' => $giftInfo['id'],
'uid' => $uid,
'push_time' => time(),
'status' => 0,
'add_time' => time()
];
// 处理不同推送渠道
$pushChannels = explode(',', $giftInfo['push_channel']);
$pushSuccess = false;
/** @var HolidayGiftPushServices $pushService */
$pushService = app()->make(HolidayGiftPushServices::class);
/** @var NoticeServices $noticeService */
$noticeService = app()->make(NoticeServices::class);
foreach ($pushChannels as $channel) {
switch ($channel) {
case 1: // 短信
if (!empty($userInfo['phone'])) {
$pushData['push_type'] = 1;
$pushData['push_content'] = '尊敬的用户,您有一份节日礼物待领取,请登录APP查看。';
// 发送短信
$noticeService->smsNotice($userInfo['phone'], ['gift_name' => $giftInfo['name']], 'HOLIDAY_GIFT');
$pushData['status'] = 1;
$pushService->save($pushData);
$pushSuccess = true;
}
break;
case 2: // 公众号
if (!empty($userInfo['openid'])) {
$pushData['push_type'] = 2;
$pushData['push_content'] = '尊敬的用户,您有一份节日礼物待领取,请登录APP查看。';
// 发送公众号消息
$noticeService->wechatNotice($userInfo['openid'], 'HOLIDAY_GIFT', [
'first' => '节日有礼提醒',
'keyword1' => $giftInfo['name'],
'keyword2' => date('Y-m-d H:i:s'),
'remark' => '点击领取您的节日礼物'
], '');
$pushData['status'] = 1;
$pushService->save($pushData);
$pushSuccess = true;
}
break;
case 3: // 弹框广告
$pushData['push_type'] = 3;
$pushData['push_content'] = '弹框广告推送';
$pushData['status'] = 1;
$pushService->save($pushData);
$pushSuccess = true;
break;
}
}
return $pushSuccess;
}
}

View File

@@ -0,0 +1,120 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\integral;
use app\dao\other\CategoryDao;
use app\services\BaseServices;
use app\services\other\CategoryServices;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 积分商品
* Class StoreIntegralServices
* @package app\services\activity\integral
* @mixin CategoryDao
*/
class StoreIntegralCategoryServices extends BaseServices
{
/**
* @var CategoryDao
*/
#[Inject]
protected CategoryDao $dao;
/**
* 分类类型
* @var int
*/
protected int $group = CategoryServices::INTEGRAL_CATEGORY_GROUP;
/**
* 获取积分分类列表
* @param array $where 查询条件
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$count = $this->dao->count($where);
$list = $this->dao->getCateList($where, $page, $limit);
if ($list) {
foreach ($list as &$item) {
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
}
return compact('list', 'count');
}
/**
* 创建新增表单
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm()
{
return create_form('添加分类', $this->form(), Url::buildUrl('/marketing/integral/category'), 'POST');
}
/**
* 创建编辑表单
* @param $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function editForm(int $id)
{
$info = $this->dao->get($id);
return create_form('编辑分类', $this->form($info), $this->url('/marketing/integral/category/' . $id), 'POST');
}
/**
* 生成表单参数
* @param array $info
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function form($info = [])
{
$f[] = Form::input('name', '分类名称:', $info['name'] ?? '')->maxlength(30)->required();
$f[] = Form::number('integral_min', '最低积分:', (int)($info['integral_min'] ?? 0))->min(0);
$f[] = Form::number('integral_max', '最高积分:', (int)($info['integral_max'] ?? 0))->min(0);
$f[] = Form::radio('is_show', '状态:', $info['is_show'] ?? 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]]);
$f[] = Form::number('sort', '排序:', (int)($info['sort'] ?? 0))->min(0);
return $f;
}
/**
* 获取积分分类树形结构列表
* @return array
*/
public function getTreeList()
{
$where = [
'is_show' => 1,
'group' => CategoryServices::INTEGRAL_CATEGORY_GROUP,
];
$list = $this->dao->search($where)->field(['name', 'integral_min', 'integral_max'])->order('sort DESC')->select()->toArray();
$data = [];
foreach ($list as $item) {
$data[] = [
'label' => $item['name'],
'value' => "{$item['integral_min']}-{$item['integral_max']}"
];
}
return $data;
}
}

View File

@@ -0,0 +1,213 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\integral;
use app\dao\activity\integral\StoreIntegralOrderDao;
use app\services\BaseServices;
use app\services\user\UserServices;
use app\services\user\UserBillServices;
use crmeb\services\SystemConfigService;
use crmeb\utils\Arr;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Log;
/**
* Class StoreIntegralOrderServices
* @package app\services\activity\integral
* @mixin StoreIntegralOrderDao
*/
class StoreIntegralOrderServices extends BaseServices
{
/**
* @var StoreIntegralOrderDao
*/
#[Inject]
protected StoreIntegralOrderDao $dao;
/**
* 获取列表
* @param array $where
* @param array $field
* @param array $with
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getOrderList(array $where, array $field = ['*'], array $with = [])
{
[$page, $limit] = $this->getPageValue();
$data = $this->dao->getOrderList($where, $field, $page, $limit, $with);
$count = $this->dao->count($where);
$data = $this->tidyOrderList($data);
$batch_url = "file/upload/1";
return compact('data', 'count', 'batch_url');
}
/**
* 获取导出数据
* @param array $where
* @param int $limit
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getExportList(array $where, int $limit = 0)
{
if ($limit) {
[$page] = $this->getPageValue();
} else {
[$page, $limit] = $this->getPageValue();
}
$data = $this->dao->getOrderList($where, ['*'], $page, $limit);
$data = $this->tidyOrderList($data);
return $data;
}
/**
* 前端订单列表
* @param array $where
* @param array|string[] $field
* @param array $with
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getOrderApiList(array $where, array $field = ['*'], array $with = [])
{
[$page, $limit] = $this->getPageValue();
$data = $this->dao->getOrderList($where, $field, $page, $limit, $with);
$data = $this->tidyOrderList($data);
return $data;
}
/**
* 订单详情数据格式化
* @param $order
* @return mixed
*/
public function tidyOrder($order)
{
$order['_add_time'] = $order['add_time'] = date('Y-m-d H:i:s', $order['add_time']);
if ($order['status'] == 1) {
$order['status_name'] = '未发货';
} else if ($order['status'] == 2) {
$order['status_name'] = '待收货';
} else if ($order['status'] == 3) {
$order['status_name'] = '已完成';
}
return $order;
}
/**
* 数据转换
* @param array $data
* @return array
*/
public function tidyOrderList(array $data)
{
foreach ($data as &$item) {
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
if (!$item['paid']) {
$item['status_name'] = '待付款';
} else if ($item['status'] == 1) {
$item['status_name'] = '未发货';
} else if ($item['status'] == 2) {
$item['status_name'] = '待收货';
} else if ($item['status'] == 3) {
$item['status_name'] = '已完成';
}
}
return $data;
}
/**
* 未支付积分订单退积分
* @return bool|void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function rebatePointsOrders()
{
//废弃 积分订单合并入统一下单流程
//先完成支付 后在支付积分
return true;
$where['is_del'] = 0;
$where['is_system_del'] = 0;
$where['paid'] = 0;
$where['is_price'] = 1;
$where['is_integral'] = 1;
$list = $this->dao->getIntegralOrderList($where);
//系统预设取消订单时间段
$keyValue = ['rebate_points_orders_time', 'order_cancel_time'];
//获取配置
$systemValue = SystemConfigService::more($keyValue);
//格式化数据
$systemValue = Arr::setValeTime($keyValue, is_array($systemValue) ? $systemValue : []);
$order_cancel_time = $systemValue['rebate_points_orders_time'] ?: $systemValue['order_cancel_time'];
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
/** @var UserBillServices $userBillServices */
$userBillServices = app()->make(UserBillServices::class);
$order_cancel_time = (int)bcmul((string)$order_cancel_time, '3600', 0);
foreach ($list as $key => $item) {
if (($item['add_time'] + $order_cancel_time) < time()) {
try {
$this->transaction(function () use ($item, $userServices, $userBillServices) {
$user_integral = $userServices->value(['uid' => $item['uid']], 'integral');
$res = $userServices->incUpdate(['uid' => $item['uid']], 'integral', (int)$item['integral']);
$integral = bcadd((string)$user_integral, (string)$item['integral']);
$res1 = $userBillServices->income('integral_refund', $item['uid'], (int)$item['integral'], (int)$integral, $item['id']);
//修改订单状态
$res = $res && $res1 && $this->dao->update($item['id'], ['is_del' => 1, 'is_system_del' => 1, 'mark' => '积分订单未支付已超过系统预设时间']);
});
} catch (\Throwable $e) {
Log::error('未支付积分订单退积分失败,失败原因:' . $e->getMessage(), $e->getTrace());
}
}
}
return true;
}
/**
*获取订单数量
* @param array $where
* @return mixed
*/
public function orderCount(array $where)
{
$where['type'] = 4;
//全部订单
$data['statusAll'] = (string)$this->dao->count($where + ['is_system_del' => 0]);
//未发货
$data['unshipped'] = (string)$this->dao->count($where + ['status' => 1, 'is_system_del' => 0]);
//待收货
$data['untake'] = (string)$this->dao->count($where + ['status' => 2, 'is_system_del' => 0]);
//待评价
// $data['unevaluate'] = (string)$this->dao->count(['status' => 3, 'time' => $where['time'], 'is_system_del' => 0]);
//交易完成
$data['complete'] = (string)$this->dao->count($where + ['status' => 3, 'is_system_del' => 0]);
return $data;
}
}

View File

@@ -0,0 +1,53 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\integral;
use app\dao\activity\integral\StoreIntegralOrderStatusDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 订单状态
* Class StoreIntegralOrderStatusServices
* @package app\services\activity\integral
* @mixin StoreIntegralOrderStatusDao
*/
class StoreIntegralOrderStatusServices extends BaseServices
{
/**
* @var StoreIntegralOrderStatusDao
*/
#[Inject]
protected StoreIntegralOrderStatusDao $dao;
/**
* 订单状态分页
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStatusList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getStatusList($where, $page, $limit);
foreach ($list as &$item) {
if (is_int($item['change_time'])) $item['change_time'] = date('Y-m-d H:i:s', $item['change_time']);
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,453 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\integral;
use app\services\BaseServices;
use app\dao\activity\integral\StoreIntegralDao;
use app\services\diy\DiyServices;
use app\services\order\StoreOrderServices;
use app\services\product\ensure\StoreProductEnsureServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrResultServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\jobs\product\ProductLogJob;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 积分商品
* Class StoreIntegralServices
* @package app\services\activity\integral
* @mixin StoreIntegralDao
*/
class StoreIntegralServices extends BaseServices
{
const THODLCEG = 'ykGUKB';
/**
* 商品活动类型
*/
const TYPE = 4;
/**
* @var StoreIntegralDao
*/
#[Inject]
protected StoreIntegralDao $dao;
/**
* 获取指定条件下的条数
* @param array $where
*/
public function getCount(array $where)
{
$this->dao->count($where);
}
/**
* 积分商品添加
* @param int $id
* @param array $data
*/
public function saveData(int $id, array $data)
{
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已移入回收站');
}
if ($productInfo['is_vip_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是svip专享");
}
if ($productInfo['is_presale_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是预售商品");
}
$data['delivery_type'] = $productInfo['delivery_type'];
$data['product_type'] = $productInfo['product_type'];
$data['type'] = $productInfo['type'] ?? 0;
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$custom_form = $productInfo['custom_form'] ?? [];
$data['custom_form'] = is_array($custom_form) ? json_encode($custom_form) : $custom_form;
$data['system_form_id'] = $productInfo['system_form_id'] ?? 0;
$store_label_id = $productInfo['store_label_id'] ?? [];
$data['store_label_id'] = is_array($store_label_id) ? implode(',', $store_label_id) : $store_label_id;
$ensure_id = $productInfo['ensure_id'] ?? [];
$data['ensure_id'] = is_array($ensure_id) ? implode(',', $ensure_id) : $ensure_id;
$specs = $productInfo['specs'] ?? [];
$data['specs'] = is_array($specs) ? json_encode($specs) : $specs;
$description = $data['description'];
$detail = $data['attrs'];
if ($detail) {
foreach ($detail as $attr) {
if ($attr['quota'] > $attr['stock']) {
throw new AdminException('限量超过了商品库存');
}
}
}
$items = $data['items'];
if (!$data['image'] && count($data['images']) > 0) {
$data['image'] = $data['images'][0];
}
$data['images'] = json_encode($data['images']);
$integral_data = array_column($detail, 'integral', 'price');
$data['integral'] = $integral_data ? (int)min($integral_data) : 0;
$data['price'] = array_search($data['integral'], $integral_data);
$data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
if ($data['quota'] > $storeProductServices->value(['id' => $data['product_id']], 'stock')) {
throw new ValidateException('限量不能超过商品库存');
}
$data['stock'] = array_sum(array_column($detail, 'stock'));
unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$this->transaction(function () use ($id, $data, $description, $detail, $items, $storeDescriptionServices, $storeProductAttrServices, $storeProductServices) {
if ($id) {
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
} else {
if (!$storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']])) {
throw new AdminException('原商品已移入回收站');
}
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$storeDescriptionServices->saveDescription((int)$id, $description, 4);
$storeProductAttrServices->setItem('store_product_id', $data['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($items, $detail, (int)$id, 4);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$id, 4);
$res = true;
foreach ($valueGroup as $item) {
$res = $res && CacheService::setStock($item['unique'], (int)$item['quota_show'], 4);
}
if (!$res) {
throw new AdminException('占用库存失败');
}
});
}
/**
* 批量添加商品
* @param array $data
*/
public function saveBatchData(array $data)
{
/** @var StoreProductServices $service */
$service = app()->make(StoreProductServices::class);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
if (!$data) {
throw new ValidateException('请先添加产品!');
}
$attrs = [];
foreach ($data['attrs'] as $k => $v) {
$attrs[$v['product_id']][] = $v;
}
foreach ($attrs as $k => $v) {
$productInfo = $service->getOne(['id' => $k]);
$productInfo = is_object($productInfo) ? $productInfo->toArray() : [];
if ($productInfo) {
$product = [];
$result = $storeProductAttrResultServices->getResult(['product_id' => $productInfo['id'], 'type' => 0]);
$product['product_id'] = $productInfo['id'];
$product['description'] = $storeDescriptionServices->getDescription(['product_id' => $productInfo['id'], 'type' => 0]);
$product['attrs'] = $v;
$product['items'] = $result['attr'];
$product['is_show'] = isset($data['is_show']) ? $data['is_show'] : 0;
$product['title'] = $productInfo['store_name'];
$product['unit_name'] = $productInfo['unit_name'];
$product['image'] = $productInfo['image'];
$product['images'] = $productInfo['slider_image'];
$product['num'] = 0;
$product['is_host'] = 0;
$product['once_num'] = 0;
$product['sort'] = 0;
$this->saveData(0, $product);
}
}
return true;
}
/**
* 积分商品列表
* @param array $where
* @return array
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取详情
* @param int $id
* @return array|\think\Model|null
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('查看的商品不存在!');
}
if ($info->is_del) {
throw new ValidateException('您查看的积分商品已被删除!');
}
$info['price'] = floatval($info['price']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
$info['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 4]);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$info['attrs'] = $storeProductAttrValueServices->attrList($id, $info['product_id'], 4);
return $info;
}
/**
* 积分商品详情
* @param int $uid
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function integralDetail(int $uid, int $id)
{
$storeInfo = $this->dao->getOne(['id' => $id], '*,title as store_name', ['getPrice']);
if (!$storeInfo) {
throw new ValidateException('商品不存在');
} else {
$storeInfo = $storeInfo->toArray();
}
/** @var DiyServices $diyServices */
$diyServices = app()->make(DiyServices::class);
$infoDiy = $diyServices->getProductDetailDiy();
//diy控制参数
if (!isset($infoDiy['showService']) || in_array(3, $infoDiy['showService'])) {
$storeInfo['specs'] = [];
}
$siteUrl = sys_config('site_url');
$storeInfo['store_name'] = $storeInfo['title'];
$storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['sale_stock'] = 0;
if ($storeInfo['stock'] > 0) $storeInfo['sale_stock'] = 1;
/** @var StoreDescriptionServices $storeDescriptionService */
$storeDescriptionService = app()->make(StoreDescriptionServices::class);
$storeInfo['description'] = $storeDescriptionService->getDescription(['product_id' => $id, 'type' => 4]);
$storeInfo['store_label'] = $storeInfo['ensure'] = [];
if ($storeInfo['store_label_id']) {
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
if ($storeInfo['ensure_id'] && isset($infoDiy['showService']) && in_array(2, $infoDiy['showService'])) {
/** @var StoreProductEnsureServices $storeProductEnsureServices */
$storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
$storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
}
$storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id']);
$data['storeInfo'] = $storeInfo;
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetail($id, $uid, 0, 4, $storeInfo['product_id']);
$data['productAttr'] = $productAttr;
$data['productValue'] = $productValue;
$data['site_name'] = sys_config('site_name');
$data['share_qrcode'] = sys_config('share_qrcode', 0);
$data['product_poster_title'] = sys_config('product_poster_title', '');
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'integral']);
return $data;
}
/**
* 修改销量和库存
* @param $num
* @param $integralId
* @return bool
*/
public function decIntegralStock(int $num, int $integralId, string $unique)
{
$product_id = $this->dao->value(['id' => $integralId], 'product_id');
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去积分商品的sku库存增加销量
$res = false !== $skuValueServices->decProductAttrStock($integralId, $unique, $num, 4);
//积分商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $integralId, 'type' => 4], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $res && $services->decProductStock($num, $product_id, (string)$productUnique);
}
//减去积分商品库存
$res = $res && false !== $this->dao->decStockIncSales(['id' => $integralId, 'type' => 4], $num);
return $res;
}
/**
* 加库存减销量
* @param int $num
* @param int $integralId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incIntegralStock(int $num, int $integralId, string $unique, int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $integralId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//增加积分商品的sku库存,减去销量
$res = false !== $skuValueServices->incProductAttrStock($integralId, $unique, $num, 4);
//积分商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $integralId, 'type' => 4], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//增加普通商品库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
//增加积分库存
$res = $res && false !== $this->dao->incStockDecSales(['id' => $integralId, 'type' => 4], $num);
}
return $res;
}
/**
* 获取一条积分商品
* @param $id
* @return mixed
*/
public function getIntegralOne($id)
{
return $this->dao->validProduct($id, '*');
}
/**
* 验证积分商品下单库存限量
* @param int $uid
* @param int $integralId
* @param int $num
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkoutProductStock(int $uid, int $integralId, int $num = 1, string $unique = '')
{
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
if ($unique == '') {
$unique = $attrValueServices->value(['product_id' => $integralId, 'type' => 4], 'unique');
}
$StoreIntegralInfo = $this->dao->validProduct($integralId, '*,title as store_name');
if (!$StoreIntegralInfo) {
throw new ValidateException('该商品已下架或删除');
}
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$userBuyCount = $orderServices->getBuyCount($uid, 4, $integralId);
if ($StoreIntegralInfo['once_num'] < $num && $StoreIntegralInfo['once_num'] != -1) {
throw new ValidateException('每个订单限购' . $StoreIntegralInfo['once_num'] . '件');
}
if ($StoreIntegralInfo['num'] < ($userBuyCount + $num) && $StoreIntegralInfo['num'] != -1) {
throw new ValidateException('每人总共限购' . $StoreIntegralInfo['num'] . '件');
}
$attrInfo = $attrValueServices->getOne(['product_id' => $integralId, 'unique' => $unique, 'type' => 4]);
if ($num > $attrInfo['quota']) {
throw new ValidateException('该商品库存不足' . $num);
}
$product_stock = $attrValueServices->value(['product_id' => $StoreIntegralInfo['product_id'], 'suk' => $attrInfo['suk'], 'type' => 0], 'stock');
if ($product_stock < $num) {
throw new ValidateException('该商品库存不足' . $num);
}
if (!CacheService::checkStock($unique, $num, 4)) {
throw new ValidateException('该商品库存不足' . $num);
}
return [$attrInfo, $unique, $StoreIntegralInfo];
}
/**
* 获取推荐积分商品
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getIntegralList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit, 'id,product_id,image,title,integral,price,sales,stock');
foreach ($list as &$item) {
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$item['brand_name'] = $storeProductServices->productIdByBrandName((int)$item['product_id']);
}
return $list;
}
/**
* 获取全部积分商品
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllIntegralList(array $where)
{
$list = $this->dao->getList($where, 0, 0, 'id,image,title,integral,price,sales');
return $list;
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace app\services\activity\integral;
use app\dao\user\UserBillDao;
use app\services\BaseServices;
use app\services\order\StoreOrderServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
class UserPointServices extends BaseServices
{
/**
* UserBillServices constructor.
* @param UserBillDao $dao
*/
#[Inject]
protected UserBillDao $dao;
/**
* 积分记录
* @param $where
* @return array
*/
public function pointRecord($where)
{
$where['category'] = 'integral';
$status = [
'invite_user' => '邀新奖励',
'system_add' => '系统增加积分',
'system_sub' => '系统减少积分',
'gain' => '下单赠送积分',
'product_gain' => '购买商品赠送积分',
'deduction' => '下单积分抵扣',
'lottery_use' => '参与抽奖使用积分',
'lottery_add' => '抽奖中奖赠送积分',
'order_deduction' => '扣除订单下单赠送积分',
'storeIntegral_use' => '积分兑换商品',
'pay_product_integral_back' => '返还下单使用积分',
'sign' => '签到获得积分',
];
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
//关联用户
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$uids = array_column($list, 'uid');
$nicknameArr = $userServices->getColumn([['uid', 'in', $uids]], 'nickname', 'uid');
//关联订单
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
/** @var StoreIntegralOrderServices $integralOrderServices */
$integralOrderServices = app()->make(StoreIntegralOrderServices::class);
foreach ($list as &$item) {
$item['nickname'] = $nicknameArr[$item['uid']] ?? '未知用户';
if ($item['type'] == 'gain' || $item['type'] == 'deduction' || $item['type'] == 'product_deduction' || $item['type'] == 'pay_product_integral_back') {
$item['relation'] = $orderServices->value(['id' => $item['link_id']], 'order_id');
} elseif ($item['type'] == 'storeIntegral_use') {
$item['relation'] = $integralOrderServices->value(['id' => $item['link_id']], 'order_id');
} else {
$item['relation'] = $status[$item['type']];
}
$item['type_name'] = $status[$item['type']];
}
$count = $this->dao->count($where);
return compact('list', 'count', 'status');
}
/**
* 积分记录备注
* @param $data
* @return bool
*/
public function recordRemark($id, $mark)
{
if (!$id) throw new AdminException('缺少参数');
if ($mark === '') throw new AdminException('请填写备注');
if ($this->dao->update($id, ['mark' => $mark])) {
return true;
} else {
throw new AdminException('备注失败');
}
}
/**
* 订单统计基础
* @param $where
* @return array
*/
public function getBasic($where)
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$data['now_point'] = $userServices->sum(['status' => 1], 'integral', true);
$data['all_point'] = $this->dao->sum(['category' => 'integral', 'pm' => 1, 'time' => $where['time']], 'number', true);
$data['pay_point'] = $this->dao->sum(['category' => 'integral', 'pm' => 0, 'time' => $where['time']], 'number', true);
return $data;
}
/**
* 订单趋势
* @param $where
* @return array
*/
public function getTrend($where)
{
$time = explode('-', $where['time']);
if (count($time) != 2) throw new AdminException('数据错误');
$dayCount = (strtotime($time[1]) - strtotime($time[0])) / 86400 + 1;
$data = [];
if ($dayCount == 1) {
$data = $this->trend($time, 0);
} elseif ($dayCount > 1 && $dayCount <= 31) {
$data = $this->trend($time, 1);
} elseif ($dayCount > 31 && $dayCount <= 92) {
$data = $this->trend($time, 3);
} elseif ($dayCount > 92) {
$data = $this->trend($time, 30);
}
return $data;
}
/**
* 订单趋势
* @param $time
* @param $num
* @param false $excel
* @return array
*/
public function trend($time, $num, $excel = false)
{
if ($num == 0) {
$xAxis = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
$timeType = '%H';
} elseif ($num != 0) {
$dt_start = strtotime($time[0]);
$dt_end = strtotime($time[1]);
while ($dt_start <= $dt_end) {
if ($num == 30) {
$xAxis[] = date('Y-m', $dt_start);
$dt_start = strtotime("+1 month", $dt_start);
$timeType = '%Y-%m';
} else {
$xAxis[] = date('m-d', $dt_start);
$dt_start = strtotime("+$num day", $dt_start);
$timeType = '%m-%d';
}
}
}
$point_add = array_column($this->dao->getPointTrend($time, $timeType, 'add_time', 'sum(number)', 'add'), 'num', 'days');
$point_sub = array_column($this->dao->getPointTrend($time, $timeType, 'add_time', 'sum(number)', 'sub'), 'num', 'days');
$data = $series = [];
foreach ($xAxis as $item) {
$data['积分积累'][] = isset($point_add[$item]) ? floatval($point_add[$item]) : 0;
$data['积分消耗'][] = isset($point_sub[$item]) ? floatval($point_sub[$item]) : 0;
}
foreach ($data as $key => $item) {
$series[] = [
'name' => $key,
'data' => $item,
'type' => 'line',
];
}
return compact('xAxis', 'series');
}
/**
* 订单来源
* @param $where
* @return array
*/
public function getChannel($where)
{
$bing_xdata = ['订单赠送', '商品赠送', '后台赠送', '签到获得', '九宫格抽奖'];
$color = ['#64a1f4', '#3edeb5', '#70869f', '#ffc653', '#fc7d6a'];
$data = ['gain', 'gain', 'system_add', 'sign', 'lottery_add'];
$bing_data = [];
foreach ($data as $key => $item) {
$bing_data[] = [
'name' => $bing_xdata[$key],
'value' => $this->dao->sum(['pm' => 1, 'type' => $item, 'category' => 'integral', 'time' => $where['time']], 'number', true),
'itemStyle' => ['color' => $color[$key]]
];
}
$list = [];
$count = array_sum(array_column($bing_data, 'value'));
foreach ($bing_data as $key => $item) {
$list[] = [
'name' => $item['name'],
'value' => $item['value'],
'percent' => $count != 0 ? bcmul((string)bcdiv((string)$item['value'], (string)$count, 4), '100', 1) : 0,
];
}
array_multisort(array_column($list, 'value'), SORT_DESC, $list);
return compact('bing_xdata', 'bing_data', 'list');
}
/**
* 订单类型
* @param $where
* @return array
*/
public function getType($where)
{
$bing_xdata = ['订单抵扣', '九宫格抽奖', '后台减少', '退款退回', '兑换商品'];
$color = ['#64a1f4', '#3edeb5', '#70869f', '#ffc653', '#fc7d6a'];
$data = ['deduction', 'lottery_use', 'system_sub', 'pay_product_integral_back', 'storeIntegral_use'];
$bing_data = [];
foreach ($data as $key => $item) {
$bing_data[] = [
'name' => $bing_xdata[$key],
'value' => $this->dao->sum(['type' => $item, 'category' => 'integral', 'time' => $where['time']], 'number', true),
'itemStyle' => ['color' => $color[$key]]
];
}
$list = [];
$count = array_sum(array_column($bing_data, 'value'));
foreach ($bing_data as $key => $item) {
$list[] = [
'name' => $item['name'],
'value' => $item['value'],
'percent' => $count != 0 ? bcmul((string)bcdiv((string)$item['value'], (string)$count, 4), '100', 1) : 0,
];
}
array_multisort(array_column($list, 'value'), SORT_DESC, $list);
return compact('bing_xdata', 'bing_data', 'list');
}
}

View File

@@ -0,0 +1,204 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\live;
use app\dao\activity\live\LiveAnchorDao;
use app\jobs\activity\AutoUpdateLiveJob;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\services\FormBuilder as Form;
use crmeb\services\wechat\MiniProgram;
use FormBuilder\Factory\Iview;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 主播
* Class LiveAnchorServices
* @package app\services\activity\live
* @mixin LiveAnchorDao
*/
class LiveAnchorServices extends BaseServices
{
/**
* @var LiveAnchorDao
*/
#[Inject]
protected LiveAnchorDao $dao;
/**
* 获取某个主播
* @param int $id
* @return array|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLiveAnchor(int $id)
{
return $this->dao->get($id);
}
/**
* 获取主播列表
* @param array $where 查询条件
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', $page, $limit);
$count = $this->dao->count($where);
return compact('count', 'list');
}
/**
* 添加修改标签表单
* @param int $id
* @return mixed
*/
public function add(int $id)
{
$anchor = $this->getLiveAnchor($id);
$field = array();
if (!$anchor) {
$title = '添加主播';
$field[] = Form::input('name', '主播名称:', '')->maxlength(20)->required('请填写名称');
$field[] = Form::input('wechat', '主播微信号:', '')->maxlength(32)->required('请填写微信号');
$field[] = Form::input('phone', '主播手机号:', '')->maxlength(20)->required('请填写手机号');
$field[] = Form::frameImage('cover_img', '主播图像:', Url::buildUrl('admin/widget.images/index', array('fodder' => 'cover_img')), '')->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->appendValidate(Iview::validateStr()->required()->message('请选择图像'));
} else {
$title = '修改主播';
$field[] = Form::hidden('id', $anchor->getData('id'));
$field[] = Form::input('name', '主播名称:', $anchor->getData('name'))->maxlength(20)->required('请填写名称');
$field[] = Form::input('wechat', '主播微信号:', $anchor->getData('wechat'))->maxlength(32)->required('请填写微信号');
$field[] = Form::input('phone', '主播手机号:', $anchor->getData('phone'))->maxlength(20)->required('请填写手机号');
$field[] = Form::frameImage('cover_img', '主播图像:', Url::buildUrl('admin/widget.images/index', array('fodder' => 'cover_img')), $anchor->getData('cover_img'))->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->appendValidate(Iview::validateStr()->required()->message('请选择图像'));
}
return create_form($title, $field, $this->url('/live/anchor/save'), 'POST');
}
/**
* 保存标签表单数据
* @param int $id
* @param array $data
* @return mixed
*/
public function save(int $id, array $data)
{
$liveAnchor = $this->dao->get(['wechat' => $data['wechat']]);
if (!MiniProgram::getRoleList(2, 0, 30, $data['wechat'])) {
return ['auth' => true];
}
if ($id) {
if ($liveAnchor && $id != $liveAnchor['id']) {
throw new AdminException('该主播已经存在');
}
if ($this->dao->update($id, $data)) {
return true;
} else {
throw new AdminException('修改失败或者您没有修改什么!');
}
} else {
unset($data['id']);
if ($liveAnchor) {
throw new AdminException('该主播已经存在');
}
if ($this->dao->save($data)) {
return true;
} else {
throw new AdminException('添加失败!');
}
}
}
/**
* 删除
* @param $id
* @throws \Exception
*/
public function delAnchor(int $id)
{
if ($anchor = $this->getLiveAnchor($id)) {
if (!$this->dao->update($id, ['is_del' => 1])) {
throw new AdminException('删除失败,请稍候再试!');
}
/** @var LiveRoomServices $liveRoom */
$liveRoom = app()->make(LiveRoomServices::class);
$room = $liveRoom->get(['anchor_wechat' => $anchor['wechat'], 'is_del' => 0], ['id']);
if ($room) {
$liveRoom->delete((int)$room->id);
}
}
return true;
}
/**
* 设置是否显示
* @param int $id
* @param $is_show
* @return mixed
*/
public function setShow(int $id, $is_show)
{
if (!$this->getLiveAnchor($id))
throw new AdminException('数据不存在');
if ($this->dao->update($id, ['is_show' => $is_show])) {
return $is_show == 1 ? '显示成功' : '隐藏成功';
} else {
throw new AdminException($is_show == 1 ? '显示失败' : '隐藏失败');
}
}
/**
* 同步微信主播列表
* @param bool $is_job 是否为定时任务调用
* @return bool
*/
public function syncAnchor($is_job = false)
{
$key = md5('Live_sync_status');
$res = CacheService::redisHandler()->get($key);
if (!$res || $is_job) {
$start = 0;
$limit = 30;
$data = $dataAll = [];
$anchors = $this->dao->getColumn([], 'id,wechat', 'wechat');
do {
$wxAnchor = MiniProgram::getRoleList(2, $start, $limit);
foreach ($wxAnchor as $anchor) {
if (isset($anchors[$anchor['username']])) {
$this->dao->update($anchors[$anchor['username']]['id'], ['cover_img' => $anchor['headingimg'], 'name' => $anchor['nickname']]);
} else {
$data['name'] = $anchor['nickname'];
$data['wechat'] = $anchor['username'];
$data['cover_img'] = $anchor['headingimg'];
$data['add_time'] = $anchor['updateTimestamp'] ?? time();
$dataAll[] = $data;
}
}
$start++;
} while (count($wxAnchor) >= $limit);
if ($dataAll) {
$this->dao->saveAll($dataAll);
}
if (!$is_job) AutoUpdateLiveJob::dispatchSece(120);
CacheService::redisHandler()->set($key, 1, 0);
}
return true;
}
}

View File

@@ -0,0 +1,281 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\live;
use app\dao\activity\live\LiveGoodsDao;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use crmeb\exceptions\AdminException;
use crmeb\services\DownloadImageService;
use crmeb\services\wechat\MiniProgram;
use crmeb\services\wechat\OfficialAccount;
use think\annotation\Inject;
use think\facade\Log;
/**
* 直播商品
* Class LiveGoodsServices
* @package app\services\activity\live
* @mixin LiveGoodsDao
*/
class LiveGoodsServices extends BaseServices
{
/**
* @var LiveGoodsDao
*/
#[Inject]
protected LiveGoodsDao $dao;
/**
* 获取直播商品列表
* @param array $where 查询条件
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', ['product'], $page, $limit);
$count = $this->dao->count($where);
return compact('count', 'list');
}
/**
* 创建直播商品(验证商品有效性)
* @param array $product_ids 商品ID数组
* @return array
* @throws AdminException
*/
public function create(array $product_ids)
{
/** @var StoreProductServices $product */
$productServices = app()->make(StoreProductServices::class);
$products = $productServices->getColumn([['id', 'IN', $product_ids], ['is_del', '=', 0], ['is_show', '=', 1]], 'id,image,store_name,price,price as cost_price,stock', 'id');
if (count($product_ids) != count($products)) {
throw new AdminException('已选商品中包括已下架或移入回收站商品');
}
$checkGoods = $this->dao->getCount([['product_id', 'IN', $product_ids], ['is_del', '=', 0], ['audit_status', '<>', 3]]);
if ($checkGoods > 0) {
throw new AdminException('其中有商品已经添加');
}
return array_merge($products);
}
/**
* @param array $data
* @return bool|mixed
* @throws \EasyWeChat\Core\Exceptions\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function add(array $goods_info)
{
$product_ids = array_column($goods_info, 'id');
$this->create($product_ids);
/** @var DownloadImageService $download */
$download = app()->make(DownloadImageService::class);
$dataAll = $data = [];
$time = time();
foreach ($goods_info as $product) {
$data = [
'product_id' => $product['id'],
'name' => substrUTf8($product['store_name'], 12, 'UTF-8', ''),
'cover_img' => $product['image'] ?? '',
'price_type' => 1,
'cost_price' => $product['cost_price'] ?? 0.00,
'price' => $product['price'] ?? 0.00,
'url' => 'pages/goods_details/index?id=' . $product['id'],
'sort' => $product['sort'] ?? 0,
'add_time' => $time
];
try {
$path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];
$coverImgUrl = MiniProgram::temporaryUpload($path)['media_id'];
@unlink($path);
} catch (\Throwable $e) {
Log::error('添加直播商品图片错误,原因:' . $e->getMessage());
$coverImgUrl = $data['cover_img'];
}
$res = MiniProgram::addGoods($coverImgUrl, $data['name'], $data['price_type'], $data['url'], floatval($data['price']));
$data['goods_id'] = $res['goodsId'];
$data['audit_id'] = $res['auditId'];
$data['audit_status'] = 1;
$dataAll[] = $data;
}
if (!$goods = $this->dao->saveAll($dataAll)) {
throw new AdminException('添加商品失败');
}
return true;
}
/**
* 同步商品
* @return bool
* @throws \EasyWeChat\Core\Exceptions\InvalidArgumentException
*/
public function syncGoods()
{
$liveGoods = $this->dao->getColumn([['goods_id', '>', 0]], '*', 'id');
if ($liveGoods) {
foreach ($liveGoods as $good) {
$path = root_path() . 'public' . app()->make(DownloadImageService::class)->thumb(true)->downloadImage($good['cover_img'])['path'];
$materialInfo = MiniProgram::temporaryUpload($path);
$coverImgUrl = $materialInfo['media_id'];
@unlink($path);
$res = MiniProgram::addGoods($coverImgUrl, $good['name'], $good['price_type'], $good['url'], floatval($good['price']));
$data['goods_id'] = $res['goodsId'];
$data['audit_id'] = $res['auditId'];
$data['audit_status'] = 1;
if (!$this->dao->update($good['id'], $data, 'id')) {
throw new AdminException('同步失败');
}
}
}
return true;
}
/**
* 微信小程序创建直播商品
* @param mixed $goods 商品信息
* @return array
* @throws AdminException
*/
public function wxCreate($goods)
{
if ($goods['goods_id'])
throw new AdminException('商品已创建');
$goods = $goods->toArray();
$path = root_path() . 'public' . app()->make(DownloadImageService::class)->thumb(true)->downloadImage($goods['cover_img'])['path'];
$url = 'pages/goods_details/index?id=' . $goods['product_id'];
$coverImgUrl = MiniProgram::temporaryUpload($path)['media_id'];
@unlink($path);
return MiniProgram::addGoods($coverImgUrl, $goods['name'], 1, $url, floatval($goods['price']));
}
/**
* 设置直播商品显示状态
* @param int $id 商品ID
* @param int $is_show 显示状态
* @return string
* @throws AdminException
*/
public function isShow(int $id, $is_show)
{
$goods = $this->dao->get(['id' => $id, 'audit_status' => 2]);
if (!$goods) {
throw new AdminException('审核中或审核失败不允许此操作');
}
$this->dao->update($id, ['is_show' => $is_show]);
return $is_show == 1 ? '显示成功' : '隐藏成功';
}
/**
* 重新提交审核
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function audit(int $id)
{
$goods = $this->dao->get($id);
if (!$goods) {
throw new AdminException('数据不存在');
}
if ($goods['audit_status'] != 0) {
throw new AdminException('在审核中或已经审核通过');
}
if (!$this->dao->update($id, ['audit_status' => 1])) {
throw new AdminException('修改审核状态失败');
}
return MiniProgram::auditGoods((int)$goods['good_id']);
}
/**
* 撤回审核
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function resetAudit(int $id)
{
$goods = $this->dao->get($id);
if (!$goods) {
throw new AdminException('数据不存在');
}
if ($goods['audit_status'] == 0) {
return true;
}
if ($goods['audit_status'] != 1) {
throw new AdminException('审核通过或失败');
}
if (!$this->dao->update($id, ['audit_status' => 0])) {
throw new AdminException('修改审核状态失败');
}
return MiniProgram::resetauditGoods((int)$goods['good_id'], $goods['audit_id']);
}
/**
* 删除商品
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function delete(int $id)
{
$goods = $this->dao->get(['id' => $id, 'is_del' => 0]);
if ($goods) {
if (in_array($goods['audit_status'], [0, 1])) {
throw new AdminException('商品审核中,无法删除');
}
if (!$this->dao->update($id, ['is_del' => 1])) {
throw new AdminException('删除失败');
}
if (MiniProgram::deleteGoods((int)$goods->goods_id)) {
/** @var LiveRoomGoodsServices $liveRoomGoods */
$liveRoomGoods = app()->make(LiveRoomGoodsServices::class);
$liveRoomGoods->delete(['live_goods_id' => $id]);
}
}
return true;
}
/**
* 同步直播商品审核状态
* @return bool
*/
public function syncGoodStatus()
{
$goodsIds = $this->dao->goodsStatusAll();
if (!count($goodsIds)) return true;
$res = MiniProgram::getGooodsInfo(array_keys($goodsIds));
foreach ($res as $item) {
if (isset($goodsIds[$item['goods_id']]) && $item['audit_status'] != $goodsIds[$item['goods_id']]) {
$data = ['audit_status' => $item['audit_status']];
// 同步商品审核状态
$this->dao->update((int)$goodsIds[$item['goods_id']]['id'], $data);
}
}
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\live;
use app\dao\activity\live\LiveRoomGoodsDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 直播间关联商品
* Class LiveRoomGoodsServices
* @package app\services\activity\live
* @mixin LiveRoomGoodsDao
*/
class LiveRoomGoodsServices extends BaseServices
{
/**
* @var LiveRoomGoodsDao
*/
#[Inject]
protected LiveRoomGoodsDao $dao;
}

View File

@@ -0,0 +1,327 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\live;
use app\dao\activity\live\LiveRoomDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\DownloadImageService;
use crmeb\services\wechat\MiniProgram;
use think\annotation\Inject;
use think\facade\Log;
/**
* 直播间
* Class LiveRoomServices
* @package app\services\activity\live
* @mixin LiveRoomDao
*/
class LiveRoomServices extends BaseServices
{
/**
* @var LiveRoomDao
*/
#[Inject]
protected LiveRoomDao $dao;
/**
* 获取直播间列表(后台)
* @param array $where 查询条件
* @return array
*/
public function getList(array $where)
{
$where['is_del'] = 0;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', [], $page, $limit);
$count = $this->dao->count($where);
return compact('count', 'list');
}
/**
* 获取直播间列表
* @param array $where
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function userList(array $where, int $limit = 0)
{
if ($limit) {
$page = 0;
} else {
[$page, $limit] = $this->getPageValue();
}
$where['is_show'] = 1;
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', ['roomGoods.goods', 'anchor'], $page, $limit);
foreach ($list as &$item) {
$item['roomid'] = $item['room_id'];
$item['goods'] = [];
$item['show_time'] = date('m/d H:i', strtotime($item['start_time']));
if (isset($item['roomGoods']) && $item['roomGoods']) {
$item['goods'] = array_column($item['roomGoods'], 'goods');
}
if (in_array($item['live_status'], [105, 106])) {
$item['live_status'] = 101;
}
if (in_array($item['live_status'], [104, 107])) {
$item['live_status'] = 103;
}
unset($item['roomGoods']);
}
return $list;
}
/**
* 获取直播间回放列表
* @param int $id 直播间ID
* @return array
*/
public function getPlaybacks(int $id)
{
$room = $this->dao->get(['id' => $id, 'is_del' => 0]);
if (!$room) {
throw new AdminException('数据不存在');
}
[$page, $limit] = $this->getPageValue();
return MiniProgram::getLivePlayback($room['room_id'], $page, $limit);
}
/**
* 添加直播间
* @param array $data 直播间数据
* @return bool
*/
public function add(array $data)
{
/** @var LiveAnchorServices $anchorServices */
$anchorServices = app()->make(LiveAnchorServices::class);
$anchor = $anchorServices->get(['wechat' => $data['anchor_wechat']]);
if (!$anchor) {
throw new AdminException('该主播不存在');
}
$data['start_time'] = strtotime($data['start_time']);
$data['end_time'] = strtotime($data['end_time']);
$time = time() + 600;
$time6 = time() + 180 * 24 * 3600;
if ($data['start_time'] < $time || $data['start_time'] > $time6) {
throw new AdminException('开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后');
}
$t = $data['end_time'] - $data['start_time'];
if ($t < 1800 || $t > 24 * 3600) {
throw new AdminException('开播时间和结束时间间隔不得短于30分钟,不得超过24小时');
}
$data['anchor_name'] = $data['anchor_name'] ?? $anchor['name'];
$data['add_time'] = time();
$wxRoom = $this->wxCreate($data);
$data['room_id'] = $wxRoom['roomId'];
$data['status'] = 2;
if (!$this->dao->save($data)) {
throw new AdminException('添加直播间数据失败');
}
return true;
}
/**
* 审核直播间
* @param int $id 直播间ID
* @param int $status 审核状态
* @param string $msg 拒绝原因
* @return void
*/
public function apply($id, $status, $msg = '')
{
$room = $this->dao->get($id);
if (!$room) {
throw new AdminException('数据不存在');
}
$room->status = $status;
if ($status == -1)
$room->error_msg = $msg;
else {
$room->room_id = $this->wxCreate($room)['roomId'];
$room->status = 2;
}
$room->save();
}
/**
* 创建微信直播间
* @param mixed $room 直播间数据
* @return array
*/
public function wxCreate($room)
{
try {
$coverImg = root_path() . 'public' . app()->make(DownloadImageService::class)->downloadImage($room['cover_img'])['path'];
$shareImg = root_path() . 'public' . app()->make(DownloadImageService::class)->downloadImage($room['share_img'])['path'];
} catch (\Throwable $e) {
Log::error('添加直播间封面图出错误,原因:' . $e->getMessage());
$coverImg = root_path() . 'public' . $room['cover_img'];
$shareImg = root_path() . 'public' . $room['share_img'];
}
$data = [
'startTime' => is_string($room['start_time']) ? strtotime($room['start_time']) : $room['start_time'],
'endTime' => is_string($room['end_time']) ? strtotime($room['end_time']) : $room['end_time'],
'name' => $room['name'],
'anchorName' => $room['anchor_name'],
'anchorWechat' => $room['anchor_wechat'],
'screenType' => $room['screen_type'],
'closeGoods' => $room['close_goods'] == 1 ? 0 : 1,
'closeLike' => $room['close_like'] == 1 ? 0 : 1,
'closeComment' => $room['close_comment'] == 1 ? 0 : 1,
'closeReplay' => $room['replay_status'] == 1 ? 0 : 1,
'type' => $room['type'],
'coverImg' => MiniProgram::temporaryUpload($coverImg)['media_id'],
'shareImg' => MiniProgram::temporaryUpload($shareImg)['media_id'],
'closeKf' => 1
];
$data['feedsImg'] = $data['coverImg'];
@unlink($coverImg);
@unlink($shareImg);
return MiniProgram::createLiveRoom($data);
}
/**
* 设置直播间显示状态
* @param int $id 直播间ID
* @param int $is_show 显示状态
* @return string
*/
public function isShow(int $id, $is_show)
{
$this->dao->update($id, ['is_show' => $is_show]);
return $is_show == 1 ? '显示成功' : '隐藏成功';
}
/**
* 删除直播间
* @param int $id 直播间ID
* @return bool
*/
public function delete(int $id)
{
$room = $this->dao->get(['id' => $id, 'is_del' => 0]);
if ($room) {
if (!$this->dao->update($id, ['is_del' => 1])) {
throw new AdminException('删除失败');
}
/** @var LiveRoomGoodsServices $liveRoomGoods */
$liveRoomGoods = app()->make(LiveRoomGoodsServices::class);
$liveRoomGoods->delete(['live_room_id' => $id]);
}
return true;
}
/**
* 设置直播间备注
* @param int $id 直播间ID
* @param string $mark 备注内容
* @return bool
*/
public function mark($id, $mark)
{
return $this->dao->update($id, compact('mark'));
}
/**
* 直播间添加商品
* @param $room_id
* @param array $ids
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function exportGoods(int $room_id, array $ids)
{
$liveGoodsServices = app()->make(LiveGoodsServices::class);
if (count($ids) != count($goods = $liveGoodsServices->goodsList($ids)))
throw new AdminException('请选择正确的直播商品');
if (!$room = $this->dao->validRoom($room_id))
throw new AdminException('直播间状态有误');
$data = [];
/** @var LiveRoomGoodsServices $liveRoomGoodsServices */
$liveRoomGoodsServices = app()->make(LiveRoomGoodsServices::class);
//查询已经关联的
$roomGoods = $liveRoomGoodsServices->getColumn(['live_room_id' => $room_id], 'live_goods_id', 'Live_goods_id');
$goods_ids = [];
foreach ($goods as $key => $item) {
if (isset($roomGoods[$item['id']])) {
unset($goods[$key]);
} else {
$goods_ids[] = $item['goods_id'];
$data[] = [
'live_room_id' => $room_id,
'live_goods_id' => $item['id']
];
}
}
if ($goods_ids) {
$liveRoomGoodsServices->saveAll($data);
return MiniProgram::roomAddGoods($room['room_id'], $goods_ids);
}
return true;
}
/**
* 同步直播间状态
* @return bool
*/
public function syncRoomStatus()
{
$start = 0;
$limit = 50;
$data = $dataAll = [];
$rooms = $this->dao->getColumn([], 'id,room_id,live_status', 'room_id');
$roomIds = [];
do {
$wxRooms = MiniProgram::getLiveInfo($start, $limit);
foreach ($wxRooms as $room) {
if (in_array($room['roomid'], $roomIds)) {
continue;
}
$roomIds[] = $room['roomid'];
if ($rooms && isset($rooms[$room['roomid']])) {
if ($room['live_status'] != $rooms[$room['roomid']]['live_status']) {
$this->dao->update($rooms[$room['roomid']]['id'], ['live_status' => $room['live_status']]);
}
} else {
$data['name'] = $room['name'];
$data['room_id'] = $room['roomid'];
$data['cover_img'] = $room['cover_img'];
$data['share_img'] = $room['share_img'];
$data['live_status'] = $room['live_status'];
$data['start_time'] = $room['start_time'];
$data['end_time'] = $room['end_time'];
$data['anchor_name'] = $room['anchor_name'];
$dataAll[] = $data;
}
}
$start += $limit;
} while (count($wxRooms) >= $limit);
if ($dataAll) {
$this->dao->saveAll($dataAll);
}
return true;
}
}

View File

@@ -0,0 +1,362 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\lottery;
use app\jobs\system\CapitalFlowJob;
use app\services\BaseServices;
use app\dao\activity\lottery\LuckLotteryRecordDao;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\order\StoreOrderCreateServices;
use app\services\user\UserBillServices;
use app\services\user\UserMoneyServices;
use app\services\user\UserServices;
use app\services\wechat\WechatUserServices;
use crmeb\services\wechat\Payment;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Log;
/**
* 抽奖记录
* Class LuckLotteryRecordServices
* @package app\services\activity\lottery
* @mixin LuckLotteryRecordDao
*/
class LuckLotteryRecordServices extends BaseServices
{
/**
* @var LuckLotteryRecordDao
*/
#[Inject]
protected LuckLotteryRecordDao $dao;
/**
* 获取抽奖记录列表
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['not_type'] = 1;
if (isset($where['factor']) && $where['factor']) {
/** @var LuckLotteryServices $luckServices */
$luckServices = app()->make(LuckLotteryServices::class);
$where['lottery_id'] = $luckServices->value(['factor' => $where['factor'], 'status' => 1], 'id');
if (!$where['lottery_id']) {
$list = [];
$count = 0;
return compact('list', 'count');
}
unset($where['factor']);
}
$list = $this->dao->getList($where, '*', ['lottery', 'prize', 'user'], $page, $limit);
$count = 0;
if ($list) {
$prizeType = app()->make(LuckPrizeServices::class)->prize_type;
foreach ($list as &$item) {
if (isset($item['prize_info']) && $item['prize_info']) {
$prize = json_decode($item['prize_info'], true);
} else {
$prize = $item['prize'];
}
if (!$item['user']) {
$item['user']['nickname'] = '用户已注销';
$item['user']['real_name'] = '用户已注销';
$item['user']['phone'] = '';
$item['user']['uid'] = 0;
$item['user']['delete_time'] = null;
}
$prize['type_name'] = $prizeType[$prize['type']] ?? '未知';
$item['prize'] = $prize;
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取中奖记录
* @param array $where
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getWinList(array $where, int $limit = 10)
{
//去除未中奖的
$where = $where + ['not_type' => 1];
$list = $this->dao->getList($where, 'id,uid,prize_id,lottery_id,receive_time,prize_info,add_time', ['user', 'prize'], 0, $limit);
foreach ($list as &$item) {
if (isset($item['prize_info']) && $item['prize_info']) {
$prize = json_decode($item['prize_info'], true);
unset($item['prize_info']);
} else {
$prize = $item['prize'];
}
$item['prize'] = [
'id' => $prize['id'] ?? 0,
'type' => $prize['type'] ?? 1,
'name' => $prize['name'] ?? '',
'image' => $prize['image'] ?? '',
'prompt' => $item['prompt'] ?? '',
];
$item['receive_time'] = $item['receive_time'] ? date('Y-m-d H:i:s', $item['receive_time']) : '';
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i', $item['add_time']) : '';
}
return $list;
}
/**
* 参与抽奖数据统计
* @param int $lottery_id
* @return int[]
*/
public function getLotteryRecordData(int $lottery_id)
{
$data = ['all' => 0, 'people' => 0, 'win' => 0];
if ($lottery_id) {
$where = [['lottery_id', '=', $lottery_id]];
$data['all'] = $this->dao->getCount($where);
$data['people'] = $this->dao->getCount($where, 'uid');
$data['win'] = $this->dao->getCount($where + [['type', '>', 1]], 'uid');
}
return $data;
}
/**
* 写入中奖纪录
* @param int $uid
* @param array $prize
* @return mixed
*/
public function insertPrizeRecord(int $uid, array $prize, array $userInfo = [])
{
if (!$userInfo) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
}
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
if (!$prize) {
throw new ValidateException('奖品不存在');
}
$services = app()->make(StoreOrderCreateServices::class);
$data = [];
$data['uid'] = $uid;
$data['order_id'] = $services->getNewOrderId('hb');
$data['lottery_id'] = $prize['lottery_id'];
$data['prize_id'] = $prize['id'];
$data['type'] = $prize['type'];
$data['prize_info'] = json_encode($prize);
$data['add_time'] = time();
if (!$res = $this->dao->save($data)) {
throw new ValidateException('写入中奖记录失败');
}
return $res;
}
/**
* 领取奖品
* @param int $uid
* @param int $lottery_record_id
* @param string $receive_info
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function receivePrize(int $uid, int $lottery_record_id, array $receive_info = [], string $channel_type = '')
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserCacheInfo($uid);
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
$lotteryRecord = $this->dao->get($lottery_record_id, ['prize_id', 'lottery_id', 'id', 'is_receive', 'order_id'], ['prize']);
if (!$lotteryRecord || !isset($lotteryRecord['prize'])) {
throw new ValidateException('请继续参与活动抽奖');
}
if ($lotteryRecord['is_receive'] == 1) {
throw new ValidateException('已经领取成功');
}
$data = ['is_receive' => 1, 'receive_time' => time(), 'receive_info' => $receive_info];
if (isset($lotteryRecord['prize_info']) && $lotteryRecord['prize_info']) {
$prize = json_decode($lotteryRecord['prize_info'], true);
} else {
$prize = $lotteryRecord['prize'];
}
$this->transaction(function () use ($uid, $userInfo, $lottery_record_id, $data, $prize, $userServices, $receive_info, $channel_type, $lotteryRecord) {
//奖品类型1:未中奖2:积分3:余额4:红包5:优惠券6:站内商品7:等级经验8:用户等级 9:svip天数
switch ($prize['type']) {
case 1:
case 7:
case 8:
case 9:
break;
case 2:
/** @var UserBillServices $userBillServices */
$userBillServices = app()->make(UserBillServices::class);
$integral = bcadd((string)$userInfo['integral'], (string)$prize['num'], 0);
$userBillServices->income('lottery_give_integral', $uid, (int)$prize['num'], (int)$integral, $prize['id']);
$userServices->update($uid, ['integral' => $integral], 'uid');
break;
case 3:
/** @var UserMoneyServices $userMoneyServices */
$userMoneyServices = app()->make(UserMoneyServices::class);
$now_money = bcadd((string)$userInfo['now_money'], (string)$prize['num'], 2);
$userMoneyServices->income('lottery_give_money', $uid, $prize['num'], $now_money, $prize['id']);
$userServices->update($uid, ['now_money' => $now_money], 'uid');
break;
case 4:
$wechat_order_id = $lotteryRecord['order_id'];
$transferDetailList = [
[
'info_type' => '活动名称',
'info_content' => '抽奖红包'
],
[
'info_type' => '奖励说明',
'info_content' => '抽奖红包'
],
];
$userName = $userInfo['real_name'] ?? '';
$res = Payment::merchantPay($channel_type, $uid, $wechat_order_id, (string)$prize['num'], '抽奖中奖红包', $userName, $transferDetailList);
if (sys_config('pay_wechat_type') == 1) {
$data = array_merge($data, [
'transfer_bill_no' => $res['transfer_bill_no'] ?? '',
'wechat_state' => $res['state'] ?? '',
'fail_reason' => $res['fail_reason'] ?? '',
'package_info' => $res['package_info'] ?? ''
]);
event('notice.notice', [[
'uid' => $uid,
'extract_price' => $prize['num'],
'type' => 2,
'order_id' => $wechat_order_id
], 'revenue_received']);
}
//记录资金流水
CapitalFlowJob::dispatch([['order_id' => $wechat_order_id, 'store_id' => 0, 'uid' => $uid, 'price' => $prize['num'], 'pay_type' => 'weixin', 'nickname' => $userInfo['nickname'], 'phone' => $userInfo['phone']], 'luck']);
break;
case 5:
/** @var StoreCouponIssueServices $couponIssueService */
$couponIssueService = app()->make(StoreCouponIssueServices::class);
try {
$couponIssueService->issueUserCoupon($uid, (int)$prize['coupon_id'], true, 'luck_lottery');
} catch (\Throwable $e) {
Log::error('抽奖领取优惠券失败,原因:' . $e->getMessage());
}
break;
case 6:
if (!$receive_info['name'] || !$receive_info['phone'] || !$receive_info['address']) {
throw new ValidateException('请输入收货人信息');
}
if (!check_phone($receive_info['phone'])) {
throw new ValidateException('请输入正确的收货人电话');
}
break;
}
$this->dao->update($lottery_record_id, $data, 'id');
});
return true;
}
/**
* 发货、备注
* @param int $lottery_record_id
* @param array $data
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setDeliver(int $lottery_record_id, array $data)
{
$lotteryRecord = $this->dao->get($lottery_record_id, ['deliver_info', 'type', 'id']);
if (!$lotteryRecord) {
throw new ValidateException('抽奖记录不存在');
}
$deliver_info = $lotteryRecord['deliver_info'];
$edit = [];
//备注
if (!$data['deliver_name'] && !$data['deliver_number']) {
$deliver_info['mark'] = $data['mark'];
} else {
if ($lotteryRecord['type'] != 6 && ($data['deliver_name'] || $data['deliver_number'])) {
throw new ValidateException('该奖品不需要发货');
}
if ($lotteryRecord['type'] == 6 && (!$data['deliver_name'] || !$data['deliver_number'])) {
throw new ValidateException('请选择快递公司或输入快递单号');
}
$deliver_info['deliver_name'] = $data['deliver_name'];
$deliver_info['deliver_number'] = $data['deliver_number'];
$edit['is_deliver'] = 1;
$edit['deliver_time'] = time();
}
$edit['deliver_info'] = $deliver_info;
if (!$this->dao->update($lottery_record_id, $edit, 'id')) {
throw new ValidateException('处理失败');
}
return true;
}
/**
* 获取中奖记录
* @param int $uid
* @return array
*/
public function getRecord(int $uid, $where = [])
{
if (!$where) {
$where['uid'] = $uid;
$where['not_type'] = 1;
}
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', ['prize'], $page, $limit);
foreach ($list as &$item) {
if (isset($item['prize_info']) && $item['prize_info']) {
$prize = $item['prize_info'] = json_decode($item['prize_info'], true);
} else {
$prize = $item['prize'];
}
$item['prize'] = $prize;
$item['deliver_time'] = $item['deliver_time'] ? date('Y-m-d H:i:s', $item['deliver_time']) : '';
$item['receive_time'] = $item['receive_time'] ? date('Y-m-d H:i:s', $item['receive_time']) : '';
}
return $list;
}
/**
* 获取抽奖次数
* @param int $uid
* @param int $lottery_id
* @return array
*/
public function getLotteryNum(int $uid, int $lottery_id)
{
$where['uid'] = $uid;
$where['lottery_id'] = $lottery_id;
$now_day = strtotime(date('Y-m-d'));//今日
$todayCount = $this->dao->getCount($where + ['add_time' => $now_day]);
$totalCount = $this->dao->getCount($where);
return compact('todayCount', 'totalCount');
}
}

View File

@@ -0,0 +1,815 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\lottery;
use app\services\BaseServices;
use app\dao\activity\lottery\LuckLotteryDao;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\product\product\StoreProductServices;
use app\services\user\label\UserLabelServices;
use app\services\user\UserBillServices;
use app\services\user\label\UserLabelRelationServices;
use app\services\user\UserMoneyServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
*
* Class LuckLotteryServices
* @package app\services\activity\lottery
* @mixin LuckLotteryDao
*/
class LuckLotteryServices extends BaseServices
{
/**
* 抽奖形式,奖品数量
* @var int[]
*/
protected array $lottery_type = [
'1' => 8 //九宫格
];
/**
* 抽奖类型
* @var string[]
*/
protected array $lottery_factor = [
'1' => '积分抽奖',
'2' => '余额抽奖',
'3' => '订单支付',
'4' => '订单评价',
'5' => '关注公众号'
];
/**
* 抽奖次数缓存时间
* @var int
*/
public int $luck_cache_time = 120;
/**
* @var LuckLotteryDao
*/
#[Inject]
protected LuckLotteryDao $dao;
/**
* 获取抽奖缓存
* @param int $factor
* @return mixed
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/16
*/
public function getFactorLotteryCache(int $factor)
{
return $this->dao->cacheTag()->remember('' . $factor, function () use ($factor) {
$lottery = $this->dao->getFactorLottery($factor);
return $lottery ? $lottery->toArray() : null;
});
}
/**
* 获取抽奖活动列表
* @param array $where 查询条件
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, '*', ['prize'], $page, $limit);
$lottery_factor = $this->lottery_factor;
/** @var LuckLotteryRecordServices $luckLotteryRecordServices */
$luckLotteryRecordServices = app()->make(LuckLotteryRecordServices::class);
$ids = [];
foreach ($list as &$item) {
$item['lottery_type'] = $lottery_factor[$item['factor']] ?? '';
$data = $luckLotteryRecordServices->getLotteryRecordData((int)$item['id']);
$item['lottery_all'] = $data['all'] ?? 0;
$item['lottery_people'] = $data['people'] ?? 0;
$item['lottery_win'] = $data['win'] ?? 0;
if ($item['status']) {
if ($item['start_time'] == 0 && $item['end_time'] == 0) {
$item['lottery_status'] = '进行中';
} else {
if ($item['start_time'] > time()) {
$item['lottery_status'] = '未开始';
} else if ($item['end_time'] < time()) {
$item['lottery_status'] = '已结束';
$item['status'] = 0;
$ids[] = $item['id'];
} else if ($item['end_time'] > time() && $item['start_time'] < time()) {
$item['lottery_status'] = '进行中';
}
}
} else $item['lottery_status'] = '已结束';
$item['start_time'] = $item['start_time'] ? date('Y-m-d H:i:s', $item['start_time']) : '';
$item['end_time'] = $item['end_time'] ? date('Y-m-d H:i:s', $item['end_time']) : '';
if (!count($item['records'])) {
$item['records_total_user'] = 0;
$item['records_wins_user'] = 0;
$item['records_total_num'] = 0;
$item['records_wins_num'] = 0;
} else {
$item['records_total_user'] = $item['records'][0]['total_user'];
$item['records_wins_user'] = $item['records'][0]['wins_user'];
$item['records_total_num'] = $item['records'][0]['total_num'];
$item['records_wins_num'] = $item['records'][0]['wins_num'];
}
}
foreach ($ids as $id) {
$this->setStatus((int)$id, 0);
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取抽奖详情
* @param int $id
* @return array|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getlotteryInfo(int $id)
{
$lottery = $this->dao->getLottery($id, '*', ['prize']);
if (!$lottery) {
throw new ValidateException('活动不存在或已删除');
}
$lottery = $lottery->toArray();
if (isset($lottery['prize']) && $lottery['prize']) {
$products = $coupons = [];
$product_ids = array_unique(array_column($lottery['prize'], 'product_id'));
$coupon_ids = array_unique(array_column($lottery['prize'], 'coupon_id'));
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$products = $productServices->getColumn([['id', 'in', $product_ids]], 'id,store_name,image', 'id');
/** @var StoreCouponIssueServices $couponServices */
$couponServices = app()->make(StoreCouponIssueServices::class);
$coupons = $couponServices->getColumn([['id', 'in', $coupon_ids]], 'id,coupon_title', 'id');
foreach ($lottery['prize'] as &$prize) {
$prize['coupon_title'] = $prize['goods_image'] = '';
if ($prize['type'] == 6) {
$prize['goods_image'] = $products[$prize['product_id']]['image'] ?? '';
}
if ($prize['type'] == 5) {
$prize['coupon_title'] = $coupons[$prize['coupon_id']]['coupon_title'] ?? '';
}
}
}
return $lottery;
}
/**
* 根据类型获取数据
* @param int $factor
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getlotteryFactorInfo(int $factor)
{
$lottery = $this->dao->getFactorLottery($factor, '*', ['prize'], false);
if (!$lottery) {
return [];
}
$lottery = $lottery->toArray();
if (isset($lottery['prize']) && $lottery['prize']) {
$products = $coupons = [];
$product_ids = array_unique(array_column($lottery['prize'], 'product_id'));
$coupon_ids = array_unique(array_column($lottery['prize'], 'coupon_id'));
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$products = $productServices->getColumn([['id', 'in', $product_ids]], 'id,store_name,image', 'id');
/** @var StoreCouponIssueServices $couponServices */
$couponServices = app()->make(StoreCouponIssueServices::class);
$coupons = $couponServices->getColumn([['id', 'in', $coupon_ids]], 'id,coupon_title', 'id');
foreach ($lottery['prize'] as &$prize) {
$prize['coupon_title'] = $prize['goods_image'] = '';
if ($prize['type'] == 6) {
$prize['goods_image'] = $products[$prize['product_id']]['image'] ?? '';
}
if ($prize['type'] == 5) {
$prize['coupon_title'] = $coupons[$prize['coupon_id']]['coupon_title'] ?? '';
}
}
/** @var UserLabelServices $labelService */
$labelService = app()->make(UserLabelServices::class);
$lottery['user_label'] = $lottery['user_label'] ? $labelService->getColumn([
['id', 'in', $lottery['user_label']],
], 'id,label_name') : [];
}
return $lottery;
}
/**
* 添加抽奖活动以及奖品
* @param array $data
* @return mixed
*/
public function add(array $data)
{
$prizes = $data['prize'];
$prize_num = $this->lottery_type[1];
if (count($prizes) != $prize_num) {
throw new ValidateException('请选择' . $prize_num . '个奖品');
}
if (array_sum(array_column($prizes, 'percent')) != 100) {
throw new ValidateException('奖品概率和必须为100');
}
unset($data['prize']);
$this->transaction(function () use ($data, $prizes) {
$time = time();
$data['add_time'] = $time;
if (!$lottery = $this->dao->save($data)) {
throw new ValidateException('添加抽奖活动失败');
}
if ($data['status']) {
$this->setStatus((int)$lottery->id, $data['status']);
}
/** @var LuckPrizeServices $luckPrizeServices */
$luckPrizeServices = app()->make(LuckPrizeServices::class);
$sort = 1;
foreach ($prizes as $prize) {
$prize = $luckPrizeServices->checkPrizeData($prize);
$prize['lottery_id'] = $lottery->id;
unset($prize['id']);
$prize['add_time'] = $time;
$prize['sort'] = $sort;
$res = $luckPrizeServices->save($prize);
if (!$res) {
throw new ValidateException('添加抽奖奖品失败');
}
$sort++;
//抽奖商品库存加入redis
CacheService::setStock((string)$res->id, (int)$prize['total'], 6);
}
return true;
});
$this->dao->cacheTag()->clear();
return true;
}
/**
* 修改抽奖活动以及奖品
* @param int $id
* @param array $data
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function edit(int $id, array $data)
{
$lottery = $this->dao->getLottery($id);
if (!$lottery) {
throw new ValidateException('抽奖活动不存在');
}
$newPrizes = $data['prize'];
if (array_sum(array_column($newPrizes, 'percent')) != 100) {
throw new ValidateException('奖品概率和必须为100');
}
unset($data['prize'], $data['id']);
$prize_num = $this->lottery_type[1];
if (count($newPrizes) != $prize_num) {
throw new ValidateException('请选择' . $prize_num . '个奖品');
}
/** @var LuckPrizeServices $luckPrizeServices */
$luckPrizeServices = app()->make(LuckPrizeServices::class);
$prizes = $luckPrizeServices->getLotteryPrizeList($id);
$this->transaction(function () use ($id, $lottery, $data, $newPrizes, $prizes, $luckPrizeServices) {
$updateIds = array_column($newPrizes, 'id');
$oldIds = array_column($prizes, 'id');
$delIds = array_merge(array_diff($oldIds, $updateIds));
$insert = [];
$time = time();
$sort = 1;
foreach ($newPrizes as $prize) {
$prize = $luckPrizeServices->checkPrizeData($prize);
$prize['sort'] = $sort;
if (isset($prize['id']) && $prize['id']) {
$prizeId = $prize['id'];
if (!$prize['lottery_id']) {
throw new ValidateException('缺少活动ID');
}
if (!$luckPrizeServices->update($prize['id'], $prize, 'id')) {
throw new ValidateException('修改奖品失败');
}
} else {
unset($prize['id']);
$prize['lottery_id'] = $id;
$prize['add_time'] = $time;
$prize['sort'] = $sort;
$res = $luckPrizeServices->save($prize);
if (!$res) {
throw new ValidateException('新增奖品失败');
}
$prizeId = $res->id;
}
$sort++;
//抽奖商品库存加入redis
CacheService::setStock((string)$prizeId, (int)$prize['total'], 6);
}
if ($delIds) {
if (!$luckPrizeServices->update([['id', 'in', $delIds]], ['is_del' => 1])) {
throw new ValidateException('删除奖品失败');
}
}
if (!$this->dao->update($id, $data)) {
throw new ValidateException('修改失败');
}
//上架
if (!$lottery['status'] && $data['status']) {
$this->setStatus($id, $data['status']);
}
return true;
});
$this->dao->cacheTag()->clear();
return true;
}
/**
* 获取用户某个抽奖活动剩余抽奖次数
* @param int $uid
* @param int $lottery_id
* @param array $userInfo
* @param array $lottery
* @return false|float|int|mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLotteryNum(int $uid, int $lottery_id, array $userInfo = [], array $lottery = [])
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
if (!$userInfo) {
$userInfo = $userServices->getUserInfo($uid, ['integral', 'uid', 'now_money', 'spread_lottery']);
}
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
if (!$lottery) {
$lottery = $this->dao->getLottery($lottery_id, '*', [], true);
}
if (!$lottery) {
throw new ValidateException('该活动已经下架,请持续关注');
}
//抽奖类型:1:积分2:余额3:下单支付成功4:订单评价5:拉新人
switch ($lottery['factor']) {
case 1:
$integral = $userInfo['integral'] - app()->make(UserBillServices::class)->getBillSum(['uid' => $uid, 'is_frozen' => 1]);
return $userInfo['integral'] > 0 && $lottery['factor_num'] > 0 ? floor($integral / $lottery['factor_num']) : 0;
break;
case 2:
return $userInfo['now_money'] > 0 && $lottery['factor_num'] > 0 ? floor($userInfo['now_money'] / $lottery['factor_num']) : 0;
break;
case 3:
return $this->getCacheLotteryNum($uid, 'order');
break;
case 4:
return $this->getCacheLotteryNum($uid, 'comment');
break;
case 5:
return $userInfo['spread_lottery'] ?? 0;
break;
default:
throw new ValidateException('暂未有该类型活动');
break;
}
}
/**
* 验证用户抽奖资格(用户等级、付费会员、用户标签)
* @param int $uid
* @param int $lottery_id
* @param array $userInfo
* @param array $lottery
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkoutUserAuth(int $uid, int $lottery_id, array $userInfo = [], array $lottery = [])
{
if (!$userInfo) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserCacheInfo($uid);
}
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
if (!$lottery) {
$lottery = $this->dao->getLottery($lottery_id, '*', [], true);
}
if (!$lottery) {
throw new ValidateException('该活动已经下架,请持续关注');
}
//部分用户参与
if ($lottery['attends_user'] == 2) {
//用户等级
if ($lottery['user_level'] && !in_array($userInfo['level'], $lottery['user_level'])) {
throw new ValidateException('您暂时无法参与该活动');
}
//用户标签
if ($lottery['user_label']) {
/** @var UserLabelRelationServices $userlableRelation */
$userlableRelation = app()->make(UserLabelRelationServices::class);
$user_labels = $userlableRelation->getUserLabels($uid);
if (!array_intersect($lottery['user_label'], $user_labels)) {
throw new ValidateException('您暂时无法参与该活动');
}
}
//是否是付费会员
if ($lottery['is_svip'] != -1) {
if (($lottery['is_svip'] == 1 && $userInfo['is_money_level'] <= 0) || ($lottery['is_svip'] == 0 && $userInfo['is_money_level'] > 0)) {
throw new ValidateException('您暂时无法参与该活动');
}
}
}
return true;
}
/**
* 抽奖
* @param int $uid
* @param int $lottery_id
* @return mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function luckLottery(int $uid, int $lottery_id, string $channel_type = '')
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
$lottery = $this->dao->getLottery($lottery_id, '*', [], true);
if (!$lottery) {
throw new ValidateException('该活动已经下架,请持续关注');
}
$userInfo = $userInfo->toArray();
$lottery = $lottery->toArray();
//验证用户身份
$this->checkoutUserAuth($uid, $lottery_id, $userInfo, $lottery);
/** @var LuckPrizeServices $lotteryPrizeServices */
$lotteryPrizeServices = app()->make(LuckPrizeServices::class);
$lotteryPrize = $lotteryPrizeServices->getPrizeList($lottery_id);
if (!$lotteryPrize) {
throw new ValidateException('该活动状态有误,请联系管理员');
}
if ($this->getLotteryNum($uid, $lottery_id, $userInfo, $lottery) < 1) {
//抽奖类型:1:积分2:余额3:下单支付成功4:订单评价5:拉新人
switch ($lottery['factor']) {
case 1:
throw new ValidateException('积分不足,没有更多抽奖次数');
break;
case 2:
throw new ValidateException('余额不足,没有更多抽奖次数');
break;
case 3:
throw new ValidateException('购买商品之后获得更多抽奖次数');
break;
case 4:
throw new ValidateException('订单完成评价之后获得更多抽奖次数');
break;
case 5:
throw new ValidateException('邀请更多好友获取抽奖次数');
break;
default:
throw new ValidateException('暂未有该类型活动');
break;
}
}
if ($lottery['factor'] == 1) {//积分抽奖
/** @var LuckLotteryRecordServices $lotteryRecordServices */
$lotteryRecordServices = app()->make(LuckLotteryRecordServices::class);
$data = $lotteryRecordServices->getLotteryNum($uid, (int)$lottery['id']);
$lotteryData['todayCount'] = (int)max(bcsub((string)$lottery['lottery_num'], (string)$data['todayCount'], 0), 0);
$lotteryData['totalCount'] = (int)max(bcsub((string)$lottery['total_lottery_num'], (string)$data['totalCount'], 0), 0);
if ($lotteryData['todayCount'] <= 0) {
throw new ValidateException('本次活动当天抽奖次数已用完!');
}
if ($lotteryData['totalCount'] <= 0) {
throw new ValidateException('本次活动总抽奖次数已用完!');
}
}
/** @var LuckPrizeServices $luckPrizeServices */
$luckPrizeServices = app()->make(LuckPrizeServices::class);
//随机抽奖
$prize = $luckPrizeServices->getLuckPrize($lotteryPrize);
if (!$prize) {
throw new ValidateException('该活动状态有误,请联系管理员');
}
return $this->transaction(function () use ($prize, $luckPrizeServices, $uid, $lotteryPrize, $userInfo, $lottery, $channel_type) {
//中奖扣除积分、余额
$this->lotteryFactor($uid, $userInfo, $lottery);
//中奖减少奖品数量
$luckPrizeServices->decPrizeNum($prize['id'], $prize);
/** @var LuckLotteryRecordServices $lotteryRecordServices */
$lotteryRecordServices = app()->make(LuckLotteryRecordServices::class);
//中奖写入记录
$record = $lotteryRecordServices->insertPrizeRecord($uid, $prize, $userInfo);
//不是站内商品直接领奖
if ($prize['type'] != 6) {
$lotteryRecordServices->receivePrize($uid, (int)$record->id, [], $channel_type);
}
$prize['lottery_record_id'] = $record->id;
return $prize;
});
}
/**
* 抽奖消耗扣除用户积分、余额等
* @param int $uid
* @param array $userInfo
* @param array $lottery
* @return bool
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function lotteryFactor(int $uid, array $userInfo, array $lottery)
{
if (!$userInfo || !$lottery) {
return true;
}
//抽奖类型:1:积分2:余额3:下单支付成功4:订单评价5:拉新人
switch ($lottery['factor']) {
case 1:
if ($userInfo['integral'] > $lottery['factor_num']) {
$integral = bcsub((string)$userInfo['integral'], (string)$lottery['factor_num'], 0);
} else {
$integral = 0;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
/** @var UserBillServices $userBillServices */
$userBillServices = app()->make(UserBillServices::class);
$userBillServices->income('lottery_use_integral', $uid, (int)$lottery['factor_num'], (int)$integral, $lottery['id']);
if (!$userServices->update($uid, ['integral' => $integral], 'uid')) {
throw new ValidateException('抽奖扣除用户积分失败');
}
break;
case 2:
if ($userInfo['now_money'] >= $lottery['factor_num']) {
$now_money = bcsub((string)$userInfo['now_money'], (string)$lottery['factor_num'], 2);
} else {
throw new ValidateException('抽奖失败,余额不足!');
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
/** @var UserMoneyServices $userMoneyServices */
$userMoneyServices = app()->make(UserMoneyServices::class);
$userMoneyServices->income('lottery_use_money', $uid, $lottery['factor_num'], $now_money, $lottery['id']);
if (!$userServices->update($uid, ['now_money' => $now_money], 'uid')) {
throw new ValidateException('抽奖扣除用户余额失败');
}
break;
case 3:
case 4:
//销毁抽奖次数缓存
$this->delCacheLotteryNum($uid, $lottery['factor'] == 3 ? 'order' : 'comment');
break;
case 5:
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$spread_lottery = 0;
if ($userInfo['spread_lottery'] > 1) {
$spread_lottery = $userInfo['spread_lottery'] - 1;
}
if (!$userServices->update($uid, ['spread_lottery' => $spread_lottery], 'uid')) {
throw new ValidateException('抽奖扣除用户推广获取抽奖次数失败');
}
break;
default:
throw new ValidateException('暂未有该类型活动');
break;
}
return true;
}
/**
* 删除
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function delLottery(int $id)
{
if ($lottery = $this->dao->getLottery($id)) {
if (!$this->dao->update(['id' => $id], ['is_del' => 1])) {
throw new AdminException('删除失败,请稍候重试');
}
}
return true;
}
/**
* 设置抽奖活动状态
* @param int $id
* @param $status
* @return false|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setStatus(int $id, $status)
{
if (!$id) return false;
$lottery = $this->dao->getLottery($id, 'id,factor');
if (!$lottery) {
return false;
}
//每一种抽奖类型只有一个上架
// if ($status) {
// $this->dao->update(['factor' => $lottery['factor']], ['status' => 0]);
// }
return $this->dao->update($id, ['status' => $status], 'id');
}
/**
* 下单支付、评论缓存抽奖次数
* @param int $uid
* @param string $type
* @return bool
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function setCacheLotteryNum(int $uid, string $type = 'order')
{
$factor = $type == 'order' ? 3 : 4;
$lottery = $this->dao->getFactorLottery($factor, 'id,factor_num');
if (!$lottery || !$lottery['factor_num']) {
return true;
}
$key = 'user_' . $type . '_luck_lottery_' . $uid;
$timeKey = 'expire_time_user_' . $type . '_luck_lottery_' . $uid;
$cache = CacheService::redisHandler();
// $num = (int)$this->getCacheLotteryNum($uid, $type);
$cache->set($timeKey, bcadd((string)time(), (string)$this->luck_cache_time), bcadd('10', (string)$this->luck_cache_time));
return $cache->set($key, $lottery['factor_num'], $this->luck_cache_time);
}
/**
* 获取抽奖到期时间
* @param int $uid
* @param string $type
* @return int|mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function getCacheLotteryExpireTime(int $uid, string $type = 'order')
{
$timeKey = 'expire_time_user_' . $type . '_luck_lottery_' . $uid;
$time = CacheService::redisHandler()->get($timeKey);
return empty($time) ? 0 : $time;
}
/**
* 取出下单支付、评论得到的抽奖此处
* @param int $uid
* @param string $type
* @return int|mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function getCacheLotteryNum(int $uid, string $type = 'order')
{
$key = 'user_' . $type . '_luck_lottery_' . $uid;
$num = CacheService::redisHandler()->get($key);
return empty($num) ? 0 : $num;
}
/**
* 抽奖之后销毁缓存
* @param int $uid
* @param string $type
* @return bool
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function delCacheLotteryNum(int $uid, string $type = 'order')
{
$key = 'user_' . $type . '_luck_lottery_' . $uid;
$timeKey = 'expire_time_user_' . $type . '_luck_lottery_' . $uid;
$num = $this->getCacheLotteryNum($uid, $type);
$cache = CacheService::redisHandler();
if ($num > 1) {
$cache->set($timeKey, bcadd((string)time(), (string)$this->luck_cache_time), bcadd('10', (string)$this->luck_cache_time));
$cache->set($key, $num - 1, $this->luck_cache_time);
} else {
$cache->delete($timeKey);
$cache->delete($key);
}
return true;
}
/**
* 获取抽奖列表以及正在使用的抽奖活动
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/3/18
*/
public function factorList()
{
$list = $this->dao->selectList([
['status', '=', 1],
['is_del', '=', 0],
['end_time', '>', time()],
['start_time', '<', time()],
], 'id,name,factor,is_use')->toArray();
$data = [
'info' => [
'point' => '',
'pay' => '',
'evaluate' => '',
'follow' => ''
],
'point' => [],
'pay' => [],
'evaluate' => [],
'follow' => []
];
foreach ($list as $item) {
if ($item['factor'] == 1) {
$data['point'][] = $item;
if ($data['info']['point'] == '') {
$data['info']['point'] = $item['is_use'] ? $item['id'] : '';
}
} elseif ($item['factor'] == 3) {
$data['pay'][] = $item;
if ($data['info']['pay'] == '') {
$data['info']['pay'] = $item['is_use'] ? $item['id'] : '';
}
} elseif ($item['factor'] == 4) {
$data['evaluate'][] = $item;
if ($data['info']['evaluate'] == '') {
$data['info']['evaluate'] = $item['is_use'] ? $item['id'] : '';
}
} else {
$data['follow'][] = $item;
if ($data['info']['follow'] == '') {
$data['info']['follow'] = $item['is_use'] ? $item['id'] : '';
}
}
}
return $data;
}
/**
* 活动类型使用
* @param $data
* @return bool
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/3/18
*/
public function factorUse($data)
{
$this->dao->update(['is_del' => 0], ['is_use' => 0]);
if ($data['point']) {
$this->dao->update(['factor' => 1, 'id' => $data['point']], ['is_use' => 1]);
}
if ($data['pay']) {
$this->dao->update(['factor' => 3, 'id' => $data['pay']], ['is_use' => 1]);
}
if ($data['evaluate']) {
$this->dao->update(['factor' => 4, 'id' => $data['evaluate']], ['is_use' => 1]);
}
if ($data['follow']) {
$this->dao->update(['factor' => 5, 'id' => $data['follow']], ['is_use' => 1]);
}
return true;
}
}

View File

@@ -0,0 +1,273 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\lottery;
use app\services\BaseServices;
use app\dao\activity\lottery\LuckPrizeDao;
use app\services\activity\coupon\StoreCouponIssueServices;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
*
* Class LuckPrizeServices
* @package app\services\activity\lottery
* @mixin LuckPrizeDao
*/
class LuckPrizeServices extends BaseServices
{
/**
* @var array 1:未中奖2:积分3:余额4:红包5:优惠券6:站内商品7:等级经验8:用户等级 9:svip天数
*/
public array $prize_type = [
'1' => '未中奖',
'2' => '积分',
'3' => '余额',
'4' => '红包',
'5' => '优惠券',
'6' => '站内商品',
'7' => '等级经验',
'8' => '用户等级',
'9' => 'svip天数'
];
/**
* 奖品数据字段
* @var array
*/
public array $prize = [
'id' => 0,
'type' => 1,
'lottery_id' => 0,
'name' => '',
'prompt' => '',
'image' => '',
'chance' => 0,
'percent' => 0,
'total' => 0,
'coupon_id' => 0,
'product_id' => 0,
'unique' => '',
'num' => 1,
'sort' => 0,
'status' => 1,
'is_del' => 0,
'add_time' => 0,
];
/**
* @var LuckPrizeDao
*/
#[Inject]
protected LuckPrizeDao $dao;
/**
* 奖品数据验证
* @param array $data
* @return array
*/
public function checkPrizeData(array $data)
{
$data = array_merge($this->prize, array_intersect_key($data, $this->prize));
if (!isset($data['name']) || !$data['name']) {
throw new ValidateException('请填写奖品名称');
}
if (!isset($data['image']) || !$data['image']) {
throw new ValidateException('请选择奖品图片');
}
if (!isset($data['chance'])) {
throw new ValidateException('请填写奖品中奖权重');
}
if (!isset($data['type']) || !isset($this->prize_type[$data['type']])) {
throw new ValidateException('请选择奖品类型');
}
if (in_array($data['type'], [2, 3, 4]) && (!isset($data['num']) || !$data['num'])) {
$msg = '';
switch ($data['type']) {
case 2:
$msg = '积分';
break;
case 3:
$msg = '余额';
break;
case 4:
$msg = '红包';
break;
}
throw new ValidateException('请填写奖品赠送' . $msg . '数');
}
if ($data['type'] == 2) {
$data['num'] = (int)$data['num'];
}
if ($data['type'] == 5 && (!isset($data['coupon_id']) || !$data['coupon_id'])) {
throw new ValidateException('请选择优惠券');
}
if ($data['type'] == 6 && (!isset($data['product_id']) || !$data['product_id'])) {
throw new ValidateException('请选择商品');
}
return $data;
}
/**
* 修改奖品
* @param int $id
* @param array $data
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function edit(int $id, array $data)
{
$this->checkPrizeData($data);
$prize = $this->dao->get($id);
if (!$prize) {
throw new ValidateException('奖品不存在');
}
if (!$this->dao->update($id, $data, 'id')) {
throw new ValidateException('修改失败');
}
return true;
}
/**
* 获取某个抽奖活动的所有奖品
* @param int $lottery_id
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLotteryPrizeList(int $lottery_id, string $field = '*')
{
return $this->dao->getPrizeList($lottery_id, $field);
}
/**
* 随机奖品
* @param array $data
* @return array|mixed
*/
public function getLuckPrize(array $data)
{
$totalPercent = array_sum(array_column($data, 'percent')) * 100;
$prize = [];
if (!$data) return $prize;
mt_srand();
$random = mt_rand(1, (int)$totalPercent);
$range = 0;
$newPrize = array_combine(array_column($data, 'type'), $data);
foreach ($data as $item) {
// 转换百分比为千分位范围
$range += $item['percent'] * 100; // 例如 12.34% -> 1234
if ($random <= $range) {
if (($item['sort'] != 1 && $item['total'] != -1 && $item['total'] <= 0)) {
$prize = $newPrize[1] ?? [];
} else {
if (CacheService::checkStock((string)$item['id'], 1, 6)) {
$prize = $item;
} else {
$prize = $newPrize[1] ?? [];
}
}
break;
}
}
return $prize;
// $prize = [];
// if (!$data) return $prize;
// $coupon = [];
// $coupon_ids = array_unique(array_column($data, 'coupon_id'));
// if ($coupon_ids) {
// /** @var StoreCouponIssueServices $couponServices */
// $couponServices = app()->make(StoreCouponIssueServices::class);
// $coupon = $couponServices->getGiveCoupon([['id', 'IN', $coupon_ids]]);
// if ($coupon) $coupon = array_combine(array_column($coupon, 'id'), $coupon);
// }
// $totalChance = 100;
// $startChance = 0;
// mt_srand();
// $prizeChance = rand(0, $totalChance - 1);
// $newPrize = array_combine(array_column($data, 'type'), $data);
// foreach ($data as $item) {
// $newStartChance = (int)bcadd((string)$item['chance'], (string)$startChance);
// //随机数在这个基数端内 且该商品数量大于0 中奖
// if ($prizeChance >= $startChance && $prizeChance < $newStartChance) {
// //奖品权重<=0 || 随机到不是未中奖奖品-》设置了奖品数量-》数量不足时 返回未中奖奖品 || 抽到优惠券 数量不足
// if ((int)$item['chance'] <= 0 ||
// (
// $item['type'] != 1 &&
// $item['total'] != -1 &&
// $item['total'] <= 0
// )
// ||
// (
// $item['coupon_id'] &&
// $coupon &&
// !isset($coupon[$item['coupon_id']])
// )
// ) {
// $prize = $newPrize[1] ?? [];
// } else {
// if (CacheService::checkStock((string)$item['id'], 1, 6)) {
// $prize = $item;
// } else {
// $prize = $newPrize[1] ?? [];
// }
// }
// break;
// }
// $startChance = $newStartChance;
// }
// return $prize;
}
/**
* 中奖后减少奖品数量
* @param int $id
* @param array $prize
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function decPrizeNum(int $id, array $prize = [])
{
if (!$id) return false;
if (!$prize) {
$prize = $this->dao->get($id);
}
if (!$prize) {
throw new ValidateException('该奖品不存在');
}
//不是未中奖奖品 减少奖品数量
if ($prize['type'] != 1 && $prize['total'] >= 1) {
$total = $prize['total'] - 1;
if (!$this->dao->update($id, ['total' => $total], 'id')) {
throw new ValidateException('抽奖减少奖品总数失败');
}
CacheService::popStock((string)$id, 1, 6);
}
return true;
}
}

View File

@@ -0,0 +1,656 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\newcomer;
use app\dao\activity\newcomer\StoreNewcomerDao;
use app\jobs\product\ProductLogJob;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\activity\coupon\StoreCouponUserServices;
use app\services\BaseServices;
use app\services\diy\DiyServices;
use app\services\order\StoreOrderServices;
use app\services\product\ensure\StoreProductEnsureServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductReplyServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\user\UserRelationServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\SystemConfigService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreNewcomerServices
* @package app\services\activity\newcomer
* @mixin StoreNewcomerDao
*/
class StoreNewcomerServices extends BaseServices
{
/**
* 商品活动类型
*/
const TYPE = 7;
/**
* @var StoreNewcomerDao
*/
#[Inject]
protected StoreNewcomerDao $dao;
/**
* 获取新人礼商品详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id, ['*'], ['product']);
$res = [];
if ($info) {
$res = $info->toArray();
$product = $res['product'] ?? [];
unset($res['product'], $product['id'], $product['price']);
$res = array_merge($res, $product);
}
return $res;
}
/**
* 获取新人专享商品
* @param array $where
* @param string $field
* @param array $with
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCustomerProduct(array $where = [], string $field = '*', array $with = ['product', 'attrValue'])
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, $field, $page, $limit, $with);
$res = [];
if ($list) {
foreach ($list as &$item) {
$product = $item['product'] ?? [];
if ($product) {
unset($item['product'], $product['id'], $product['price']);
$item = array_merge($item, $product);
$res[] = $item;
}
}
}
return $res;
}
/**
* 保存新人礼专属商品
* @param $data
* @return bool
*/
public function saveNewcomer($data)
{
$productIds = [];
$oldProductIds = $this->dao->getColumn(['is_del' => 0], 'id,product_id');
if ($oldProductIds) $oldProductIds = array_column($oldProductIds, 'product_id');
if ($data) {
$productIds = array_column($data, 'product_id');
$this->transaction(function () use ($data) {
foreach ($data as $product) {
$this->saveData(0, $product);
}
});
}
$deleteIds = array_merge(array_diff($oldProductIds, $productIds));
//清空现有新人礼商品
if ($deleteIds) {
$this->dao->update(['is_del' => 0, 'product_id' => $deleteIds], ['is_del' => 1, 'update_time' => time()]);
}
return true;
}
/**
* 保存数据
* @param int $id
* @param array $info
*/
public function saveData(int $id, array $info)
{
if (!$info || !isset($info['product_id']) || !$info['product_id']) {
throw new ValidateException('请重新选择新人专享商品');
}
$attr = $info['attr'];
if ($attr) {
foreach ($attr as $a) {
if (!isset($a['unique']) || !isset($a['price'])) throw new ValidateException('请重新选择新人专享商品');
if (!$a['price']) {
throw new ValidateException('请填写商品专享价');
}
}
} else {
if (!$info['price']) {
throw new ValidateException('请填写商品专享价');
}
}
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_show' => 1, 'is_del' => 0, 'is_verify' => 1, 'id' => $info['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已下架或移入回收站');
}
if ($productInfo['is_vip_product'] || $productInfo['is_presale_product']) {
throw new AdminException('该商品是预售或svip专享');
}
if (!$id) {
$newcomer = $this->dao->getOne(['product_id' => $info['product_id'], 'is_del' => 0]);
$id = $newcomer['id'] ?? 0;
}
$data = [];
$data['product_id'] = $productInfo['id'];
$data['type'] = $productInfo['type'] ?? 0;
$data['product_type'] = $productInfo['product_type'];
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$data['price'] = $info['price'] ?? 0;
if ($attr) $data['price'] = min(array_column($attr, 'price'));
/** @var StoreProductAttrValueServices $productAttrValueServices */
$productAttrValueServices = app()->make(StoreProductAttrValueServices::class);
/** @var StoreProductAttrServices $productAttrServices */
$productAttrServices = app()->make(StoreProductAttrServices::class);
$attrValue = $productAttrValueServices->getList(['product_id' => $info['product_id'], 'type' => 0]);
$skus = array_column($attr, 'unique');
$attr = array_combine($skus, $attr);
$this->transaction(function () use ($id, $data, $attrValue, $skus, $attr, $productAttrValueServices, $productAttrServices) {
$newcomerAttrValue = [];
if ($id) {
$data['update_time'] = time();
$res = $this->dao->update($id, $data);
$newcomerAttrValue = $productAttrValueServices->getList(['product_id' => $id, 'type' => 7]);
if (!$res) throw new AdminException('修改失败');
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$detail = [];
foreach ($attrValue as $item) {
if (in_array($item['unique'], $skus)) {
$item['product_id'] = $id;
$item['type'] = 7;
if (isset($attr[$item['unique']]['price'])) $item['price'] = $attr[$item['unique']]['price'];
$item['unique'] = $productAttrServices->createAttrUnique((int)$id, $item['suk']);
unset($item['id']);
$detail[] = $item;
}
}
if ($newcomerAttrValue) {
foreach ($newcomerAttrValue as $item) {
if (in_array($item['unique'], $skus)) {
$item['product_id'] = $id;
$item['type'] = 7;
if (isset($attr[$item['unique']]['price'])) $item['price'] = $attr[$item['unique']]['price'];
unset($item['id']);
$detail[] = $item;
}
}
}
$productAttrValueServices->delete(['product_id' => $id, 'type' => 7]);
if ($detail) $productAttrValueServices->saveAll($detail);
});
}
/**
* 获取新人专享商品详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function newcomerDetail(int $uid, int $id)
{
$storeInfo = $this->getInfo($id);
if (!$storeInfo) {
throw new ValidateException('新人商品已下架或删除');
}
/** @var DiyServices $diyServices */
$diyServices = app()->make(DiyServices::class);
$infoDiy = $diyServices->getProductDetailDiy();
//diy控制参数
if (!isset($infoDiy['showService']) || in_array(3, $infoDiy['showService'])) {
$storeInfo['specs'] = [];
}
$configData = SystemConfigService::more(['site_url', 'routine_contact_type', 'site_name', 'share_qrcode', 'store_self_mention', 'store_func_status', 'product_poster_title']);
$siteUrl = $configData['site_url'] ?? '';
$storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
/** @var StoreDescriptionServices $descriptionServices */
$descriptionServices = app()->make(StoreDescriptionServices::class);
$storeInfo['description'] = $descriptionServices->getDescription(['product_id' => $storeInfo['product_id']]);
//品牌名称
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->get($storeInfo['product_id'], ['id', 'delivery_type', 'brand_id', 'sales', 'ficti']);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id'], $productInfo);
$delivery_type = $storeInfo['delivery_type'] ?? $productInfo['delivery_type'];
$storeInfo['delivery_type'] = is_string($delivery_type) ? explode(',', $delivery_type) : $delivery_type;
/**
* 判断配送方式
*/
$storeInfo['delivery_type'] = $storeProductServices->getDeliveryType($storeInfo['type'], $storeInfo['relation_id'], $storeInfo['delivery_type']);
$storeInfo['total'] = $productInfo['sales'] + $productInfo['ficti'];
$storeInfo['store_label'] = $storeInfo['ensure'] = [];
if ($storeInfo['store_label_id']) {
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
if ($storeInfo['ensure_id'] && isset($infoDiy['showService']) && in_array(2, $infoDiy['showService'])) {
/** @var StoreProductEnsureServices $storeProductEnsureServices */
$storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
$storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
}
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$data['buy_num'] = $storeOrderServices->getBuyCount($uid, 7, $id);
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$storeInfo['userCollect'] = $userRelationServices->isProductRelationCache(['uid' => $uid, 'relation_id' => $storeInfo['product_id'], 'type' => 'collect', 'category' => UserRelationServices::CATEGORY_PRODUCT]);
$storeInfo['userLike'] = 0;
$storeInfo['uid'] = $uid;
//商品详情
$storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
$data['storeInfo'] = $storeInfo;
$data['reply'] = [];
$data['replyChance'] = $data['replyCount'] = 0;
if (isset($infoDiy['showReply']) && $infoDiy['showReply']) {
/** @var StoreProductReplyServices $storeProductReplyService */
$storeProductReplyService = app()->make(StoreProductReplyServices::class);
$reply = $storeProductReplyService->getRecProductReply($storeInfo['product_id'], (int)($infoDiy['replyNum'] ?? 1));
$data['reply'] = $reply ? get_thumb_water($reply, 'small', ['pics']) : [];
[$replyCount, $goodReply, $replyChance] = $storeProductReplyService->getProductReplyData((int)$storeInfo['product_id']);
$data['replyChance'] = $replyChance;
$data['replyCount'] = $replyCount;
}
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetail($id, $uid, 0, 7, $storeInfo['product_id']);
$data['productAttr'] = $productAttr;
$data['productValue'] = $productValue;
$data['routine_contact_type'] = $configData['routine_contact_type'] ?? 0;
$data['store_func_status'] = (int)($configData['store_func_status'] ?? 1);//门店是否开启
$data['store_self_mention'] = $data['store_func_status'] ? (int)($configData['store_self_mention'] ?? 0) : 0;//门店自提是否开启
$data['site_name'] = $configData['site_name'] ?? '';
$data['share_qrcode'] = $configData['share_qrcode'] ?? 0;
$data['product_poster_title'] = $configData['product_poster_title'] ?? '';
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'newcomer']);
return $data;
}
/**
* 修改秒杀库存
* @param int $num
* @param int $newcomerId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decNewcomerStock(int $num, int $newcomerId, string $unique = '', int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $newcomerId], 'product_id');
if ($product_id && $unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//新人商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $newcomerId, 'type' => 7], 'suk');
//平台普通商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = false !== $services->decProductStock($num, $product_id, (string)$productUnique);
} else {
$res = false;
}
return $res;
}
/**
* 加库存减销量
* @param int $num
* @param int $newcomerId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incNewcomerStock(int $num, int $newcomerId, string $unique = '', int $store_id = 0)
{
$product_id = $this->dao->value(['id' => $newcomerId], 'product_id');
if ($product_id && $unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//新人商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $newcomerId, 'type' => 7], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $services->incProductStock($num, $product_id, (string)$productUnique);
} else {
$res = false;
}
return $res;
}
/**
* 下单|加入购物车验证秒杀商品库存
* @param int $uid
* @param int $newcomerId
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkNewcomerStock(int $uid, int $newcomerId, int $cartNum = 1, string $unique = '')
{
if (!$this->checkUserNewcomer($uid)) {
throw new ValidateException('您已无法享受新人专享价');
}
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
if ($unique == '') {
$unique = $attrValueServices->value(['product_id' => $newcomerId, 'type' => 7], 'unique');
}
//检查商品活动状态
$newcomerInfo = $this->getInfo($newcomerId);
if (!$newcomerInfo) {
throw new ValidateException('该活动已下架');
}
$attrInfo = $attrValueServices->getOne(['product_id' => $newcomerId, 'unique' => $unique, 'type' => 7]);
if (!$attrInfo || $attrInfo['product_id'] != $newcomerId) {
throw new ValidateException('请选择有效的商品属性');
}
$suk = $attrInfo['suk'];
$productAttrInfo = $attrValueServices->getOne(['suk' => $suk, 'product_id' => $newcomerInfo['product_id'], 'type' => 0]);
if (!$productAttrInfo) {
throw new ValidateException('请选择有效的商品属性');
}
//库存要验证愿商品库存
if ($cartNum > $productAttrInfo['stock']) {
throw new ValidateException('该商品库存不足' . $cartNum);
}
return [$attrInfo, $unique, $newcomerInfo];
}
/**
* 验证用户是否可以享受首单优惠
* @param int $uid
* @param $userInfo
* @return array
*/
public function checkUserFirstDiscount(int $uid, $userInfo = [])
{
if (!$uid) {
return [];
}
//开启新人礼
if (!sys_config('newcomer_status')) {
return [];
}
//开启首单优惠
if (!sys_config('first_order_status')) {
return [];
}
if (!$userInfo) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
return [];
}
}
if (isset($userInfo['is_first_order']) && $userInfo['is_first_order'] != 0) {
return [];
}
$timeStatus = sys_config('newcomer_limit_status', 1);
if ($timeStatus) {//设置限时,且超过时间不再享受
$time = sys_config('newcomer_limit_time');
if ($time) {
$time = (int)bcsub((string)time(), (string)bcmul((string)$time, '86400'));
if ($time > $userInfo['add_time']) {
return [];
}
}
}
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$count = $storeOrderServices->getCount([['type', 'not in', [7]], ['uid', '=', $uid], ['paid', '=', 1]]);
if ($count) {//有过订单
return [];
}
$discount = sys_config('first_order_discount', 100);
$discount_limit = sys_config('first_order_discount_limit', 0);
return [$discount, $discount_limit];
}
/**
* 验证用户是否可以购买新人专享
* @param int $uid
* @param $userInfo
* @return bool
*/
public function checkUserNewcomer(int $uid, $userInfo = [])
{
if (!$uid) {
return false;
}
//开启新人礼
if (!sys_config('newcomer_status')) {
return false;
}
//开启新人专享
if (!sys_config('register_price_status')) {
return false;
}
if (!$userInfo) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserCacheInfo($uid);
if (!$userInfo) {
return false;
}
}
if (isset($userInfo['is_newcomer']) && $userInfo['is_newcomer'] != 0) {
return false;
}
$timeStatus = sys_config('newcomer_limit_status', 1);
if ($timeStatus) {//设置限时,且超过时间不再享受
$time = sys_config('newcomer_limit_time');
if ($time) {
$time = (int)bcsub((string)time(), (string)bcmul((string)$time, '86400'));
if ($time > $userInfo['add_time']) {
return false;
}
}
}
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$count = $orderServices->count(['uid' => $uid, 'type' => 7, 'paid' => 1]);
if ($count) {
return false;
}
return true;
}
/**
* 用户会员卡激活状态
* @param int $uid
* @return bool|mixed
*/
public function setUserLevel(int $uid)
{
if (!$uid) {
return false;
}
//开启会员卡 且不需要激活 直接设置为已激活状态
if (sys_config('member_func_status') && !sys_config('level_activate_status')) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
return $userServices->update($uid, ['level_status' => 1]);
}
return true;
}
/**
* 用户注册设置:首单优惠、新人专享是否可用 -1:不可用 0:未使用 1:已使用
* @param int $uid
* @return false|mixed
*/
public function setUserNewcomer(int $uid)
{
if (!$uid) {
return false;
}
//是否开启新人礼
$newcomer_status = sys_config('newcomer_status');
$update = [];
if ($newcomer_status) {
//开启首单优惠
$first_order = sys_config('first_order_status');
$update['is_first_order'] = $first_order ? 0 : -1;
//开启新人专享
$is_newcomer = sys_config('register_price_status');
$update['is_newcomer'] = $is_newcomer ? 0 : -1;
} else {//不可用,数据记录已使用
$update = ['is_first_order' => -1, 'is_newcomer' => -1];
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
return $userServices->update($uid, $update);
}
/**
* 下单修改用户首单优惠
* @param int $uid
* @param $orderInfo
* @return bool
*/
public function updateUserNewcomer(int $uid, $orderInfo)
{
if (!$uid || !$orderInfo) {
return false;
}
$update = [];
//享受首单优惠
if (isset($orderInfo['first_order_price']) && $orderInfo['first_order_price'] > 0) {
$update['is_first_order'] = 1;
}
//新人专享
if (isset($orderInfo['type']) && $orderInfo['type'] == 7) {
$update['is_newcomer'] = 1;
}
if ($update) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userServices->update($uid, $update);
$userServices->cacheTag()->clear();
}
return true;
}
/**
* 获取新人专享商品
* @param int $uid
* @param array $where
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getDiyNewcomerList(int $uid = 0, array $where = [], int $limit = 0)
{
$newcomer_products = $newcomer_integral = $newcomer_coupon = [];
if ($uid) {//验证权限
//验证用户是否享受过新人礼
if (!$this->checkUserNewcomer($uid)) {
return compact('newcomer_products', 'newcomer_integral', 'newcomer_coupon');
}
} else {//验证是否开启 无登录:如果开启新人礼默认要展示
//开启新人礼
if (!sys_config('newcomer_status')) {
return compact('newcomer_products', 'newcomer_integral', 'newcomer_coupon');
}
}
$data = SystemConfigService::more([
'register_integral_status',
'register_give_integral',
'register_coupon_status',
'register_give_coupon'
]);
$ids = $data['register_give_coupon'] ?? [];
$newcomer_coupon = [];
if ($data['register_coupon_status'] && $ids) {
[$page, $limit] = $this->getPageValue();
if ($uid) {//查询之前赠送他的
/** @var StoreCouponUserServices $couponServices */
$couponServices = app()->make(StoreCouponUserServices::class);
$coupon = $couponServices->getList(['cid' => $ids, 'uid' => $uid], '*', ['issue'], $page, $limit);
if ($coupon) $coupon = $couponServices->tidyCouponList($coupon);
} else {
/** @var StoreCouponIssueServices $couponServices */
$couponServices = app()->make(StoreCouponIssueServices::class);
$coupon = $couponServices->getList(['id' => $ids], $page, $limit);
}
$newcomer_coupon = $coupon;
}
$newcomer_integral = $data['register_integral_status'] ? $data['register_give_integral'] : 0;
//开启新人专享
if (sys_config('register_price_status')) {
$newcomer_products = $this->getCustomerProduct($where, 'id,type,product_id,relation_id,product_type,price', ['product' => function ($query) {
$query->field('id,image,store_name,stock,sales,ot_price');
}]);
}
return compact('newcomer_products', 'newcomer_integral', 'newcomer_coupon');
}
}

View File

@@ -0,0 +1,277 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\activity\promotions;
use app\dao\activity\promotions\StorePromotionsAuxiliaryDao;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 优惠活动辅助表
* Class StorePromotionsAuxiliaryServices
* @package app\services\activity\promotions
* @mixin StorePromotionsAuxiliaryDao
*/
class StorePromotionsAuxiliaryServices extends BaseServices
{
/**
* @var StorePromotionsAuxiliaryDao
*/
#[Inject]
protected StorePromotionsAuxiliaryDao $dao;
/**
* 优惠活动关联保存
* @param int $promotionsId
* @param int $type
* @param array $promotionsAuxiliaryData
* @param array $couponData
* @param array $giveProductData
* @return bool
*/
public function savePromotionsRelation(int $promotionsId, int $type, array $promotionsAuxiliaryData, array $couponData = [], array $giveProductData = [], bool $isAttr = true)
{
$this->dao->delete(['promotions_id' => $promotionsId]);
if ($promotionsAuxiliaryData) $this->savePromotionsProducts($promotionsId, $type, $promotionsAuxiliaryData, $isAttr);
if ($couponData) $this->savePromotionsGiveCoupon($promotionsId, $couponData);
if ($giveProductData) $this->savePromotionsGiveProducts($promotionsId, $giveProductData);
return true;
}
/**
* 设置活动关联商品
* @param int $promotionsId
* @param int $type
* @param array $productIds
* @return bool
*/
public function savePromotionsProducts(int $promotionsId, int $type, array $promotionsAuxiliaryData, bool $isAttr = true)
{
if ($promotionsAuxiliaryData) {
$data = [];
$unitData = ['type' => 1, 'promotions_id' => $promotionsId, 'product_partake_type' => $type];
switch ($type) {
case 1://所有商品
$data[] = $unitData;
break;
case 2:
case 3:
if ($isAttr) {
$productIds = array_column($promotionsAuxiliaryData, 'product_id');
$promotionsAuxiliaryData = array_combine($productIds, $promotionsAuxiliaryData);
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
foreach ($productIds as $productId) {
$unique = $promotionsAuxiliaryData[$productId]['unique'] ?? [];
$skuCount = $skuValueServices->count(['product_id' => $productId, 'type' => 0]);
$unitData['product_id'] = $productId;
$unitData['is_all'] = count($unique) >= $skuCount ? 1 : 0;
$unitData['unique'] = implode(',', $unique);
$data[] = $unitData;
}
} else {
$productIds = $promotionsAuxiliaryData['product_id'] ?? [];
foreach ($productIds as $productId) {
$unitData['product_id'] = $productId;
$data[] = $unitData;
}
}
break;
case 4://品牌
$brandIds = $promotionsAuxiliaryData['brand_id'] ?? [];
if ($brandIds) {
foreach ($brandIds as $id) {
$unitData['brand_id'] = $id;
$data[] = $unitData;
}
}
break;
case 5://标签
$storeLabelIds = $promotionsAuxiliaryData['store_label_id'] ?? [];
if ($storeLabelIds) {
foreach ($storeLabelIds as $id) {
$unitData['store_label_id'] = $id;
$data[] = $unitData;
}
}
break;
}
if ($data) $this->dao->saveAll($data);
$this->setPromotionsAuxiliaryCache($promotionsId, $data);
}
return true;
}
/**
* 设置活动关联赠送优惠券
* @param int $promotionsId
* @param array $couponData
* @return bool
*/
public function savePromotionsGiveCoupon(int $promotionsId, array $couponData)
{
if ($couponData) {
$data = [];
$couponIds = array_column($couponData, 'give_coupon_id');
$couponData = array_combine($couponIds, $couponData);
foreach ($couponIds as $couponId) {
$data[] = [
'type' => 2,
'promotions_id' => $promotionsId,
'coupon_id' => $couponId,
'limit_num' => $couponData[$couponId]['give_coupon_num'] ?? 0,
'surplus_num' => $couponData[$couponId]['give_coupon_num'] ?? 0
];
}
$this->dao->saveAll($data);
}
return true;
}
/**
* 设置活动关联赠送商品
* @param int $promotionsId
* @param array $giveProductData
* @return bool
*/
public function savePromotionsGiveProducts(int $promotionsId, array $giveProductData)
{
if ($giveProductData) {
$data = [];
foreach ($giveProductData as $product) {
$data[] = [
'type' => 3,
'promotions_id' => $promotionsId,
'product_id' => $product['give_product_id'],
'limit_num' => $product['give_product_num'] ?? 0,
'surplus_num' => $product['give_product_num'] ?? 0,
'unique' => $product['unique'] ?? ''
];
}
$this->dao->saveAll($data);
}
return true;
}
/**
* 优惠活动关联赠品限量处理
* @param array $promotions_id
* @param int $type
* @param int $id
* @param bool $is_dec
* @param string $unique
* @param int $num
* @return bool
*/
public function updateLimit(array $promotions_id, int $type, int $id, bool $is_dec = true, string $unique = '', int $num = 1)
{
if (!$promotions_id) return true;
$where = ['promotions_id' => $promotions_id, 'type' => $type];
if ($type == 2) {
$where['coupon_id'] = $id;
} else {
$where['product_id'] = $id;
$where['unique'] = $unique;
}
$info = $this->dao->get($where);
if ($info) {
if ($is_dec) {
if ($info['surplus_num'] < $num) {
$surplus_num = 0;
} else {
$surplus_num = bcsub((string)$info['surplus_num'], (string)$num, 0);
}
} else {
$surplus_num = bcadd((string)$info['surplus_num'], (string)$num, 0);
}
$this->dao->update($info['id'], ['surplus_num' => $surplus_num]);
}
return true;
}
/**
* 设置优惠活动关联缓存
* @param int $promotions_id
* @param array $data
* @param int $product_partake_type
* @return array
*/
public function setPromotionsAuxiliaryCache(int $promotions_id, array $data, int $product_partake_type = 1)
{
$key = 'cache_promotions_auxiliary_' . $promotions_id;
$cacheData = [];
if ($data) {
switch ($product_partake_type) {
case 1://所有商品
break;
case 2:
case 3:
$cacheData = array_unique(array_column($data, 'product_id'));
break;
case 4://品牌
$cacheData = array_unique(array_column($data, 'brand_id'));
break;
case 5://标签
$cacheData = array_unique(array_column($data, 'store_label_id'));
break;
}
}
$this->dao->cacheHander()->delete($key);
$this->dao->cacheTag()->set($key, $cacheData);
return $cacheData;
}
/**
* 更新缓存
* @param int $promotions_id
* @param string $key
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updatePromotionsAuxiliaryCache(int $promotions_id)
{
$data = $this->dao->getList(['promotions_id' => $promotions_id, 'type' => 1]);
$cacheData = [];
if ($data) {
$product_partake_type = $data[0]['product_partake_type'] ?? 0;
$cacheData = $this->setPromotionsAuxiliaryCache($promotions_id, $data, $product_partake_type);
}
return $cacheData;
}
/**
* 获取商品关联缓存
* @param int $product_id
* @param array $type
* @param bool $isCache
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getPromotionsAuxiliaryCache(int $promotions_id, bool $isCache = false)
{
$key = 'cache_promotions_auxiliary_' . $promotions_id;
$data = $this->dao->cacheHander()->get($key);
if (!$data || $isCache) {
$data = $this->updatePromotionsAuxiliaryCache($promotions_id);
}
return $data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,886 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\seckill;
use app\services\activity\StoreActivityServices;
use app\services\BaseServices;
use app\dao\activity\seckill\StoreSeckillDao;
use app\services\diy\DiyServices;
use app\services\order\StoreOrderServices;
use app\services\other\QrcodeServices;
use app\services\product\ensure\StoreProductEnsureServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\user\UserRelationServices;
use app\services\product\product\StoreProductReplyServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\jobs\product\ProductLogJob;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\services\GroupDataService;
use crmeb\services\SystemConfigService;
use crmeb\traits\OptionTrait;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreSeckillServices
* @package app\services\activity\seckill
* @method getSeckillIdsArray(array $ids, array $field)
* @mixin StoreSeckillDao
*/
class StoreSeckillServices extends BaseServices
{
use OptionTrait;
const OPOXMWTJ = 'k8kkOJ';
/**
* 商品活动类型
*/
const TYPE = 1;
/**
* @var StoreSeckillDao
*/
#[Inject]
protected StoreSeckillDao $dao;
/**
* @param array $productIds
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/14
*/
public function getSeckillIdsArrayCache(array $productIds)
{
$list = $this->dao->cacheList();
if (!$list) {
return $this->dao->getSeckillIdsArray($productIds, ['id', 'time_id', 'product_id']);
} else {
$seckill = [];
$time = time();
foreach ($list as $item) {
if ($item['is_del'] == 0 && $item['status'] == 1 && $item['start_time'] <= $time && $item['stop_time'] >= ($time - 86400) && in_array($item['product_id'], $productIds)) {
$seckill[] = [
'id' => $item['id'],
'time_id' => $item['time_id'],
'product_id' => $item['product_id'],
];
}
}
return $seckill;
}
}
/**
* 获取秒杀活动数量
* @param array $where 查询条件
* @return int
*/
public function getCount(array $where)
{
return $this->dao->count($where);
}
/**
* 秒杀是否存在
* @param int $id
* @param string $field
* @return array|int|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSeckillCount(int $id = 0, string $field = 'time_id')
{
$where = [];
$where[] = ['is_del', '=', 0];
$where[] = ['status', '=', 1];
$config = [];
$currentHour = date('Hi');
/** @var StoreSeckillTimeServices $storeSeckillTimeServices */
$storeSeckillTimeServices = app()->make(StoreSeckillTimeServices::class);
if ($id) {
$time = time();
$where[] = ['id', '=', $id];
$where[] = ['start_time', '<=', $time];
$where[] = ['stop_time', '>=', $time - 86400];
$seckill_one = $this->dao->getOne($where, $field);
if (!$seckill_one) {
throw new ValidateException('活动已结束');
}
$time_id = $seckill_one['time_id'] ?? [];
if ($time_id) {
$time_id = is_string($time_id) ? explode(',', $time_id) : $time_id;
$timeList = $storeSeckillTimeServices->getList(['id' => $time_id, 'status' => 1]);
foreach ($timeList as $value) {
$value['start_time'] = $start = str_replace(':', '', $value['start_time']);
$value['end_time'] = $end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
}
if (!$config) {
throw new ValidateException('活动已结束');
}
//获取秒杀商品状态
if ($config['start_time'] <= $currentHour && $config['end_time'] > $currentHour) {
return $seckill_one;
} else if ($config['start_time'] > $currentHour) {
throw new ValidateException('活动未开始');
} else {
throw new ValidateException('活动已结束');
}
} else {
$timeList = $storeSeckillTimeServices->getList(['status' => 1]);
foreach ($timeList as $value) {
$start = str_replace(':', '', $value['start_time']);
$end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
if (!$config) return 0;
//获取秒杀商品状态
$start = substr_replace($config['start_time'], ':', 2, 0);
$end = substr_replace($config['end_time'], ':', 2, 0);
$startTime = strtotime(date('Y-m-d') . ' ' . $start);
$stopTime = strtotime(date('Y-m-d') . ' ' . $end);
$where[] = ['start_time', '<', $startTime];
$where[] = ['stop_time', '>', $stopTime];
return $this->dao->getCount($where);
}
}
/**
* 保存数据
* @param int $id
* @param array $data
*/
public function saveData(int $id, array $data)
{
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已移入回收站');
}
if ($productInfo['is_vip_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是svip专享");
}
if ($productInfo['is_presale_product']) {
throw new AdminException("【{$productInfo["store_name"]}】是预售商品");
}
$data['product_type'] = $productInfo['product_type'];
$data['type'] = $productInfo['type'] ?? 0;
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$custom_form = $productInfo['custom_form'] ?? [];
$data['custom_form'] = is_array($custom_form) ? json_encode($custom_form) : $custom_form;
$data['system_form_id'] = $productInfo['system_form_id'] ?? 0;
$store_label_id = $productInfo['store_label_id'] ?? [];
$data['store_label_id'] = is_array($store_label_id) ? implode(',', $store_label_id) : $store_label_id;
$ensure_id = $productInfo['ensure_id'] ?? [];
$data['ensure_id'] = is_array($ensure_id) ? implode(',', $ensure_id) : $ensure_id;
$specs = $productInfo['specs'] ?? [];
$data['specs'] = is_array($specs) ? json_encode($specs) : $specs;
if (in_array($data['product_type'], [1, 2, 3])) {
$data['freight'] = 2;
$data['temp_id'] = 0;
$data['postage'] = 0;
} else {
if ($data['freight'] == 1) {
$data['temp_id'] = 0;
$data['postage'] = 0;
} elseif ($data['freight'] == 2) {
$data['temp_id'] = 0;
} elseif ($data['freight'] == 3) {
$data['postage'] = 0;
}
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
}
$description = $data['description'];
$detail = $data['attrs'];
$items = $data['items'];
$data['start_time'] = strtotime($data['section_time'][0]);
$data['stop_time'] = strtotime($data['section_time'][1]);
$data['image'] = $data['images'][0] ?? '';
$data['images'] = json_encode($data['images']);
$data['price'] = min(array_column($detail, 'price'));
$data['ot_price'] = min(array_column($detail, 'ot_price'));
$data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
if ($data['quota'] > $productInfo['stock']) {
throw new AdminException('限量不能超过商品库存');
}
$data['stock'] = array_sum(array_column($detail, 'stock'));
unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$id = $this->transaction(function () use ($id, $data, $description, $detail, $items, $storeDescriptionServices, $storeProductAttrServices) {
if ($id) {
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$storeDescriptionServices->saveDescription((int)$id, $description, 1);
$storeProductAttrServices->setItem('store_product_id', $data['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($items, $detail, (int)$id, 1);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$id, 1);
$res = true;
foreach ($valueGroup as $item) {
if ($item['quota_show']) $res = $res && CacheService::setStock($item['unique'], (int)$item['quota_show']);
}
if (!$res) {
throw new AdminException('占用库存失败');
}
return $id;
});
$this->dao->cacheTag()->clear();
//保存
$seckill = $this->dao->get($id, ['*'], ['descriptions']);
$this->dao->cacheUpdate($seckill->toArray());
CacheService::redisHandler('product_attr')->clear();
}
/**
* @param int $timeId
* @param int $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/4
*/
public function createSeckillListCache(int $timeId, int $id = 0)
{
//创建数据缓存
$list = $this->dao->getListByTime($timeId);
$timeId = (string)$timeId;
if ($id) {
$this->dao->cacheUpdateList($list, $timeId);
} else {
$this->dao->cacheCreate($list, $timeId);
}
}
/**
* 获取列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
$activityIds = array_column($list, 'activity_id');
$activityInfos = app()->make(StoreActivityServices::class)->getColumn(['id' => $activityIds], 'id,name', 'id');
foreach ($list as &$item) {
$item['store_name'] = $item['title'];
if ($item['status']) {
if ($item['start_time'] > time())
$item['start_name'] = '未开始';
else if (bcadd($item['stop_time'], '86400') < time())
$item['start_name'] = '已结束';
else if (bcadd($item['stop_time'], '86400') > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
}
} else $item['start_name'] = '已结束';
$end_time = $item['stop_time'] ? (date('Y-m-d', (int)$item['stop_time']) . ' 23:59:59') : '';
$item['_stop_time'] = $end_time;
$item['stop_status'] = $item['stop_time'] + 86400 < time() ? 1 : 0;
$item['activity_name'] = $activityInfos[$item['activity_id']]['name'] ?? '';
}
return compact('list', 'count');
}
/**
* 获取秒杀详情
* @param int $id
* @return array|\think\Model|null
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if ($info) {
if ($info['start_time'])
$start_time = date('Y-m-d', (int)$info['start_time']);
if ($info['stop_time'])
$stop_time = date('Y-m-d', (int)$info['stop_time']);
if (isset($start_time) && isset($stop_time))
$info['section_time'] = [$start_time, $stop_time];
else
$info['section_time'] = [];
unset($info['start_time'], $info['stop_time']);
$info['give_integral'] = intval($info['give_integral']);
$info['price'] = floatval($info['price']);
$info['ot_price'] = floatval($info['ot_price']);
$info['postage'] = floatval($info['postage']);
$info['cost'] = floatval($info['cost']);
$info['weight'] = floatval($info['weight']);
$info['volume'] = floatval($info['volume']);
if (!$info['delivery_type']) {
$info['delivery_type'] = [1];
}
if ($info['postage']) {
$info['freight'] = 2;
} elseif ($info['temp_id']) {
$info['freight'] = 3;
} else {
$info['freight'] = 1;
}
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
$info['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 1]);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$info['attrs'] = $storeProductAttrValueServices->attrList($id, $info['product_id'], 1);
}
return $info;
}
/**
*
* @param int $time
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/4
*/
public function getListByTimeCache(int $time)
{
[$page, $limit] = $this->getPageValue();
$res = $this->dao->cacheList();
$list = [];
$stime = time();
$etime = $stime - 86400;
foreach ($res as $value) {
if (isset($value['is_del']) && $value['is_del'] == 0 && isset($value['start_time']) && $value['start_time'] <= $stime && isset($value['stop_time']) && $value['stop_time'] >= $etime && isset($value['status']) && isset($value['time_id']) && $value['status'] && $value['time_id'] == $time) {
$list[] = $value;
}
}
$newResList = array_slice($list, ($page - 1) * $limit, $limit);
if (!$newResList) {
$newResList = $this->getListByTime($time);
} else {
foreach ($newResList as &$item) {
if ($item['quota'] > 0) {
$percent = (int)(($item['quota_show'] - $item['quota']) / $item['quota_show'] * 100);
$item['percent'] = $percent;
$item['stock'] = $item['quota'];
} else {
$item['percent'] = 100;
$item['stock'] = 0;
}
$item['price'] = floatval($item['price']);
$item['ot_price'] = floatval($item['ot_price']);
}
}
return $newResList;
}
/**
* 获取某个时间段的秒杀列表
* @param int $time
* @param int $limit
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getListByTime(int $time)
{
[$page, $limit] = $this->getPageValue();
/** @var StoreActivityServices $storeActivityServices */
$storeActivityServices = app()->make(StoreActivityServices::class);
$activityList = $storeActivityServices->getList(['time_id' => $time, 'type' => 1, 'status' => 1, 'is_del' => 0, 'activityTime' => true], 'id,image');
$seckillInfo = [];
if ($activityList) {
$activityIds = array_column($activityList, 'id');
$field = 'id,product_id,activity_id,title,image,price,ot_price,quota,quota_show,freight,stock,store_label_id';
$seckillInfo = $this->dao->getListByTime($activityIds, [], $field, $page, $limit);
if (count($seckillInfo)) {
$activityList = array_combine($activityIds, $activityList);
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
foreach ($seckillInfo as $key => &$item) {
if ($item['quota'] > 0) {
$percent = (float)sprintf("%.1f", (($item['quota_show'] - $item['quota']) / $item['quota_show'] * 100));
$item['percent'] = $percent;
$item['stock'] = $item['quota'];
} else {
$item['percent'] = 100;
$item['stock'] = 0;
}
$item['price'] = floatval($item['price']);
$item['ot_price'] = floatval($item['ot_price']);
$item['discount_num'] = $item['ot_price'] ? (float)bcmul(bcdiv((string)$item['price'], (string)$item['ot_price'], 4), '10', 1) : 10;
$item['activity_image'] = $activityList[$item['activity_id']]['image'] ?? '';
$item['store_label'] = [];
if ($item['store_label_id']) {
$item['store_label'] = $storeProductLabelServices->getLabelCache($item['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$item['brand_name'] = $storeProductServices->productIdByBrandName((int)$item['product_id']);
}
}
}
return $seckillInfo;
}
/**
* 获取秒杀详情
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function seckillDetail(int $uid, int $id)
{
//读取秒杀商品缓存信息
$storeInfo = $this->dao->cacheRemember($id, function () use ($id) {
$storeInfo = $this->dao->getOne(['id' => $id], '*,title as store_name', ['descriptions']);
if (!$storeInfo) {
throw new ValidateException('商品不存在');
} else {
$storeInfo = $storeInfo->toArray();
}
return $storeInfo;
});
$storeInfo['activity'] = [];
if ($storeInfo['activity_id']) {
$activityServices = app()->make(StoreActivityServices::class);
$storeInfo['activity'] = $activityServices->getInfo((int)$storeInfo['activity_id'], ['id', 'start_day', 'end_day', 'time_id']);
}
/** @var DiyServices $diyServices */
$diyServices = app()->make(DiyServices::class);
$infoDiy = $diyServices->getProductDetailDiy();
//diy控制参数
if (!isset($infoDiy['showService']) || in_array(3, $infoDiy['showService'])) {
$storeInfo['specs'] = [];
}
$configData = SystemConfigService::more(['site_url', 'routine_contact_type', 'site_name', 'share_qrcode', 'store_self_mention', 'store_func_status', 'product_poster_title']);
$siteUrl = $configData['site_url'] ?? '';
$storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
//品牌名称
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getCacheProductInfo((int)$storeInfo['product_id']);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id'], $productInfo);
$delivery_type = $storeInfo['delivery_type'] ?? $productInfo['delivery_type'];
$storeInfo['delivery_type'] = is_string($delivery_type) ? explode(',', $delivery_type) : $delivery_type;
/**
* 判断配送方式
*/
$storeInfo['delivery_type'] = $storeProductServices->getDeliveryType($storeInfo['type'], $storeInfo['relation_id'], $storeInfo['delivery_type']);
$storeInfo['total'] = $productInfo['sales'] + $productInfo['ficti'];
$storeInfo['store_label'] = $storeInfo['ensure'] = [];
if ($storeInfo['store_label_id']) {
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
if ($storeInfo['ensure_id'] && isset($infoDiy['showService']) && in_array(2, $infoDiy['showService'])) {
/** @var StoreProductEnsureServices $storeProductEnsureServices */
$storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
$storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
}
$time = $this->getItem('time', '');
$status = $this->getItem('status', '');
$time_id = (int)$this->getItem('time_id', '');
/** @var QrcodeServices $qrcodeService */
$qrcodeService = app()->make(QrcodeServices::class);
if (($configData['share_qrcode'] ?? 0) && request()->isWechat()) {
$storeInfo['code_base'] = $qrcodeService->getTemporaryQrcode('seckill-' . $id . '-' . $uid . '-' . $time . '-' . $status, $uid)->url;
} else {
$storeInfo['code_base'] = $qrcodeService->getWechatQrcodePath($id . '_product_seckill_detail_wap.jpg', '/pages/activity/goods_seckill_details/index?id=' . $id . '&spid=' . $uid . '&time=' . $time . '&status=' . $status);
}
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$data['buy_num'] = $storeOrderServices->getBuyCount($uid, 1, $id);
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$storeInfo['userCollect'] = $userRelationServices->isProductRelationCache(['uid' => $uid, 'relation_id' => $storeInfo['product_id'], 'type' => 'collect', 'category' => UserRelationServices::CATEGORY_PRODUCT]);
$storeInfo['userLike'] = 0;
$storeInfo['uid'] = $uid;
if ($storeInfo['quota'] > 0) {
$percent = (float)sprintf("%.1f", ($storeInfo['quota_show'] - $storeInfo['quota']) / $storeInfo['quota_show'] * 100);
$storeInfo['percent'] = $percent;
$storeInfo['stock'] = $storeInfo['quota'];
} else {
$storeInfo['percent'] = 100;
$storeInfo['stock'] = 0;
}
$storeInfo['last_time'] = 0;
$time_id = $time_id ?: ($storeInfo['activity']['time_id'] ?? []);
$storeInfo['status'] = (int)$storeInfo['status'];
if ($time_id && $storeInfo['status'] == 1) {
$time_id = is_string($time_id) ? explode(',', $time_id) : $time_id;
/** @var StoreSeckillTimeServices $storeSeckillTimeServices */
$storeSeckillTimeServices = app()->make(StoreSeckillTimeServices::class);
$timeList = $storeSeckillTimeServices->getList(['id' => $time_id, 'status' => 1]);
$config = [];
$today = date('Y-m-d');
$currentHour = date('Hi');
if (count($timeList) <= 1) {
$config = $timeList[0] ?? [];
} else {
foreach ($timeList as $value) {
$start = str_replace(':', '', $value['start_time']);
$end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
}
//获取秒杀商品状态
if ($storeInfo['status'] == 1) {
if ($config) {//正在进行中的
$start = str_replace(':', '', $config['start_time']);
$end = str_replace(':', '', $config['end_time']);
if ($start <= $currentHour && $end > $currentHour) {
$storeInfo['status'] = 1;
$storeInfo['last_time'] = strtotime($today . ' ' . $config['end_time']);
} else if ($start > $currentHour) {
$storeInfo['status'] = 2;
} else {
$storeInfo['status'] = 0;
}
} else {
$storeInfo['status'] = 0;
}
}
}
//商品详情
$storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
$data['storeInfo'] = $storeInfo;
$storeInfo['product_id'] = (int)$storeInfo['product_id'];
$data['reply'] = [];
$data['replyChance'] = $data['replyCount'] = 0;
if (isset($infoDiy['showReply']) && $infoDiy['showReply']) {
/** @var StoreProductReplyServices $storeProductReplyService */
$storeProductReplyService = app()->make(StoreProductReplyServices::class);
$reply = $storeProductReplyService->getRecProductReplyCache($storeInfo['product_id'], (int)($infoDiy['replyNum'] ?? 1));
$data['reply'] = $reply ? get_thumb_water($reply, 'small', ['pics']) : [];
[$replyCount, $goodReply, $replyChance] = $storeProductReplyService->getProductReplyData((int)$storeInfo['product_id']);
$data['replyChance'] = $replyChance;
$data['replyCount'] = $replyCount;
}
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetailCache($id, $uid, 0, 1, $storeInfo['product_id'], $productInfo);
$data['productAttr'] = $productAttr;
$data['productValue'] = $productValue;
$data['routine_contact_type'] = $configData['routine_contact_type'] ?? 0;
$data['store_func_status'] = (int)($configData['store_func_status'] ?? 1);//门店是否开启
$data['store_self_mention'] = $data['store_func_status'] ? (int)($configData['store_self_mention'] ?? 0) : 0;//门店自提是否开启
$data['site_name'] = $configData['site_name'] ?? '';
$data['share_qrcode'] = $configData['share_qrcode'] ?? 0;
$data['product_poster_title'] = $configData['product_poster_title'] ?? '';
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'seckill']);
return $data;
}
/**
* 修改秒杀库存
* @param int $num
* @param int $seckillId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decSeckillStock(int $num, int $seckillId, string $unique = '', int $store_id = 0)
{
return $this->transaction(function () use ($num, $seckillId, $unique) {
$product_id = $this->dao->value(['id' => $seckillId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去秒杀商品的sku库存增加销量
$res = false !== $skuValueServices->decProductAttrStock($seckillId, $unique, $num, 1);
//减去当前普通商品sku的库存增加销量
//秒杀商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $seckillId, 'type' => 1], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $res && $services->decProductStock($num, $product_id, (string)$productUnique);
}
//减去秒杀库存
$res = $res && false !== $this->dao->decStockIncSales(['id' => $seckillId, 'type' => 1], $num);
}
//更新单个缓存
$info = $this->dao->getOne(['id' => $seckillId], '*', ['descriptions']);
if ($info) {
$info = $info->toArray();
$this->dao->cacheUpdate($info);
}
return $res;
});
}
/**
* 加库存减销量
* @param int $num
* @param int $seckillId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incSeckillStock(int $num, int $seckillId, string $unique = '', int $store_id = 0)
{
return $this->transaction(function () use ($num, $seckillId, $unique) {
$product_id = $this->dao->value(['id' => $seckillId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去秒杀商品的sku库存增加销量
$res = false !== $skuValueServices->incProductAttrStock($seckillId, $unique, $num, 1);
//减去当前普通商品sku的库存增加销量
//秒杀商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $seckillId, 'type' => 1], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
//减去秒杀库存
$res = $res && false !== $this->dao->incStockDecSales(['id' => $seckillId, 'type' => 1], $num);
}
//更新单个缓存
$info = $this->dao->getOne(['id' => $seckillId], '*', ['descriptions']);
if ($info) {
$info = $info->toArray();
$this->dao->cacheUpdate($info);
}
return $res;
});
}
/**
* 下单|加入购物车验证秒杀商品库存
* @param int $uid
* @param int $seckillId
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkSeckillStock(int $uid, int $seckillId, int $cartNum = 1, string $unique = '')
{
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
if ($unique == '') {
$unique = $attrValueServices->value(['product_id' => $seckillId, 'type' => 1], 'unique');
}
//检查商品活动状态
$storeSeckillinfo = $this->getSeckillCount($seckillId, '*,title as store_name');
if (!$storeSeckillinfo) {
throw new ValidateException('该活动已下架');
}
if ($storeSeckillinfo['once_num'] < $cartNum) {
throw new ValidateException('每个订单限购' . $storeSeckillinfo['once_num'] . '件');
}
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$userBuyCount = $orderServices->getBuyCount($uid, 1, $seckillId);
if ($storeSeckillinfo['num'] < ($userBuyCount + $cartNum)) {
throw new ValidateException('每人总共限购' . $storeSeckillinfo['num'] . '件');
}
if ($storeSeckillinfo['num'] < $cartNum) {
throw new ValidateException('每人限购' . $storeSeckillinfo['num'] . '件');
}
$attrInfo = $attrValueServices->getOne(['product_id' => $seckillId, 'unique' => $unique, 'type' => 1]);
if (!$attrInfo || $attrInfo['product_id'] != $seckillId) {
throw new ValidateException('请选择有效的商品属性');
}
if ($cartNum > $attrInfo['quota']) {
throw new ValidateException('该商品库存不足' . $cartNum);
}
return [$attrInfo, $unique, $storeSeckillinfo];
}
/**
* 获取当前的秒杀时间time
* @return int|string
*/
public function getSeckillTime()
{
$seckillTime = GroupDataService::getData('routine_seckill_time') ?? [];
$currentHour = (int)date('H');
$time = 0;
foreach ($seckillTime as $item) {
$activityEndHour = (int)bcadd((string)$item['time'], (string)$item['continued'], 0);
if ($currentHour >= $item['time'] && $currentHour < $activityEndHour) {
$time = $item['id'];
}
}
return $time;
}
/**
* 秒杀统计
* @return array
*/
public function seckillStatistics($id)
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$pay_count = $orderServices->getDistinctCount(['type' => 1, 'activity_id' => $id, 'paid' => 1, 'pid' => [0, -1]], 'uid');
$order_count = $orderServices->getDistinctCount(['type' => 1, 'activity_id' => $id, 'pid' => [0, -1]], 'uid');
$all_price = $orderServices->sum(['type' => 1, 'activity_id' => $id, 'paid' => 1, 'refund_type' => [0, 3], 'pid' => [0, -1]], 'pay_price');
$seckillInfo = $this->dao->get($id);
$pay_rate = $seckillInfo['quota'] . '/' . $seckillInfo['quota_show'];
return compact('pay_count', 'order_count', 'all_price', 'pay_rate');
}
/**
* 秒杀参与人统计
* @param $id
* @param string $keyword
* @return array
*/
public function seckillPeople($id, $keyword = '')
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->seckillPeople($id, $keyword, $page, $limit);
$count = $orderServices->getDistinctCount([['paid', '=', 1], ['type', '=', 1], ['activity_id', '=', $id], ['pid', 'in', [0, -1]], ['real_name|uid|user_phone', 'like', '%' . $keyword . '%']], 'uid', false);
foreach ($list as &$item) {
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
}
return compact('list', 'count');
}
/**
* 秒杀订单统计
* @param $id
* @param array $where
* @return array
*/
public function seckillOrder(int $id, array $where = [])
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->activityStatisticsOrder($id, 1, $where, $page, $limit);
$where['type'] = 1;
$where['activity_id'] = $id;
$count = $orderServices->count($where);
foreach ($list as &$item) {
if ($item['is_del'] || $item['is_system_del']) {
$item['status'] = '已删除';
} else if ($item['paid'] == 0 && $item['status'] == 0) {
$item['status'] = '待付款';
} else if ($item['paid'] == 1 && $item['status'] == 4 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '部分发货';
} else if ($item['paid'] == 1 && $item['refund_status'] == 2) {
$item['status'] = '已退款';
} else if ($item['paid'] == 1 && $item['status'] == 5 && $item['refund_status'] == 0) {
$item['status'] = $item['shipping_type'] == 2 ? '部分核销' : '部分收货';
$item['_status'] = 12;//已支付 部分核销
} else if ($item['paid'] == 1 && $item['refund_status'] == 1) {
$item['status'] = '申请退款';
} else if ($item['paid'] == 1 && $item['refund_status'] == 4) {
$item['status'] = '退款中';
} else if ($item['paid'] == 1 && $item['status'] == 0 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '未发货';
$item['_status'] = 2;//已支付 未发货
} else if ($item['paid'] == 1 && in_array($item['status'], [0, 1]) && $item['shipping_type'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '未核销';
} else if ($item['paid'] == 1 && in_array($item['status'], [1, 5]) && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '待收货';
} else if ($item['paid'] == 1 && $item['status'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '待评价';
} else if ($item['paid'] == 1 && $item['status'] == 3 && $item['refund_status'] == 0) {
$item['status'] = '已完成';
} else if ($item['paid'] == 1 && $item['refund_status'] == 3) {
$item['status'] = '部分退款';
} else {
$item['status'] = '未知';
}
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
$item['pay_time'] = $item['pay_time'] ? date('Y-m-d H:i:s', $item['pay_time']) : '';
}
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,182 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\seckill;
use app\dao\activity\seckill\StoreSeckillTimeDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use FormBuilder\Factory\Iview;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 秒杀时间
* Class StoreSeckillTimeServices
* @package app\services\activity\seckill
* @mixin StoreSeckillTimeDao
*/
class StoreSeckillTimeServices extends BaseServices
{
/**
* @var StoreSeckillTimeDao
*/
#[Inject]
protected StoreSeckillTimeDao $dao;
/**
* 获取当前的秒杀时间time
* @return int
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSeckillTime()
{
$seckillTime = $this->time_list();
$currentHour = date('Hi');
$time = 0;
foreach ($seckillTime as $value) {
$start = str_replace(':', '', $value['start_time']);
$end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$time = $value['id'];
}
}
return (int)$time;
}
/**
* 获取列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = $this->dao->count($where);
foreach ($list as &$item) {
$item['start_time'] = substr_replace($item['start_time'], ':', 2, 0);
$item['end_time'] = substr_replace($item['end_time'], ':', 2, 0);
}
return compact('list', 'count');
}
/**
* 可用秒杀时间段
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function time_list()
{
$list = $this->dao->getList(['status' => 1], 'id,title,start_time,end_time,pic');
foreach ($list as &$item) {
$item['start_time'] = substr_replace($item['start_time'], ':', 2, 0);
$item['end_time'] = substr_replace($item['end_time'], ':', 2, 0);
$item['slide'] = $item['pic'];
};
return $list;
}
/**
* 验证时间是否重复
* @param array $where
* @param int $id
* @return bool
* @throws \think\db\exception\DbException
*/
public function checkTime(array $where, int $id)
{
if (!$this->dao->valStartTime($where['start_time'], $id) && !$this->dao->valEndTime($where['end_time'], $id) && !$this->dao->valAllTime($where, $id)) return true;
return false;
}
/**
* 获取秒杀时间
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('数据不存在');
}
$info = $info->toArray();
$info['time'] = [
substr_replace($info['start_time'], ':', 2, 0) . ':00',
substr_replace($info['end_time'], ':', 2, 0) . ':00'
];
return $info;
}
/**
* 添加、编辑表单
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function createForm(int $id)
{
$info = [];
if ($id) {
$info = $this->getInfo($id);
}
$f[] = Form::input('title', '标题:', $info['title'] ?? '')->required();
$f[] = Form::timeRange('time', '时间选择:', $info['time'][0] ?? '', $info['time'][1] ?? '')->prop('picker-options', ['format' => 'HH'])->appendValidate(Iview::validateArr()->required()->message('请选择时间'))->format('HH:mm');
// $f[] = Form::frameImage('pic', '图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'pic')), $info['pic'] ?? '')->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->appendValidate(Iview::validateStr()->required()->message('请选择图片'));
$f[] = Form::radio('status', '状态:', $info['status'] ?? 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]]);
$f[] = Form::input('describe', '描述:',$info['describe'] ?? '')->required();
return create_form($id ? '编辑' : '添加秒杀时间', $f, $this->url('/marketing/seckill/time/' . $id), 'POST');
}
/**
* 组合4位秒杀时间
* @param $time
* @return array|string|string[]
*/
public function changeTime($time)
{
$str_time = '';
switch (strlen($time)) {
case 1:
$str_time = '0' . $time . '00';
break;
case 2:
$str_time = $time . '00';
break;
case 3:
$str_time = '0' . $time;
break;
case 4:
$str_time = $time;
break;
}
return substr_replace($str_time, ':', 2, 0);
}
}

View File

@@ -0,0 +1,256 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\video;
use app\dao\activity\video\VideoCommentDao;
use app\services\BaseServices;
use app\services\user\UserRelationServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Route as Url;
/**
* Class VideoCommentServices
* @package app\services\activity\video
* @mixin VideoCommentDao
*/
class VideoCommentServices extends BaseServices
{
/**
* @var VideoCommentDao
*/
#[Inject]
protected VideoCommentDao $dao;
/**
* 视频评价列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function sysPage(array $where)
{
$where['pid'] = 0;
$where['is_del'] = 0;
[$page, $limit] = $this->getPageValue();
$with = ['video', 'replyContent'];
$list = $this->dao->getList($where, '*', $with, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 回复评论
* @param int $id
* @param string $content
*/
public function setReply(int $id, string $content)
{
if ($content == '') throw new AdminException('请输入回复内容');
$comment = $this->dao->get($id);
if (!$comment) {
throw new AdminException('回复的评论不存在');
}
$save = [];
$save['type'] = $comment['type'];
$save['relation_id'] = $comment['relation_id'];
$save['pid'] = $comment['pid'] ? $comment['pid'] : $id;
$save['video_id'] = $comment['video_id'];
$save['content'] = $content;
$save['nickname'] = sys_config('site_name');
$save['avatar'] = sys_config('wap_login_logo');
$where = ['video_id' => $comment['video_id'], 'uid' => 0, 'pid' => $id, 'is_del' => 0];
if ($this->dao->count($where)) {
$res = $this->dao->update($where, ['content' => $content, 'update_time' => time()]);
} else {
$save['add_time'] = time();
$res = $this->dao->save($save);
/** @var VideoServices $videoServices */
$videoServices = app()->make(VideoServices::class);
$videoServices->bcInc($comment['video_id'], 'comment_num', '1');
}
if (!$res) throw new AdminException('回复失败,请稍后再试');
$this->dao->update($id, ['is_reply' => 1]);
return true;
}
/**
* 获取评论回复列表
* @param int $id
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCommentReplyList(int $id, array $where)
{
$comment = $this->dao->get($id);
if (!$comment) {
throw new AdminException('回复的评论不存在');
}
$where['pid'] = $id;
$where['is_del'] = 0;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', ['video', 'user' => function ($query) {
$query->field('uid,avatar,nickname,is_money_level');
}, 'children' => function ($query) {
$query->with([
'user' => function ($query) {
$query->field('uid,avatar,nickname,is_money_level');
}
]);
}], $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 创建虚拟评论表单
* @param int $video_id
* @param $store_id
* @return mixed
*/
public function createForm(int $video_id, $store_id = 0)
{
if ($video_id == 0) {
$field[] = Form::frameImage('video', '视频', Url::buildUrl($store_id ? 'store/video.shortVideo/index' : 'admin/video.shortVideo/index', array('fodder' => 'video')))->icon('ios-add')->width('960px')->height('560px')->modal(['footer-hide' => true])->Props(['srcKey' => 'image']);
} else {
$field[] = Form::hidden('video_id', $video_id);
}
$field[] = Form::frameImage('avatar', '用户头像', Url::buildUrl($store_id ? 'store/widget.images/index' : 'admin/widget.images/index', array('fodder' => 'avatar')))->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true]);
$field[] = Form::input('nickname', '用户名称')->col(24);
$field[] = Form::input('content', '评价文字')->type('textarea');
$field[] = Form::dateTime('add_time', '评论时间', '')->placeholder('请选择评论时间(不选择默认当前添加时间)');
return create_form('添加虚拟评论', $field, Url::buildUrl('/marketing/video/comment/save_fictitious'), 'POST');
}
/**
* 保存评价、回复
* @param int $uid
* @param int $video_id
* @param $id
* @param array $data
* @return \crmeb\basic\BaseModel|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveComment(int $uid, int $video_id, $id, array $data)
{
/** @var VideoServices $videoServices */
$videoServices = app()->make(VideoServices::class);
$video = $videoServices->get($video_id);
if (!$video) {
throw new AdminException('评论的视频不存在');
}
$pid = $id;
if ($id) {//回复评价
$comment = $this->dao->get($id);
if (!$comment) {
throw new ValidateException('评论不存在或已删除');
}
if ($comment['pid']) $pid = $comment['pid'];
}
if ($uid && $uid != -1) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
$data['nickname'] = $userInfo['nickname'] ?? '';
$data['avatar'] = $userInfo['avatar'] ?? '';
}
//ip转城市信息
if (isset($data['ip']) && $data['ip']) {
$data['city'] = $this->convertIp($data['ip']);
}
$data['uid'] = $uid;
$data['video_id'] = $video_id;
$data['pid'] = $pid;
$data['type'] = $video['type'];
$data['relation_id'] = $video['relation_id'];
$time = time();
if (isset($data['add_time']) && $data['add_time']) {
if ($data['add_time'] > $time) {
throw new AdminException('评论时间应小于当前时间');
}
$data['add_time'] = strtotime($data['add_time']);
} else {
$data['add_time'] = $time;
}
$res = $this->dao->save($data);
/** @var VideoServices $videoServices */
$videoServices = app()->make(VideoServices::class);
$videoServices->bcInc($video_id, 'comment_num', 1);
if (!$res) throw new AdminException('保存评论失败');
return $res;
}
/**
* 获取短视频评价列表
* @param int $uid
* @param int $id
* @param int $pid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getVideoCommentList(int $uid, int $id, int $pid = 0)
{
$where = ['video_id' => $id, 'is_del' => 0];
[$page, $limit] = $this->getPageValue();
if ($limit > 20) $limit = 20;
if ($pid) {
$comment = $this->dao->get($pid);
if (!$comment) {
throw new ValidateException('评论不存在或已删除');
}
$where['video_id'] = $comment['video_id'];
}
$where['pid'] = $pid;
$list = $this->dao->getList($where, 'id,pid,video_id,uid,nickname,avatar,content,like_num,city,add_time', ['children' => function ($query) {
$query->field("id,pid")->with(['userVip']);
}, 'userVip'], $page, $limit);
if ($list) {
$userLike = [];
if ($uid) {
$ids = array_column($list, 'id');
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$userLike = $userRelationServices->getColumn([['uid', '=', $uid], ['relation_id', 'in', $ids], ['category', '=', 'video_comment'], ['type', '=', 'like']], 'id,relation_id', 'relation_id');
}
foreach ($list as &$item) {
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
$item['is_like'] = isset($userLike[$item['id']]);
$item['reply'] = [];
$item['reply_count'] = isset($item['children']) ? (int)count($item['children']) : 0;
$item['city'] = $this->addressHandle($item['city'])['city'] ?? '';
unset($item['children']);
}
}
return $list;
}
}

View File

@@ -0,0 +1,346 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\video;
use app\dao\activity\video\VideoDao;
use app\jobs\activity\VideoJob;
use app\services\activity\live\LiveRoomServices;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\user\UserRelationServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class VideoServices
* @package app\services\activity\video
* @mixin VideoDao
*/
class VideoServices extends BaseServices
{
/**
* 移动端视频需要默认值
* @var array
*/
protected array $videoDefault = [
'isMore' => false,
'state' => "pause",
'playIng' => false,
'isShowimage' => false,
'isShowProgressBarTime' => false,
'isplay' => true
];
/**
* @var VideoDao
*/
#[Inject]
protected VideoDao $dao;
/**
* 获取短视频信息(获取不到抛出异常)
* @param int $id
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getVideoInfo(int $id, string $field = '*')
{
$videoInfo = $this->dao->getOne(['id' => $id], $field);
if (!$videoInfo) {
throw new ValidateException('获取短视频信息失败');
}
return $videoInfo->toArray();
}
/**
* 获取视频详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('视频不存在');
}
$info = $info->toArray();
$productInfo = [];
$info['type_name'] = '平台';
if ($info['product_id']) {
$info['product_id'] = is_string($info['product_id']) ? explode(',', $info['product_id']) : $info['product_id'];
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$product_where = ['is_del' => 0, 'is_show' => 1, 'is_verify' => 1];
$productInfo = $productServices->getSearchList($product_where + ['ids' => $info['product_id']], 0, 0, ['id', 'type', 'product_type', 'price', 'image', 'store_name', 'cate_id', 'sales', 'stock'], '', []);
if ($productInfo) {
$cateIds = implode(',', array_column($productInfo, 'cate_id'));
/** @var StoreProductCategoryServices $categoryService */
$categoryService = app()->make(StoreProductCategoryServices::class);
$cateList = $categoryService->getCateParentAndChildName($cateIds);
foreach ($productInfo as $key => &$item) {
$item['cate_name'] = '';
if (isset($item['cate_id']) && $item['cate_id']) {
$cate_ids = explode(',', $item['cate_id']);
$cate_name = $categoryService->getCateName($cate_ids, $cateList);
if ($cate_name) {
$item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
}
}
}
}
}
$info['productInfo'] = $productInfo;
return $info;
}
/**
* 后台获取视频列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function sysPage($where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = 0;
if ($list) {
$site_name = sys_config('site_name');
$site_image = sys_config('wap_login_logo');
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$product_where = ['is_del' => 0, 'is_show' => 1, 'is_verify' => 1];
foreach ($list as &$item) {
$item['product_id'] = is_string($item['product_id']) ? explode(',', $item['product_id']) : $item['product_id'];
$item['product_info'] = [];
$item['product_num'] = 0;
if ($item['product_id']) {
$item['product_info'] = $productServices->getSearchList($product_where + ['ids' => $item['product_id']], 0, 0, ['id', 'store_name', 'image', 'price'], '', []);
$item['product_num'] = count($item['product_info']);
}
$item['type_name'] = $site_name;
$item['type_image'] = $site_image;
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取短视频列表
* @param int $uid
* @param string $field
* @param int $order_type
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getVideoList(int $uid, string $field = '*', int $order_type = 1, int $id = 0)
{
//短视频未启用
if (!sys_config('video_func_status', 1)) {
return [];
}
$where = ['is_show' => 1, 'is_del' => 0, 'is_verify' => 1];
[$page, $limit] = $this->getPageValue();
//限制一次最多请求条数
if ($limit > 10) $limit = 10;
if ($id) {//指定视频进入
$ids = array_merge([$id], $this->dao->getColumn($where, 'id'));
$where['order_by_id'] = array_slice($ids, 0, $limit);
$order = '';
} elseif ($order_type == 1) {//最新
$order = 'id desc,sort desc';
} else if ($order_type == 2) {//推荐
$where['is_recommend'] = 1;
$order = 'is_recommend desc,sort desc,id desc';
} else {
$order = 'sort desc,id desc';
}
$list = $this->dao->getList($where, $field, $page, $limit, $order);
if ($list) {
/** @var LiveRoomServices $liveServices */
$liveServices = app()->make(LiveRoomServices::class);
$liveCount = $liveServices->count(['is_show' => 1, 'is_del' => 0, 'status' => 1, 'live_status' => [101, 105, 106]]);
$is_live = (bool)$liveCount;
$site_name = sys_config('site_name');
$site_image = sys_config('wap_login_logo');
$userLike = $userCollect = [];
$ids = array_column($list, 'id');
if ($uid) {
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$userLike = $userRelationServices->getColumn([['uid', '=', $uid], ['relation_id', 'in', $ids], ['category', '=', 'video'], ['type', '=', 'like']], 'id,relation_id', 'relation_id');
$userCollect = $userRelationServices->getColumn([['uid', '=', $uid], ['relation_id', 'in', $ids], ['category', '=', 'video'], ['type', '=', 'collect']], 'id,relation_id', 'relation_id');
}
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productWhere = ['is_show' => 1, 'is_del' => 0, 'is_verify' => 1];
foreach ($list as &$item) {
$item['product_id'] = is_string($item['product_id']) ? explode(',', $item['product_id']) : $item['product_id'];
$item['product_num'] = count($item['product_id']);
//过滤下架、审核中等商品
$item['product_num'] = $item['product_num'] ? $storeProductServices->getCount($productWhere + ['ids' => $item['product_id']]) : 0;
$item['type_name'] = $site_name;
$item['type_image'] = $site_image;
$item['date'] = $item['add_time'] ? date('m月d日', $item['add_time']) : '';
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
$item['is_like'] = isset($userLike[$item['id']]);
$item['is_collect'] = isset($userCollect[$item['id']]);
//app需要
$item['id'] = (string)$item['id'];
$item = array_merge($item, $this->videoDefault);
$item['is_live'] = $is_live;
}
//增加浏览量
VideoJob::dispatchDo('setVideoPlayNum', [$ids, $uid]);
}
return $list;
}
/**
* 获取推荐短信视频
* @param int $id
* @param int $limit
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getRecommend(int $id, int $limit = 1)
{
$where = ['is_show' => 1, 'is_del' => 0, 'is_recommend' => 1, 'notId' => $id];
$list = $this->dao->getList($where, 'id,desc', 0, $limit, 'rand');
$res = [];
if ($limit == 1) {
if ($list) {
$res = $list[0] ?? [];
}
} else {
$res = $list;
}
return $res;
}
/**
* diy获取短视频
* @param int $uid
* @param string $field
* @param int $order_type
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getDiyVideoList(int $uid, string $field = '*', int $order_type = 0)
{
//短视频未启用
if (!sys_config('video_func_status', 1)) {
return [];
}
$where = ['is_show' => 1, 'is_del' => 0, 'is_verify' => 1];
[$page, $limit] = $this->getPageValue();
//限制一次最多请求条数
if ($limit > 10) $limit = 10;
if ($order_type == 1) {//最新
$order = 'id desc,sort desc';
} else if ($order_type == 2) {//推荐
$where['is_recommend'] = 1;
$order = 'is_recommend desc,sort desc,id desc';
} else {
$order = 'sort desc,id desc';
}
$list = $this->dao->getList($where, $field, $page, $limit, $order);
if ($list) {
$site_name = sys_config('site_name');
$site_image = sys_config('wap_login_logo');
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$product_where = ['is_del' => 0, 'is_show' => 1, 'is_verify' => 1];
foreach ($list as &$item) {
$item['product_id'] = is_string($item['product_id']) ? explode(',', $item['product_id']) : $item['product_id'];
$item['product_info'] = [];
$item['product_num'] = 0;
if ($item['product_id']) {
$item['product_info'] = $productServices->getSearchList($product_where + ['ids' => $item['product_id']], 0, 0, ['id', 'store_name', 'image', 'price'], '', []);
$item['product_num'] = count($item['product_info']);
}
$item['type_name'] = $site_name;
$item['type_image'] = $site_image;
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
$ids = array_column($list, 'id');
//增加浏览量
VideoJob::dispatchDo('setVideoPlayNum', [$ids, $uid]);
}
return $list;
}
/**
* 用户点赞、收藏、分享、浏览播放视频
* @param int $uid
* @param int $id
* @param string $type
* @param int $num
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function userRelationVideo(int $uid, int $id, string $type = 'like', int $num = 1)
{
$info = $this->getVideoInfo((int)$id);
$data = ['uid' => $uid, 'relation_id' => $id, 'category' => UserRelationServices::CATEGORY_VIDEO, 'type' => $type];
$typeArr = UserRelationServices::TYPE_NAME;
if (!isset($typeArr[$type])) {
throw new ValidateException('类型错误');
}
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
//播放 每一次都是增加数量
if ($type != 'play') {
$relation = $userRelationServices->get($data);
} else {
$relation = [];
}
$field = $type . '_num';
$balance = $info[$field] ?? 0;
if ($relation) {//取消
$userRelationServices->delete($relation['id']);
$new = (int)bcsub((string)$balance, (string)$num);
} else {//增加
$data['add_time'] = time();
$userRelationServices->save($data);
$new = (int)bcadd((string)$balance, (string)$num);
}
$new = max($new, 0);
$this->dao->update($id, [$field => $new]);
return true;
}
}