UserBrokerageRepository.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\common\repositories\user;
  12. use app\common\dao\user\UserBrokerageDao;
  13. use app\common\model\user\User;
  14. use app\common\model\user\UserBrokerage;
  15. use app\common\repositories\BaseRepository;
  16. use app\common\repositories\system\CacheRepository;
  17. use FormBuilder\Factory\Elm;
  18. use think\exception\ValidateException;
  19. use think\facade\Db;
  20. use think\facade\Route;
  21. /**
  22. * @mixin UserBrokerageDao
  23. */
  24. class UserBrokerageRepository extends BaseRepository
  25. {
  26. const BROKERAGE_RULE_TYPE = ['spread_user', 'pay_money', 'pay_num', 'spread_money', 'spread_pay_num'];
  27. public function __construct(UserBrokerageDao $dao)
  28. {
  29. $this->dao = $dao;
  30. }
  31. /**
  32. * 获取列表数据
  33. *
  34. * 根据给定的条件数组 $where、页码 $page 和每页数据数量 $limit,查询并返回符合条件的数据列表。
  35. * 此方法主要用于数据的分页查询,通过 dao 层的 search 方法进行查询,首先根据条件进行筛选,
  36. * 然后按照佣金级别升序、创建时间降序进行排序。最后,根据页码和每页数据数量进行分页,返回查询结果。
  37. *
  38. * @param array $where 查询条件数组
  39. * @param int $page 当前页码
  40. * @param int $limit 每页数据数量
  41. * @return array 返回包含列表数据和总条数的数组
  42. */
  43. public function getList(array $where, $page, $limit)
  44. {
  45. // 根据条件进行查询,并按照佣金级别升序、创建时间降序排序
  46. $query = $this->dao->search($where)->order('brokerage_level ASC,create_time DESC');
  47. // 统计符合条件的数据总条数
  48. $count = $query->count();
  49. // 根据当前页码和每页数据数量进行分页查询,并获取查询结果
  50. $list = $query->page($page, $limit)->select();
  51. // 返回包含查询结果列表和总条数的数组
  52. return compact('list', 'count');
  53. }
  54. /**
  55. * 根据当前等级和类型获取下一个等级的信息
  56. *
  57. * 本函数旨在查询数据库中,当前等级($level)之后的下一个等级的信息。可以选择性地通过$type参数来筛选特定类型的等级。
  58. * 查询结果将按照佣金等级升序和创建时间降序返回第一个匹配项。
  59. *
  60. * @param string $level 当前等级的值
  61. * @param int $type 等级的类型,默认为0,表示不进行类型筛选
  62. * @return object 返回查询结果的对象,包含下一个等级的信息
  63. */
  64. public function getNextLevel($level,$type = 0)
  65. {
  66. // 使用查询构建器进行查询,筛选出next_level为$level且type为$type的数据,按佣金等级升序和创建时间降序排序,并返回第一条数据
  67. return $this->search(['next_level' => $level,'type' => $type])->order('brokerage_level ASC,create_time DESC')->find();
  68. }
  69. /**
  70. * 根据条件查询佣金等级选项
  71. *
  72. * 本函数用于根据传入的条件数组,从数据库中查询并返回佣金等级的相关信息。
  73. * 返回的信息格式化为适用于选项列表的形式,每个选项包含value和label属性。
  74. * value表示佣金等级的值,label表示佣金等级的显示名称。
  75. * 查询结果按照佣金等级升序,创建时间降序排序。
  76. *
  77. * @param array $where 查询条件数组
  78. * @return array 返回查询结果,格式为每个元素包含value和label属性的数组
  79. */
  80. public function options(array $where)
  81. {
  82. // 使用DAO层的search方法进行条件查询,指定返回的字段为brokerage_level和brokerage_name,
  83. // 并设置排序方式为brokerage_level升序,create_time降序。
  84. // 最后执行查询并返回结果。
  85. return $this->dao->search($where)
  86. ->field('brokerage_level as value,brokerage_name as label')
  87. ->order('brokerage_level ASC,create_time DESC')
  88. ->select()->each(function($item){
  89. $item['value'] = (int)$item['value'];
  90. return $item;
  91. });
  92. }
  93. /**
  94. * 获取所有类型为$type$的数据
  95. *
  96. * 本函数通过调用DAO层的search方法,查询类型为$type$的所有数据。
  97. * 查询结果将按照经纪等级(brokerage_level)升序和创建时间(create_time)降序进行排序。
  98. * 最后,函数返回查询结果。
  99. *
  100. * @param int $type 数据的类型,用于筛选查询条件。
  101. * @return array 返回查询结果,是一个包含多个数据项的数组。
  102. */
  103. public function all(int $type)
  104. {
  105. // 调用DAO层的search方法查询数据,指定类型为$type$,并设定排序规则
  106. // 返回查询结果,未对返回值做进一步处理,直接返回查询结果数组
  107. return $this->dao->search(['type' => $type])->order('brokerage_level ASC,create_time DESC')->select();
  108. }
  109. /**
  110. * 用户佣金增加函数
  111. *
  112. * 该函数用于处理用户佣金的增加逻辑。它首先根据当前用户的经纪等级获取下一个等级信息,
  113. * 然后检查是否存在相应的佣金记录。如果存在,则更新该记录的金额;如果不存在,则创建新的佣金记录。
  114. * 最后,检查用户是否满足升级到下一个经纪等级的条件。
  115. *
  116. * @param User $user 用户对象,表示当前操作的用户
  117. * @param string $type 佣金类型,用于区分不同种类的佣金
  118. * @param float $inc 佣金增加的金额,使用浮点数表示
  119. * @return bool 返回布尔值,表示用户是否成功升级到下一个经纪等级
  120. */
  121. public function inc(User $user, $type, $inc)
  122. {
  123. // 获取用户下一个经纪等级信息
  124. $nextLevel = $this->getNextLevel($user->brokerage_level);
  125. // 如果没有下一个等级,则返回false
  126. if (!$nextLevel) return false;
  127. // 实例化用户账单仓库类
  128. $make = app()->make(UserBillRepository::class);
  129. // 根据条件查询是否存在对应的佣金记录
  130. $bill = $make->getWhere(['uid' => $user->uid, 'link_id' => $nextLevel->user_brokerage_id, 'category' => 'sys_brokerage', 'type' => $type]);
  131. // 如果记录存在,则更新记录的金额并保存
  132. if ($bill) {
  133. $bill->number = bcadd($bill->number, $inc, 2);
  134. $bill->save();
  135. } else {
  136. // 如果记录不存在,则创建新的佣金记录
  137. $make->incBill($user->uid, 'sys_brokerage', $type, [
  138. 'number' => $inc,
  139. 'title' => $type,
  140. 'balance' => 0,
  141. 'status' => 0,
  142. 'link_id' => $nextLevel->user_brokerage_id
  143. ]);
  144. }
  145. // 检查用户是否满足升级到下一个经纪等级的条件,并返回检查结果
  146. return $this->checkLevel($user, $nextLevel);
  147. }
  148. /**
  149. * 检查用户是否满足升级经纪人的条件
  150. *
  151. * 本函数用于评估当前用户是否满足升级为其指定经纪人的条件。它通过查询用户的交易记录,
  152. * 并对比经纪人的升级规则,来判断用户是否达到升级标准。如果用户满足条件,则进行升级操作,
  153. * 包括更新用户和经纪人的相关数据,并记录用户的经纪级别。
  154. *
  155. * @param User $user 当前待评估的用户对象
  156. * @param UserBrokerage $nextLevel 用户待升级的下一个经纪级别对象
  157. * @return bool 如果用户满足升级条件,则返回true;否则返回false。
  158. */
  159. public function checkLevel(User $user, UserBrokerage $nextLevel)
  160. {
  161. // 查询用户的交易记录,满足特定条件的记录将用于评估升级条件
  162. $info = app()->make(UserBillRepository::class)->search(['uid' => $user->uid, 'category' => 'sys_brokerage', 'link_id' => $nextLevel->user_brokerage_id])
  163. ->column('number', 'type');
  164. // 遍历经纪人的升级规则,检查用户是否满足所有规则条件
  165. foreach ($nextLevel['brokerage_rule'] as $k => $rule) {
  166. // 如果用户交易记录中不存在对应类型的记录,且规则要求的交易数量大于0,则不满足升级条件
  167. if (!isset($info[$k]) && $rule['num'] > 0) return false;
  168. // 如果用户交易记录数量小于规则要求的数量,则不满足升级条件
  169. if ($rule['num'] > 0 && $rule['num'] > $info[$k]) return false;
  170. }
  171. // 用户满足升级条件,准备进行数据更新
  172. $nextLevel->user_num++;
  173. Db::transaction(function () use ($nextLevel, $user) {
  174. // 保存更新后的下一个经纪级别信息
  175. $nextLevel->save();
  176. // 如果当前用户已有经纪人,并且其用户数量大于0,则减少该经纪人的用户数量
  177. if ($user->brokerage && $user->brokerage->user_num > 0) {
  178. $user->brokerage->user_num--;
  179. $user->brokerage->save();
  180. }
  181. // 更新用户经纪级别,并保存
  182. $user->brokerage_level = $nextLevel->brokerage_level;
  183. $user->save();
  184. // 将用户的经纪级别信息缓存起来,以便后续快速查询
  185. $key = 'notice_brokerage_level_' . $user->uid;
  186. app()->make(CacheRepository::class)->save($key,$nextLevel->brokerage_level);
  187. });
  188. // 返回true,表示用户满足升级条件
  189. return true;
  190. }
  191. /**
  192. * 根据用户的交易记录和下级经纪人的佣金规则,计算下级经纪人的佣金比率。
  193. *
  194. * @param User $user 当前用户对象
  195. * @param UserBrokerage $nextLevel 下级经纪人的佣金规则对象
  196. * @return array 返回计算后的佣金比率和任务完成情况
  197. */
  198. public function getLevelRate(User $user, UserBrokerage $nextLevel)
  199. {
  200. // 查询用户指定条件的交易记录,用于后续计算佣金比率
  201. $info = app()->make(UserBillRepository::class)->search(['uid' => $user->uid, 'category' => 'sys_brokerage', 'link_id' => $nextLevel->user_brokerage_id])
  202. ->column('number', 'type');
  203. // 获取下级经纪人的佣金规则
  204. $brokerage_rule = $nextLevel['brokerage_rule'];
  205. // 遍历佣金规则,计算每个规则对应的佣金比率和完成任务的数量
  206. foreach ($nextLevel['brokerage_rule'] as $k => $rule) {
  207. // 如果规则的交易数量要求为0或负数,则移除该规则
  208. if ($rule['num'] <= 0) {
  209. unset($brokerage_rule[$k]);
  210. continue;
  211. }
  212. // 根据用户的交易数量和规则的要求数量,计算佣金比率
  213. if (!isset($info[$k])) {
  214. $rate = 0;
  215. } else if ($rule['num'] > $info[$k]) {
  216. $rate = bcdiv($info[$k], $rule['num'], 2) * 100;
  217. } else {
  218. $rate = 100;
  219. }
  220. // 更新规则信息,添加佣金比率和已完成的任务数量
  221. $brokerage_rule[$k]['rate'] = $rate;
  222. $brokerage_rule[$k]['task'] = (float)(min($info[$k] ?? 0, $rule['num']));
  223. }
  224. // 返回计算后的佣金规则信息
  225. return $brokerage_rule;
  226. }
  227. /**
  228. * 创建或编辑会员等级表单
  229. *
  230. * @param int|null $id 会员等级ID,如果提供ID,则为编辑模式,否则为添加模式
  231. * @return \FormBuilder\Form|\think\form\Form
  232. */
  233. public function form(?int $id = null)
  234. {
  235. // 初始化表单数据数组
  236. $formData = [];
  237. // 如果ID存在,进入编辑模式
  238. if ($id) {
  239. // 创建表单,表单提交地址为更新会员等级的URL
  240. $form = Elm::createForm(Route::buildUrl('systemUserMemberUpdate', ['id' => $id])->build());
  241. // 通过ID获取会员等级数据
  242. $data = $this->dao->get($id);
  243. // 如果数据不存在,抛出异常
  244. if (!$data) throw new ValidateException('数据不存在');
  245. // 将获取的会员等级数据转换为数组,并赋值给formData
  246. $formData = $data->toArray();
  247. } else {
  248. // 如果ID不存在,进入添加模式
  249. // 创建表单,表单提交地址为创建会员等级的URL
  250. $form = Elm::createForm(Route::buildUrl('systemUserMemberCreate')->build());
  251. }
  252. // 定义表单规则,包括会员等级、会员名称、会员图标、所需成长值和背景图
  253. $rules = [
  254. Elm::number('brokerage_level', '会员等级:')->required(),
  255. Elm::input('brokerage_name', '会员名称:')->placeholder('请输入会员名称')->required(),
  256. Elm::frameImage('brokerage_icon', '会员图标:', '/' . config('admin.admin_prefix') . '/setting/uploadPicture?field=brokerage_icon&type=1')
  257. ->required()
  258. ->value($formData['brokerage_icon'] ?? '')
  259. ->modal(['modal' => false])
  260. ->icon('el-icon-camera')
  261. ->width('1000px')
  262. ->height('600px'),
  263. Elm::number('value', ' 所需成长值:',$formData['brokerage_rule']['value'] ?? 0)->required(),
  264. Elm::frameImage('image', '背景图:', '/' . config('admin.admin_prefix') . '/setting/uploadPicture?field=image&type=1')
  265. ->value($formData['brokerage_rule']['image']??'')
  266. ->required()
  267. ->modal(['modal' => false])
  268. ->icon('el-icon-camera')
  269. ->width('1000px')
  270. ->height('600px'),
  271. ];
  272. // 设置表单规则
  273. $form->setRule($rules);
  274. // 设置表单标题,根据ID是否存在决定是添加还是编辑会员等级
  275. return $form->setTitle(is_null($id) ? '添加会员等级' : '编辑会员等级')->formData($formData);
  276. }
  277. /**
  278. * 增加用户会员值
  279. *
  280. * 根据用户的特定行为(如下单、签到、评价等),增加用户的会员值。
  281. * 可以根据配置的成长值规则,以及用户是否为VIP,动态计算增长的会员值。
  282. * 如果用户是VIP,并且系统配置允许,会员值的增长还会受到VIP加成的影响。
  283. *
  284. * @param int $uid 用户ID
  285. * @param string $type 行为类型,对应不同的会员值增长规则
  286. * @param int $id 关联的ID,如订单ID、评论ID等,根据行为类型不同而变化
  287. * @param int $money 当行为类型为下单时,订单的支付金额,用于计算额外的会员值增长
  288. */
  289. public function incMemberValue(int $uid, string $type, int $id, int $money = 0)
  290. {
  291. // 检查是否启用了会员功能,如果没有启用,则直接返回
  292. if (!systemConfig('member_status')) return;
  293. // 创建用户账单仓库实例
  294. $make = app()->make(UserBillRepository::class);
  295. // 检查是否需要重复添加,如果需要,则直接返回
  296. // 判断是否要重复添加
  297. if ($make->ToRepeat($uid, $type, $id)) {
  298. return;
  299. }
  300. // 定义不同行为类型的描述和默认增长值
  301. $config = [
  302. 'member_pay_num' => '下单获得成长值',
  303. 'member_sign_num' => '签到获得成长值',
  304. 'member_reply_num' => '评价获得成长值',
  305. 'member_share_num' => '邀请获得成长值',
  306. 'member_community_num' => '社区种草内容获得成长值',
  307. 'member_order_pay_num' => ';下单获得比例成长值'
  308. ];
  309. // 根据行为类型获取配置的增长值,如果配置值小于等于0,则默认为0
  310. $inc = systemConfig($type) > 0 ? systemConfig($type) : 0;
  311. // 获取用户信息,包括会员值等
  312. $user = app()->make(UserRepository::class)->getWhere(['uid' => $uid], '*', ['member']);
  313. // 判断用户是否为VIP,并且VIP功能是否开启
  314. $svip_status = $user->is_svip > 0 && systemConfig('svip_switch_status') == '1';
  315. // 如果用户是VIP,并且设置了VIP加成比例,则根据比例调整增长值
  316. if ($svip_status) {
  317. $svipRate = app()->make(MemberinterestsRepository::class)->getSvipInterestVal(MemberinterestsRepository::HAS_TYPE_MEMBER);
  318. if ($svipRate > 0) {
  319. $inc = bcmul($svipRate, $inc, 0);
  320. }
  321. }
  322. // 构建增长标记,描述增长的原因和值
  323. $mark = $config[$type].':'.$inc;
  324. // 如果行为类型为下单,并且指定了订单支付金额,则根据配置的规则计算额外的会员值增长
  325. // 下单通过经验值获得比例乘以订单金额作为成长值
  326. $inc_ = 0;
  327. if ($type == 'member_pay_num' && $money) {
  328. $inc_ = systemConfig('member_order_pay_num') > 0 ? systemConfig('member_order_pay_num') : 0;
  329. $inc_ = (int)bcmul($money, $inc_, 0);
  330. $mark .= $config['member_order_pay_num'].':'.$inc_;
  331. }
  332. // 计算最终的会员值增长量
  333. $inc = $inc + $inc_;
  334. // 创建用户账单,记录会员值的增长情况
  335. $make->incBill($user->uid, 'sys_members', $type, [
  336. 'number' => $inc,
  337. 'title' => $config[$type],
  338. 'balance' => $user->member_value + $inc,
  339. 'status' => 0,
  340. 'link_id' => $id,
  341. 'mark' => $mark,
  342. ]);
  343. // 检查用户的会员值是否满足升级条件
  344. $this->checkMemberValue($user, $inc);
  345. }
  346. /**
  347. * 连续升级
  348. * @param $nextLevel
  349. * @param $num
  350. * @return array
  351. * @author Qinii
  352. * @day 1/11/22
  353. */
  354. public function upUp($nextLevel, $num, $use_value)
  355. {
  356. $newLevel = $this->getNextLevel($nextLevel->brokerage_level, 1);
  357. if ($newLevel) {
  358. $newNum = $num - $newLevel->brokerage_rule['value'];
  359. if ($newNum > 0) {
  360. $use_value += $newLevel->brokerage_rule['value'];
  361. [$nextLevel,$num,$use_value] = $this->upUp($newLevel, $newNum, $use_value);
  362. }
  363. }
  364. return [$nextLevel,$num,$use_value];
  365. }
  366. /**
  367. * 升级操作
  368. * @param User $user
  369. * @param int $inc
  370. * @author Qinii
  371. * @day 1/11/22
  372. */
  373. public function checkMemberValue(User $user, int $inc)
  374. {
  375. /**
  376. * 下一级所需经验值
  377. * 当前的经验值加上增加经验值是否够升级
  378. */
  379. $nextLevel = $this->getNextLevel($user->member_level, 1);
  380. return Db::transaction(function () use ($inc, $user, $nextLevel) {
  381. $num = $user->member_value + $inc;
  382. if ($nextLevel) {
  383. if ($user->member_value >= $nextLevel->brokerage_rule['value']) {
  384. $num = $user->member_value - $nextLevel->brokerage_rule['value'];
  385. $use_value = $nextLevel->brokerage_rule['value']; // 升级消耗成长值
  386. if ($num > 0) {
  387. [$nextLevel, $num, $use_value] = $this->upUp($nextLevel, $num, $use_value);
  388. }
  389. if ($user->member) {
  390. $user->member->user_num--;
  391. $user->member->save();
  392. }
  393. $nextLevel->user_num++;
  394. $nextLevel->save();
  395. $user->member_level = $nextLevel->brokerage_level;
  396. $key = 'notice_member_level_' . $user->uid;
  397. app()->make(CacheRepository::class)->save($key, $nextLevel->brokerage_level);
  398. // 添加升级所需成长值记录
  399. app()->make(UserBillRepository::class)->decBill($user->uid, 'sys_members', 'member_upgrade', ['number' => $use_value,
  400. 'title' => '升级消耗成长值',
  401. 'balance' => $num,
  402. 'status' => 0,
  403. 'mark' => '升级消耗成长值' . ':' . $use_value,
  404. ]);
  405. }
  406. }
  407. $user->member_value = $num;
  408. $user->save();
  409. return $user;
  410. });
  411. }
  412. }