feat(fsgx): HJF queue merge, brokerage timing, cycle commission, points release

- Add HJF jobs, services, DAOs, models, admin/API controllers, release command
- Respect brokerage_timing (on_pay vs confirm); dispatch HjfOrderPayJob for queue goods
- Queue-only cycle commission and position index fix in StoreOrderCreateServices
- UserBill income types: frozen_points_brokerage, frozen_points_release
- Timer: fsgx_release_frozen_points -> PointsReleaseServices
- Agent tasks: no_assess filtering for direct/umbrella counts
- Migrations: queue_pool, points_release_log, fsgx_v1 checklist updates
- Admin/uniapp: crontab preset, membership level, user list, finance routes, docs

Made-with: Cursor
This commit is contained in:
apple
2026-03-24 11:59:09 +08:00
parent 434aa8c69d
commit 76ccb24679
59 changed files with 2902 additions and 237 deletions

View File

@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace app\controller\api\v1\hjf;
use app\Request;
use app\services\agent\AgentLevelServices;
use app\services\agent\AgentLevelTaskServices;
use app\services\hjf\MemberLevelServices;
use app\services\hjf\PointsRewardServices;
use app\dao\hjf\PointsReleaseLogDao;
use app\services\user\UserServices;
use think\annotation\Inject;
use think\facade\Db;
/**
* 用户端 · 会员信息接口(改造复用版)
*
* 复用 eb_agent_level 体系,使用 eb_user.agent_level 字段。
*
* GET /api/hjf/member/info — 当前用户等级信息
* GET /api/hjf/member/team — 团队成员列表
* GET /api/hjf/member/income — 团队收益明细
*
* Class MemberController
* @package app\controller\api\v1\hjf
*/
class MemberController
{
#[Inject]
protected MemberLevelServices $memberLevelServices;
#[Inject]
protected AgentLevelServices $agentLevelServices;
#[Inject]
protected AgentLevelTaskServices $agentLevelTaskServices;
/**
* 获取当前用户会员信息
*/
public function info(Request $request): \think\Response
{
$uid = (int)$request->uid();
$agentLevel = (int)Db::name('user')->where('uid', $uid)->value('agent_level');
// 直接从 eb_agent_level 取 name避免 grade 解析失败时等级徽章不显示
$levelRow = $agentLevel > 0 ? $this->agentLevelServices->getLevelInfo($agentLevel) : null;
$grade = $levelRow ? (int)$levelRow['grade'] : 0;
$levelName = $levelRow ? ($levelRow['name'] ?? '普通会员') : '普通会员';
$directCount = $this->memberLevelServices->getDirectSpreadCount($uid);
$umbrellaCount = $this->memberLevelServices->getUmbrellaQueueOrderCount($uid);
$directOrderCount = $this->memberLevelServices->getDirectQueueOrderCount($uid);
// 用已修正的 level row ID 查找下一等级,避免旧 status=0 记录导致 grade 被误判为 0
$effectiveLevelId = $levelRow ? (int)$levelRow['id'] : 0;
$nextLevel = $this->agentLevelServices->getNextLevelInfo($effectiveLevelId);
$nextLevelName = $nextLevel ? $nextLevel['name'] : null;
$upgradeProgress = [];
if ($nextLevel) {
$taskList = $this->agentLevelTaskServices->getUpgradeTasksForLevel((int)$nextLevel['id']);
foreach ($taskList as $task) {
$item = ['name' => $task['name'], 'number' => $task['number']];
switch ($task['type']) {
case 6:
$item['current'] = $directOrderCount;
break;
case 7:
$item['current'] = $umbrellaCount;
break;
case 8:
$item['current'] = $directCount;
break;
default:
$item['current'] = 0;
}
$item['completed'] = $item['current'] >= $item['number'];
$upgradeProgress[] = $item;
}
}
return app('json')->success([
'agent_level' => $agentLevel, // eb_user.agent_level 原始 ID供前端判断是否有等级
'member_level' => $grade,
'member_level_name' => $levelName, // eb_agent_level.name 直接值
'direct_count' => $directCount,
'umbrella_count' => $umbrellaCount,
'direct_order_count' => $directOrderCount,
'next_level_name' => $nextLevelName,
'upgrade_progress' => $upgradeProgress,
]);
}
/**
* 团队成员列表(直推/伞下)
*/
public function team(Request $request): \think\Response
{
$uid = (int)$request->uid();
$page = (int)$request->get('page', 1);
$limit = (int)$request->get('limit', 20);
$type = $request->get('type', 'direct');
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
if ($type === 'direct') {
$where = ['spread_uid' => $uid];
} else {
$directUids = $userServices->getColumn(['spread_uid' => $uid], 'uid');
if (empty($directUids)) {
return app('json')->success(['list' => [], 'count' => 0]);
}
$where = [['spread_uid', 'in', $directUids]];
}
$count = $userServices->count($where);
$list = Db::name('user')
->where($where)
->field('uid,nickname,avatar,phone,agent_level,add_time')
->page($page, $limit)
->order('uid desc')
->select()
->toArray();
$maps = $this->agentLevelServices->loadHjfUserListLevelMaps();
foreach ($list as &$item) {
$alId = (int)($item['agent_level'] ?? 0);
$levelInfo = $this->agentLevelServices->pickHjfLevelRowForUserListDisplay($alId, $maps);
$item['member_level'] = $levelInfo ? (int)$levelInfo['grade'] : 0;
$item['member_level_name'] = $levelInfo ? $levelInfo['name'] : '普通会员';
$item['join_time'] = date('Y-m-d', (int)$item['add_time']);
$item['direct_orders'] = $this->agentLevelTaskServices->getDirectQueueOrderCount((int)$item['uid']);
unset($item['agent_level'], $item['add_time']);
}
unset($item);
return app('json')->success(compact('list', 'count'));
}
/**
* 团队收益明细(积分奖励记录)
*/
public function income(Request $request): \think\Response
{
$uid = (int)$request->uid();
$page = (int)$request->get('page', 1);
$limit = (int)$request->get('limit', 20);
/** @var PointsReleaseLogDao $logDao */
$logDao = app()->make(PointsReleaseLogDao::class);
$where = [
'uid' => $uid,
'pm' => 1,
];
$where[] = ['type', 'in', ['reward_direct', 'reward_umbrella']];
$count = $logDao->count($where);
$list = Db::name('points_release_log')
->where($where)
->field('id,uid,points,type,title,mark,order_id,add_time')
->page($page, $limit)
->order('id desc')
->select()
->toArray();
foreach ($list as &$item) {
$item['time'] = date('Y-m-d H:i:s', (int)$item['add_time']);
}
unset($item);
return app('json')->success(compact('list', 'count'));
}
}