Files
huangjingfen/pro_v3.5.1/app/services/hjf/PointsRewardServices.php
apple 4351064013 fix(fsgx): 直推积分奖励规则重大调整(问题3)
- 移除 directCascadeActive 整体开关
- 创客(grade=1)仅 depth=0 时获得 reward_direct
- 云店及以上(grade>1)只需伞下关系即可获得级差 reward_direct
- 级差下限(lowerDirectReward)无论节点是否获奖均始终更新

Made-with: Cursor
2026-04-02 11:56:43 +08:00

216 lines
8.6 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;
}
// 取买家自身的直推奖励积分作为级差下限,确保第一级父节点只拿差额
$buyerLevelId = array_key_exists($orderUid, $preUpgradeLevels)
? $preUpgradeLevels[$orderUid]
: (int)($buyer['agent_level'] ?? 0);
$buyerDirectReward = $this->agentLevelServices->getDirectRewardPoints($buyerLevelId);
$this->propagateReward((int)$buyer['spread_uid'], $orderUid, $orderId, $buyerDirectReward, 0, $orderDbId, $preUpgradeLevels, $qty);
} catch (\Throwable $e) {
Log::error("[PointsReward] 积分奖励失败 orderUid={$orderUid} orderId={$orderId}: " . $e->getMessage());
}
}
/**
* 向上递归发放直推积分奖励(标准逐级级差)
*
* 发放规则fsgx 问题3
* - grade=0非分销员跳过不获奖继续向上$lowerDirectReward 不变
* - grade=1创客仅当 depth=0买家直接推荐人才获得级差 reward_direct
* depth>0 时不获奖,但其 direct_reward_points 仍计入级差下限
* - grade>1云店/服务中心/合伙人):无论 depth只要在伞下链上就获得级差 reward_direct
*
* @param int $uid 当前被奖励用户
* @param int $fromUid 触发方(下级)用户 ID
* @param string $orderId 来源订单号
* @param int $lowerDirectReward 链中已被下级拿走的最高 direct_reward_points级差下限
* @param int $depth 递归深度(防止无限递归)
* @param int $orderDbId 订单表主键 id
* @param array $preUpgradeLevels 升级前各用户的 agent_level 快照 [uid => level_id]B2
* @param int $qty 报单商品数量积分按数量倍乘B3
*/
private function propagateReward(
int $uid,
int $fromUid,
string $orderId,
int $lowerDirectReward,
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) {
// 非分销员:跳过奖励,继续向上传递,$lowerDirectReward 保持不变
if ($user['spread_uid']) {
$this->propagateReward((int)$user['spread_uid'], $uid, $orderId, $lowerDirectReward, $depth + 1, $orderDbId, $preUpgradeLevels, $qty);
}
return;
}
$directReward = $this->agentLevelServices->getDirectRewardPoints($agentLevelId);
// 创客(grade=1)必须是买家直推人(depth=0)才获得级差奖励;
// 高于创客(grade>1)则伞下关系即可获得级差奖励
$isEligibleForDirect = ($grade > 1) || ($grade === 1 && $depth === 0);
if ($isEligibleForDirect) {
$actual = max(0, $directReward - $lowerDirectReward) * $qty;
if ($actual > 0) {
$this->grantFrozenPoints(
$uid,
$actual,
$orderId,
'reward_direct',
'直推奖励(级差)' . " x{$qty} - 来源订单 {$orderId}",
$orderDbId
);
}
}
// 伞下积分奖励独立逻辑仅对间接上级depth>0生效受开关控制
// 使用 umbrella_reward_points 平额发放(非级差),各等级独立拿自身额度
if ($depth > 0) {
$umbrellaRewardEnable = (int)sys_config('hjf_umbrella_reward_enable', 0);
if ($umbrellaRewardEnable) {
$umbrellaPoints = $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
if ($umbrellaPoints > 0) {
$this->grantFrozenPoints(
$uid,
$umbrellaPoints * $qty,
$orderId,
'reward_umbrella',
'伞下奖励' . " x{$qty} - 来源订单 {$orderId}",
$orderDbId
);
}
}
}
// 级差下限始终更新(不论本节点是否获奖),保证上级只拿增量差额
$nextLower = max($directReward, $lowerDirectReward);
if ($user['spread_uid']) {
$this->propagateReward(
(int)$user['spread_uid'],
$uid,
$orderId,
$nextLower,
$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);
});
}
}