From 451918bc73338b2ea0afb375ac99f00f090566d8 Mon Sep 17 00:00:00 2001 From: mac Date: Tue, 24 Mar 2026 20:40:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(fsgx):=20=E4=BF=AE=E5=A4=8D5=E4=B8=AA?= =?UTF-8?q?=E6=9C=AA=E4=BF=AE=E5=A4=8DBug=20=E2=80=94=20=E7=A7=AF=E5=88=86?= =?UTF-8?q?=E8=A7=A3=E8=80=A6/=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1/?= =?UTF-8?q?=E7=A7=AF=E5=88=86=E6=97=A5=E5=BF=97/=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1/=E5=8E=86=E5=8F=B2=E8=A1=A5=E5=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug3: 解耦积分奖励与佣金发放,报单订单只要推荐人存在即触发积分, 不再依赖 brokeragePrice > 0;grantFrozenPointsByBrokerage 移至 佣金判断之前独立执行。 Bug1: 定时任务手动触发返回真实结果 —— 补充 fsgx_release_frozen_points 到 taskName 映射;runNow() try/catch 后抛出异常;控制器捕获并返回 fail;修复 SystemTimer listener catch 块运算符优先级 bug。 Bug5: PointsReleaseServices 每日释放同步写入 eb_user_bill,使管理 后台积分日志页面可见;UserPointServices::pointRecord $status 数组 补充 hjf_frozen_direct/hjf_frozen_umbrella/frozen_points_release 等 fsgx 类型映射,防止未知类型报错。 Bug2: hjfMember.js getTeamData 改为 POST 与路由匹配;loadTeamData 字段映射 total/totalLevel/order_count → 界面展示字段。 Bug4: 新增 HjfPatchMissingRewards 命令(hjf:patch-rewards),支持 扫描全量/指定订单补发缺失积分奖励,支持 --dry-run 预览;注册命令 到 config/console.php。 Made-with: Cursor --- docs/issues-0323-1.md | 9 +- .../app/command/HjfPatchMissingRewards.php | 118 ++++++++++++++++++ .../admin/v1/system/SystemTimer.php | 8 +- .../app/listener/system/timer/SystemTimer.php | 2 +- .../activity/integral/UserPointServices.php | 9 +- .../services/hjf/PointsReleaseServices.php | 16 +++ .../services/order/StoreOrderTakeServices.php | 17 +-- .../system/timer/SystemTimerServices.php | 12 +- pro_v3.5.1/config/console.php | 2 + pro_v3.5.1/view/uniapp/api/hjfMember.js | 2 +- .../pages/users/user_spread_money/index.vue | 8 +- 11 files changed, 180 insertions(+), 23 deletions(-) create mode 100644 pro_v3.5.1/app/command/HjfPatchMissingRewards.php diff --git a/docs/issues-0323-1.md b/docs/issues-0323-1.md index c0328f25..5f2fdb4c 100644 --- a/docs/issues-0323-1.md +++ b/docs/issues-0323-1.md @@ -20,7 +20,9 @@ ## 定时任务页面,路径:/admin/system/crontab -1. 增加“手动触发”功能按钮,可以手动触发即执行任务立, +1. **已修复**增加“手动触发”功能按钮,可以手动触发即执行任务立, +2. 手动触发“fsgx每日积分释放”任务失败, + ## 用户列表页面,路径:/admin/user/list 1. **已修复**“直推人数满、伞下订单数”没有显示数据,参考分销员管理页面实现逻辑(/admin/agent/agent_manage/index)实现数据显示。 @@ -46,13 +48,14 @@ 1. 测试账号:UID:1, 手机号:1860001111; UID:2, 手机号:18621813282 ;UID:3, 手机号:17887996868 ; UID:4, 手机号:15324401259;UID:5, 手机号:17887996868; UID:6, 手机号:15821676725; 测试账号密码默认:A123456 2. 推荐关系: uid=2推荐uid=4,uid=5, uid=6 -3. uid=4,5,6购买报单商品后推荐人uid=2没有佣金/返现产生, +3. **已修复**uid=4,5,6购买报单商品后推荐人uid=2没有佣金/返现产生, 4. 分销会员uid=2的积分奖励“待释放(冻结)积分”没有 + ## 手动测试问题 1. 排查原因:eb_store_order中id=11在管理后台中的2个页面看不到返现佣金明细和奖励积分明细, 2. 积分日志页面/admin/marketing/user_point/index看不到奖励积分明细, -3. 佣金记录页面/admin/finance/finance/commission中用户返现佣金记录详情中看不到返现佣金明细 +3. **已修复**佣金记录页面/admin/finance/finance/commission中用户返现佣金记录详情中看不到返现佣金明细 4. **相关文件**:`docs/PRD_fsgx_V1.0.md` `docs/page-dev-specs-fsgx.md`, diff --git a/pro_v3.5.1/app/command/HjfPatchMissingRewards.php b/pro_v3.5.1/app/command/HjfPatchMissingRewards.php new file mode 100644 index 00000000..58cbf02b --- /dev/null +++ b/pro_v3.5.1/app/command/HjfPatchMissingRewards.php @@ -0,0 +1,118 @@ +setName('hjf:patch-rewards') + ->setDescription('补偿历史报单订单缺失的冻结积分奖励') + ->addOption('order-id', null, Option::VALUE_OPTIONAL, '指定订单ID,不传则扫描全部') + ->addOption('dry-run', null, Option::VALUE_NONE, '仅扫描打印,不实际执行'); + } + + protected function execute(Input $input, Output $output): int + { + $orderId = $input->getOption('order-id'); + $dryRun = $input->getOption('dry-run'); + + $output->writeln('[HjfPatchRewards] 开始扫描缺失积分奖励的报单订单...'); + + $query = Db::name('store_order') + ->where('is_queue_goods', 1) + ->where('paid', 1) + ->where('is_del', 0) + ->where('is_system_del', 0); + + if ($orderId) { + $query->where('id', (int)$orderId); + } + + $orders = $query->field('id,order_id,uid,spread_uid,one_brokerage')->select()->toArray(); + + if (empty($orders)) { + $output->writeln('[HjfPatchRewards] 没有找到符合条件的报单订单'); + return 0; + } + + $output->writeln(sprintf('[HjfPatchRewards] 找到 %d 笔报单订单', count($orders))); + + /** @var PointsRewardServices $pointsService */ + $pointsService = app()->make(PointsRewardServices::class); + $patched = 0; + $skipped = 0; + + foreach ($orders as $order) { + $hasRewardLog = Db::name('points_release_log') + ->where('order_id', $order['order_id']) + ->where('type', 'reward_direct') + ->count(); + + $hasRewardBill = Db::name('user_bill') + ->where('link_id', $order['id']) + ->where('type', 'hjf_frozen_direct') + ->count(); + + if ($hasRewardLog > 0 || $hasRewardBill > 0) { + $skipped++; + $output->writeln(sprintf(' [SKIP] 订单 #%d (%s) 已有积分奖励记录', $order['id'], $order['order_id'])); + continue; + } + + if (!$order['spread_uid'] || $order['spread_uid'] <= 0) { + $skipped++; + $output->writeln(sprintf(' [SKIP] 订单 #%d (%s) 无推荐人', $order['id'], $order['order_id'])); + continue; + } + + if ($dryRun) { + $output->writeln(sprintf( + ' [DRY-RUN] 订单 #%d (%s) uid=%d spread_uid=%d 需要补发积分', + $order['id'], $order['order_id'], $order['uid'], $order['spread_uid'] + )); + $patched++; + continue; + } + + try { + $pointsService->reward((int)$order['uid'], (string)$order['order_id'], (int)$order['id']); + $patched++; + $output->writeln(sprintf( + ' [PATCHED] 订单 #%d (%s) 已补发积分奖励', + $order['id'], $order['order_id'] + )); + } catch (\Throwable $e) { + $output->writeln(sprintf( + ' [ERROR] 订单 #%d (%s): %s', + $order['id'], $order['order_id'], $e->getMessage() + )); + Log::error("[HjfPatchRewards] 订单 #{$order['id']} 补发失败: " . $e->getMessage()); + } + } + + $output->writeln(sprintf( + '[HjfPatchRewards] 完成:补发 %d 笔,跳过 %d 笔%s', + $patched, $skipped, $dryRun ? ' (dry-run 模式)' : '' + )); + + return 0; + } +} diff --git a/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php b/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php index 7957a3c2..89e61018 100644 --- a/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php +++ b/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php @@ -118,8 +118,12 @@ class SystemTimer extends AuthController if (!$mark) { return $this->fail('定时任务标识不存在'); } - $this->services->runNow($mark); - return $this->success('任务已触发'); + try { + $this->services->runNow($mark); + return $this->success('任务已触发并执行成功'); + } catch (\Throwable $e) { + return $this->fail($e->getMessage()); + } } /** diff --git a/pro_v3.5.1/app/listener/system/timer/SystemTimer.php b/pro_v3.5.1/app/listener/system/timer/SystemTimer.php index 4d42e7f6..af108fe2 100644 --- a/pro_v3.5.1/app/listener/system/timer/SystemTimer.php +++ b/pro_v3.5.1/app/listener/system/timer/SystemTimer.php @@ -201,7 +201,7 @@ class SystemTimer extends Cron implements ListenerInterface $timerServices = app()->make(SystemTimerServices::class); $taskName = $timerServices->getTasKName(); response_log_write([ - 'message' => '定时任务:[' . $taskName[$mark] ?? '未知' . '],失败原因:[' . class_basename($this) . ']', + 'message' => '定时任务:[' . ($taskName[$mark] ?? '未知') . '],失败原因:[' . class_basename($this) . ']', 'file' => $e->getFile(), 'line' => $e->getLine(), 'msg' => $e->getMessage() diff --git a/pro_v3.5.1/app/services/activity/integral/UserPointServices.php b/pro_v3.5.1/app/services/activity/integral/UserPointServices.php index 079ad4f9..314cd477 100644 --- a/pro_v3.5.1/app/services/activity/integral/UserPointServices.php +++ b/pro_v3.5.1/app/services/activity/integral/UserPointServices.php @@ -39,6 +39,11 @@ class UserPointServices extends BaseServices 'storeIntegral_use' => '积分兑换商品', 'pay_product_integral_back' => '返还下单使用积分', 'sign' => '签到获得积分', + 'hjf_frozen_direct' => '直推积分奖励', + 'hjf_frozen_umbrella' => '伞下积分奖励', + 'frozen_points_brokerage' => '佣金奖励积分(待释放)', + 'frozen_points_release' => '每日积分释放', + 'holiday_gift_integral' => '节日有礼赠送积分', ]; [$page, $limit] = $this->getPageValue(); $list = $this->dao->getList($where, '*', $page, $limit); @@ -59,9 +64,9 @@ class UserPointServices extends BaseServices } elseif ($item['type'] == 'storeIntegral_use') { $item['relation'] = $integralOrderServices->value(['id' => $item['link_id']], 'order_id'); } else { - $item['relation'] = $status[$item['type']]; + $item['relation'] = $status[$item['type']] ?? $item['type']; } - $item['type_name'] = $status[$item['type']]; + $item['type_name'] = $status[$item['type']] ?? $item['type']; } $count = $this->dao->count($where); return compact('list', 'count', 'status'); diff --git a/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php b/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php index 7241c510..02663849 100644 --- a/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php +++ b/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php @@ -6,6 +6,7 @@ namespace app\services\hjf; use app\dao\hjf\PointsReleaseLogDao; use app\dao\user\UserDao; use app\services\BaseServices; +use app\services\user\UserBillServices; use crmeb\services\SystemConfigService; use think\annotation\Inject; use think\facade\Db; @@ -29,6 +30,9 @@ class PointsReleaseServices extends BaseServices #[Inject] protected UserDao $userDao; + #[Inject] + protected UserBillServices $userBillServices; + /** * 执行今日积分释放(批量) * @@ -89,6 +93,18 @@ class PointsReleaseServices extends BaseServices 'status' => 'released', 'release_date' => $releaseDate, ]); + + // 同步写入 eb_user_bill,使管理后台积分日志页面可见 + $integralBalance = (int)($this->userDao->value(['uid' => $user['uid']], 'integral') ?: 0); + $this->userBillServices->income( + 'frozen_points_release', + (int)$user['uid'], + (int)$releaseAmount, + $integralBalance, + 0, + 0, + "积分每日自动解冻,释放日期 {$releaseDate}" + ); }); $totalReleased += $releaseAmount; diff --git a/pro_v3.5.1/app/services/order/StoreOrderTakeServices.php b/pro_v3.5.1/app/services/order/StoreOrderTakeServices.php index ba594882..a168f4c0 100644 --- a/pro_v3.5.1/app/services/order/StoreOrderTakeServices.php +++ b/pro_v3.5.1/app/services/order/StoreOrderTakeServices.php @@ -275,6 +275,12 @@ class StoreOrderTakeServices extends BaseServices } //订单中取出 $brokeragePrice = $orderInfo['one_brokerage'] ?? 0; + + // fsgx: 积分奖励独立于佣金金额,只要是报单订单且推荐人存在就触发 + if ($isQueueOrder && $one_spread_uid > 0) { + $this->grantFrozenPointsByBrokerage($one_spread_uid, $brokeragePrice, $orderInfo); + } + // 返佣金额小于等于0 直接返回不返佣金 if ($brokeragePrice <= 0) { return true; @@ -299,9 +305,6 @@ class StoreOrderTakeServices extends BaseServices //给上级发送获得佣金的模板消息 $this->sendBackOrderBrokerage($orderInfo, $one_spread_uid, $brokeragePrice); - // fsgx: 若推荐人有分销等级(创客及以上),在佣金发放时同步发放积分到 frozen_points - $this->grantFrozenPointsByBrokerage($one_spread_uid, $brokeragePrice, $orderInfo); - // 一级返佣成功 跳转二级返佣 $res = $res1 && $res2 && $this->backOrderBrokerageTwo($orderInfo, $userInfo, $isSelfBrokerage, $frozen_time); return $res; @@ -309,21 +312,21 @@ class StoreOrderTakeServices extends BaseServices /** - * fsgx: 佣金发放后,按照 eb_agent_level 配置的「直推/伞下奖励积分」发放 frozen_points + * fsgx: 按照 eb_agent_level 配置的「直推/伞下奖励积分」发放 frozen_points * * 积分奖励规则: * - 直推上级:获得其等级 direct_reward_points 配置的积分 * - 更上级(伞下):按级差规则获得 umbrella_reward_points 积分 - * - 具体计算由 PointsRewardServices::reward() 统一实现,同 HjfOrderPayJob 逻辑保持一致 + * - 具体计算由 PointsRewardServices::reward() 统一实现 * * @param int $spreadUid 直推上级uid(仅用于前置校验) - * @param string|float $brokeragePrice 本次佣金金额(前置校验用) + * @param string|float $brokeragePrice 本次佣金金额(仅日志参考,不再作为前置条件) * @param array $orderInfo 订单信息(需含 uid、order_id) */ protected function grantFrozenPointsByBrokerage(int $spreadUid, $brokeragePrice, array $orderInfo): void { try { - if ($spreadUid <= 0 || $brokeragePrice <= 0) return; + if ($spreadUid <= 0) return; $buyerUid = (int)($orderInfo['uid'] ?? 0); $orderId = (string)($orderInfo['order_id'] ?? ''); if ($buyerUid <= 0 || $orderId === '') return; diff --git a/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php b/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php index 01bd8fe6..20e44be1 100644 --- a/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php +++ b/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php @@ -51,6 +51,7 @@ class SystemTimerServices extends BaseServices 'auto_presale_product' => '预售商品到期处理数据', 'auto_card_code' => '清理到期礼品卡', 'holiday_gift_push_task'=>'节日有礼赠送礼品', + 'fsgx_release_frozen_points' => 'fsgx每日积分释放', ]; /** @@ -240,9 +241,14 @@ class SystemTimerServices extends BaseServices public function runNow(string $mark): void { $this->update(['mark' => $mark], ['last_execution_time' => time()]); - $ref = new \ReflectionClass(SystemTimerListener::class); - $instance = $ref->newInstanceWithoutConstructor(); - $instance->implement_timer($mark); + try { + $ref = new \ReflectionClass(SystemTimerListener::class); + $instance = $ref->newInstanceWithoutConstructor(); + $instance->implement_timer($mark); + } catch (\Throwable $e) { + $taskName = $this->taskName[$mark] ?? $mark; + throw new \RuntimeException("定时任务[{$taskName}]执行失败: " . $e->getMessage(), 0, $e); + } } /**获取下次执行时间 diff --git a/pro_v3.5.1/config/console.php b/pro_v3.5.1/config/console.php index e7465e06..96088e6b 100644 --- a/pro_v3.5.1/config/console.php +++ b/pro_v3.5.1/config/console.php @@ -23,5 +23,7 @@ return [ 'clear:cache' => \app\command\ClearCache::class, 'reset:password' => \app\command\ResetAdminPwd::class, 'holiday_gift_push_task' => \app\command\HolidayGiftPushTask::class, + 'hjf:release-points' => \app\command\HjfReleasePoints::class, + 'hjf:patch-rewards' => \app\command\HjfPatchMissingRewards::class, ], ]; diff --git a/pro_v3.5.1/view/uniapp/api/hjfMember.js b/pro_v3.5.1/view/uniapp/api/hjfMember.js index a69c43f1..6812b170 100644 --- a/pro_v3.5.1/view/uniapp/api/hjfMember.js +++ b/pro_v3.5.1/view/uniapp/api/hjfMember.js @@ -16,7 +16,7 @@ export function getMemberInfo() { * 获取团队成员列表(复用 CRMEB 推广用户列表接口) */ export function getTeamData(params) { - return request.get('spread/people', params); + return request.post('spread/people', params); } /** diff --git a/pro_v3.5.1/view/uniapp/pages/users/user_spread_money/index.vue b/pro_v3.5.1/view/uniapp/pages/users/user_spread_money/index.vue index c9f0e9c0..0d500e98 100644 --- a/pro_v3.5.1/view/uniapp/pages/users/user_spread_money/index.vue +++ b/pro_v3.5.1/view/uniapp/pages/users/user_spread_money/index.vue @@ -229,12 +229,12 @@ * @returns {void} */ loadTeamData() { - getTeamData().then(res => { + getTeamData({ grade: 0 }).then(res => { const d = (res && res.data) || {}; this.teamData = { - direct_count: d.direct_count || 0, - umbrella_count: d.umbrella_count || 0, - umbrella_orders: d.umbrella_orders || 0, + direct_count: d.total || 0, + umbrella_count: d.totalLevel || 0, + umbrella_orders: d.order_count || 0, }; }).catch(() => {});