Files
huangjingfen/pro_v3.5.1/app/services/hjf/PointsRewardServices.php
panchengyong ec56ae3286 fix(fsgx): 修复 issues-0325-1 前端与后端问题
UniApp:会员码图片兜底、海报下载 Promise、账单移除公排退款、
佣金状态与资产页 NavBar、资产接口 total_points_earned。

后端:推荐人须自报单才得周期佣金;升级前快照等级再发积分;
积分按报单商品数量倍乘;伞下级差按伞下基数传递;直推/伞下任务
统计补充 refund_status;周期佣金在事务内锁推荐人行防竞态;
新增 hjf:verify-agent-config 命令做等级与任务 e2e 验收。

Made-with: Cursor
2026-03-28 10:23:20 +08:00

186 lines
6.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\services\hjf;
use app\dao\hjf\PointsReleaseLogDao;
use app\dao\user\UserDao;
use app\services\agent\AgentLevelServices;
use app\services\BaseServices;
use app\services\user\UserBillServices;
use think\annotation\Inject;
use think\facade\Db;
use think\facade\Log;
/**
* 积分奖励服务(级差计算)—— 改造复用版
*
* 改造要点PRD 3.2.2
* - 使用 eb_user.agent_level (FK → eb_agent_level.id) 获取会员等级
* - 从 eb_agent_level 表的 direct_reward_points / umbrella_reward_points 字段读取奖励积分
* - 不再使用独立的 member_level 字段和系统配置表中的 hjf_reward_* 键
*
* Class PointsRewardServices
* @package app\services\hjf
*/
class PointsRewardServices extends BaseServices
{
#[Inject]
protected PointsReleaseLogDao $logDao;
#[Inject]
protected UserDao $userDao;
#[Inject]
protected AgentLevelServices $agentLevelServices;
#[Inject]
protected UserBillServices $userBillServices;
/**
* 对一笔报单订单发放积分奖励
*
* @param int $orderDbId 订单表主键 id用于 user_bill.link_id 关联后台订单
* @param array $preUpgradeLevels 升级前各用户的 agent_level 快照 [uid => level_id],用于 B2 校验
* @param int $qty 订单中报单商品数量积分按数量倍乘B3
*/
public function reward(int $orderUid, string $orderId, int $orderDbId = 0, array $preUpgradeLevels = [], int $qty = 1): void
{
$qty = max(1, $qty);
try {
// 幂等检查:若该订单已有积分奖励记录则跳过,防止重复发放
$exists = Db::name('points_release_log')
->where('order_id', $orderId)
->whereIn('type', ['reward_direct', 'reward_umbrella'])
->count();
if ($exists > 0) {
Log::info("[PointsReward] 订单 {$orderId} 已有积分奖励记录,跳过重复发放");
return;
}
$buyer = $this->userDao->get($orderUid);
if (!$buyer || !$buyer['spread_uid']) {
return;
}
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0, 0, $orderDbId, $preUpgradeLevels, $qty);
} catch (\Throwable $e) {
Log::error("[PointsReward] 积分奖励失败 orderUid={$orderUid} orderId={$orderId}: " . $e->getMessage());
}
}
/**
* 向上递归发放级差积分
*
* @param int $uid 当前被奖励用户
* @param int $fromUid 触发方(下级)用户 ID
* @param string $orderId 来源订单号
* @param int $lowerReward 下级已获得的伞下奖励积分(用于级差扣减)
* @param int $depth 递归深度
* @param array $preUpgradeLevels 升级前各用户的 agent_level 快照 [uid => level_id]
* @param int $qty 报单商品数量积分按数量倍乘B3
*/
private function propagateReward(
int $uid,
int $fromUid,
string $orderId,
int $lowerReward,
int $depth = 0,
int $orderDbId = 0,
array $preUpgradeLevels = [],
int $qty = 1
): void {
if ($depth >= 10 || $uid <= 0) {
return;
}
$user = $this->userDao->get($uid);
if (!$user) {
return;
}
// fsgx B2使用升级前的 agent_level 判断资格,避免触发升级的那笔订单就给新等级积分
$agentLevelId = array_key_exists($uid, $preUpgradeLevels)
? $preUpgradeLevels[$uid]
: (int)($user['agent_level'] ?? 0);
$grade = $this->agentLevelServices->getGradeByLevelId($agentLevelId);
if ($grade === 0) {
if ($user['spread_uid']) {
$this->propagateReward((int)$user['spread_uid'], $uid, $orderId, 0, $depth + 1, $orderDbId, $preUpgradeLevels, $qty);
}
return;
}
$isDirect = ($depth === 0);
// fsgx B4直推奖励和伞下奖励分别读取级差计算只用伞下奖励做扣减
// 这样当中间层如创客umbrella=0上报时上级云店umbrella=300
// 计算 max(0, 300-0)=300而不会被直推奖励 500 错误抵消
$directReward = $this->agentLevelServices->getDirectRewardPoints($agentLevelId);
$umbrellaReward = $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
if ($isDirect) {
// fsgx B3直推奖励按报单商品数量倍乘
$actual = $directReward * $qty;
} else {
// 级差:基于单件奖励做差值,再乘以数量
$actual = max(0, $umbrellaReward - $lowerReward) * $qty;
}
if ($actual > 0) {
$this->grantFrozenPoints(
$uid,
$actual,
$orderId,
$isDirect ? 'reward_direct' : 'reward_umbrella',
($isDirect ? '直推奖励' : '伞下奖励(级差)') . " x{$qty} - 来源订单 {$orderId}",
$orderDbId
);
}
if ($user['spread_uid']) {
$this->propagateReward(
(int)$user['spread_uid'],
$uid,
$orderId,
$umbrellaReward, // 向上传单件伞下奖励(级差基数),让上级自行乘以 $qty
$depth + 1,
$orderDbId,
$preUpgradeLevels,
$qty
);
}
}
/**
* 写入待释放积分frozen_points并记录明细
*/
private function grantFrozenPoints(
int $uid,
int $points,
string $orderId,
string $type,
string $mark,
int $orderDbId = 0
): void {
Db::transaction(function () use ($uid, $points, $orderId, $type, $mark, $orderDbId) {
$this->userDao->bcInc($uid, 'frozen_points', (string)$points, 'uid');
$this->logDao->save([
'uid' => $uid,
'points' => $points,
'pm' => 1,
'type' => $type,
'title' => ($type === 'reward_direct') ? '直推奖励' : '伞下奖励',
'mark' => $mark,
'status' => 'frozen',
'order_id' => $orderId,
]);
// PRD §3.3:营销后台积分日志读 eb_user_bill双写待释放积分明细不增加可消费 integral
$integralBalance = (int)($this->userDao->value(['uid' => $uid], 'integral') ?: 0);
$billType = ($type === 'reward_direct') ? 'hjf_reward_direct_integral' : 'hjf_reward_umbrella_integral';
$this->userBillServices->income($billType, $uid, $points, $integralBalance, $orderDbId, 0, $mark);
});
}
}