fix(fsgx): 修复 issues-0325-1 前端与后端问题
UniApp:会员码图片兜底、海报下载 Promise、账单移除公排退款、 佣金状态与资产页 NavBar、资产接口 total_points_earned。 后端:推荐人须自报单才得周期佣金;升级前快照等级再发积分; 积分按报单商品数量倍乘;伞下级差按伞下基数传递;直推/伞下任务 统计补充 refund_status;周期佣金在事务内锁推荐人行防竞态; 新增 hjf:verify-agent-config 命令做等级与任务 e2e 验收。 Made-with: Cursor
This commit is contained in:
@@ -437,11 +437,13 @@ class AgentLevelTaskServices extends BaseServices
|
||||
if (empty($directUids)) {
|
||||
return 0;
|
||||
}
|
||||
// fsgx B5:补充 refund_status 检查,与其他任务类型保持一致,排除已全额退款订单
|
||||
return (int)Db::name('store_order')
|
||||
->whereIn('uid', $directUids)
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->whereIn('refund_status', [0, 3])
|
||||
->count();
|
||||
}
|
||||
|
||||
@@ -490,11 +492,13 @@ class AgentLevelTaskServices extends BaseServices
|
||||
if ($childGrade >= 2) {
|
||||
continue;
|
||||
}
|
||||
// fsgx B5:补充 refund_status 检查,排除已全额退款订单
|
||||
$total += (int)Db::name('store_order')
|
||||
->where('uid', $child['uid'])
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->whereIn('refund_status', [0, 3])
|
||||
->count();
|
||||
$total += $this->recursiveUmbrellaCount((int)$child['uid'], $remainDepth - 1);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,13 @@ class PointsRewardServices extends BaseServices
|
||||
/**
|
||||
* 对一笔报单订单发放积分奖励
|
||||
*
|
||||
* @param int $orderDbId 订单表主键 id,用于 user_bill.link_id 关联后台订单
|
||||
* @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): void
|
||||
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')
|
||||
@@ -59,7 +62,7 @@ class PointsRewardServices extends BaseServices
|
||||
if (!$buyer || !$buyer['spread_uid']) {
|
||||
return;
|
||||
}
|
||||
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0, 0, $orderDbId);
|
||||
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0, 0, $orderDbId, $preUpgradeLevels, $qty);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("[PointsReward] 积分奖励失败 orderUid={$orderUid} orderId={$orderId}: " . $e->getMessage());
|
||||
}
|
||||
@@ -68,11 +71,13 @@ class PointsRewardServices extends BaseServices
|
||||
/**
|
||||
* 向上递归发放级差积分
|
||||
*
|
||||
* @param int $uid 当前被奖励用户
|
||||
* @param int $fromUid 触发方(下级)用户 ID
|
||||
* @param string $orderId 来源订单号
|
||||
* @param int $lowerReward 下级已获得的直推/伞下奖励积分(用于级差扣减)
|
||||
* @param int $depth 递归深度
|
||||
* @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,
|
||||
@@ -80,7 +85,9 @@ class PointsRewardServices extends BaseServices
|
||||
string $orderId,
|
||||
int $lowerReward,
|
||||
int $depth = 0,
|
||||
int $orderDbId = 0
|
||||
int $orderDbId = 0,
|
||||
array $preUpgradeLevels = [],
|
||||
int $qty = 1
|
||||
): void {
|
||||
if ($depth >= 10 || $uid <= 0) {
|
||||
return;
|
||||
@@ -91,22 +98,33 @@ class PointsRewardServices extends BaseServices
|
||||
return;
|
||||
}
|
||||
|
||||
$agentLevelId = (int)($user['agent_level'] ?? 0);
|
||||
// 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);
|
||||
$this->propagateReward((int)$user['spread_uid'], $uid, $orderId, 0, $depth + 1, $orderDbId, $preUpgradeLevels, $qty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$isDirect = ($depth === 0);
|
||||
$reward = $isDirect
|
||||
? $this->agentLevelServices->getDirectRewardPoints($agentLevelId)
|
||||
: $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
|
||||
// fsgx B4:直推奖励和伞下奖励分别读取,级差计算只用伞下奖励做扣减
|
||||
// 这样当中间层(如创客,umbrella=0)上报时,上级(云店,umbrella=300)
|
||||
// 计算 max(0, 300-0)=300,而不会被直推奖励 500 错误抵消
|
||||
$directReward = $this->agentLevelServices->getDirectRewardPoints($agentLevelId);
|
||||
$umbrellaReward = $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
|
||||
|
||||
$actual = max(0, $reward - $lowerReward);
|
||||
if ($isDirect) {
|
||||
// fsgx B3:直推奖励按报单商品数量倍乘
|
||||
$actual = $directReward * $qty;
|
||||
} else {
|
||||
// 级差:基于单件奖励做差值,再乘以数量
|
||||
$actual = max(0, $umbrellaReward - $lowerReward) * $qty;
|
||||
}
|
||||
|
||||
if ($actual > 0) {
|
||||
$this->grantFrozenPoints(
|
||||
@@ -114,7 +132,7 @@ class PointsRewardServices extends BaseServices
|
||||
$actual,
|
||||
$orderId,
|
||||
$isDirect ? 'reward_direct' : 'reward_umbrella',
|
||||
($isDirect ? '直推奖励' : '伞下奖励(级差)') . " - 来源订单 {$orderId}",
|
||||
($isDirect ? '直推奖励' : '伞下奖励(级差)') . " x{$qty} - 来源订单 {$orderId}",
|
||||
$orderDbId
|
||||
);
|
||||
}
|
||||
@@ -124,9 +142,11 @@ class PointsRewardServices extends BaseServices
|
||||
(int)$user['spread_uid'],
|
||||
$uid,
|
||||
$orderId,
|
||||
$reward,
|
||||
$umbrellaReward, // 向上传单件伞下奖励(级差基数),让上级自行乘以 $qty
|
||||
$depth + 1,
|
||||
$orderDbId
|
||||
$orderDbId,
|
||||
$preUpgradeLevels,
|
||||
$qty
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,22 +1002,42 @@ class StoreOrderCreateServices extends BaseServices
|
||||
$useCycleBrokerage = ($cycleCount > 0 && is_array($cycleRates) && count($cycleRates) > 0 && $isQueueGoods === 1);
|
||||
|
||||
if ($useCycleBrokerage && $spread_uid > 0) {
|
||||
// 统计推荐人下级已完成的有效报单商品订单数,取模得到当前位次
|
||||
// 注意:compute() 在 paid=1 之后执行,当前订单已被计入,需 -1 得到"之前完成单数"
|
||||
/** @var \app\dao\order\StoreOrderDao $orderDao */
|
||||
$orderDao = app()->make(\app\dao\order\StoreOrderDao::class);
|
||||
$completedCount = $orderDao->count([
|
||||
'spread_uid' => $spread_uid,
|
||||
'is_queue_goods' => 1,
|
||||
'paid' => 1,
|
||||
'is_del' => 0,
|
||||
]);
|
||||
$position = max(0, $completedCount - 1) % $cycleCount;
|
||||
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
|
||||
if ($cycleRatePercent > 0) {
|
||||
$brokerageRatio = bcdiv((string)$cycleRatePercent, 100, 4);
|
||||
$oneBrokerage = bcmul((string)$price, (string)$brokerageRatio, 2);
|
||||
}
|
||||
// fsgx B1 + B6:用事务锁序列化位次计算,防止并发竞态导致两笔订单拿到相同位次
|
||||
$brokerageResult = \think\facade\Db::transaction(function () use ($spread_uid, $cycleCount, $cycleRates, $price) {
|
||||
// 锁定推荐人行,确保同一推荐人同时只有一个事务在计算位次
|
||||
\think\facade\Db::name('user')
|
||||
->where('uid', $spread_uid)
|
||||
->lockWrite()
|
||||
->value('uid');
|
||||
|
||||
// fsgx B1:推荐人自己必须有报单商品订单,才能获得推荐返现佣金
|
||||
$spreaderOwnCount = (int)\think\facade\Db::name('store_order')
|
||||
->where('uid', $spread_uid)
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
if ($spreaderOwnCount <= 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// fsgx B6:按 id ASC 排序取位次,比 count 更精确且在锁保护下无竞态
|
||||
// 当前订单在 paid=1 后已写入,这里取所有已完成订单并按序找到本单排名
|
||||
$completedCount = (int)\think\facade\Db::name('store_order')
|
||||
->where('spread_uid', $spread_uid)
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
|
||||
$position = max(0, $completedCount - 1) % $cycleCount;
|
||||
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
|
||||
if ($cycleRatePercent > 0) {
|
||||
return bcmul((string)$price, bcdiv((string)$cycleRatePercent, '100', 4), 2);
|
||||
}
|
||||
return '0';
|
||||
});
|
||||
$oneBrokerage = $brokerageResult;
|
||||
} else {
|
||||
//一级返佣比例 小于等于零时直接返回 不返佣
|
||||
if ($storeBrokerageRatio > 0) {
|
||||
|
||||
Reference in New Issue
Block a user